应该没有人会质疑,现在是一个网络时代了。应该不少程序员在编程中需要考虑多机、局域网、广域网的各种问题。所以网络知识也是避免不了学习的。而且笔者一直觉得TCP/IP网络知识在一个程序员知识体系中必需占有一席之地的。

在TCP协议中RST表示复位,用来异常的关闭连接,在TCP的设计中它是不可或缺的。发送RST包关闭连接时,不必等缓冲区的包都发出去,直接就丢弃缓存区的包发送RST包。而接收端收到RST包后,也不必发送ACK包来确认。

其实在网络编程过程中,各种RST错误其实是比较难排查和找到原因的。下面我列出几种会出现RST的情况。

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。

看两段程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//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里面的处理:

1
2
3
char pcContent[4096];
read(real_fd,pcContent,4096);
close(real_fd);

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

然后再看一下client的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//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断开了连接。和我们的预期一致。

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

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

代码如下:

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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

几种TCP连接中出现RST的情况(转载)的更多相关文章

  1. 几种TCP连接中出现RST的情况

    http://blog.chinaunix.net/uid-24517549-id-3991141.html http://blog.chinaunix.net/uid-24517549-id-399 ...

  2. TCP 中出现RST的情况

    http://www.360doc.com/content/13/0702/10/1073512_297069771.shtml 原 几种TCP连接中出现RST的情况 发表于1年前(2013-05-0 ...

  3. 【NodeJs】Ctrl+C在Linux平台和Windows平台下的TCP连接中的不同表现

    Linux平台:CentOS release 6.5 (Final) Windows平台:Windows 7 旗舰版 服务器端代码如下: var net = require('net'); var s ...

  4. TCP连接中time_wait在开发中的影响-搜人以鱼不如授之以渔

    根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方socket将进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),T ...

  5. tcp连接的状态变迁以及如何调整tcp连接中处于time_wait的时间

    一.状态变迁图 二.time_wait状态 针对time_wait和close_wait有个简单的描述帮助理解: Due to the way TCP/IP works, connections ca ...

  6. 修改Linux内核参数,减少TCP连接中的TIME-WAIT

    一台服务器CPU和内存资源额定有限的情况下,如何提高服务器的性能是作为系统运维的重要工作.要提高Linux系统下的负载能力,当网站发展起来之后,web连接数过多的问题就会日益明显.在节省成本的情况下, ...

  7. 服务器TCP连接中 TIME_WAIT 状态过多

    今天查看服务器的TCP连接数,发现其中 TIME_WAIT 状态的太多了: # netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a ...

  8. TCP/IP详解--TCP连接中TIME_WAIT状态过多

    TIMEWAIT状态本身和应用层的客户端或者服务器是没有关系的.仅仅是主动关闭的一方,在使用FIN|ACK|FIN|ACK四分组正常关闭TCP连接的时候会出现这个TIMEWAIT.服务器在处理客户端请 ...

  9. TCP 连接中的TIME_WAIT

    原文:http://blog.csdn.net/wangpengqi/article/details/17245349 这就有个细节,一次http请求,谁会先断开TCP连接?什么情况下客户端先断,什么 ...

随机推荐

  1. python基础之类与对象,继承与派生

    类与对象 对象的本质也就是一个名称空间而已,用于存放自己独有的属性,而类中存放的是对象共有的属性. __init__会在调用类时自动触发 调用类时发生两件事: 1.创建一个空对象stu1 2.自动触发 ...

  2. [转]Hibernate中Session的get和load

    hibernate中Session接口提供的get()和load()方法都是用来获取一个实体对象,在使用方式和查询性能上有一些区别.测试版本:hibernate 4.2.0. get Session接 ...

  3. textField代理方法

    //textField代理方法// 返回no 不能输入文字- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange ...

  4. 研究人员发现绝大部分酷派(Coolpad)手机暗藏后门(转)

    隐私问题被曝光得越来越多,随着物联网的发展,只会变得越来越严重,不过从当前看来 ,国人对隐私的重视度还远没有国外,期待加强对隐私的保护策略.   转自:http://www.freebuf.com/n ...

  5. 关于 js 中的回调函数 callback

    本文写于1年前 曾经的学习文章如今拿出来分享 前言 其实我一直很困惑关于js中的callback,困惑的原因是,学习中这块看的资料少,但是平时又经常见,偶尔复制一下前人代码,功能实现了也就不再去追其原 ...

  6. Android内存优化2 了解java内存分配 2

    JVM内存模型 Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是: 1. 程序计数器 2. Java虚拟机栈 3. 本地方法栈 4. 堆 5. 方法区. ...

  7. 表单对象属性disabled和readOnly

    简而言之: disabled 和 readonly 区别: disabled 被禁用后的元素,不会随表单提交 readonly 不可修改, 会随表单提交

  8. hibernate反向工程 (eclipse和myeclipse)【转】

    myeclipse下hibernate反向工程: 1.选择myeclipse hibernate视图 2.建立与后台数据库的连接 1).configure database driver: 2).添加 ...

  9. python利用os和getopt实现删除指定文件

    工作中经常遇到要删除某些目录下的特定文件 例如删除xxx目录下的所有test开头文件或者.pyc结尾的文件 如果手动删除的话,很麻烦,写个程序自动删除 只需要运行的时候输入路径和文件名即可,不输入文件 ...

  10. Python 爬取汽车领域问答语料(自用)

    #coding=utf-8 import time import requests from lxml import etree from pymongo import MongoClient fro ...