一、Socket通信基础概念
1.1 什么是Socket?
Socket(套接字)是计算机网络中不同主机间进程进行双向通信的端点,本质是操作系统提供的进程间通信机制。它封装了TCP/IP协议栈的复杂操作,为应用程序提供了标准API。
1.2 TCP协议特性
- 面向连接的可靠传输协议
- 保证数据顺序到达
- 基于字节流的传输(无消息边界)
- 流量控制和拥塞控制机制
1.3 通信要素
- IP地址:定位网络中的主机
- 端口号:标识主机中的具体应用
- 协议类型:TCP/UDP
二、Java Socket基础实现
2.1 服务端实现
java">public class SimpleServer {public static void main(String[] args) throws IOException {// 创建服务端Socket并绑定端口try (ServerSocket serverSocket = new ServerSocket(8888)) {System.out.println("服务端启动,等待连接...");// 接受客户端连接try (Socket clientSocket = serverSocket.accept();BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))) {System.out.println("客户端已连接");String receivedMsg;// 持续读取客户端数据while ((receivedMsg = in.readLine()) != null) {System.out.println("收到消息:" + receivedMsg);}}}}
}
2.2 客户端实现
java">public class SimpleClient {public static void main(String[] args) throws IOException {// 连接服务端try (Socket socket = new Socket("localhost", 8888);PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {System.out.println("已连接到服务端");// 发送测试消息out.println("Hello Server!");out.println("This is a test message.");}}
}
三、拆包与粘包现象模拟
3.1 问题现象说明
- 粘包:多个数据包被接收方一次性接收
- 拆包:一个数据包被拆分成多次接收
3.2 客户端改造(模拟快速发送)
java">public class PacketIssueClient {public static void main(String[] args) throws IOException, InterruptedException {try (Socket socket = new Socket("localhost", 8888);OutputStream out = socket.getOutputStream()) {// 连续发送小数据包for (int i = 0; i < 5; i++) {String msg = "Msg-" + i + "\n"; // 注意换行符out.write(msg.getBytes());System.out.println("已发送:" + msg.trim());Thread.sleep(500); // 降低发送速度}// 发送大数据包(超过缓冲区)StringBuilder bigData = new StringBuilder();for (int i = 0; i < 100; i++) {bigData.append("ABCDEFGHIJ");}out.write(bigData.toString().getBytes());}}
}
3.3 服务端改造(原始读取方式)
java">public class RawDataServer {public static void main(String[] args) throws IOException {try (ServerSocket serverSocket = new ServerSocket(8888)) {System.out.println("服务端启动...");try (Socket clientSocket = serverSocket.accept();InputStream in = clientSocket.getInputStream()) {byte[] buffer = new byte[1024];int bytesRead;// 原始字节流读取while ((bytesRead = in.read(buffer)) != -1) {String received = new String(buffer, 0, bytesRead);System.out.println("收到原始数据:\n" + received);}}}}
}
3.4 运行结果示例
收到原始数据:
Msg-0
Msg-1
Msg-2
收到原始数据:
Msg-3
Msg-4
ABCDEFGHIJABCDEF...
收到原始数据:
...(剩余数据)
四、现象分析
- 粘包产生:客户端快速发送多个小包时,服务端可能一次性读取多个消息
- 拆包出现:当发送大数据包时,由于接收缓冲区限制,数据被分割成多次读取
- 根本原因:TCP是字节流协议,不维护消息边界
五、下篇预告
在下一篇中,我们将深入探讨如何解决拆包/粘包问题,包括:
- 定长消息解码器实现
- 特殊分隔符处理方案
- 基于长度字段的编解码器
- 使用主流框架(Netty)的解决方案
思考题:为什么UDP协议不存在拆包/粘包问题?欢迎在评论区留下你的见解!