在TCP/IP协议中,由于TCP是面向字节流的协议,发送方把需要传输的数据流按照MSS(Maximum Segment Size,最大报文段长度)来分割成若干个TCP分节,在接收端则重新组装成完整的数据。但由于网络等原因,接收端可能会出现接收到的TCP分节大于发送方发送的TCP分节的情况,也就是俗称的粘包问题。
一、TCP粘包问题原因
造成TCP粘包问题的主要原因有以下几种:
1、应用程序写入数据太快,每次仅发送少量数据,这样TCP底层的协议栈就会把多次写入的数据发送到接收方并且底层协议也可能会将其一起打包发送;
2、网络有延迟,可能是由于网络拥堵、带宽狭窄等原因导致,接收端在处理完一部分数据后再进行下一次读取数据,而这期间又有数据到来,从而造成了粘包现象;
3、系统处理能力不足,当接收端的应用程序不能及时的处理接收到的数据,就会导致缓冲区的堆积,也就是读取了多个数据然后才进行处理,导致同样的粘包现象。
二、TCP粘包问题解决方案
解决TCP粘包问题的方式通常有以下几种:
1、消息定长
在应用层协议中,将每个消息的大小固定为固定字节长度,如果不足用空格补齐,不支持中文,支持英文和数字。在每个消息头中存储消息的字节长度,接收方在接收到数据后,根据读取到消息头的长度去读取固定长度的内容,如果不足字节长度继续等待。
function strPadLeft(str, pad, length) {
while (str.length < length)
str = pad + str;
return str;
}
//发送函数
function send(socket, data) {
data = strPadLeft(data, ' ', 20); //固定长度为20
socket.write(data + '\n');
}
//接收函数
function handle(socket) {
var buffer = '';
//消息头长度为等于20个字节
var headerLength = 20;
socket.on('data', function(data) {
buffer += data.toString('utf8');
while (buffer.length >= headerLength) {
var packet = buffer.slice(0, headerLength);
buffer = buffer.slice(headerLength);
var packetLength = parseInt(packet.trim(), 10);
if (buffer.length >= packetLength) {
var msg = buffer.slice(0, packetLength);
buffer = buffer.slice(packetLength);
//处理接收到的消息
} else {
break;
}
}
});
}
2、在TCP数据包尾部增加特殊字符
在每个TCP数据包的结束位置添加特殊字符,例如“\r\n”,接收方以此来判断每个TCP数据包的结束位置,并且拆分每个数据包。但是该方式也存在缺陷,在处理粘包问题时会把特殊字符也当作分隔符,从而产生额外的麻烦。
function send(socket, data) {
socket.write(data + '\r\n'); //注意这里添加了特殊字符\r\n
}
function handle(socket) {
var buffer = '';
//特殊字符分隔符为"\r\n"
var separator = '\r\n';
socket.on('data', function(data) {
buffer += data.toString('utf8');
var endIndex = buffer.lastIndexOf(separator);
if (endIndex != -1) {
var msg = buffer.substring(0, endIndex);
buffer = buffer.substring(endIndex + separator.length);
//处理接收到的消息
}
});
}
3、将消息分为消息头和消息体
将每个数据包分为两部分:消息头+消息体,消息头固定长度,包含消息体的长度信息。接收方先读取消息头,然后再根据消息头中长度信息的大小读取整个消息。该方式可以独立处理消息头和消息体,更灵活可控。
function sendMessage(socket, message) {
var header = Buffer.alloc(4);
var body = typeof message == 'string' ? Buffer.from(message, 'utf8') : message;
header.writeInt32BE(body.length, 0); //写入消息体的长度
socket.write(Buffer.concat([header, body])); //发送消息
}
function handle(socket) {
var buffer = null, header = null;
socket.on('data', function(data) {
if (!buffer) {
buffer = data;
} else {
buffer = Buffer.concat([buffer, data]);
}
while (buffer.length > 4) {
if (!header) {
header = buffer.slice(0, 4);
}
var bodyLength = header.readInt32BE(0); //读取消息体的长度
if (buffer.length >= 4 + bodyLength) { //判断是否已经读取完整个消息
var body = buffer.slice(4, 4 + bodyLength);
buffer = buffer.slice(4 + bodyLength);
//处理接收到的消息头和消息体
header = null;
} else {
break;
}
}
});
}
三、总结
TCP粘包问题是一个常见的网络问题,通常是由于网络延迟、数据量过大或接收端处理能力不足等原因导致的。
为了解决TCP粘包问题,我们可以通过三种方式进行处理:消息定长、在TCP数据包尾部增加特殊字符和将消息分为消息头和消息体。
其中,消息定长和在TCP数据包尾部增加特殊字符的方式比较简单,但解决粘包问题不够灵活;而将消息分为消息头和消息体的方式较为灵活,可控性也较强。
原创文章,作者:CNSAV,如若转载,请注明出处:https://www.506064.com/n/375208.html