Socket 编程中,如何处理粘包和拆包问题?

粘包和拆包是Socket编程中经常遇到的问题。
粘包:发送方发送的多个数据包在接收方粘合成一个大的数据包。
拆包:接收方将一个大的数据包拆分成多个小的数据包。
主要原因是TCP协议的MSS(最大报文长度)限制和硬件限制,导致在高速传输下,发送方发送的多个小包可能被接收方的网卡合并,或者接收方接收到的大包可能被接收方的应用层拆成多个小包。

常用的解决粘包和拆包的方法主要有:

  1. 添加消息头和长度字段:
  • 在每个消息前添加消息长度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);
  1. 使用消息定界符:
  • 发送方在每个消息末尾添加定界符,接收方根据定界符识别消息边界。
// 发送方 
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);
    }
}
  1. 序列化对象:
  • 发送方将对象序列化后发送,接收方根据对象边界解序列化成完整对象。
// 发送方
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); // 解序列化对象