UNIX網絡編程——Socket粘包問題

1、兩個簡單概念長鏈接與短鏈接:
一、長鏈接
算法

    Client方與Server方先創建通信鏈接,鏈接創建後不斷開, 而後再進行報文發送和接收。編程

二、短鏈接 網絡

    Client方與Server每進行一次報文收發交易時才進行通信鏈接,交易完畢後當即斷開鏈接。此種方式經常使用於一點對多點 通信,好比多個Client鏈接一個Server。tcp

 

二 、何時須要考慮粘包問題?函數

       若是利用tcp每次發送數據,就與對方創建鏈接,而後雙方發送完一段數據後,就關閉鏈接,這樣就不會出現粘包問題(由於只有一種包結構,相似於http協議)。關閉鏈接主要要雙方都發送close鏈接(參考tcp關閉協議)。如:A須要發送一段字符串給B,那麼A與B創建鏈接,而後發送雙方都默認好的協議字符如"hello give me sth abour yourself",而後B收到報文後,就將緩衝區數據接收,而後關閉鏈接,這樣粘包問題不用考慮到,由於你們都知道是發送一段字符。
      
性能

       若是發送數據無結構,如文件傳輸,這樣發送方只管發送,接收方只管接收存儲就ok,也不用考慮粘包。
優化


       若是雙方創建鏈接,須要在鏈接後一段時間內發送不一樣結構數據,如鏈接後,有好幾種結構
 1)"hello give me sth abour yourself"
 2)"Don't give me sth abour yourself"
      那這樣的話,若是發送方連續發送這個兩個包出去,接收方一次接收可能會是"hello give me sth abour yourselfDon't give me sth abour yourself" 這樣接收方就傻了,究竟是要幹嗎?不知道,由於協議沒有規定這麼詭異的字符串,因此要處理把它分包,怎麼分也須要雙方組織一個比較好的包結構,因此通常可能會在頭加一個數據長度之類的包,以確保接收。
 
spa

3、 粘包出現緣由:設計

       在TCP傳輸中會出現粘包,UDP不會出現粘包,由於它有消息邊界。
進程

  • 發送端須要等發送緩衝區滿才發送出去,形成粘包;
  • 接收方不及時接收緩衝區的包,形成多個包接收。


4、解決辦法:

       爲了不粘包現象,可採起如下三種措施:

  • 對於發送方引發的粘包現象,用戶可經過編程設置來避免,TCP提供了強制數據當即傳送的操做指令push,TCP軟件收到該操做指令後,就當即將本段數據發送出去,而沒必要等待發送緩衝區滿;
  • 對於接收方引發的粘包,則可經過優化程序設計、精簡接收進程工做量、提升接收進程優先級等措施,使其及時接收數據,從而儘可能避免出現粘包現象;
  • 由接收方控制,將一包數據按結構字段,人爲控制分屢次接收,而後合併,經過這種手段來避免粘包。

       以上提到的三種措施,都有其不足之處:

       第一種編程設置方法雖然能夠避免發送方引發的粘包,但它關閉了優化算法,下降了網絡發送效率,影響應用程序的性能,通常不建議使用。

       第二種方法只能減小出現粘包的可能性,但並不能徹底避免粘包,當發送頻率較高時,或因爲網絡突發可能使某個時間段數據包到達接收方較快,接收方仍是有可能來不及接收,從而致使粘包。

       第三種方法雖然避免了粘包,但應用程序的效率較低,對實時應用的場合不適合。



       一個包沒有固定長度,以太網限制在46-1500字節,1500就是以太網的MTU,超過這個量,TCP會爲IP數據報設置偏移量進行分片傳輸,如今通常可容許應用層設置8k(NTFS系)的緩衝區,8k的數據由底層分片,而應用看來只是一次發送。Socket自己分爲兩種,流(TCP)和數據報(UDP),你的問題針對這兩種不一樣使用而結論不同。甚至還和你是用阻塞、仍是非阻塞Socket來編程有關。


       一、通訊長度,這個是你本身決定的,沒有系統強迫你要發多大的包,實際應該根據需求和網絡情況來決定。對於TCP,這個長度能夠大點,但要知道,Socket內部默認的收發緩衝區大小大概是8K,你能夠用setsockopt來改變。但對於UDP,就不要太大,通常在1024至10K。注意一點,你不管發多大的包,IP層和鏈路層都會把你的包進行分片發送通常局域網就是1500左右,廣域網就只有幾十字節。分片後的包將通過不一樣的路由到達接收方,對於UDP而言,要是其中一個分片丟失,那麼接收方的IP層將把整個發送包丟棄,這就造成丟包。顯然,要是一個UDP發包佷大,它被分片後,鏈路層丟失分片的概率就佷大,你這個UDP包,就佷容易丟失,可是過小又影響效率。最好能夠配置這個值,以根據不一樣的環境來調整到最佳狀態。

       send()函數返回了實際發送的長度,在網絡不斷的狀況下,它毫不會返回(發送失敗的)錯誤,最多就是返回0。對於TCP你能夠字節寫一個循環發送。當send函數返回SOCKET_ERROR時,才標誌着有錯誤。但對於UDP,你不要寫循環發送,不然將給你的接收帶來極大的麻煩。因此UDP須要用setsockopt來改變Socket內部Buffer的大小,以能容納你的發包。明確一點,TCP做爲流,發包是不會整包到達的,而是源源不斷的到,那接收方就必須組包UDP做爲消息或數據報,它必定是整包到達接收方

       二、關於接收,通常的發包都有包邊界,首要的就是你這個包的長度要讓接收方知道,因而就有個包頭信息,對於TCP,接收方先收這個包頭信息,而後再收包數據。一次收齊整個包也能夠,可要對結果是否收齊進行驗證。這也就完成了組包過程。UDP,那你只能整包接收了。要是你提供的接收Buffer太小,TCP將返回實際接收的長度,餘下的還能夠收,而UDP不一樣的是,餘下的數據被丟棄並返回WSAEMSGSIZE錯誤。注意TCP,要是你提供的Buffer佷大,那麼可能收到的就是多個發包,你必須分離它們,還有就是當Buffer過小,而一次收不完Socket內部的數據,那麼Socket接收事件(OnReceive),可能不會再觸發,使用事件方式進行接收時,密切注意這點。這些特性就是體現了流和數據包的區別。

相關文章
相關標籤/搜索