10-在accept之前中止连接(连接异常)

   相信大家对基于TCP的客户端/服务端整个网络通信流程已经非常清楚了,现在假设这么一种情况,当客户端和服务端完成三次握手建立连接后,客户端tcp在服务端调用accept之前,马上又发送了一个RST中止连接,这将会发生什么?

这里写图片描述

  如上图所示,为了模拟这种出错情况需要在服务端调用accept之前先休眠一段时间,并在服务端进程休眠期间,启动客户端调用connect发起连接,一旦connect返回就设置SO_LINGER套接字选项并发送RST,然后中止连接。

//客户端connect连接返回,通过SO_LINGER选项立刻发送RST
lgr.l_onoff = 1;
lgr.l_linger = 0;
ret = setsockopt(sfd, SOL_SOCKET, SO_LINGER, &lgr, sizeof lgr);

  但是不同的实现对于这种情况的处理方式也不一样,例如Berkeley实现在内核就中止了这条连接,服务器进程根本就看不到,而大多数SVR4实现则会返回一个错误给服务器进程,不过具体怎么处理要取决于具体实现。因为对于服务器来说,当它调用accept时并不知道之前有一个已完成的连接已经从已完成队列中删除掉了。

  如果有些实现的服务器accept返回的是ECONNABORTED错误,那么服务器可以忽略这个错误,再次调用accept即可。



客户端主要代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>

#define SERV_IP "192.168.0.107"
#define SERV_PORT 10001

int main(void) {
    int sfd, len,ret;
    struct sockaddr_in serv_addr;
    struct linger lgr;
    char buf[BUFSIZ];

    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(ret < 0){
        perror("set SO_LINGER Error");
    }

    bzero(&serv_addr, sizeof(serv_addr));                       
    serv_addr.sin_family = AF_INET;                         
    inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); 
    serv_addr.sin_port = htons(SERV_PORT);                      

    //客户端connect连接返回,通过SO_LINGER选项立刻发送RST
    lgr.l_onoff = 1;
    lgr.l_linger = 0;
    ret = setsockopt(sfd, SOL_SOCKET, SO_LINGER, &lgr, sizeof lgr);

    ret = connect(sfd, (struct sockaddr *)&serv_addr ,  sizeof(serv_addr));
    if(ret == 0)
    {
        printf("client connect successful\n");
    }

    //关闭链接
    close(sfd);
    return 0;
}



服务端代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>

#define SERV_PORT 10001
#define SERV_IP "192.168.0.107"

int main(void) {
    int sfd, cfd;
    int len, i;
    //BUFSIZ是系统内嵌的一个宏,用来指定buf大小
    char buf[BUFSIZ], clie_IP[BUFSIZ];
    struct sockaddr_in serv_addr, clie_addr;
    socklen_t clie_addr_len;
    sfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&serv_addr, sizeof(serv_addr));      
    serv_addr.sin_family = AF_INET;           
    inet_pton(AF_INET , SERV_IP , &serv_addr.sin_addr.s_addr);
    serv_addr.sin_port = htons(SERV_PORT);              

    //绑定套接字
    bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    listen(sfd, 64);
    printf("wait for client connect ...\n");
    clie_addr_len = sizeof(clie_addr);
    while(1)
    {   
        //在accept之前休眠10s
        sleep(10);

        //阻塞等待客户端发起连接
        cfd = accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
        //打印accept后的信息和客户端的连接
        if(cfd > 0) {
            printf("accept cfd = %d\n" , cfd);
        }

        printf("client IP:%s\tport:%d\n", 
            inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), 
            ntohs(clie_addr.sin_port));

        //服务端读取数据
        len = read(cfd, buf, sizeof(buf));
        //read返回0说明对端已经关闭
        if(len < 0) {
            //进一步判断是否收到RST
            if(errno == ECONNRESET) {
                printf("read reset by peer\n");
                break;
            }
        }
    }
    close(sfd);
    close(cfd);
    return 0;
}



  执行./server命令先启动服务端,然后服务器等待客户端连接,此时服务器在调用accept之前休眠了10秒钟。

这里写图片描述

然后执行./client命令再启动客户端
这里写图片描述

tcpdump抓到的数据包来看,确实验证了这一点:
这里写图片描述

  客户端和服务端建立tcp连接后,然后客户端又马上发送了RST断开连接,但是此时服务端的accept并不知道这条完成“三次握手”的未决连接已经从未决连接队列中删除掉了,所以accept调用最后会返回成功。不过当服务器收到RST时,read调用返回了一个ECONNRESET 错误,打印read reset by peer。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值