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的一些說明:
對一個已經收到FIN包的socket調用read方法, 如果接收緩沖已空, 則返回0, 這就是常說的表示連接關閉. 但第一次對其調用write方法時, 如果發送緩沖沒問題, 會返回正確寫入(發送). 但發送的報文會導致對端發送RST報文, 因為對端的socket已經調用了close, 完全關閉, 既不發送, 也不接收數據. 所以, 第二次調用write方法(假設在收到RST之后), 會生成SIGPIPE信號, 導致進程退出
這個參數對應大量短鏈接的服務器很有必要!
-
shutdown(fd, SHUT_RDWR);
-
struct linger linger;
-
linger.l_onoff = 1;
-
linger.l_linger = 0;
-
setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger));
-
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完成。