当接收端的内核缓冲区中的数据没有全部交给应用程序时, 如果接收端关闭socket(比如调用close或者进程挂掉),就会回以RST报文

来源:互联网 发布:入驻淘宝企业店铺 编辑:程序博客网 时间:2024/05/09 19:30

        在通常情况下,  调用close的时候, 会发FIN包, 但是, 如果接收端没有用recv把内核缓冲区的数据取完, 却执行了关闭socket的操作(比如调用close或者进程挂掉), 那么这就是异常的情况, 此时接收端会回RST报文。

        我们来看看, 服务端程序为:

#include <string.h>#include <ctype.h>#include <errno.h>#include <malloc.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/ioctl.h>#include <stdarg.h>#include <fcntl.h>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 <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netdb.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>#include <errno.h>#include <malloc.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/ioctl.h>#include <stdarg.h>#include <fcntl.h>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 20000char 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,  也是没有问题的。


       先说到这里。



阅读全文
1 0
原创粉丝点击