如果你只是在编写 Web 应用,你完全无需了解三次握手的细节——因为我真的没想到应用场景,如果你知道,可以告诉我。

故事要从我第一次编写长连接应用说起,从 HTTP 下的需求开发,到编写 TCP 长连接应用,于我而言,倘若不理解握手流程,就很难理解:

  • 什么是数据包?
  • 如何解析一个包?
  • 握手流程里的事件用途?
  • 怎样的表现是传输出错?
  • 如果出现传输问题,哪个步骤出了错?该怎么解决?
  • 框架有问题,想提个修复 PR,但这个代码注释说 MSS?MTU?
  • 我需要支持自有包格式,该怎么写解析、封装?
  • TCP/UDP/HTTP 等实际使用的协议(此处并非指某一层,譬如 HTTP 也是基于 TCP,不是说这个),性能差距差在哪里?
  • etc.

如果此时你选用 Socket.IO 或类似的框架,情况还好一些,如果没有必要,你无需关注数据包问题——当数据出现异常时,理解握手有时候会成为解决问题的必须条件。

这就是我对 TCP 握手第一次产生兴趣的时刻。

TCP 握手流程

SYN:Synchronize;
ACK:Acknowledge;
FIN:Finished。

发起方接收方意义
发送:SYN + A序列号;状态:SYN-SENT
发送:ACK-SYN + A序列号 + B序列号
状态:SYN-RCVD
接收方确认:接收方可收,发起方可发
发送:ACK,状态:Established 发起方确认双方可收发,我方序列号被接受
状态:Established接收方确认双方可收发,我方序列号被接受
通讯中....通讯中....
发送:FIN 发起方想结束连接
发送:ACK接收方了解发起方想结束连接
发起方收到 发起方了解接收方已知晓
发送:FIN接收方知晓数据传完,可关闭
发送:ACK 发起方知晓数据传完,可关闭
接收方收到,不再维持连接接收方知晓发起方已知晓连接可关闭
等待 2 个 MSL 时间...不再维持连接 发起方需确认最后消息未丢包,则关闭自身

序列号

TCP 协议采用序列号+1的方式,来确认双方通讯的有序性。

在很老的 TCP 版本中,该起始序列号是固定的——这导致了轻松的连接劫持,只需要模拟固定的序列号即可。

不用担心,后续版本已将它升级为随机起始值。且对采用了 SSL / HTTPS 等安全协议的服务,这种问题几乎不存在。

注意安全,仍有其他劫持可能运作,譬如复合了 IP 欺骗或盲目劫持等手段后,发起的钓鱼、中间人攻击等劫持方式。

MSL 时间

数据段的最大生命时间(Maximum segment lifetime),这里有三个重点:

  • 超过此时间的数据段将被丢弃(数据段或叫报文?)。
  • 在 Linux,你可以通过 tcp_fin_timeouttcp_keepalive_time 来调整它。
  • 如果你喜欢 Windows Service,可以尝试 How Do I Reduce the Time for Canceling TCP Connections in TIME_WAIT State on Windows
  • 1981 年的原始协议规定 MSL = 120s,实际上,Linux 内核将其缩短为 30s——这对于某些应用来说,仍然过长了。

SYN 泛洪攻击

指:

  1. 通过大量构造连接,且在最后一步不发送 ACK 数据包,从而构造半开连接;
  2. 服务端为连接分配资源后,长时间维持无用连接的情况(考虑 MSL 时间,这个连接会存在很久)。
  3. 当大量的连接被建立、分配资源、搁置后,整个服务进入停摆状态。

可以通过回收最先创建的 TCP 半开连接、 IP 鉴别并暂时拉黑,云服务商还会有握手沙盒服务等。

让我们回到 TCP 本身。

超时重传

TCP 的安全性有各种机制保证,超时重传就是其中之一。

超时重传时间,也叫 RTO 时间(Retransmission-TimeOut),它如何如何发挥作用呢?

  • 当一个包传输 RTO 时间后仍然没有结果,则确认包丢失;
  • 当确认包丢失后,发送端将重新发送信息给对方,此时 RTO 时间加倍;
  • 重复上述过程,重试 3 次后仍无效,放弃。

RTT 时间

往返时延时间(Round-Trip-Time),这里介绍这个概念,主要是因为 RTO 时间会受到 RTT 时间的影响。

首先,RTT 计算方式:一端发送 SYN 包起,另一端回复 SYN-ACK 包结束。

接着,考虑到网络传输的时延和不稳定,TCP 会计算出 SRTT(Smoothed RTT),作为新 RTO 的计算系数之一。

(具体计算方式可以参考 RFC 的相关文档,写的很明确,附在了引用中)

ARQ 协议

自动重传请求(Automatic Repeat-reQuest),它本身只是一个协议,各种安全通讯协议都有对应的实现。

当数据包超时后,会触发 ARQ 机制进行重传。

TCP 包细节

标头

TCP 包中,标头是指 IP Header、TCP Header 这部分,常规而言,两者均为 20 字节(可以扩展,但几乎没有需要)。

标头用于指定各种地址、协议、包相关的信息:

TCP 包的标头示意图

需要注意的是:源/目标端口号、序列号、控制标记(ACK 等),它们都在标头中。

载荷

跟随在标头后,是数据包的载荷部分,这里面则包含了数据包的具体数据。

多数应用层开发者都更关注载荷,而非标头。

MTU 尺寸

包头 + 载荷,构成了一个数据包的尺寸,那我们用 MTU(Maximum transmission unit,最大传输单位) 来描述它。

若一次数据传输超过这个单位,将进行分段传输。

MSS 尺寸

MSS(最大分段大小)用于标识数据块的尺寸,也就是:MTU - 标头尺寸 = MSS。

此处有 MSS 协商的概念,连接双方在握手的 SYN 阶段确认彼此的 MSS 尺寸,同时以最小值作为本次连接的信息交换尺寸。

这个协商并非强制进行的,可能一端并不会传输自己的 MSS 尺寸,导致:

如标头后加入了其他信息(IPsec、GRE 隧道传输等信息),可能会导致 MTU 超过 1500 的常见尺寸,造成隐蔽的包体内容不全。

引用

本文参考或引用以下资料,在此致谢:

The TCP / IP Guide
淘宝二面,面试官居然把TCP三次握手问的这么详细
Understanding RTT Impact on TCP Retransmissions
TCP 的特性 - # 笔试面试知识整理
Why is TCP MSL so long? - ServerFault
RFC - 6298

引申1:找资料时看到有博主表示,抓包时了解 TCP 很有用,这一点对 tcpdumpfiddler 是有意义的,可如果你是用 CharlesChrome Console 的 Network 抓包,它已经把 懒人化 做到极致了,其实你需要的数据,98% 都只是传输信息,而非包数据。

引申2:TCP 的相关知识点很充足,导致写的有点久。很多有趣的内容也没有说到,譬如:

  • TCP 的流量控制、拥塞控制,这是 TCP 重要的设计概念,对 KCP 也有不小的影响;
  • TCP 的攻防机制,与 UDP 攻防的不同点。
  • TCP 的配置,在 Linux 环境的性能优化,知名的网游 WOW(魔兽世界)就采用了 TCP 协议。

可能后续会写一些跟进文章来解释。


UioSun
603 声望33 粉丝

use google find the world. "该用户太懒", dead was yesterday.