在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/zh-hant/n/375208.html