阻塞模式下的send、recv、close


對於send函數: 

    send函數只負責將數據提交給協議層。 
   當調用該函數時,send先比較待發送數據的長度len和套接字s的發送緩衝區的長度,如果len大於s的發送緩衝區的長度,該函數返回SOCKET_ERROR; 
    如果len小於或者等於s的發送緩衝區的長度,那麼send先檢查協議是否正在發送s的發送緩衝中的數據; 
    如果是就等待協議把數據發送完,如果協議還沒有開始發送s的發送緩衝中的數據或者s的發送緩衝中沒有數據,那麼send就比較s的發送緩衝區的剩餘空間和len; 
    如果len大於剩餘空間大小,send就一直等待協議把s的發送緩衝中的數據發送完,如果len小於剩餘空間大小,send就僅僅把buf中的數據copy到剩餘空間裏(注意並不是send把s的發送緩衝中的數據傳到連接的另一端的,而是協議傳的,send僅僅是把buf中的數據copy到s的發送緩衝區的剩餘空間裏)。 
    如果send函數copy數據成功,就返回實際copy的字節數,如果send在copy數據時出現錯誤,那麼send就返回SOCKET_ERROR; 
    如果send在等待協議傳送數據時網絡斷開的話,那麼send函數也返回SOCKET_ERROR。要注意send函數把buf中的數據成功copy到s的發送緩衝的剩餘空間裏後它就返回了,但是此時這些數據並不一定馬上被傳到連接的另一端。 
    如果協議在後續的傳送過程中出現網絡錯誤的話,那麼下一個Socket函數就會返回SOCKET_ERROR。(每一個除send外的Socket函數在執行的最開始總要先等待套接字的發送緩衝中的數據被協議傳送完畢才能繼續,如果在等待時出現網絡錯誤,那麼該Socket函數就返回SOCKET_ERROR)    

對於recv函數: 

    recv先檢查套接字s的接收緩衝區,如果s接收緩衝區中沒有數據或者協議正在接收數據,那麼recv就一直等待,直到協議把數據接收完畢。當協議把數據接收完畢,recv函數就把s的接收緩衝中的數據copy到buf中(注意協議接收到的數據可能大於buf的長度,所以在這種情況下要調用幾次recv函數才能把s的接收緩衝中的數據copy完。recv函數僅僅是copy數據,真正的接收數據是協議來完成的),recv函數返回其實際copy的字節數。如果recv在copy時出錯,那麼它返回SOCKET_ERROR;如果recv函數在等待協議接收數據時網絡中斷了,那麼它返回0 
    對方優雅的關閉socket並不影響本地recv的正常接收數據;如果協議緩衝區內沒有數據,recv返回0,指示對方關閉;如果協議緩衝區有數據,則返回對應數據(可能需要多次recv),在最後一次recv時,返回0,指示對方關閉; 


對於"關閉連接" 
socket連接的關閉分爲:"優雅關閉"和"強制關閉"; 
MSDN上有說明:closesocket的關閉動作依賴於socket選項SO_LINGER和SO_DONTLINGER;(SO_DONTLINGER爲缺省值),其含義如下: 
選項          阻塞時間  關閉方式  等待關閉與否 
SO_DONTLINGER 不關心    優雅      否 
SO_LINGER     零        強制      否 
SO_LINGER     非零      優雅      是 
MSDN上,還有說明:爲了確保數據能被對方接收,應用程序應當在調用closesocket之前調用shutdown。 
closesocket應最後被調用,以便系統釋放socket句柄及相關資源。

 

在發送端,一次發送4092個字節,

在接收端,一次接收4092個字節,

但是在接收端,偶爾會出現 socket.receive 接收不全的情況 ,

ret = sockTemp.Receive(bBuffer,iBufferLen,0); //也有可能無法收到全部數據! 
必須要考慮0 < ret < iBufferLen的情況:繼續接收iBufferLen - ret字節,然後合併

 

Socket的Send,Recv的長度問題:

一個包沒有固定長度,以太網限制在46-1500字節,1500就是以太網的MTU,超過這個量,TCP會爲IP數據報設置偏移量進行分片傳輸,現在一般可允許應用層設置8k(NTFS系統)的緩衝區,8k的數據由底層分片,而應用層看來只是一次發送。
        windows的緩衝區經驗值是4k。
        Socket本身分爲兩種,流(TCP)和數據報(UDP),你的問題針對這兩種不同使用而結論不一樣。甚至還和你是用阻塞、還是非阻塞Socket來編程有關。
        1、通信長度,這個是你自己決定的,沒有系統強迫你要發多大的包,實際應該根據需求和網絡狀況來決定。對於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作爲消息或數據報,它一定是整包到達接收方。
        2、關於接收,一般的發包都有包邊界,首要的就是你這個包的長度要讓接收方知道,於是就有個包頭信息,對於TCP,接收方先收這個包頭信息,然後再收包數據。一次收齊整個包也可以,可要對結果是否收齊進行驗證。這也就完成了組包過程。UDP,那你只能整包接收了。要是你提供的接收Buffer過小,TCP將返回實際接收的長度,餘下的還可以收,而UDP不同的是,餘下的數據被丟棄並返回WSAEMSGSIZE錯誤。注意TCP,要是你提供的Buffer佷大,那麼可能收到的就是多個發包,你必須分離它們,還有就是當Buffer太小,而一次收不完Socket內部的數據,那麼Socket接收事件(OnReceive),可能不會再觸發,使用事件方式進行接收時,密切注意這點。這些特性就是體現了流和數據包的區別。
        補充一點,接收BuffSize >= 發送BuffSize >= 實際發送Size,對於內外部的Buffer都適用,上面講的主要是Socket內部的Buffer大小關係。
        3、TCP是有多少就收多少,如果沒有當然阻塞Socket的recv就會等,直到有數據,非阻塞Socket不好等,而是返回WSAEWOULDBLOCK。UDP,如果沒有數據,阻塞Socket就會等,非阻塞Socket也返回WSAEWOULDBLOCK。如果有數據,它是會等整個發包到齊,並接收到整個發包,才返回。


發佈了20 篇原創文章 · 獲贊 3 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章