資料接收中粘包及半包的處理
在使用TCP協議的網路應用中,不可避免需要處理的一個問題就是半包和粘包的情況。
一種做法是在接收端設一個比較大的緩衝區,先將收到的資料包都放到緩衝區中,然後從該緩衝區中選取完整的資料包出來。該緩衝區的實現可以使用環形緩衝區進行優化,避免頻繁的資料移動。使用該方法的一個描述見http://www.vckbase.com/document/viewdoc/?id=1203
另外一種做法就是在接收的時候就只接收完整包。這要求資料包有固定的包頭結構體,其中還要包含資料包的長度資訊。在服務端接收的時候,先接收該包頭資料,然後再接收指定長度的資料體。
在ACE中,用於儲存訊息的ACE_Message_Block有一個重要的特性:複合。即將多條訊息連線在一起,形成一個單連結串列。這樣便可以將先收到的包頭和後收到的包體連成一個複合體,而不用建一個大的資料包,將兩個Message_Block拷貝進去。
下面的示例採用ACE的Proactor框架完成,實現了伺服器端半包及粘包的處理,以及ACE_Message_Block的複合,網路IO與邏輯處理的分離。
在Proactor框架中,接收新連線後,會初始化一個讀請求,此時只要求讀包頭長度的資料:
void init_read_stream()
{
ACE_NEW_NORETURN (recv_data_, ACE_Message_Block (sizeof(PacketHeader), MB_NORMAL_PACKET));
ACE_HANDLE handle = this->handle ();
this->recv_data_->copy ((const char *)&handle, sizeof(ACE_HANDLE));
this->reader_.read (*recv_data_, recv_data_->space ());
}
這裡由於使用了網路IO與邏輯處理分執行緒處理的方式,遞交給邏輯執行緒的資料包前面還加上了標識網路連線的handle,用以告訴邏輯執行緒該資料包是哪個客戶端連線發上來的。
遞交給邏輯執行緒的資料包頭結構為:
struct PacketHeader
{
ACE_HANDLE handler;
int data_length;
};
其中data_length就是從接收到的資料包中獲取到的。
對於粘包的情況比較容易處理,先收了包頭後再接收指定長度的資料包,多餘的資料由下次再讀取。
半包情況稍微複雜一點,每個資料包是分兩次接收的,兩次接收的時候都有可能接收不完全。
當接收包頭不完全時所做的處理是繼續提交讀請求,讀的資料長度為剩餘的包頭長度
if (this->recv_data_->length() < sizeof(PacketHeader))
{
// 資料包長度資訊還未接收完
this->reader_.read (*recv_data_, recv_data_->space ());
return;
}
當包頭接收完後,新建一個Message_Block,長度為需要接收的資料體長度,並將該Message_Block連結到包頭後
PacketHeader * hdr = reinterpret_cast<PacketHeader *> (this->recv_data_->rd_ptr());
ACE_Message_Block * data_mb = this->recv_data_->cont();
if (!data_mb)
{
// 剛剛接收完長度資訊
ACE_NEW (data_mb, ACE_Message_Block(hdr->data_length));
this->recv_data_->cont (data_mb);
}
如果該資料包的包體接收完全,則將該完整的資料包傳送到邏輯執行緒的訊息佇列,然後初始化一個新的接收請求
if (data_mb->length () == hdr->data_length)
{
// 資料已接收完
// 再繼續接收下一個資料包
logic_thread->putq (recv_data_);
this->init_read_stream();
return;
}
否則表示資料體還未接收完全,處理方法也是繼續提交剩餘資料的讀請求
this->reader_.read (*data_mb, data_mb->space ());
直接該資料包讀取完全。
資料包接收處理函式的完整實現為:
virtual void handle_read_stream (const ACE_Asynch_Read_Stream::Result &result)
{
ACE_Message_Block &mb = result.message_block ();
if (!result.success () || result.bytes_transferred () == 0)
{
mb.release ();
delete this;
}
else
{
if (this->recv_data_->length() < sizeof(PacketHeader))
{
// 資料包長度資訊還未接收完
this->reader_.read (*recv_data_, recv_data_->space ());
return;
}
PacketHeader * hdr = reinterpret_cast<PacketHeader *> (this->recv_data_->rd_ptr());
ACE_Message_Block * data_mb = this->recv_data_->cont();
if (!data_mb)
{
// 剛剛接收完長度資訊
ACE_NEW (data_mb, ACE_Message_Block(hdr->data_length));
this->recv_data_->cont (data_mb);
}
if (data_mb->length () == hdr->data_length)
{
// 資料已接收完
// 再繼續接收下一個資料包
logic_thread->putq (recv_data_);
this->init_read_stream();
return;
}
// 否則繼續接收該資料包
this->reader_.read (*data_mb, data_mb->space ());
}
}
完整的伺服器實現和模擬半包及粘包情況的客戶端程式碼見
http://helloqinglan.googlepages.com/repack.rar
伺服器僅實現了最簡單的資料接收功能,為精減程式碼,未做錯誤檢查
相關文章
- tcp中的粘包、半包的處理方法TCP
- 詳說tcp粘包和半包TCP
- Netty粘包&半包解決方案Netty
- 網路遊戲資料傳輸:粘包的處理遊戲
- socket的半包,粘包與分包的問題
- java nio解決半包 粘包問題Java
- C# 優雅的處理TCP資料(心跳,超時,粘包斷包,SSL加密 ,資料處理等)C#TCP加密
- java nio訊息半包、粘包解決方案Java
- Netty Protobuf處理粘包分析Netty
- go語言處理TCP拆包/粘包GoTCP
- TCP通訊處理粘包詳解TCP
- 即時通訊下資料粘包、斷包處理例項(基於CocoaAsyncSocket)
- Netty解決半包(TCP粘包/拆包導致)讀寫問題NettyTCP
- GO語言手動處理TCP粘包GoTCP
- [ gev ] Go 語言優雅處理 TCP “粘包”GoTCP
- 粘包拆包及解決方案
- Nodejs教程06:處理接收到的GET資料NodeJS
- Nodejs教程07:處理接收到的POST資料NodeJS
- Netty 中的粘包和拆包Netty
- NIO框架之MINA原始碼解析(四):粘包與斷包處理及編碼與解碼框架原始碼
- 利用ASP傳送和接收XML資料的處理方法XML
- 再聊t-io網路程式設計架構的基礎知識:半包和粘包程式設計架構
- 資料泵匯出資料包錯處理
- 資料處理及跳轉
- TCP 粘包 - 拆包問題及解決方案TCP
- 硬核圖解TCP粘包 資料包:我只是犯了每個資料包都會犯的錯圖解TCP
- Netty原始碼學習6——netty編碼解碼器&粘包半包問題的解決Netty原始碼
- sklearn基礎及資料處理
- TCP 粘包拆包TCP
- python使用flask接收前端資料,處理後返回結果PythonFlask前端
- EXCEL表格匯入訂單資料 go怎麼接收處理?ExcelGo
- anisble部署及包衝突處理
- springboot統一異常處理及返回資料的處理Spring Boot
- TCP的粘包拆包技術TCP
- SpringMVC:資料處理及跳轉SpringMVC
- 粘包問題
- java傳送接收組播(多播)資料包(UDP包)JavaUDP
- ETL中後設資料處理的方式