socket關閉連接與close和shutdown的區別及優雅關閉socket的方法:SO_LINGER


TCP主動關閉連接 
appl: close(),  --> FIN     FIN_WAIT_1 //主動關閉socket方,調用close關閉socket,發FIN                         
                <-- ACK     FIN_WAIT_2 //對方操作系統的TCP層,給ACK響應。然后給FIN                  
                <-- FIN                  
                --> ACK     "TIME_WAIT"   -- 2MSL timeout -->CLOSED
                                        //TIME_WAIT,防止ACK沒有給到對方。
注意:close時,如果TCP發送隊列中還有數據,那么將會發送RST包而不是FIN包。
參看:http://rdc.taobao.com/blog/cs/?p=1055
另外:對於Linux下來說,無論是FIN還是RST,應用層read將會返回0,可以認為對方請求關閉鏈接,調用close關閉fd即可。
TCP被動關閉連接 
            <-- FIN     "CLOSE_WAIT"   //被動方,收到對方的FIN,處於CLOSE_WAIT狀態                  
                --> ACK                    //被動方的TCP層,給ACK響應  
                appl: close(),  --> FIN     LAST_ACK      
                                         //被動方調用close,從CLOSE_WAIT轉到LAST_ACK
                                         不調close,將一直在CLOSE_WAIT狀態。                 
                 <-- ACK         --> CLOSED  
tcp是全雙工::
   close()會關閉讀寫。
   shutdown()可以選擇性的關閉讀、寫或讀寫。 
 
  主動關系SOCKET鏈接的一方,會進入TIME_WAIT(作用是防止最后一個ACK包丟失)
   TIME_WAIT的時間會非常長,因此server盡量減少主動關閉連接。
 
close和shutdown的區別:
   int close(int sockfd);   

   close(fd)調用會將描述字的引用計數減1,只有當socket描述符的引用計數為0時,才關閉socket,即發送FIN包,因此,在fork()模式中,父進程在accept()返回后,fork()子進程,由子進程處理connfd,而父進程將close(connfd);由於connfd這個socket描述符的引用計數不為0,因此並不引發FIN,所以就沒有關閉和客戶端的連接。  

  int shutdown(int sockfd, int howto);    
  // howto: SHUT_RD, SHUT_WR, SHUT_RDWR  
  shutdown()則不管socket描述符的引用計數,而直接發生FIN,因此會直接關閉鏈接。 
  shutdown()可控制read/write兩個方向的管道。 
   SHUT_RD     shutdown(sockfd, SHUT_RD);后,來自對端的數據都被確認,然后悄然丟棄。       
   SHUT_WR     half close狀態。  
close()引發的4次交互:(這里的close是client發起的) 
            client                             server  
               FIN_WAIT_1   ---- FIN M ------>       
                                       //(Server端操作系統的TCP層(網絡協議棧)響應ACK包)           
               FIN_WAIT_2   <---- ACK M+1----   CLOSE_WAIT
                                       //(這里必須調用close,才能從CLOSE_WAIT到LAST_ACK)                  
               TIME_WAIT    <------ FIN N -----  LAST_ACK 
                                        //(TIME_WAIT有一個重要的作用就是防止最后一個ACK丟失)           
                            ------- ACK N+1 ---->  CLOSE  
 

  2020/2/27更新

最近又理解了一下,close是linux中關閉文件的命令,根據linux中任何皆為文件的特性,調用shutdown后還要調用close才能消除操作系統對這個socket的管理

關於shutdown和close的一些說明:

1. 如果有多個進程共享一個套接字,close每被調用一次,計數減1,直到計數為0時,也就是所用進程都調用了close,套接字將被釋放。
2. 在多進程中如果一個進程中shutdown(sfd, SHUT_RDWR)后其它的進程將無法進行通信. 如果一個進程close(sfd)將不會影響到其它進程. 得自己理解引用計數的用法了. 有Kernel編程知識的更好理解了.
3. 只要TCP棧的讀緩沖里還有未讀取(read)數據,則調用close時會直接向對端發送RST。
4. shutdown與socket描述符沒有關系,即使調用shutdown(fd, SHUT_RDWR)也不會關閉fd,最終還需close(fd)。
5. 可以認為shutdown(fd, SHUT_RD)是空操作,因為shutdown后還可以繼續從該socket讀取數據,這點也許還需要進一步證實。
6. 

對一個已經收到FIN包的socket調用read方法, 如果接收緩沖已空, 則返回0, 這就是常說的表示連接關閉. 但第一次對其調用write方法時, 如果發送緩沖沒問題, 會返回正確寫入(發送). 但發送的報文會導致對端發送RST報文, 因為對端的socket已經調用了close, 完全關閉, 既不發送, 也不接收數據. 所以, 第二次調用write方法(假設在收到RST之后), 會生成SIGPIPE信號, 導致進程退出

7. 當有多個socket描述符指向同一socket對象時,調用close時首先會遞減該對象的引用計數,計數為0時才會發送FIN包結束TCP連接。shutdown不同,只要以SHUT_WR/SHUT_RDWR方式調用即發送FIN包。
8. SO_LINGER與close,當SO_LINGER選項開啟但超時值為0時,調用close直接發送RST(這樣可以避免進入TIME_WAIT狀態,但破壞了TCP協議的正常工作方式),SO_LINGER對shutdown無影響。
9. TCP連接上出現RST與隨后可能的TIME_WAIT狀態沒有直接關系,主動發FIN包方必然會進入TIME_WAIT狀態,除非不發送FIN而直接以發送RST結束連接。
 
SO_LINGER

這個參數對應大量短鏈接的服務器很有必要!

 
  1. shutdown(fd, SHUT_RDWR);

  2. struct linger linger;

  3. linger.l_onoff = 1;

  4. linger.l_linger = 0;

  5. setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger));

  6. close(fd);



            linger.l_linger = 0;
            setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger));
            close(fd);

    

 

 此選項指定函數close對面向連接的協議如何操作(如TCP)。內核缺省close操作是立即返回,如果有數據殘留在套接口緩沖區中則系統將試着將這些數據發送給對方。

 

SO_LINGER選項用來改變此缺省設置。使用如下結構:

struct linger {

     int l_onoff; /* 0 = off, nozero = on */

     int l_linger; /* linger time */

};

 

有下列三種情況:

1、設置 l_onoff為0,則該選項關閉,l_linger的值被忽略,等於內核缺省情況,close調用會立即返回給調用者,如果可能將會傳輸任何未發送的數據;

 

2、設置 l_onoff為非0,l_linger為0,則套接口關閉時TCP夭折連接,TCP將丟棄保留在套接口發送緩沖區中的任何數據並發送一個RST給對方,而不是通常的四分組終止序列,這避免了TIME_WAIT狀態;

 

3、設置 l_onoff 為非0,l_linger為非0,當套接口關閉時內核將拖延一段時間(由l_linger決定)。如果套接口緩沖區中仍殘留數據,進程將處於睡眠狀態,直 到(a)所有數據發送完且被對方確認,之后進行正常的終止序列(描述字訪問計數為0)或(b)延遲時間到。此種情況下,應用程序檢查close的返回值是非常重要的,如果在數據發送完並被確認前時間到,close將返回EWOULDBLOCK錯誤且套接口發送緩沖區中的任何數據都丟失。close的成功返回僅告訴我們發送的數據(和FIN)已由對方TCP確認,它並不能告訴我們對方應用進程是否已讀了數據。如果套接口設為非阻塞的,它將不等待close完成。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM