TCP 的信息传输

TCP 是面向连接的传输层协议, 因此在传输数据之前要先建立连接.

连接是一个很抽象的概念. 从广义上来说, 连接是指将两个个体按照某种方式建立联系的过程. 而在 TCP 协议中, 由于需要进行数据传输, 因此此时的个体即为 TCP 的客户端和服务端, 而建立的联系即为信息传输的通道.

连接建立

即然是信息传输的通道, 那么就需要确保连接的双方都能够从通道中读取信息和写入信息. 并且对于双方来说, 都需要知道对方是有写入和读取这两种能力的. 换句话来说, 如果客户端和服务端都知道对方具有读取和写入的能力, 这个连接就算建立了.

以上就是常见的 TCP 建立的三次握手的逻辑图. 接下来我们将客户端和服务端拟人化, 并以报文的形式, 来逐步分析这三次信息传输.

在最开始的时候, 服务端是不知道有人会写信过来的, 而客户端是主动的那一个.

因此, 最初的状态是:

对方能读取嘛? 对方能写入嘛?
服务端 我不到啊 😧 我不到啊 😧
客户端 我不到啊 😧 我不到啊 😧

第一次握手

此时客户端发送第一个报文给服务端. 其中报文中的 SYN 标志符设置为 1, 表示想要建立连接. 并且生成一个 client_isn, 发送给服务端.

而服务端接收到这个报文后, 就明白有一个客户端想要和自己建立连接, 并且知道这个客户端能够写入信息.

所以现在的连接状态如下:

对方能读取嘛? 对方能写入嘛?
服务端 我不到啊 😧 😎👌
客户端 我不到啊 😧 我不到啊 😧

第二次握手

当服务端接收到连接建立的报文时, 就需要向客户端发送报文来让对方看看自己的实力了 😎

因此服务端也将 SYN 标志符设置为 1, 表示想要建立连接. 并且将 ACK 设置为 1, 表示自己有应答的信息, 应答的信息写入成 client_isn+1. 并且自己生成了一个 server_isn, 发送给客户端.

而当客户端接收到这个报文后, 自然明白服务端能够写入信息. 在此基础上, 发现在应答的信息正好为 client_isn+1, 这不是说明服务端读取到了自己发送的报文嘛, 那说明服务端能够读取到信息嘛.

所以现在的连接状态如下:

对方能读取嘛? 对方能写入嘛?
服务端 我不到啊 😧 😎👌
客户端 😎👌 😎👌

第三次握手

这个时候, 客户端也知道了就剩自己还没证明自己的读信息的能力了.

并且客户端觉得, 反正我已经知道你可以读写了, 不如简单一点, 直接开始传输信息吧, 我就当连接已经创建了. 于是设置 ACK 为 1 并将获取到的 server_isn+1 写入到应答, 并直接传输想要发送的数据.

而当服务端接收到第三次传输的信息后, 就知道自己的 server_isn 也被客户端获取到了. 这才放心地进入连接状态.

所以现在的连接状态如下:

对方能读取嘛? 对方能写入嘛?
服务端 😎👌 😎👌
客户端 😎👌 😎👌

此时, 连接就算建立完成了.

连接断开

而在连接断开的时候, 需要连接的双方知道对方都不会再接收和发送任何消息. 这样, 双方就都可以结束这次连接了.

以上就是常见的 TCP 连接断开的四次挥手逻辑图. 可以很明显地看出来, 这四次挥手, 可以分为两次 FIN 请求和 ACK 回应和最后的等待时间. 我们将继续使用拟人化的形式, 来分析这四次挥手的逻辑.

由于在开始的时候, 连接还是存在的, 所以双方的连接状态是:

对方结束发送数据了嘛? 对方结束接收了嘛?
结束请求方 我不到啊 😧 我不到啊 😧
结束响应方 我不到啊 😧 我不到啊 😧

第一次 FIN-ACK

在连接建立状态时, 若我们将想要结束连接的一方称为结束请求方, 另一方为结束响应方.

在结束请求方想要结束这段连接时, 会将 FIN 标记为 1, 并发送生成的序列号.

而请求响应方接收到这个报文后, 就会知道对方已经不会再发送数据了, 那么我也赶紧发送完了就结束吧.

并为了让对方得知自己收到的信息, 将 ACK 标记为 1, 并将接收报文的序列号+1 写入到回应序列号中.

这样, 当回应报文被结束请求方接收到后, 就知道自己的结束决定已经被对方接收到了, 就可以开始等待对方的结束决定了.

因此, 这时的连接状态就是:

对方结束发送数据了嘛? 对方结束接收了嘛?
结束请求方 我不到啊 😧 我不到啊 😧
结束响应方 😎👌 我不到啊 😧

第二次 FIN-ACK

在请求接收方回应对方的结束决定后, 自己在发送完最后一个数据包后, 也会开始着手结束发送了.

于是, 请求接收方如第一次 FIN-ACK 一样, 将 FIN 标记为 1, 并发送生成的序列号.

同样的, 在结束请求方接收到这个报文后, 也会给予回应, 告知自己已经接收到了, 所以将 ACK 标记为 1, 并将接收报文的序列号+1 写入到回应序列号中.

这样, 当请求结束方接收到回应报文后, 就可以结束发送和接收了.

因此, 此时的连接状态是:

对方结束发送数据了嘛? 对方结束接收了嘛?
结束请求方 😎👌 我不到啊 😧
结束响应方 😎👌 😎👌

等待时间

而在结束请求方发送完回应报文之后, 需要确认两件要求:

  1. 尽量确保对方收到了我的回应
  2. 确保自己断开接收后, 自己就要搬家了, 别人可能会住进我的地址, 别人不会收到对方的信件.

由于我们已知, 一个报文在网络中最长存活的时间是 MSL, 在 Linux 中一般是 30s, 而超时重传的时间一般在毫秒级别. 所以需要考虑的最坏情况就是:

自己的 ACK 报文在接近 MSL 的时间才被接收到, 而在接收到的时间点前几毫秒, 触发了超时重传. 这时就会有最后一个 FIN 报文还在网络中. 因此, 还需要 1 个 MSL 时间才会时期过期.

因此, 当结束请求方等待了 2 个 MSL 后, 就能够确保两种情况:

  1. 对方已经接收到了 ACK, 并且所有的 FIN 报文已经过期了
  2. 网络实在不好, 也不强求对方接收 ACK 包了, 并且对方大概率已经超过重传次数了. 而且此时所有的报文已经过期了.

这两种情况下, 都能对第二个要求有了保证. 所以, 结束请求方也就可以关闭确认对方关闭接收了.

因此, 此时的状态是:

对方结束发送数据了嘛? 对方结束接收了嘛?
结束请求方 😎👌 😎👌
结束响应方 😎👌 😎👌

此时, 就算是连接断开完成了.


正是你花费在玫瑰上的时间才使得你的玫瑰花珍贵无比