1. 程式人生 > 其它 >13解決TCP粘包缺包問題

13解決TCP粘包缺包問題

一、粘包現象出現

a)客戶端粘包現象
---- 客戶端因為有一個Nagle優化演算法要是有三個資料包,則他們可能會被Nagle優化演算法直接合並一個數據包傳送出去
b)伺服器端粘包現象
---- 伺服器端兩次 recv之間可能間隔100毫秒,那可能在這100毫秒內,客戶端這三個包都到了,這三個包都被儲存到了伺服器端的 針對該TCP連線收資料的 緩衝(區)中。
c)缺包
---- send("abc......."); //8000位元組;這個可能被客戶端拆成6個包傳送出去了;但是網路可能出現延遲或者阻塞,多次才能接收完,伺服器端第一次recv() = "ab",recv = "c...",recv.........,recv() = ".....de".... [缺包]

二、解決辦法

如何解決拆包問題:給收發的資料包定義一個統一的格式[規則];c/s都按照這個格式來,就能夠解決粘包問題;
包格式: 包頭+包體 的格式;
其中 包頭 是固定長度【10位元組】,在包頭中,有一個成員變數會記錄整個包【包頭+包體】的長度;
這樣的話,先收包頭,從包頭中,我知道了整個包的長度,然後 用整個包的長度 - 10個位元組 = 包體的長度。
我再收 “包體的長度”這麼多的位元組; 收滿了包體的長度位元組數,我就認為,一個完整的資料包【包頭+包體】收完;

三、程式設計

3.1準備

這裡我們是以LT的模式進行讀取的。所以我們讀取緩衝區的資料,一直讀到其為空。
ssize_t reco = recvproc(c,c->precvbuf,c->irecvlen);


reco的意義是:讀取了這麼多。
讀取的資料一直儲存在c->precvbuf的位置,在緩衝區是以追加的模式儲存。c是全域性的。

要是以ET模式進行讀取,該怎麼讀取?檢視上一隨筆的內容。

在處理一個TCP連線的時候,要是連線的緩衝區中有資料,那麼epool_wait()會把裡面的資料讀取出來,讀取的過程中,我們一開始的時候並不知道讀取的是一個包,還是幾個包,還是半個包,只有通過讀取裡面的資料才能知道。
-------------------------------------------------------
下面在讀一個包的過程中,包有五個代表位置可能被截斷出來。定義讀取包的狀態。

包:
-----------------包頭-----------+--------------包體-----------------|
--^-----------------^------------^-------------^--------------------^

在收完了包頭,我們需要為整個資料包加上訊息頭。
加上訊息頭的目的是為了標記這個訊息有沒有過期,還是不是原來的連線,用於處理過期事件,和標記這個訊息是屬於那個物件連線,方便之後收發資料。

3.2程式流程

\------------------------------------------------------------------
ngx_wait_request_handler()
\------------------------------------------------------------------
\----c->curStat == \_PKG_HD_INIT(接收包頭開始階段)
\----recvproc(c,c->precvbuf,c->irecvlen)
\----c->curStat == \_PKG_HD_RECVING(接收包頭中階段)
\--------中間可能多次進入接收包頭recvproc(c,c->precvbuf,c->irecvlen)
\--------ngx_wait_request_handler_proc_p1(c);
\------------c->curStat = \_PKG_BD_INIT;(接收包體開始)
\-------------------------------------------------------------------
\----recvproc(c,c->precvbuf,c->irecvlen)
\----c->curStat == \_PKG_BD_RECVING(接收包體中階段)
\--------中間可能多次進入接收包體recvproc(c,c->precvbuf,c->irecvlen)
\--------ngx_wait_request_handler_proc_plast(c);
\------------c->curStat == \_PKG_HD_INIT(接收包頭開始下一階段)
\-------------------------------------------------------------------

3.3具體函式

ngx_wait_request_handler_proc_p1(c);包頭收完整後的處理
----1.解析包頭資料
----2.加訊息頭

ngx_wait_request_handler_proc_plast(c);收到一個完整包後的處理
----1.將整個(一條)資料包(記憶體)加入訊息佇列中
--------inMsgRecvQueue(c->pnewMemPointer);