在socket系統調用中,如何完成三次握手和四次揮手

在socket系統調用中,如何完成三次握手和四次揮手:

        SOCK_DGRAM,即UDP中的connect操作知識在內核中註冊對方機器的IP和PORT信息,並沒有建立鏈接的過程,即沒有發包,close也不發包)。

       而SOCK_STREAM對應如下:

        connect會完成TCP的三次握手,客戶端調用connect後,由內核中的TCP協議完成TCP的三次握手;

        close操作會完成四次揮手。

三次握手對應的Berkeley Socket API:

          從圖中,可以看出和連接建立相關的API有:connect, listen, accept   3個,connect用在客戶端,另外2個用在服務端。

          對於TCP/IP protocol stack來說,TCP層的tcp_in&tcp_out也參與這個過程。我們這裏只討論這3個應用層的API幹了什麼事情。

         (1) connect

                     發送了一個SYN,收到Server的SYN+ACK後,代表連接完成發送最後一個ACK是protocol stack,tcp_out完成的

         (2)listen

                    在server這端,準備了一個未完成的連接隊列,保存只收到SYN_C的socket結構;

                    準備了已完成的連接隊列,即保存了收到了最後一個ACK的socket結構。

        (3)accept

                   應用進程調用accept的時候,就是去檢查上面說的已完成的連接隊列,如果隊列裏有連接,就返回這個連接;

                   如果沒有,即空的,blocking方試調用,就睡眠等待;

                                                   nonblocking方式調用,就直接返回,一般一"EWOULDBLOCK“ errno告訴調用者,連接隊列是空的。

注意:

        在上面的socket API和TCP STATE的對應關係中,TCP協議中,客戶端收到Server響應時,可能會有會延遲確認。

        即客戶端收到數據後,會阻塞給Server端確認。

        可以在每次收到數據後:

               調用setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, (int[]){1}, sizeof(int));  快速給Server端確認。

              

我們如何判斷有一個建立鏈接請求一個關閉鏈接請求

      建立鏈接請求:

1、connect將完成三次握手,accept所監聽的fd上,產生讀事件,表示有新的鏈接請求;          

關閉鏈接請求:

1、close將完成四次揮手,如果有一方關閉sockfd,對方將感知到有讀事件,

      如果read讀取數據時,返回0,即讀取到0個數據,表示有斷開鏈接請求。(在操作系統中已經這麼定義)


關閉鏈接過程中的TCP狀態和SOCKET處理,及可能出現的問題:

1. TIME_WAIT

TIME_WAIT 是主動關閉 TCP 連接的那一方出現的狀態,系統會在 TIME_WAIT 狀態下等待 2MSL(maximum segment lifetime  )後才能釋放連接(端口)。通常約合 4 分鐘以內。

TIME_WAIT 狀態等待 2MSL 的意義:

         1、確保連接可靠地關閉; 即防止最後一個ACK丟失

          2、避免產生套接字混淆(同一個端口對應多個套接字)

在這裏要解釋一個概念:化身。當關閉一個連接後,過一段時間在相同的IP地址和端口之間建立另一個連接,後一個連接就叫做前一個連接的化身。TCP不給處於TIME_WAIT狀態的連接發起新的化身。

爲什麼說可以用來避免套接字混淆呢?

       一方close發送了關閉鏈接請求,對方的應答遲遲到不了(例如網絡原因),導致TIME_WAIT超時,此時這個端口又可用了,我們在這個端口上又建立了另外一個socket鏈接。如果此時對方的應答到了,怎麼處理呢?其實這個在TCP層已經處理了,由於有TCP序列號,所以內核TCP層,就會將包丟掉,並給對方發包,讓對方將sockfd關閉。所以應用層是沒有關係的。即我們用socket API編寫程序,就不用處理。

注意::

         TIME_WAIT是指操作系統的定時器會等2MSL而主動關閉sockfd的一方,並不會阻塞。(即應用程序在close時,並不會阻塞)。

         當主動方關閉sockfd後,對方可能不知道這個事件。那麼當對方(被動方)寫數據,即send時,將會產生錯誤,即errno爲: ECONNRESET。

服務器產生大量 TIME_WAIT 的原因:(一般我們不這樣開發Server,但是web服務器等這種多客戶端的Server,是需要在完成一次請求後,主動關閉連接的,否則可能因爲句柄不夠用,而造成無法提供服務。)

  服務器存在大量的主動關閉操作,需關注程序何時會執行主動關閉(如批量清理長期空閒的套接字等操作)。

  一般我們自己寫的服務器進行主動斷開連接的不多,除非做了空閒超時之類的管理。(TCP短鏈接是指,客戶端發送請求給服務器,客戶端收到服務器端的響應後,關閉鏈接)。


