在通常情况下, 调用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 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 8765
tcp 0 0 10.100.70.139:32972 10.100.70.140:8765 ESTABLISHED off (0.00/0/0)
xxxxxx$
xxxxxx$
xxxxxx$ lsof -i:32972
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
client 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:32972
xxxxxx$
xxxxxx$
xxxxxx$ ps -aux | grep 1897
user_00 1897 0.0 0.0 3452 636 pts/8 S+ 19:47 0:00 ./client
user_00 3630 0.0 0.0 12152 684 pts/7 R+ 19:50 0:00 grep --color=auto 1897
xxxxxx$
可见, 在RST之前, tcp处理ESTABLISHED的状态, 客户单临时端口为32972, 且可知, 这个端口是client进程打开的。 但是, 在RST之后, client进程打开的这个socke就不存在了(从lsof -i:32972的结果可以看出)。
所以, 难怪send会失败呢, socket都关闭了, 还send个毛线。
对了,还有个问题, 客户端的socket被RST关闭后, 如果程序再调用close来关闭socket, 也是没有问题的。
先说到这里。