本文共 3497 字,大约阅读时间需要 11 分钟。
在通常情况下, 调用close的时候, 会发FIN包, 但是, 如果接收端没有用recv把内核缓冲区的数据取完, 却执行了关闭socket的操作(比如调用close或者进程挂掉), 那么这就是异常的情况, 此时接收端会回RST报文。
我们来看看, 服务端程序为:
#include启动它。#include #include #include #include #include #include #include #include int main(){ int sockSrv = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addrSrv; addrSrv.sin_family = AF_INET; addrSrv.sin_addr.s_addr = INADDR_ANY; addrSrv.sin_port = htons(8765); bind(sockSrv, (const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in)); listen(sockSrv, 5); struct sockaddr_in addrClient; int len = sizeof(struct sockaddr_in); int sockConn = accept(sockSrv, (struct sockaddr *)&addrClient, (socklen_t*)&len); getchar(); close(sockConn); while(1); close(sockSrv); return 0;}
客户端程序为:
#include启动它。#include #include #include #include #include #include #include #include #include #include #include #include #include #include int main(){ int sockClient = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addrSrv; addrSrv.sin_addr.s_addr = inet_addr("10.100.70.140"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(8765); connect(sockClient, ( const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in)); while(1) { #define N 20000 char szSendBuf[N] = {0}; for(unsigned int i = 0; i < N; i++) //字符数组最后一个字符不要求是‘\0’ { szSendBuf[i] = 'a'; } int iRet = send(sockClient, szSendBuf, sizeof(szSendBuf) , 0); printf("send size is %d, iRet is %d\n", sizeof(szSendBuf), iRet); getchar(); } close(sockClient); return 0;}
我们看到, 客户端在向服务端发送数据, 但是, 服务端并没有recv的操作, 此时如果在服务端按enter键盘执行close操作, 或者直接ctrl c杀掉进程, 都会关闭服务端的socket, 我们来抓包看看:
19:34:06.307961 IP 10.100.70.140.ultraseek-http > 10.100.70.139.31664: Flags [R.], seq 2861123895, ack 2489467932, win 122, options [nop,nop,TS val 1558194579 ecr 1558194032], length 0 0x0000: 4500 0034 6bad 4000 4006 2d38 0a64 468c E..4k.@.@.-8.dF. 0x0010: 0a64 468b 223d 7bb0 aa89 4937 9462 441c .dF."={...I7.bD. 0x0020: 8014 007a 5f6f 0000 0101 080a 5ce0 2993 ...z_o......\.). 0x0030: 5ce0 2770 0000 0000 0000 0000 0000 0000 \.'p............ 0x0040: 0000 0000 ....可以看到, 服务端没有把内核缓冲区的数据转移到应用程序时,突然关闭socket, 会发出RST包。 收到RST包后, 客户端并不会回ACK包。
而且, 如果客户端执行继续发送数据, 那么会有如下结果: send size is 20000, iRet is -1, 实际上就是没有发送任何数据发送, 这一点从tcpdump实际抓包也可以看出来。
最后一个问题, 此时, 如果客户端关掉close socket或者杀死客户端进程(也就是关闭socket), 那么客户端还会有网络包(比如FIN)发出吗? 不会的! 为什么? 因为在服务端发出RST的时候, 客户端的socket已经被关闭了, 这一点, 不仅仅可以从tcpdump抓包验证, 还可以从客户端打开的socket句柄来验证。 一起来看下, 在服务端发送RST前后, 客户端进程打开socket的变化为:
xxxxxx$ netstat -nao | grep 8765tcp 0 0 10.100.70.139:32972 10.100.70.140:8765 ESTABLISHED off (0.00/0/0)xxxxxx$xxxxxx$xxxxxx$ lsof -i:32972COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEclient 1897 user_00 3u IPv4 449256072 0t0 TCP 10.100.70.139:32972->10.100.70.140:ultraseek-http (ESTABLISHED)xxxxxx$ xxxxxx$ xxxxxx$ lsof -i:32972xxxxxx$ xxxxxx$ xxxxxx$ ps -aux | grep 1897user_00 1897 0.0 0.0 3452 636 pts/8 S+ 19:47 0:00 ./clientuser_00 3630 0.0 0.0 12152 684 pts/7 R+ 19:50 0:00 grep --color=auto 1897xxxxxx$可见, 在RST之前, tcp处理ESTABLISHED的状态, 客户单临时端口为32972, 且可知, 这个端口是client进程打开的。 但是, 在RST之后, client进程打开的这个socke就不存在了(从lsof -i:32972的结果可以看出)。
所以, 难怪send会失败呢, socket都关闭了, 还send个毛线。
对了,还有个问题, 客户端的socket被RST关闭后, 如果程序再调用close来关闭socket, 也是没有问题的。
先说到这里。
转载地址:http://ejwti.baihongyu.com/