面试题答案
一键面试粘包和拆包产生原因
- 发送端:
- 由于 TCP 是面向流的协议,没有明确的消息边界。当应用层多次调用
Send
方法发送数据时,TCP 协议栈可能会将这些小的数据包合并成一个大的数据包发送出去,这就导致接收端收到的数据包可能包含了多个应用层的数据包,即产生粘包现象。
- 由于 TCP 是面向流的协议,没有明确的消息边界。当应用层多次调用
- 接收端:
- 接收缓冲区的机制。接收端从网络中读取数据到接收缓冲区,当接收缓冲区中有数据时,应用层调用
Receive
方法读取数据。如果应用层每次读取的数据量小于缓冲区中的数据量,就可能导致一个完整的数据包被拆分读取,产生拆包现象;或者多次读取的数据刚好组合成多个应用层数据包,形成粘包。
- 接收缓冲区的机制。接收端从网络中读取数据到接收缓冲区,当接收缓冲区中有数据时,应用层调用
解决方法及代码示例
方法一:定长包
在发送端,将每个数据包都填充到固定长度。接收端每次按固定长度读取数据。
// 发送端示例
string message = "Hello, World!";
byte[] data = Encoding.UTF8.GetBytes(message);
// 假设固定长度为100
byte[] fixedLengthData = new byte[100];
Array.Copy(data, fixedLengthData, data.Length);
socket.Send(fixedLengthData);
// 接收端示例
byte[] buffer = new byte[100];
int bytesRead = socket.Receive(buffer);
string receivedMessage = Encoding.UTF8.GetString(buffer, 0, bytesRead).TrimEnd('\0');
方法二:包头 + 包体
在数据包前加上包头,包头中包含包体的长度等信息。接收端先读取包头获取包体长度,再按长度读取包体。
// 发送端示例
string message = "Hello, World!";
byte[] body = Encoding.UTF8.GetBytes(message);
// 包头包含包体长度
byte[] header = BitConverter.GetBytes(body.Length);
byte[] sendData = new byte[header.Length + body.Length];
Array.Copy(header, 0, sendData, 0, header.Length);
Array.Copy(body, 0, sendData, header.Length, body.Length);
socket.Send(sendData);
// 接收端示例
NetworkStream stream = new NetworkStream(socket);
byte[] headerBuffer = new byte[4];
stream.Read(headerBuffer, 0, 4);
int bodyLength = BitConverter.ToInt32(headerBuffer, 0);
byte[] bodyBuffer = new byte[bodyLength];
stream.Read(bodyBuffer, 0, bodyLength);
string receivedMessage = Encoding.UTF8.GetString(bodyBuffer);