一、什么是Socket?
Socket(套接字)是计算机网络中用于进程间通信的一种机制,它充当应用层与传输层之间的接口,使得两个不同主机上的程序能够进行数据交互。
本质与作用
本质上,Socket 封装了 TCP/IP 协议族的复杂细节,提供了统一的编程接口,使开发者可以像操作文件一样,轻松实现网络通信(读取、写入)。
核心功能作用
建立连接: 在客户端和服务器之间建立可靠的网络通信通道。
数据传输: 支持传输任意格式的数据,如文本、音视频、文件等。
双向通信: 支持全双工通信,即可读可写,数据可同时双向传输。
类型
流式 Socket(SOCK_STREAM): 基于 TCP 协议,面向连接,传输可靠,适用于需要稳定数据传输的场景,如文件传输。
数据报 Socket(SOCK_DGRAM): 基于 UDP 协议,无连接,效率高但不保证顺序和可靠性,适合短消息或实时性要求高的场景。
二、TCP Socket 通信流程
客户端 | 服务端 |
创建Socket | 创建ServerSocket |
连接服务器 | 等待并接受连接 |
获取输入输出流 | 获取输入输出流 |
发送/接收数据 | 发送/接收数据 |
关闭连接 | 关闭连接 |
三、示例代码
为了实现客户端与服务端间的数据结构化传输(如区分消息、心跳),引入 FastJSON:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.57</version>
</dependency>
FastJSON 是阿里巴巴开源的高性能 JSON 处理框架,支持快速地在 Java 对象与 JSON 字符串之间进行转换。
服务器端代码 Server.java
/**
* 服务端主类
* 实现逻辑:
* 1. 启动 ServerSocket 监听端口
* 2. 接收客户端连接请求
* 3. 为每个连接创建监听线程和发送线程
*/
public class Server {
public static void main(String[] args) {
System.out.println("服务器启动成功,监听端口8888");
try(ServerSocket serverSocket = new ServerSocket(8888)){
while (true) {
//接收客户端连接请求
Socket socket = serverSocket.accept();
System.out.println("客户端已连接:" +socket.getRemoteSocketAddress());
//创建监听线程和发送线程
new Thread(new ServerListen(socket)).start();
new Thread(new ServerSend(socket)).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 服务端监听线程
* 作用:持续读取客户端发送的数据并输出
*/
class ServerListen implements Runnable{
private Socket socket;
public ServerListen(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try(ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream())){
while (true) {
//接收数据
Object msg = objectInputStream.readObject();
System.out.println("接收到客户端信息:" + msg);
}
} catch (Exception e) {
System.out.println("客户端已断开:" + socket.getRemoteSocketAddress());
}
}
}
/**
* 服务端发送线程
* 作用:持续向客户端发送数据
*/
class ServerSend implements Runnable{
private Socket socket;
public ServerSend(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try(
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
Scanner scanner = new Scanner(System.in);
) {
while(true){
System.out.println("服务器 请输入:");
String input = scanner.nextLine(); //控制台输入内容
//构建JSON消息
JSONObject jsonObject = new JSONObject();
jsonObject.put("type","message");
jsonObject.put("content", input);
objectOutputStream.writeObject(jsonObject); //发送数据
objectOutputStream.flush();
}
} catch (Exception e) {
System.out.println("发送线程异常:" + socket.getRemoteSocketAddress());
}
}
}
客户端代码 Client.java
/**
* 客户端主类
* 实现逻辑:
* 1. 启动时尝试连接服务器
* 2. 创建监听线程、发送线程、心跳线程
* 3. 若连接失败则每5秒重试
*/
public class Client {
//客户端与服务端连接的 Socket
private static Socket socket;
//连接状态标志
public static boolean socket_status = false;
public static void main(String[] args) {
//尝试连接服务器,失败则循环重连
while (!socket_status) {
connect();
try{
Thread.sleep(5000); //等待5秒后重试
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 建立客户端 Socket 连接,初始化输入输出流和线程
*/
private static void connect() {
try{
//创建socket 并连接服务器
socket = new Socket("127.0.0.1", 8888);
socket_status = true;
//创建输入输出流,用于对象传输
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
//创建监听线程,发送线程,心跳线程
new Thread(new ClientListen(socket, objectInputStream)).start();
new Thread(new ClientSend(socket, objectOutputStream)).start();
new Thread(new ClientHeart(socket, objectOutputStream)).start();
}catch (Exception e){
System.out.println("连接失败,等待重试...");
socket_status = false;
}
}
/**
* 当连接断开时调用,持续尝试重新连接
*/
public static void reconnect() {
while(!socket_status){
System.out.println("正在尝试重新连接...");
connect();
try{
Thread.sleep(5000); //等待5秒后重试
}catch (Exception e){
e.printStackTrace();
}
}
}
}
/**
* 客户端监听线程
* 作用:持续接收服务端发送过来的数据并输出到控制台
*/
class ClientListen implements Runnable {
private Socket socket;
private ObjectInputStream objectInputStream;
public ClientListen(Socket socket, ObjectInputStream objectInputStream){
this.socket = socket;
this.objectInputStream = objectInputStream;
}
@Override
public void run() {
try{
while (true) {
//接收服务端发送过来的数据
Object response = objectInputStream.readObject(); //读取服务端消息
System.out.println("服务端: " + response);
}
}catch (Exception e) {
System.out.println("服务端断开连接,监听停止");
}
}
}
/**
* 客户端发送线程
* 作用:从控制台读取用户输入,并通过 Socket 发送给服务端
*/
class ClientSend implements Runnable {
private Socket socket;
private ObjectOutputStream objectOutputStream;
public ClientSend(Socket socket, ObjectOutputStream objectOutputStream){
this.socket = socket;
this.objectOutputStream = objectOutputStream;
}
@Override
public void run() {
try(Scanner scanner = new Scanner(System.in)){
while (true) {
System.out.println("客户端 请输入:");
String input = scanner.nextLine(); //控制台输入内容
// 使用 JSON 封装消息数据
JSONObject jsonObject = new JSONObject();
jsonObject.put("type", "message");
jsonObject.put("content", input);
objectOutputStream.writeObject(jsonObject); //发送数据
objectOutputStream.flush();
}
}catch (Exception e){
System.out.println("发送线程异常,尝试重连");
Client.socket_status = false;
}
}
}
/**
* 客户端心跳线程
* 作用:定时向服务器发送心跳包,防止连接超时断开
*/
class ClientHeart implements Runnable {
private Socket socket;
private ObjectOutputStream objectOutputStream;
public ClientHeart(Socket socket, ObjectOutputStream objectOutputStream){
this.socket = socket;
this.objectOutputStream = objectOutputStream;
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(5000); //每5秒发送一次心跳包
System.out.println("客户端 发送心跳");
//使用 JSON 封装心跳包数据
JSONObject jsonObject = new JSONObject();
jsonObject.put("type", "heart");
jsonObject.put("content", "心跳");
//发送心跳包
objectOutputStream.writeObject(jsonObject);
objectOutputStream.flush();
}
}catch (Exception e) {
System.out.println("心跳线程异常,尝试重连...");
try {
socket.close(); //关闭socket
}catch (Exception ex) {
ex.printStackTrace();
}
Client.socket_status = false;
Client.reconnect(); //重连
}
}
}
实现效果:
为什么要加入心跳机制?
在 Socket 长连接通信中,如果客户端与服务器之间长时间没有数据交互,连接可能会因为网络设备(如路由器、防火墙)的空闲超时策略而被关闭。为此,我们在客户端实现了一个心跳线程,每隔 5 秒向服务器发送一个 `type: heart` 的 JSON 消息。
服务端可通过接收心跳包判断客户端是否仍在线。一旦检测不到心跳,或者发送失败,即可断开连接并释放资源。同时,客户端也能在心跳失败时自动重连,确保通信的持续性与可靠性。
注意:
ServerSocket 和 Socket 的区别
在 Java 网络编程中,ServerSocket 和 Socket 是实现 TCP 通信的两个核心类。但是它们的作用不同:
类名 | 作用范围 | 作用描述 |
ServerSocket | 服务器端 | 用于监听客户端的连接请求 |
Socket | 客户端、服务器端 | 客户端用于发起连接,服务器端用于与某个客户端通信 |
ServerSocket (服务器端监听器)
专用于服务器,负责监听端口、接收客户端连接。调用 accept()返回一个 Socket
Socket (通信通道)
双方通信的实际通道,客户端用它发起连接,服务端用于和某一客户端通信。
示例代码
// 服务端 ServerSocket serverSocket = new ServerSocket(8888); Socket clientSocket = serverSocket.accept(); // 接收连接 // 客户端 Socket socket = new Socket("127.0.0.1", 8888); // 发起连接
四、总结
本文介绍了 Socket 的基本原理、通信流程以及基于 Java 的 TCP 编程实践。通过示例代码,我们实现了客户端与服务端的双向通信,支持消息发送、心跳机制与断线重连。
Socket 是构建网络程序的基础工具,理解它的工作方式,有助于我们开发出更加稳定可靠的通信系统。至此,Java Socket 实现双向通信、多线程与心跳机制就完成了。