当前位置:首页 > 技术分析 > 正文内容

linux网络编程Socket之RST详解

ruisui883个月前 (01-15)技术分析15

产生RST的三个条件:

1. 目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器;

2. TCP想取消一个已有的连接;

3. TCP接收到一个根本不存在的连接上的分节;


现在模拟上面的三种情况:

client:

struct sockaddr_in serverAdd;        bzero(&serverAdd, sizeof(serverAdd));    serverAdd.sin_family = AF_INET;    serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);    serverAdd.sin_port = htons(SERV_PORT);        int connfd = socket(AF_INET, SOCK_STREAM, 0);        int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));    if (connResult < 0) {        printf("连接失败\n");        close(connfd);        return;    }        ssize_t writeLen;    char sendMsg[5000] = {0};    unsigned long long totalSize = 0;        while (1) {                writeLen = write(connfd, sendMsg, sizeof(sendMsg));        if (writeLen < 0) {            printf("发送失败 errno = \n",errno);            return;        }        else        {            totalSize += writeLen;            printf("发送成功 totalSize = %zd\n",totalSize);        }                   }

server:

#define SERV_PORT 8000
 
int main(int argc, const char * argv[])
{
 
    struct sockaddr_in serverAdd;
    struct sockaddr_in clientAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAdd.sin_port = htons(SERV_PORT);
    
    socklen_t clientAddrLen;
    
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1;
    setsockopt(listenfd,
               SOL_SOCKET, SO_REUSEADDR,
               (void *)&yes, sizeof(yes));
    
    if (listenfd < 0) {
        printf("创建socket失败\n");
        return -1;
    }
    
    int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (bindResult < 0) {
        printf("绑定端口失败\n");
        close(listenfd);
        return -1;
    }
    
    listen(listenfd, 20);
    
    int connfd;
    unsigned char recvMsg[246988];
    unsigned long long totalSize = 0;
    
    clientAddrLen = sizeof(clientAdd);
    connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen);
    if (connfd < 0) {
        printf("连接失败\n");
        return -1;
    }
    else{
//        这里我们用于测试,只接收一个连接
        close(listenfd);
    }
    
    close(connfd);
    
    return 0;
}

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

情况一:

不运行服务端,直接运行客户端程序:

客户端打印信息:

连接失败

用抓包工具抓包:

可以看到客户端通过connect方法发起三次握手,发送完第一个SYN分节以后,收到来自服务端的RST分节;

情况二:

运行服务端,再运行客户端程序:

客户端打印信息:

发送成功 totalSize = 5000

发送成功 totalSize = 10000

发送成功 totalSize = 15000

... ...

发送成功 totalSize = 125000

发送成功 totalSize = 130000

(lldb)

可以看到客户端发送第130001-135000个字节的时候程序在write方法处崩溃,是因为TCP套接字发送缓冲区的大小为131768字节,在发送前130000个字节的时候发送缓冲区还未满,因此write方法返回成功,接着继续发送

用抓包工具抓包:

假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client,此时server不能再通过socket发送和接收数据,此时client调用read,如果接收到FIN 段会返回0,但client此时还是可以write 给server的,write调用只负责把数据交给TCP发送缓冲区就可以成功返回了,所以不会出错,而server收到数据后应答一个RST段,表示服务器已经不能接收数据,连接重置,client收到RST段后无法立刻通知应用层,只把这个状态保存在TCP协议层。如果client再次调用write发数据给server,由于TCP协议层已经处于RST状态了,因此不会将数据发出,而是发一个SIGPIPE信号给应用层,SIGPIPE信号的缺省处理动作是终止程序。

当一个进程向某个已收到RST的套接字执行写操作时,(此时写操作返回EPIPE错误)内核向该进程发送一个SIGPIPE信号,该信号的默认行为是终止进程,因此进程必须捕获它以免不情愿地被终止;

继续修改客户端程序如下,服务端不变:

    struct sockaddr_in serverAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
    serverAdd.sin_port = htons(SERV_PORT);
    
    int connfd = socket(AF_INET, SOCK_STREAM, 0);
    
    int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (connResult < 0) {
        printf("连接失败\n");
        close(connfd);
        return;
    }
    
    ssize_t writeLen;
    ssize_t readLen;
    char recvMsg[65535];
    char sendMsg[5000] = {0};
    unsigned long long totalSize = 0;
    
    while (1) {
        
        writeLen = write(connfd, sendMsg, sizeof(sendMsg));
        if (writeLen < 0) {
            printf("发送失败 errno = %d\n",errno);
            return;
        }
        else
        {
            totalSize += writeLen;
            printf("发送成功 totalSize = %zd\n",totalSize);
        }
        sleep(1);
        readLen = read(connfd, recvMsg, sizeof(recvMsg));
        if (readLen < 0) {
            printf("读取失败 errno = %d\n",errno);
            return;
        }
        else
        {
            printf("readLen:%ld\n",readLen);
        }
        
    }

客户端向服务端写5000字节以后先休眠一秒是为了将数据发送出去,确认TCP协议层已收到服务端响应的RST分节,然后再进行读操作,此时read返回-1.而不再是0;

