粘包和拆包是Socket编程中经常遇到的问题。
粘包:发送方发送的多个数据包在接收方粘合成一个大的数据包。
拆包:接收方将一个大的数据包拆分成多个小的数据包。
主要原因是TCP协议的MSS(最大报文长度)限制和硬件限制,导致在高速传输下,发送方发送的多个小包可能被接收方的网卡合并,或者接收方接收到的大包可能被接收方的应用层拆成多个小包。
常用的解决粘包和拆包的方法主要有:
- 添加消息头和长度字段:
- 在每个消息前添加消息长度header,接收方根据长度读取完整消息。
// 发送方
byte[] data = "Hello".getBytes();
byte[] header = intToBytes(data.length);
byte[] packet = join(header, data);
socket.send(packet);
// 接收方
byte[] header = new byte[4];
socket.receive(header);
int length = bytesToInt(header);
byte[] data = new byte[length];
socket.receive(data);
- 使用消息定界符:
- 发送方在每个消息末尾添加定界符,接收方根据定界符识别消息边界。
// 发送方
byte[] data1 = "Hello".getBytes();
byte[] data2 = "World".getBytes();
byte[] delimiter = "#".getBytes();
byte[] packet = join(data1, delimiter, data2, delimiter);
socket.send(packet);
// 接收方
boolean isEnd = false;
List<byte[]> list = new ArrayList<>();
while (!isEnd) {
byte[] buffer = new byte[1024];
socket.receive(buffer);
int index = -1;
for (int i = 0; i < buffer.length; i++) {
if (buffer[i] == '#') { // 找到定界符
index = i;
break;
}
}
if (index != -1) {
byte[] msg = new byte[index];
System.arraycopy(buffer, 0, msg, 0, index);
list.add(msg);
isEnd = buffer[index + 1] == '#'; // 连续两个定界符表示消息结束
} else {
list.add(buffer);
}
}
- 序列化对象:
- 发送方将对象序列化后发送,接收方根据对象边界解序列化成完整对象。
// 发送方
User user = new User(1, "John");
byte[] data = serialize(user); // 序列化对象
socket.send(data);
// 接收方
byte[] buffer = new byte[1024];
socket.receive(buffer);
User user = (User) deserialize(buffer); // 解序列化对象