2. CLOSE_WAIT

CLOSE_WAIT 是被動關閉 TCP 連接時產生的

如果收到另一端關閉連接的請求後本地(Server端)不關閉相應套接字就會導致本地套接字進入這一狀態

(如果對方關閉了,沒有收到關閉鏈接請求,就是下面的不正常情況)

按TCP狀態機,我方收到FIN,則由TCP實現發送ACK,因此進入CLOSE_WAIT狀態。但如果我方不執行close(),就不能由CLOSE_WAIT遷移到LAST_ACK,則系統中會存在很多CLOSE_WAIT狀態的連接

如果存在大量的 CLOSE_WAIT,則說明客戶端併發量大,且服務器未能正常感知客戶端的退出,也並未及時 close 這些套接字。(如果不及時處理,將會出現沒有可用的socket描述符的問題,原因是sockfd耗盡)。

正常情況下::  

       一方關閉sockfd,外一方將會有讀事件產生, 當recv數據時,如果返回值爲0,表示對端已經關閉。此時我們應該調用close,將對應的sockfd也關閉掉。

不正常情況下:: 

       一方關閉sockfd,另外一方並不知道,(比如在close時,自己斷網了,對方就收不到發送的數據包)。此時,如果另外一方在對應的sockfd上寫send或讀recv數據。

recv時,將會返回0,表示鏈接已經斷開。

send時, 將會產生錯誤,errno爲ECONNRESET


長連接API小心“串包”問題:

有時候,我們以API的方式爲客戶提供服務,如果此時你提供的API採用TCP長連接,而且還使用了TCP接收超時機制(API一般都會提供設置超時的接口,例如通過setsockopt設置SO_RCVTIMEO或這select),那你可能需要小心下面這種情況(這裏姑且稱之爲“竄包”,應用程序沒有將應答包與請求包正確對應起來):
      如果某一筆以TCP接收的請求超時(例如設置爲3秒)返回客戶,此時客戶繼續使用該鏈接發送第二個請求,此時後者就有可能收到前一筆請求的應答(前一筆的應答在3秒後纔到達),倘若錯誤的將此應答當做後者的應答處理,那就可能會導致嚴重的問題。如果網絡不穩定,或者後臺處理較慢,超時嚴重,其中一筆請求應答竄包了,很可能導致後續多個請求應答竄包。例如網上常見的抽獎活動,第一個用戶中了一個iPad,而第二個用戶在後臺中僅爲一個虛擬物品,若此時出現竄包,那第二個用戶也會被提示中了iPad。

SOCKET API和TCP STATE的對應關係__三次握手(listen,accept,connect)__四次揮手close及TCP延遲確認(調用一次setsockopt函數,設置TCP_QUICKACK)__長連接API小心“竄包”問題 - 無影 - 專注、堅持、思索

這個問題,初看起來最簡單的解決辦法就是:一旦發現有請求超時,就斷開並重新建立連接,但這種方案理論上是不嚴謹的,考慮下面這種情況:
        1、應答超時的原因是因爲應答包在網絡中游蕩(例如某個路由器崩潰等原因,這類在網絡中游蕩的包,俗稱迷途的分組);
        2、API在檢測到超時後,斷開並重新建立的連接的IP和Port與原有連接相同(新連接爲被斷開連接的化身);
        3、在新連接建立後,立即發送了一個新的請求,但隨後那個迷途的應答包又找到了回家的路,重新到達,此時新連接很有可能將這個不屬於自己的包,當做第二個請求的應答(該包的TCP Sequence恰好是新連接期望的TCP Sequence,這種情況是可能的,但是基本不可能發生)。
       注:正常情況下,TCP通過維持TIME_WAIT狀態2MSL時間,以避免因化身可能帶來的問題。但是在實際應用中,我們可以通過調整系統參數,或者利用SO_LINGER選項使得close一個連接時,直接到CLOSE狀態,跳過TIME_WAIT狀態,又或者利用了端口重用,這樣就可能會出現化身。在實際應用中,上面這種情況基本不會發生,但是從理論上來說,是可能的。

再仔細分析,就會發現這個問題表面上看是因爲“竄包”導致,但本質原因是程序在應用層沒有對協議包效驗。例如另外一種情況:A、B兩個客戶端與Server端同時建立了兩個連接,如果此時Server端有BUG,錯將A的應答,發到B連接上,此時如果沒有效驗,那同樣會出現A請求收到B應答的情況。所以這個問題解決之道就是:在應用層使用類似序列號這類驗證機制,確保請求與應答的一一對應。

轉載: http://blog.csdn.net/coppersmith/article/details/10390105

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