前言
我们知道 Ip 层包裹着 tcp 报文段把它从源 Ip 运送到目的 Ip, 如果过程中出现差错(16 位的 Ip 检验和错误),Ip 协议会直接丢弃该数据报并且不生成差错报文这种情况 tcp 会发现数据丢失并进行重传
这篇文章想探讨一下 TCP 协议是通过什么方式做到这些的, 曾经做过设计师的我忍不住抄起老本行画两张图
IPUDPTCP 差异
IP 是网路层协议, TCP 和 UDP 都是运输层协议, 他们都是基于 IP 协议传递数据接下来我们通过几个重要首部分析一下它们的异同
三个协议首部中都有 16 位检验和, 但是, IP 协议首部中的检验和只覆盖 IP 首部, 而 TCP 和 UDP 首部中的检验和是包含所有数据的, 也就是说 IP 协议不关心数据是否正确, 而 TCP 和 UDP 关心
IP 首部中最重要的是 32 位源 IP 和目的 IP 地址, 其它首部都是配合它顺利的把数据从源 IP 传到目的 IP,TCP 和 UDP 负责标记对应端口, 那么 TCP 和 UDP 的区别有是什么呢?
我们先看 UDP, 它只有 4 个首部, 前两个确定端口, 后两个保证数据的准确 IP 协议把数据传递到目的 IP 地址的机器上, UDP 通过首部中的长度和检验和校验数据, 如果校验没有通过, 那么 UDP 协议会直接丢弃数据, 而不会向目的 IP 和端口发送消息, 所以说 UDP 协议是面向数据报的
UDP 的缺点就是: 我们不能保证对方一定收到了我发送的数据但是从首部中很容易能看出它的优点: 就是轻量速度快
在看 TCP, 它除了关心数据之外明显还关心更多的东西, 其中最重要的就是关心连接的可靠性下面我们将主要通过 TCP 连接和断开过程看看这些首部都是什么作用
TCP 首部中有 6 个标志比特位, 它们默认都是 0, 需要时设置为 1, 通过它们完成询问和应答它们分别表示的含义如下:
URG: 紧急指针
ACK: 确认序号有效
PSH: 尽可能快地将数据送往接收进程
RST: 重建连接
SYN: 同步序号用来发起一个连接
FIN: 发端完成发送任务
连接过程和状态
我画了一张图来描述 TCP 连接的过程和状态变化:
一般情况(图中紫色和绿色部分)
三次握手
第一条紫色箭头(从上到下):
服务器端默认处于 LISTEN 状态监听着某个端口当客户端想要连接这个端口的时候, 客户端首先会随机生成一个初始序号 (图中的 j), 首部中的 32 位序号(以下简称序号) 就变成 j+1, 同时首部中的 SYN 由 0 置为 1 客户端发送完带有这些首部的 TCP 段后状态后 SYN_SENT
第一条绿色箭头:
当服务器收到客户端第一个带有 SYN 的 TCP 段的时候, 客户端由 LISTEN 状态变为 SYN_RCVD 状态, 并发送一段数据, 其中首部 ACK 置为 1,32 位确认号 (以下简称确认号) 为客户端发过来的序号(j)+1, 含义就是客户端的数据已经收到同时 SYN 也会置为 1, 序号为 k 含义是我也要与你建立连接
第二条紫色箭头:
当客户端收到服务器的 ACK 后, 状态变更为 ESTABLISLISHED, 同时发送数据, 首部 ACK 为 k+1, 含义是服务器的数据已经收到这时客户端已经建立连接, 而服务器端是否能建立连接取决于它能否收到当前客户端发送的这段带有 ACK 的数据如果服务器收到的话, 它的状态也会变成 ESTABLISLISHED
到此, 连接建立, 可以继续交换数据
四次挥手
第三条紫色箭头:
在不考虑断电等特殊情况下, 主动关闭的一方 (图中为客户端但不总是) 发送 FIN + 序号 m, 状态变更为 FIN_WAIT_1
第二条绿色箭头:
被动关闭的一方 (图中为服务器但不总是) 收到后状态变更为 CLOSE_WAIT 同时里立即发送确认号 m+1, 头部 ACK 置为 1, 这时服务端状态变更为 LAST_ACK, 很容易理解, 这是最后一次确认, 客户端收到 ACK 后, 状态变更为 FIN_WAIT_2
第三条绿色箭头:
服务器 LAST_ACK 后还可能继续做其它的操作, 做完之后, 服务器也会发送 FIN + 序号 n
第四条紫色箭头:
客户端收到 FIN 后, 状态变更为 TIME_WAIT, 同时发送确认信息 ACK n+1, 服务器收到 ACK 后, 状态变更为 CLOSE
到此, 连接断开
为什么挥手是 4 次而不是 3 次?
挥手的时候有个问题, 为什么 ACK 和 FIN 不能一起发送呢? 这是由 TCP 的半关闭 (half-close) 造成的 TCP 连接是全双工(即数据在两个方向上能同时传递), 因此每个方向必须单独地进行关闭
收到一个 FIN 只意味着对方不会再向我发送数据了, 但是 TCP 仍然支持我继续向对方发送数据(当然, 在实际的应用中, 直有很少的程序这样做), 如果我们用 Wireshark 等工具抓包时也经常会看到挥手只有 3 次的情况
TIME_WAIT 旁边的 2MSL 是什么?
TIME_WAIT 状态也称为 2 倍 MSL 等待状态 MSL 是 TCP 的最大报文生存时间当一个 TCP 主动关闭并发送最后一个 ACK(图中右下角最后一条紫色线)后, 必须在 TIME_WAIT 状态等待两倍的 MSL 时间, 防止对方没有收到这个 ACK 然后重发 FIN 所以一去一回最多就是两倍时间
操作系统默认 240s(也就是 2 倍的两分钟)后会关闭处于 TIME_WAIT 的连接, 在这之前端口一直会被占用
在 Linux 服务器上可以通过变更 / etc/sysctl.conf 文件去修改该缺省值(秒):net.ipv4.tcp_fin_timeout=30 但这通常也会出现一些问题
同时打开(图中紫色和橙色部分)
两个应用程序彼此同时打开的可能性很小这时双方都是客户端也都是服务器具体流程和一般情况一样, 只不过握手的时候是 4 次而不是 3 次 TCP 协议认为这种情况是建立一条连接而不是两条
同时关闭(图中紫色和橙色部分)
为了方便我把同时打开和同时关闭画在了一起, 其实同时打开不一定同时关闭不同时打开也有可能同时关闭
总结
对于前端工程师来讲, 了解 TCP 协议能够帮助我们更好的理解并使用 HTTP 协议也能够帮助我们对网络进行更加细致的优化我的理解也很有限, 希望能够和大家共同讨论
来源: https://juejin.im/post/5a7fea206fb9a06333151e99