有关TCP 连接的退出,你该知道的事....(上)

前言

在这里插入图片描述

ok,这幅图熟悉吧(如果不熟悉,那么你可能不太适合看这篇博文)。 正常的整个流程还是比较简单的,然而在现实生活中,生活并不是总是一帆风顺,异常情况还是要时常考虑的。

ok,今天就来总结一波在socket编程中,有关tcp/ip 整个通信过程中的一些异常(尤其是异常退出)的情况。

由于要讨论的情况比较多,有些乱 。为了方便、清晰起见,我打算按照流程从上到下挨着分析。因此,对于上图做了一点小改动。

在这里插入图片描述

注意,下文中有些异常情况包括上图中好几个步骤(圈圈),所以不太好划分,所以下文中的阶段只是大概区分。


废话不多说,策马崩腾,我们走起。



一、建立连接阶段可能出现的异常

对于使用socket 建立的tcp连接来说,除了上图中描述的过程外,还有一些辅助性的过程,如绑定ip和端口号等,这个过程可能也会出现错误,这种情况是静态的情况,比较简单,这里就不考虑了。

正儿八经的建立连接(开始有数据交互)从connect开始, 这个阶段可能会在上图中的①②③出现异常。

1.1 在①②阶段的异常:服务器端口未打开

当客户端connect的第一阶段,内核(客户端的内核)会发送建立连接请求(发送SYN)给服务器端,服务器端如果没有对应的端口在接受服务,那么内核(服务器端的内核)会给客户端返回一个RST分节。 客户端内核接受到之后,知道发生了异常,会返回给上层Connection refused; 如下图所示


在这里插入图片描述

默认你都能看得懂哦,(* ^ ▽ ^ *)

1.2 在①②阶段的异常:客户端等待ACK超时

这种情况最经典一般出在非阻塞connect ,比较少见。【1】中有叙述,但是不太准确。

具体的场景是这样:客户端设置了非阻塞的connect 连接,并且设置了超时时间。 这样客户端在发送SYN分节后,如果在设定的超时时间内没有接收到服务器端的ACK+SYN,那么客户端的connect 就会立即返回一个EINPROGRESS(阻塞时的connect在成功或者失败之后才会返回)。 但是注意,如果客户端不做特殊处理的话(比如说关闭连接),那么内核会继续TCP的三路握手进行下去。

下图在connect返回EINPROGRESS后关闭了程序,第二个SYN是服务器端发来的SYN+ACK(已经超时了);第三个RST是我在程序中关闭了描述符,然后客户端内核发送的RST(作为对上一个SYN+ACK的回应:自己已经因为超时关闭socket了,服务器端还在那等着呢,无论怎么样,得给他一个回复啊,RST异常回复也是回复)
在这里插入图片描述

下图,是我在connect超时之后,没有立即关闭socket,三次握手得以继续进行下去。
在这里插入图片描述

1.3 在②③阶段的异常:服务器端等待ACK超时

在这里插入图片描述

这个就是著名的SYN Flood攻击。其基本原理是在握手的第三阶段,如果服务器要等待客户端的ACK。 如果客户端迟迟不发送这个ACK, 服务器就会重试,重试超过一定次数(或者说超过一定时间,也即SYN Timeout,一般是30s -2 min),服务器端才会丢弃这个未完成的连接。

如果有恶意的客户端不断伪造TCP连接请求(不发送第三阶段的ACK), 那么服务器负载就会相当巨大,造成正常用于的访问延迟加大,这就是服务端SYN Flood受攻击。

1.4 在③阶段的异常:服务器端accept异常

在这里插入图片描述

这种情况描述的是客户端和服务器端建立连接,但是当服务器端准备accept从连接队列中取出连接之前,因为各种原因客户端发送了一个断开连接的过程(比如说进程crash),导致客户端发送了一个RST分节,那么服务器端会出现什么状况?


我给这种情况形象的表述成:明明说好的一起牵手,当我准备伸出手时,你缺转头而去!


对于这种情况,不同的系统内核处理的方式可能不同。有常见的处理方式如下(来自【3】):
在这里插入图片描述


我在linux下做了个实验,发现在linux上accept不会报错,但是在在对取得的socket进行读写的时候,会报错“Connection reset by peer”。

在【4】中有对linux的这种现象做解释:

在这里插入图片描述


这里附上实验代码和对应的结果截图。

//客户端
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdint.h>
#include <assert.h>
#include <fcntl.h>
#include <string.h>
#include <bits/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>

const char* ip = "x.x.x.x";
uint16_t port = 9999;

int main()
{
    int sock_fd = socket(PF_INET, SOCK_STREAM, 0);

    struct sockaddr_in sock_addr;
    memset(&sock_addr, 0, sizeof(sock_addr));
    sock_addr.sin_family = AF_INET;
    sock_addr.sin_port = htons(port);
    inet_pton(AF_INET, ip, &(sock_addr.sin_addr));

    int ret = connect(sock_fd, (sockaddr*)&sock_addr, sizeof(sock_addr));

    if(ret < 0)
    {
        printf("%s\n", strerror(errno));
    }

    struct linger my_linger;
    memset(&my_linger, 0, sizeof(my_linger));
    my_linger.l_onoff = 1;
    my_linger.l_linger = 0;

    setsockopt(sock_fd, SOL_SOCKET, SO_LINGER, &my_linger, sizeof(my_linger));

    close(sock_fd);

    return 0;
}

//服务器端
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdint.h>
#include <assert.h>
#include <fcntl.h>
#include <string.h>
#include <bits/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>

const char* ip = "0.0.0.0";
uint16_t port = 9999;
char buff[1024];

void make_sock_noblocking(int sock_fd)
{
    int flags = fcntl(sock_fd, F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl(sock_fd, F_SETFL, flags);
}

int main()
{
    int listen_fd = socket(PF_INET, SOCK_STREAM, 0);

    struct sockaddr_in sock_addr;
    memset(&sock_addr, 0, sizeof(sock_addr));
    sock_addr.sin_family = AF_INET;
    sock_addr.sin_port = htons(port);
    inet_pton(AF_INET, ip, &(sock_addr.sin_addr));

    int optval = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,
               (const void *)&optval , sizeof(int));
    int ret = bind(listen_fd, (sockaddr*)&sock_addr, sizeof(sock_addr));

    listen(listen_fd, 1024);

    make_sock_noblocking(listen_fd);

    sleep(10);

    struct sockaddr_in client_addr;
    memset(&sock_addr, 0, sizeof(client_addr));
    socklen_t len = sizeof(client_addr);
    int conn_fd = accept(listen_fd, (sockaddr*)&client_addr, &(len));

    printf("%d--------%s\n", conn_fd, strerror(errno));

    int read_bytes = read(conn_fd, buff, sizeof(buff));

    printf("%d %s\n", read_bytes, strerror(errno));

    return 0;
}


实验截图如下:
客户端:
在这里插入图片描述

服务器端:
在这里插入图片描述




enen…
我后来有深究了一下,如果客户端退出的时候发送的不是RST(异常关闭连接),而是FIN(正常关闭连接),那会有什么情况?

你猜猜…

我做了一个实验,正常连接,可以获取socket,但是read的时候会返回0(大概是对方连接已关闭)。 如下图所示,

服务器端:
在这里插入图片描述

客户端:
在这里插入图片描述

服务器端:
在这里插入图片描述


参考

【1】、TCP/IP详解–接收RST回应的几种情况
【2】、SYN Flood攻击原理与防范
【3】、TCP异常处理(accept返回前连接中止)与SO_LINGER选项
【4】、linux下accept之前异常终止连接问题?

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值