先运行服务端,再运行客户端,客户端打印信息如下:

发送成功 totalSize = 5000

读取失败 errno = 54

#defineECONNRESET 54/* Connection reset by peer */

当一个进程向某个已收到RST的套接字执行读操作时,(此时读操作返回ECONNRESET错误)

抓包信息如下:

上述情况会引发一个问题:服务器主机进程终止或者崩溃后重启,客户端在不write的情况下不会知道,read会返回ECONNRESET错误或者超时;

解决方法用select:

1、如果对端TCP发送一个FIN(对端进程终止),那么该套接字变为可读,并且read返回0;

2、如果对端TCP发送一个RST(对端主机崩溃并重新启动),那么该套接字变为可读,并且read返回-1,而errno中含有确切的错误码;


情况三:

修改客户端程序如下,服务端不变;

运行服务端,再运行客户端程序,客户端打印连接成功,if语句开头会休眠20秒,(服务端程序里面,接收一个连接以后就close套接字然后立马退出程序了)在这期间内再次打开服务端,等待客户端的读取数据的分节到达,然后返回一个RST分节给客户端,是因为TCP接收到一个根本不存在的连接上的分节;服务器主机崩溃后重启:它的TCP丢失了崩溃前的所有连接信息,因此服务器TCP对于所有收到的来自客户的数据分节响应一个RST;

    struct sockaddr_in serverAdd;        bzero(&serverAdd, sizeof(serverAdd));    serverAdd.sin_family = AF_INET;    serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);    serverAdd.sin_port = htons(SERV_PORT);        int connfd = socket(AF_INET, SOCK_STREAM, 0);        int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));    if (connResult < 0) {        printf("连接失败\n");        close(connfd);        return;    }        ssize_t writeLen;    ssize_t readLen;    char recvMsg[65535];    char sendMsg[5000] = {0};    unsigned long long totalSize = 0;        while (1) {                writeLen = write(connfd, sendMsg, sizeof(sendMsg));        if (writeLen < 0) {            printf("发送失败 errno = %d\n",errno);            return;        }        else        {            totalSize += writeLen;            printf("发送成功 totalSize = %zd\n",totalSize);        }        sleep(1);        readLen = read(connfd, recvMsg, sizeof(recvMsg));        if (readLen < 0) {            printf("读取失败 errno = %d\n",errno);            return;        }        else        {            printf("readLen:%ld\n",readLen);        }            }

抓包信息如下:

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/289.html

标签: .rst
分享给朋友:

“linux网络编程Socket之RST详解” 的相关文章

vue项目-父页面数据变化使子页面更新的几种情况

当操作页面时候,特别是增删改操作之后,数据会有所改变,这个时候我们希望组件中的数据要和最新数据一致,就需要重新更新渲染。以下是针对几种不同情况下方式:一.子页面调用接口后重新渲染1.使用ref方式父组件中用ref=“xxx” 来声明子组件,然后通过在父组件值改变的地方来调用子组件中的方法this.$...

快速上手React

web前端三大主流框架1、Angular大家眼里比较牛的框架,甚至有人说三大框架中只有它能称得上一个完整的框架,因为它包含的东西比较完善,包含模板,数据双向绑定,路由,模块化,服务,过滤器,依赖注入等所有功能。对于刚开始学习使用框架的小伙伴们,可以推荐这个框架,学会之后简直能颠覆之前你对前端开发的认...

基于gitlab的PR操作教程

基于gitlab的PR操作教程注:该教程主要基于git命令行操作,其他图形化工具也可完成以下所有操作步骤,顺手即可。推荐工具:Source Tree ,TortoiseGit参考:gitflow一 . 基于分支的PR操作1. 本地切换到master分支1. 拉取最新代码2. 基于master创建ho...

前后端分离自动化运维平台开发

运维平台采用前后端分离:前端vue,框架vue-element-admin;后端python,框架django-rest-framework.目前运维平台模块如下:1、 CMDB管理应用管理、环境管理、开发语言管理、产品项目管理、资产管理2、 构建发布持续构建、持续部署、Jar工程依赖构建3、 容器...

编码 10000 个小时后,开发者悟了:“不要急于发布!”

【CSDN 编者按】在软件开发的道路上,时间是最好的老师。根据“一万小时定律”,要成为某个领域的专家,通常需要大约一万小时的刻意练习。本文作者身为一名程序员,也经历了一万小时的编程,最终悟出了一个道理:慢即是快,重视架构设计和代码质量,确保每一行代码都经得起时间的考验。作者 | Sotiris Ko...

2024最新版:前端性能优化方案汇总

前端训练营:1v1私教,终身辅导计划,帮你拿到满意的 offer。 已帮助数百位同学拿到了中大厂 offer。欢迎来撩~~~~~~~~Hello,大家好,我是 Sunday。前端性能优化一直是很多同学非常关注的问题,在日常的面试中也是经常会被问到的点。所以今天咱们就花一点时间来了解一下2024最新的...