接收RST回应的几种情况

来源:互联网 发布:网络应用层协议有哪些 编辑:程序博客网 时间:2024/05/22 07:40



1 端口未打开

服务器程序端口未打开而客户端来连接。这种情况是最为常见和好理解的一种了。去telnet一个未打开的TCP的端口可能会出现这种错误。这个和操作系统的实现有关。在某些情况下,操作系统也会完全不理会这些发到未打开端口请求。

比如在下面这种情况下,主机241向主机114发送一个SYN请求,表示想要连接主机114的40000端口,但是主机114上根本没有打开40000这个端口,于是就向主机241发送了一个RST。这种情况很常见。特别是服务器程序core dump之后重启之前连续出现RST的情况会经常发生。

当然在某些操作系统的主机上,未必是这样的表现。比如向一台WINDOWS7的主机发送一个连接不存在的端口的请求,这台主机就不会回应。



2 请求超时

曾经遇到过这样一个情况:一个客户端连接服务器,connect返回-1并且error=EINPROGRESS。 直接telnet发现网络连接没有问题。ping没有出现丢包。用抓包工具查看,客户端是在收到服务器发出的SYN之后就莫名其妙的发送了RST。

比如像下面这样:


有89、27两台主机。主机89向主机27发送了一个SYN,表示希望连接8888端口,主机27回应了主机89一个SYN表示可以连接。但是主机27却很不友好,莫名其妙的发送了一个RST表示我不想连接你了。

后来经过排查发现,在主机89上的程序在建立了socket之后,用setsockopt的SO_RCVTIMEO选项设置了recv的超时时间为100ms。而我们看上面的抓包结果表示,从主机89发出SYN到接收SYN的时间多达110ms。(从15:01:27.799961到15:01:27.961886, 小数点之后的单位是微秒)。因此主机89上的程序认为接收超时,所以发送了RST拒绝进一步发送数据。

3 提前关闭

关于TCP,我想我们在教科书里都读到过一句话,'TCP是一种可靠的连接'。 而这可靠有这样一种含义,那就是操作系统接收到的来自TCP连接中的每一个字节,我都会让应用程序接收到。如果应用程序不接收怎么办?你猜对了,RST。

看两段程序:


//server.cint main(int argc, char** argv)  {      int listen_fd, real_fd;      struct sockaddr_in listen_addr, client_addr;      socklen_t len = sizeof(struct sockaddr_in);      listen_fd = socket(AF_INET, SOCK_STREAM, 0);      if(listen_fd == -1)      {          perror("socket failed   ");          return -1;      }      bzero(&listen_addr,sizeof(listen_addr));      listen_addr.sin_family = AF_INET;      listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);      listen_addr.sin_port = htons(SERV_PORT);      bind(listen_fd,(struct sockaddr *)&listen_addr, len);      listen(listen_fd, WAIT_COUNT);      while(1)      {          real_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);          if(real_fd == -1)          {              perror("accpet fail  ");              return -1;          }          if(fork() == 0)          {              close(listen_fd);              char pcContent[4096];            read(real_fd,pcContent,4096);            close(real_fd);              exit(0);                      }          close(real_fd);      }         return 0;  }

这一段是server的最简单的代码。逻辑很简单,监听一个TCP端口然后当有客户端来连接的时候fork一个子进程来处理。注意看的是这一段fork里面的处理:

[cpp] view plain copy
  1. char pcContent[4096];  
  2. read(real_fd,pcContent,4096);  
  3. close(real_fd);  

 
每次只是读socket的前4096个字节,然后就关闭掉连接。


然后再看一下client的代码:

 
//client.cint main(int argc, char** argv)  {      int send_sk;      struct sockaddr_in s_addr;      socklen_t len = sizeof(s_addr);      send_sk = socket(AF_INET, SOCK_STREAM, 0);      if(send_sk == -1)      {          perror("socket failed  ");          return -1;      }      bzero(&s_addr, sizeof(s_addr));      s_addr.sin_family = AF_INET;      inet_pton(AF_INET,SER_IP,&s_addr.sin_addr);      s_addr.sin_port = htons(SER_PORT);      if(connect(send_sk,(struct sockaddr*)&s_addr,len) == -1)      {          perror("connect fail  ");          return -1;      }      char pcContent[5000]={0};    write(send_sk,pcContent,5000);    sleep(1);    close(send_sk);}
这段代码更简单,就是打开一个socket然后连接一个服务器并发送5000个字节。刚才我们看服务器的代码,每次只接收4096个字节,那么就是说客户端发送的剩下的4个字节服务端的应用程序没有接收到,服务器端的socket就被关闭掉,这种情况下会发生什么状况呢,还是抓包看一看。


