应该没有人会质疑,如今是一个网络时代了。应该很多程序员在编程中须要考虑多机、局域网、广域网的各类问题。因此网络知识也是避免不了学习的。并且笔者一直以为TCP/IP网络知识在一个程序员知识体系中必需占有一席之地的。 程序员
在TCP协议中RST表示复位,用来异常的关闭链接,在TCP的设计中它是不可或缺的。发送RST包关闭链接时,没必要等缓冲区的包都发出去,直接就丢弃缓存区的包发送RST包。而接收端收到RST包后,也没必要发送ACK包来确认。 编程
其实在网络编程过程当中,各类RST错误实际上是比较难排查和找到缘由的。下面我列出几种会出现RST的状况。 缓存
服务器程序端口未打开而客户端来链接。这种状况是最为常见和好理解的一种了。去telnet一个未打开的TCP的端口可能会出现这种错误。这个和操做系统的实现有关。在某些状况下,操做系统也会彻底不理会这些发到未打开端口请求。 服务器
好比在下面这种状况下,主机241向主机114发送一个SYN请求,表示想要链接主机114的40000端口,可是主机114上根本没有打开40000这个端口,因而就向主机241发送了一个RST。这种状况很常见。特别是服务器程序core dump以后重启以前连续出现RST的状况会常常发生。 网络
固然在某些操做系统的主机上,未必是这样的表现。好比向一台WINDOWS7的主机发送一个链接不存在的端口的请求,这台主机就不会回应。 并发
曾经遇到过这样一个状况:一个客户端链接服务器,connect返回-1而且error=EINPROGRESS。 直接telnet发现网络链接没有问题。ping没有出现丢包。用抓包工具查看,客户端是在收到服务器发出的SYN以后就莫名其妙的发送了RST。 socket
好比像下面这样: 工具
有8九、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拒绝进一步发送数据。 spa
关于TCP,我想咱们在教科书里都读到过一句话,'TCP是一种可靠的链接'。 而这可靠有这样一种含义,那就是操做系统接收到的来自TCP链接中的每个字节,我都会让应用程序接收到。若是应用程序不接收怎么办?你猜对了,RST。
看两段程序:
//server.c 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; }这一段是server的最简单的代码。逻辑很简单,监听一个TCP端口而后当有客户端来链接的时候fork一个子进程来处理。注意看的是这一段fork里面的处理:
char pcContent[4096]; read(real_fd,pcContent,4096); close(real_fd);每次只是读socket的前4096个字节,而后就关闭掉链接。
而后再看一下client的代码:
//client.c 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[5000]={0}; write(send_sk,pcContent,5000); sleep(1); close(send_sk); }这段代码更简单,就是打开一个socket而后链接一个服务器并发送5000个字节。刚才咱们看服务器的代码,每次只接收4096个字节,那么就是说客户端发送的剩下的4个字节服务端的应用程序没有接收到,服务器端的socket就被关闭掉,这种状况下会发生什么情况呢,仍是抓包看一看。
前三行就是TCP的3次握手,从第四行开始看,客户端的49660端口向服务器的9877端口发送了5000个字节的数据,而后服务器端发送了一个ACK进行了确认,紧接着服务器向客户端发送了一个RST断开了链接。和咱们的预期一致。
若是某个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。
总结,本文讲了几种TCP链接中出现RST的状况。实际上确定还有无数种的RST发生,我之后会慢慢收集把更多的例子加入这篇文章。
1 从TCP协议的原理来谈谈RST攻击 http://russelltao.iteye.com/blog/1405349
2 TCP客户-服务器程序例子http://blog.csdn.net/youkuxiaobin/article/details/6917880