1、如何解決粘包問題?
在設計網絡協議時,可能會存在粘包、丟包或者包亂序問題,但TCP協議時可靠性協議,大多數情況不存在丟包和亂序問題,但UDP協議如果不能接受少量丟包,就必須自己設計有序和可靠性傳輸機制(比如:RTP協議、RUDP協議,)。因此,只存在如何解決粘包的問題。
TCP 協議是流式數據格式。解決問題的思路還是想辦法從收到的數據中把包與包的邊界給區分出來。那麼如何區分呢?目前主要有三種方法:
- 固定包長的數據包
- 以指定字符(串)爲包的結束標誌
- 包頭 + 包體格式
2、如何處理解包問題?
包頭 + 包體 這種格式的數據包的捷豹處理的流程如下:
假如 包頭格式如下:
//強制一字節對齊
#pragma pack(push, 1)
//協議頭
struct msg_header
{
int32_t bodysize; //包體大小
};
#pragma pack(pop)
代碼流程如下:
//包最大字節數限制爲10M
#define MAX_PACKAGE_SIZE 10 * 1024 * 1024
void ChatSession::OnRead(const std::shared_ptr<TcpConnection>& conn, Buffer* pBuffer, Timestamp receivTime)
{
while (true)
{
//不夠一個包頭大小
if (pBuffer->readableBytes() < (size_t)sizeof(msg_header))
{
//LOGI << "buffer is not enough for a package header, pBuffer->readableBytes()=" << pBuffer->readableBytes() << ", sizeof(msg_header)=" << sizeof(msg_header);
return;
}
//取包頭信息
msg_header header;
memcpy(&header, pBuffer->peek(), sizeof(msg_header));
//包頭有錯誤,立即關閉連接
if (header.bodysize <= 0 || header.bodysize > MAX_PACKAGE_SIZE)
{
//客戶端發非法數據包,服務器主動關閉之
LOGE("Illegal package, bodysize: %lld, close TcpConnection, client: %s", header.bodysize, conn->peerAddress().toIpPort().c_str());
conn->forceClose();
return;
}
//收到的數據不夠一個完整的包
if (pBuffer->readableBytes() < (size_t)header.bodysize + sizeof(msg_header))
return;
pBuffer->retrieve(sizeof(msg_header));
//inbuf用來存放當前要處理的包
std::string inbuf;
inbuf.append(pBuffer->peek(), header.bodysize);
pBuffer->retrieve(header.bodysize);
//解包和業務處理
if (!Process(conn, inbuf.c_str(), inbuf.length()))
{
//客戶端發非法數據包,服務器主動關閉之
LOGE("Process package error, close TcpConnection, client: %s", conn->peerAddress().toIpPort().c_str());
conn->forceClose();
return;
}
}// end while-loop
}
pBuffer 這裏是一個自定義的接收緩衝區,這裏的代碼,已經將收到的數據放入了這個緩衝區,所以判斷當前已收取的字節數目只需要使用這個對象的相應方法即可,需要注意如下問題:
- 取包頭之前:需判斷緩衝區 pBuffer 中的可讀數據大小是否超過一個報文長度,否則直接退出。
- 取包頭之後:需判斷報文體大小是否超過自己規定最大值,避免發超大型數據耗盡內存。
- 多幀處理:使用while一直處理報文,直到緩衝區數據不足一幀報文結束。