粘包問題及解決
1>先模擬ssh,編寫一個遠端執行命令的程式
其實也就是客戶端輸入命令,再由服務端執行並返回結果的一個過程,基於之前的簡單通訊指令碼,改一下,如下
當服務端收取命令之後,用subpcocess模組執行命令,並且把執行結果返回給客戶端,客戶端解碼展示,客戶端
的程式碼及執行結果如下
2>粘包問題
客戶端更改接收字串位數的上限,200,並且再傳遞一個新的命令,如‘檢視C盤的檔案’,但是注意返回結果,並不是當 前命令的結果,而是上一條命令的剩餘部分!
理解TCP也稱‘流式協議’,其實這個問題也很好理解,因服務端這邊給客戶端傳送的資料肯定超過200位元組,
而客戶端這邊這一次只收取200位元組,所以,沒取完的剩餘部分依然存留在IO緩衝區(也可以理解為服務端
通往客戶端的管道)裡,等到客戶端下一次來取值,所以,客戶端再發送另外一個命令,服務端這邊執行並把
新的結果再從管道傳送給客戶端,客戶端取值就從上次的剩餘部分開始了。
這就是-------粘包現象
粘包問題只存在於TCP,not UDP
3>粘包問題解析
看一下客戶端的‘粘包現象’,進行TCP協議通訊時,,會啟用
一種優化方法(Nagle演算法),將資料量小且傳送間隔小的資料包合併成一個大的資料包傳送,(這樣可以節省網路IO
,減少延遲),所以如下所示,客戶端傳送了兩次資料,但是服務端卻一次就收完了,因為在傳送時就合併了,所以服務端
的第二次收取,沒有任何資料。
記住一點:客戶端的傳送與服務端的接收並不是一對一的關係,他們實質上都是把資料拷貝只各自的作業系統,
至於作業系統怎麼發,何時發,應用程式就根本管不到了。
4>解決粘包問題
既然產生這個問題的原因就是----客戶端設定的接收值小於實際值,其實也就是客戶端並不知道服務端究竟會傳多少資料
過來(因為就是把接收的最大值寫成無限大,也有超過的可能),所以,可以這樣解決:傳送端在傳送資料前,把自己將
要傳送的位元組流總大小讓接收端知曉,然後接收端來一個死迴圈接收完所有資料。
解決這個問題之前,先介紹一個內建的庫,struck
如上,不管原始資料大小(應該也有個範圍),它的pack方法都能轉換成一個4位長度的Bytes型別資料,然後再通過
unpack解開,得到一個元祖型別的資料格式,第一個就是真實的值,所以,可以把個值加在真實資料的前面(頭部),
就當做資料的描述資訊,告知對方我的資料大小,對方收到後,先取錢4個位元組,解碼得到資料大小,這樣,知道了
資料的大小,就知道如何收取資料了。
步驟拆解,如下服務端的寫法(注意下面的send,兩次雖然分開寫了,但是他們會組裝到一起傳送,nagle演算法)
客戶端的寫法
這樣寫其實也可以。只是也有 total_size[0] 超出接收範圍的情況。
5>更高階的解決粘包問題的方法
上面的方法始終都有資料超出範圍的風險,接下來設計一個字典型別的頭部資訊,讓頭部資訊能包含更多資料,也更完美
的解決 資料範圍這個問題,
因為始終還是在服務端返回資料問題,所以還是從服務端開始分析,優化,分解步驟
思路:服務端步驟: 1>接收命令,2>執行命令,3>【注意注意:這裡並不是傳送結果也不是傳送頭大小】,而是先
設計一個dic型別的頭資訊,裡面至少應該包含真實檔案的大小,再發送這個頭資訊的固定長度,4>傳送頭部資訊,
5>傳送真實資料。
客戶端步驟:1>傳送命令,2>接收頭部資訊大小,3>根據頭部資訊大小接收頭部資訊,4>根據頭部資訊中真實
資料大小,接收真實資料。(這裡的2,3,4步就是對應服務端的3,4,5)
服務端程式碼如下
客戶端