咱們考慮簡單的客戶端——服務器通訊的場景,其典型模式爲:linux
服務端經過close()主動關閉一個TCP鏈接編程
客戶端經過read()得到了0(表示服務端沒有數據),調用close()關閉這個鏈接。服務器
在TCP層面表現爲:網絡
服務器調用close()後,向客戶端發送FIN,客戶端迴應FIN-ACK。服務器進入FIN-WAIT-2狀態,客戶端進入CLOSE-WAIT狀態。socket
客戶端調用close()後,向服務端發送FIN,服務端會用FIN-ACK。服務端進入TIME-WAIT狀態,客戶端直接進入CLOSE狀態,鏈接結束。tcp
咱們考慮一些異常狀況,客戶端上:測試
客戶端在得到read()==0後,沒有及時地調用close()調用;ui
客戶端在得到read()==0後,仍然向服務端寫入數據。code
選用《UNIX網絡編程》(第一卷)中的服務器——客戶端樣例程序。服務器部分:server
int main(int argc, char **argv) { int listenfd, connfd; socklen_t len; struct sockaddr_in servaddr, cliaddr; char buff[MAXLINE]; time_t ticks; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(9999); /* daytime server */ Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); for ( ; ; ) { len = sizeof(cliaddr); connfd = Accept(listenfd, (SA *) &cliaddr, &len); printf("connection from %s, port %d\n", Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)), ntohs(cliaddr.sin_port)); ticks = time(NULL); snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); Write(connfd, buff, strlen(buff)); Close(connfd); } }
服務器監聽9999端口,新連接創建後,發出當前時間,隨後調用close()關閉鏈接。
客戶端部分:
int main(int argc, char **argv) { int sockfd, n; socklen_t len; char recvline[MAXLINE + 1]; struct sockaddr_in servaddr, cliaddr; if (argc != 2) err_quit("usage: a.out <IPaddress>"); if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) err_sys("socket error"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(9999); /* daytime server */ if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) err_quit("inet_pton error for %s", argv[1]); if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0) err_sys("connect error"); len = sizeof(cliaddr); Getsockname(sockfd, (SA *) &cliaddr, &len); printf("local addr: %s\n", Sock_ntop((SA *) &cliaddr, sizeof(cliaddr))); while ( (n = read(sockfd, recvline, MAXLINE)) > 0) { recvline[n] = 0; /* null terminate */ if (fputs(recvline, stdout) == EOF) err_sys("fputs error"); } if (n < 0) err_sys("read error"); exit(0); }
咱們修改客戶端調用close()的處理流程。當read()==0時,阻塞睡眠。等待一段時間後,發送SIGINT使進程退出,至關於調用close()。注意到如下的現象:
服務端SOCKET處於FIN-WAIT-2狀態時,發送SIGINT信號使客戶端退出,客戶端發送FIN,服務端回覆FIN-ACK。此時,按正常流程結束連接。
服務端SOCKET等待FIN-WAIT-2狀態超時後,客戶端發送FIN,服務端回覆RST結束連接。
Orphan Socket: 從應用程序來看,此條socket鏈接已經收發數據完畢,關閉了此鏈接,可是linux內核中爲了完成正常的tcp協議(好比緩衝區中的數據)轉換,會在內核的tcp協議層繼續維護這些sock狀態,直至系統回收。處於此種狀態下的socket就是orphan socket。
分析:
服務端SOCKET關閉後,沒有對這一SOCKET的引用。這一SOCKET進入到「孤兒SOCKET「的狀態。孤兒Socket存在時,系統協議棧負責完成後續的FIN流程。當孤兒Socket超時後,系統協議棧將不存在這一Socket的信息。客戶端此時發送FIN,將收到RST應答。