前三行就是TCP的3次握手,从第四行开始看,客户端的49660端口向服务器的9877端口发送了5000个字节的数据,然后服务器端发送了一个ACK进行了确认,紧接着服务器向客户端发送了一个RST断开了连接。和我们的预期一致。

4 在一个已关闭的socket上收到数据

如果某个socket已经关闭,但依然收到数据也会产生RST。

代码如下:

客户端:

int main(int argc, char** argv)  {      int send_sk;      struct sockaddr_in s_addr;      socklen_t len = sizeof(s_addr);      send_sk = socket(AF_INET, SOCK_STREAM, 0);      if(send_sk == -1)      {          perror("socket failed  ");          return -1;      }      bzero(&s_addr, sizeof(s_addr));      s_addr.sin_family = AF_INET;      inet_pton(AF_INET,SER_IP,&s_addr.sin_addr);      s_addr.sin_port = htons(SER_PORT);      if(connect(send_sk,(struct sockaddr*)&s_addr,len) == -1)      {          perror("connect fail  ");          return -1;      }      char pcContent[4096]={0};    write(send_sk,pcContent,4096);    sleep(1);    write(send_sk,pcContent,4096);    close(send_sk);}  
服务端:
int main(int argc, char** argv)  {      int listen_fd, real_fd;      struct sockaddr_in listen_addr, client_addr;      socklen_t len = sizeof(struct sockaddr_in);      listen_fd = socket(AF_INET, SOCK_STREAM, 0);      if(listen_fd == -1)      {          perror("socket failed   ");          return -1;      }      bzero(&listen_addr,sizeof(listen_addr));      listen_addr.sin_family = AF_INET;      listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);      listen_addr.sin_port = htons(SERV_PORT);      bind(listen_fd,(struct sockaddr *)&listen_addr, len);      listen(listen_fd, WAIT_COUNT);      while(1)      {          real_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);          if(real_fd == -1)          {              perror("accpet fail  ");              return -1;          }          if(fork() == 0)          {              close(listen_fd);              char pcContent[4096];            read(real_fd,pcContent,4096);            close(real_fd);              exit(0);                      }          close(real_fd);      }         return 0;  }  
客户端在服务端已经关闭掉socket之后,仍然在发送数据。这时服务端会产生RST。

5、异常终止一个连接

终止一个连接的正常方式是一方发送F I N。有时这也称为有序释放(orderly release),因为在所有排队数据都已发送之后才发送F I N,正常情况下没有任何数据丢失。但也有可能发送一个复位报文段(RST)而不是F I N来中途释放一个连接。有时称这为异常释放。

异常终止一个连接对应用程序来说有两个优点:

1)丢弃任何待发数据并立即发送复位报文段;

2R S T的接收方会区分另一端执行的是异常关闭还是正常关闭。

应用程序使用的A P I必须提供产生异常关闭而不是正常关闭的手段

6、向处于LISTEN的端口发送数据

如果杀掉服务器端处理客户端的子进程,进程退出后,关闭它打开的所有文件描述符,此时,当服务器TCP接收到来自此客户端的数据时,由于先前打开的那个套接字接口的进程已终止,所以以RST响应。

父进程:192.168.0.80:5800*.*LISTENING

子进程:192.168.0.80:5800192.201.0.75:6500ESTALBING

杀掉子进程后,客户端向服务器端(192.168.0.80:5800 192.201.0.75:6500)发送数据,则tcp_input调用in_pcblookup查找对应的协议控制块时,会找到服务器端的父进程的协议控制块,tcp_input继续处理,在TCP_LISTEN状态收到数据的处理方式如下:

n 如果报文段中带有RST标志,则丢弃它。

n 如果带有ACK标志,则丢弃并以RST作为响应。(本例正是这种情况)

n 如果没带有SYN,也没有ACK标志,则丢弃它。

7、连接没有服务进程的端口号

在客户端连接服务器时,如果该服务器主机在制定的端口上没有进程在等待与之连接(例如服务器进程也许没有运行),那么客户端接收到RST回应,客户一接收到RST就马上返回ECONNERFUSED错误。




原创粉丝点击