百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

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

mhr18 2025-05-23 18:46 29 浏览 0 评论

现在是一个网络时代了。应该不少程序员在编程中需要考虑多机、局域网、广域网的各种问题。所以网络知识也是避免不了学习的。而且笔者一直觉得 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。

看两段程序:

//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 断开了连接。和我们的预期一致。

相关视频推荐

tcp/ip accept,11个状态,细枝末节的秘密,还有哪些你不知道?

100行代码实现tcp/ip协议栈,自行准备好Linux系统

学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享


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。

总结

总结,本文讲了几种 TCP 连接中出现 RST 的情况。实际上肯定还有无数种的 RST 发生,我以后会慢慢收集把更多的例子加入这篇文章。

相关推荐

Dubai's AI Boom Lures Global Tech as Emirate Reinvents Itself as Middle East's Silicon Gateway

AI-generatedimageAsianFin--Dubaiisrapidlytransformingitselffromadesertoilhubintoaglob...

OpenAI Releases o3-pro, Cuts o3 Prices by 80% as Deal with Google Cloud Reported to Make for Compute Needs

TMTPOST--OpenAIisescalatingthepricewarinlargelanguagemodel(LLM)whileseekingpartnershi...

黄仁勋说AI Agent才是未来!但究竟有些啥影响?

,抓住风口(iOS用户请用电脑端打开小程序)本期要点:详解2025年大热点你好,我是王煜全,这里是王煜全要闻评论。最近,有个词被各个科技大佬反复提及——AIAgent,智能体。黄仁勋在CES展的发布...

商城微服务项目组件搭建(五)——Kafka、Tomcat等安装部署

1、本文属于mini商城系列文档的第0章,由于篇幅原因,这篇文章拆成了6部分,本文属于第5部分2、mini商城项目详细文档及代码见CSDN:https://blog.csdn.net/Eclipse_...

Python+Appium环境搭建与自动化教程

以下是保姆级教程,手把手教你搭建Python+Appium环境并实现简单的APP自动化测试:一、环境搭建(Windows系统)1.安装Python访问Python官网下载最新版(建议...

零配置入门:用VSCode写Java代码的正确姿

一、环境准备:安装JDK,让电脑“听懂”Java目标:安装Java开发工具包(JDK),配置环境变量下载JDKJava程序需要JDK(JavaDevelopmentKit)才能运行和编译。以下是两...

Mycat的搭建以及配置与启动(mycat2)

1、首先开启服务器相关端口firewall-cmd--permanent--add-port=9066/tcpfirewall-cmd--permanent--add-port=80...

kubernetes 部署mysql应用(k8s mysql部署)

这边仅用于测试环境,一般生产环境mysql不建议使用容器部署。这里假设安装mysql版本为mysql8.0.33一、创建MySQL配置(ConfigMap)#mysql-config.yaml...

Spring Data Jpa 介绍和详细入门案例搭建

1.SpringDataJPA的概念在介绍SpringDataJPA的时候,我们首先认识下Hibernate。Hibernate是数据访问解决技术的绝对霸主,使用O/R映射(Object-Re...

量子点格棋上线!“天衍”邀您执子入局

你是否能在策略上战胜量子智能?这不仅是一场博弈更是一次量子智力的较量——量子点格棋正式上线!试试你能否赢下这场量子智局!游戏玩法详解一笔一画间的策略博弈游戏目标:封闭格子、争夺领地点格棋的基本目标是利...

美国将与阿联酋合作建立海外最大的人工智能数据中心

当地时间5月15日,美国白宫宣布与阿联酋合作建立人工智能数据中心园区,据称这是美国以外最大的人工智能园区。阿布扎比政府支持的阿联酋公司G42及多家美国公司将在阿布扎比合作建造容量为5GW的数据中心,占...

盘后股价大涨近8%!甲骨文的业绩及指引超预期?

近期,美股的AI概念股迎来了一波上升行情,微软(MSFT.US)频创新高,英伟达(NVDA.US)、台积电(TSM.US)、博通(AVGO.US)、甲骨文(ORCL.US)等多股亦出现显著上涨。而从基...

甲骨文预计新财年云基础设施营收将涨超70%,盘后一度涨8% | 财报见闻

甲骨文(Oracle)周三盘后公布财报显示,该公司第四财季业绩超预期,虽然云基建略微逊于预期,但管理层预计2026财年云基础设施营收预计将增长超过70%,同时资本支出继上年猛增三倍后,新财年将继续增至...

Springboot数据访问(整合MongoDB)

SpringBoot整合MongoDB基本概念MongoDB与我们之前熟知的关系型数据库(MySQL、Oracle)不同,MongoDB是一个文档数据库,它具有所需的可伸缩性和灵活性,以及所需的查询和...

Linux环境下,Jmeter压力测试的搭建及报错解决方法

概述  Jmeter最早是为了测试Tomcat的前身JServ的执行效率而诞生的。到目前为止,它的最新版本是5.3,其测试能力也不再仅仅只局限于对于Web服务器的测试,而是涵盖了数据库、JM...

取消回复欢迎 发表评论: