咱们考虑简单的客户端——服务器通讯的场景,其典型模式为: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应答。