百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

linux网络编程Socket之RST详解

bigegpt 2024-08-28 12:27 6 浏览

产生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);        }            }

抓包信息如下:

相关推荐

Go语言泛型-泛型约束与实践(go1.7泛型)

来源:械说在Go语言中,Go泛型-泛型约束与实践部分主要探讨如何定义和使用泛型约束(Constraints),以及如何在实际开发中利用泛型进行更灵活的编程。以下是详细内容:一、什么是泛型约束?**泛型...

golang总结(golang实战教程)

基础部分Go语言有哪些优势?1简单易学:语法简洁,减少了代码的冗余。高效并发:内置强大的goroutine和channel,使并发编程更加高效且易于管理。内存管理:拥有自动垃圾回收机制,减少内...

Go 官宣:新版 Protobuf API(go pro版本)

原文作者:JoeTsai,DamienNeil和HerbieOng原文链接:https://blog.golang.org/a-new-go-api-for-protocol-buffer...

Golang开发的一些注意事项(一)(golang入门项目)

1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...

golang 托盘菜单应用及打开系统默认浏览器

之前看到一个应用,用go语言编写,说是某某程序的windows图形化客户端,体验一下发现只是一个托盘,然后托盘菜单的控制面板功能直接打开本地浏览器访问程序启动的webserver网页完成gui相关功...

golang标准库每日一库之 io/ioutil

一、核心函数概览函数作用描述替代方案(Go1.16+)ioutil.ReadFile(filename)一次性读取整个文件内容(返回[]byte)os.ReadFileioutil.WriteFi...

文件类型更改器——GoLang 中的 CLI 工具

我是如何为一项琐碎的工作任务创建一个简单的工具的,你也可以上周我开始玩GoLang,它是一种由Google制作的类C编译语言,非常轻量和快速,事实上它经常在Techempower的基准测...

Go (Golang) 中的 Channels 简介(golang channel长度和容量)

这篇文章重点介绍Channels(通道)在Go中的工作方式,以及如何在代码中使用它们。在Go中,Channels是一种编程结构,它允许我们在代码的不同部分之间移动数据,通常来自不同的goro...

Golang引入泛型:Go将Interface「」替换为“Any”

现在Go将拥有泛型:Go将Interface{}替换为“Any”,这是一个类型别名:typeany=interface{}这会引入了泛型作好准备,实际上,带有泛型的Go1.18Beta...

一文带你看懂Golang最新特性(golang2.0特性)

作者:腾讯PCG代码委员会经过十余年的迭代,Go语言逐渐成为云计算时代主流的编程语言。下到云计算基础设施,上到微服务,越来越多的流行产品使用Go语言编写。可见其影响力已经非常强大。一、Go语言发展历史...

Go 每日一库之 java 转 go 遇到 Apollo?让 agollo 来平滑迁移

以下文章来源于GoOfficialBlog,作者GoOfficialBlogIntroductionagollo是Apollo的Golang客户端Apollo(阿波罗)是携程框架部门研...

Golang使用grpc详解(golang gcc)

gRPC是Google开源的一种高性能、跨语言的远程过程调用(RPC)框架,它使用ProtocolBuffers作为序列化工具,支持多种编程语言,如C++,Java,Python,Go等。gR...

Etcd服务注册与发现封装实现--golang

服务注册register.gopackageregisterimport("fmt""time"etcd3"github.com/cor...

Golang:将日志以Json格式输出到Kafka

在上一篇文章中我实现了一个支持Debug、Info、Error等多个级别的日志库,并将日志写到了磁盘文件中,代码比较简单,适合练手。有兴趣的可以通过这个链接前往:https://github.com/...

如何从 PHP 过渡到 Golang?(php转golang)

我是PHP开发者,转Go两个月了吧,记录一下使用Golang怎么一步步开发新项目。本着有坑填坑,有错改错的宗旨,从零开始,开始学习。因为我司没有专门的Golang大牛,所以我也只能一步步自己去...