TCP粘包问题
概念
TCP三大特性:面向连接、可靠传输和基于字节流,第三个特性面向字节流是指TCP协议属于流式协议,内容与内容之间没有明确的分界符号,需要人为去给消息协议定义边界规则,来区分不同的消息。举一个例子
- 发送端发送了两份数据,在传输层被划分为7个数据包,两份数据各占3.5个数据包,由于是字节流,两份数据的0.5个数据包之间没有边界
- 接收端接收到数据后,需要通过边界规则将0.5个数据包正确拼接到其他数据包还原两份完整数据
粘包根源
- TCP协议:面向字节流是指TCP协议属于流式协议,内容与内容之间没有明确的分界符号,多个消息可能会被编码到同一个数据包中
- 发送端:Nagle算法会将小数据包积累到一定大小后再进行发送,导致多个小包被合并为一个大包,从而产生粘包现象
- 接收端:当接收端从 TCP 缓冲区中读取数据时,TCP 并不知道数据包的边界,因此接收到的数据可能会包含多个已粘在一起的数据包
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* TCP粘包问题演示 - 服务端
* 模拟接收多个消息被粘在一起的情况
*/
public class TcpStickyPacketServer {
private static final int PORT = 8888;
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("TCP粘包问题演示服务端启动,端口: " + PORT);
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("客户端连接: " + clientSocket.getRemoteSocketAddress());
// 为每个客户端创建新线程处理
new Thread(new ClientHandler(clientSocket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
static class ClientHandler implements Runnable {
private final Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try (InputStream inputStream = clientSocket.getInputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
// 模拟粘包:一次性读取可能包含多个消息
String receivedData = new String(buffer, 0, bytesRead);
System.out.println("【服务端接收】原始数据长度: " + bytesRead + ", 内容: " + receivedData);
// 尝试解析消息(这里会出现粘包问题)
parseMessages(receivedData);
}
} catch (IOException e) {
System.out.println("客户端断开连接");
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 有问题的消息解析方法 - 无法正确处理粘包
*/
private void parseMessages(String data) {
// 假设每个消息应该是 "Message X" 的格式
// 但由于粘包,可能收到 "Message 1Message 2Message 3"
System.out.println("【解析结果】无法正确分割消息: " + data);
System.out.println("----------------------------------------");
}
}
}
解决方案
解决粘包问题的关键就是:定义消息边界,具体有以下方案:
- 固定消息长度:消息长度固定,适合各类消息长度近似的场景,一般不会使用,会造成大量的空间浪费
- 消息分隔符:通过一个特殊符号标识消息的末尾,其不足之处在于,如果消息体本身包含这些分隔符,会导致消息被错误解析,因此需要对分隔符进行转义
- 消息头+消息体:消息的头部添加长度字段,表示消息的总长度,接收端可以先读取长度字段,再根据该长度读取完整的消息。这种方法较为通用且适用于各种长度的消息

浙公网安备 33010602011771号