tcp连接、断开状态机

TCP 是一个很复杂的课题,这篇文章只是我在学习过程中的一些浅层的想法与思考的记录。先放一张 TCP 的状态迁移图,本篇博客记录一下对于这个状态迁移图中的 建立连接断开连接 的情况,下面的内容针对图中有标记的部分进行介绍。

image-20240108115317170

1 建立连接

建立连接对应图中的红色和橙色的箭头,其中 红色实线 是一般情况下客户端的状态迁移路线,红色虚线 是一般情况下服务器的状态迁移路线,而 橙色实线 说明了另一种特殊的状态迁移路线:P2P 连接

一般情况建立连接(红色实线/虚线)

下图是正常连接建立的状态图,也就是 TCP 的三次握手建立连接,左侧是客户端,对应大图中红色实线的状态迁移,右侧是服务器端,对应大图中红色虚线的状态迁移

image-20240108115704131

关注以下几个点:

    1. listen()
    • listen() 函数是用于准备接受传入连接的系统调用。当在一个套接字上调用此函数时,它将该套接字标记为“被动套接字”,这意味着套接字将用于接受传入的连接请求,而不是主动发起连接。

    • tcb(传输控制块,Transmission Control Block)是一个数据结构,用于在TCP协议内部跟踪TCP连接的状态和控制信息。当调用 listen() 函数后,tcb->status = TCP_STATUS_LISTEN,该状态被设置为 TCP_STATUS_LISTEN,表示套接字正在监听模式,准备接受传入的连接请求。

    • tcb->syn_queue:是一个用于存放已经收到的SYN(同步)包,但还未完成整个TCP三次握手过程的连接请求的队列。当一个客户端尝试连接到服务器时,它首先发送一个带有SYN标志的TCP包。服务器接收到这个包后,会将该连接放入SYN队列,并发送SYN-ACK响应。

    • tcb->accept_queue:是一个用于存放已经完成三次握手并准备被应用程序“接受”的连接队列。当TCP三次握手成功完成后,连接从SYN队列移动到接受队列。应用程序随后可以通过调用 accept()函数从此队列中获取连接。

    1. 三次握手与api的关系
    • SYN:客户端发送一个SYN包(通过connect()函数)来发起一个连接。
    • SYN-ACK:服务端接收到SYN包,然后返回一个SYN-ACK包(在accept()之前的内部处理)。
    • ACK:客户端接收到SYN-ACK包后,发送一个ACK包(这通常是connect()函数的一部分)。
    • 一旦三次握手完成,TCP连接就建立了,此时服务端的accept()函数返回,客户端的connect()函数完成,双方可以开始数据传输。
    1. 第三次握手数据包匹配半连接队列中的节点
    • 接收ACK包:服务器接收到来自客户端的ACK包。
    • 查找匹配的连接请求:
      1. 服务器使用ACK包中的信息(源和目标IP地址和端口号)来识别和匹配半连接队列中相应的连接请求。
      2. 检查ACK包中的确认号是否与半连接队列中某个条目的序列号加1匹配(因为ACK的确认号应该是服务器SYN-ACK包中的序列号加1)。
    1. listen(fd, backlog) 中 backlog 参数
    • SYN/半连接队列。早期为了防止syn泛洪设置的,但是对于半连接队列的控制可以在防火墙完成,因此这种定义逐渐被弃用。
    • SYN+ACCEPT 队列总长度。
    • ACCEPT 队列。已经完成三次握手,等待应用程序接受的连接数。

P2P 建立连接的过程(橙色实线)

TCP 连接的建立并不一定需要使用 listen() 函数,原因就在于有橙色的这条状态迁移路线,两端对等的情况下,相互调用 connect() 函数,也就是都向对方发送 syn 包,然后收到对方的 syn 后向对方回复 ack、syn 包,收到之后再向对方发送 ack 包的过程,如下图所示

image-20240108120739440

描述这个过程的部分代码如下

int main(int argc, char *argv[]) {

    if (argc < 3) {
        printf("wrong arg number\n");
        exit(EXIT_FAILURE);
    }

    char *ip = argv[1];
    int port = atoi(argv[2]);

    int sockfd = bind_port("0.0.0.0", 28888);

    while (1) {

        if (connect_remote(sockfd, ip, port) < 0) {
            usleep(1);
            continue;
        } else {
            printf("connected\n\n");
            break;
        }

    }
    ……
  • bind_port():返回一个fd,该套接字绑定了本地的ip和端口
  • connect_remote():返回 connect() 函数的结果,-1为connect失败,0为成功。在while循环里尝试连接参数ip、port的远程主机,连接上了就退出循环,连接不上就一直尝试连接

2 断开连接

断开连接对应大图中下半段绿色的部分,绿色虚线 描述了一般情况下服务器断开连接的状态迁移过程,绿色实线 描述了客户端一般情况下断开连接的状态迁移过程,方框中表达的是:由于数据包到达先后造成的 1 和 2 ,双方同时断开连接的情况 3)。所以分类为实线客户端和虚线服务器不是很准确,暂时这样描述。下面主要讲解一下方框中的 3 种情况

*1* 一般的正常情况

image-20240108121623623

*2* 先收到 fin,再收到 ack

  1. A发送FIN:A完成数据发送,发送FIN包到B,表明A已完成发送数据。A进入 FIN_WAIT_1 状态。
  2. B收到FIN,发送ACK和FIN:B几乎同时发送ACK响应A的FIN,并发送自己的FIN。由于网络原因,B的FIN比ACK先到达A。
  3. A先收到B的FIN:A首先收到B的FIN,作为响应发送ACK。此时,A没有更多的数据要发送,也已经知道B已经完成了数据发送,所以A跳过 FIN_WAIT_2 状态,直接进入 TIME_WAIT 状态。
  4. A收到B的ACK:随后,A收到对自己原始FIN的ACK响应。由于A已经处于 TIME_WAIT 状态,这个ACK不会导致状态改变。

image-20240108121707030

*3* 没收到ack,先收到fin,直接发送ack

没收到ack,先收到fin,直接发送ack。收到fin变为closing状态。这是双方同时调用close()的结果

  1. 双方各自发送FIN:每个端点发送FIN报文以关闭其发送方向的连接。这意味着每个端点都不再发送数据,但仍然可以接收数据。
  2. 接收对方的FIN:每个端点接收到对方的FIN报文。
  3. 发送ACK响应对方的FIN:每个端点在接收到对方的FIN后,发送ACK作为响应。

在这种特殊情况下,因为双方都在等待对方对自己发送的FIN的确认,所以他们可能会进入 CLOSING 的状态。当一个端点发送了 FIN 并收到了对方的 FIN(但还没有收到对方对其FIN的确认),它就会进入 CLOSING 状态。

image-20240108122123041

写在最后

这里只是讨论了 TCP 状态迁移图中建立连接和断开连接的几种情况,主要关注的是一些特殊的情况,比如 ack 和 fin 没有按照正常的顺序到达、以及建立连接的特殊情况:P2P 方式

这个状态迁移图还有一些重点比如: TIME_WAIT 状态、服务器出现大量 TIME_WAIT 状态的原因、滑动窗口、拥塞控制、可靠传输机制等,后续再整理学习

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值