我们将TCP协议的内容分为五部分: 连接和终止, 超时重传, 流量控制, 拥塞控制, 保活机制总共5部分. 在进行TCP协议的可靠机制分析前, 我们先对TCP协议的组成进行分析.
TCP协议的组成
如上图所示, 即为TCP头部组成的部分, 我们着重来分析的是下面几个组成部分:
-
序列号 TCP协议为此次连接发起的每一个字节进行编码, TCP头部的序列号, 表示此报文段第一个数据字节的编码, 每个序列号标识一个字节, 总共可以表示
2^32 - 1
个字节,一旦超出, 就回溯为0, TCP协议可以据此进行数据的流量控制和超时处理 -
确认号 也叫做ACK号, 表示,ACK发送方期望接收的序列号, 即
最后成功接收的数据字节的序列号 + 1
- 标志字段 标志字段是TCP协议中相当重要的部分, 最初常用的字段是6个, 现在扩充为8个. 这几个字段的基本含义如下:
CWR
: 表示拥塞窗口减少ECE
: ECN回显URG
: 紧急指针字段有效ACK
: 确认, 确认号字段有效PSH
: 推送,RST
: 连接错误, 重置连接SYN
: 用于初始化一个连接的同步序列号FIN
: 结束连接
这里我们将SYN
字段标志拿出来讲. 它用来表示一个连接的发起. 更重要在于: 用于初始化一个连接的序列号, 即ISN(初始化序列号). ISN一般是采用算法取得随机数, 为了安全. 当序列号耗尽时, 又重新归0, 从头继续计数 一般, 本端发送[SYN M]
->
[SYN N, ACK M + 1]
->
[ACK N + 1]
其中ACK
都是对端SYN的初始序列号 + 1
, 这里SYN消耗了序列号, 表示SYN属于可靠的传输, 有超时重传机制. 同时, ACK不消耗序列号, 说明ACK有丢失的可能性, 而且ACK实际上, 确认的是至今为止的连接数, 也就是说: 即使我们曾经丢失了一个ACK, 它也可能被下一个到来的ACK确认
最后是关于TCP协议的选项
, 这部分内容, 我们一般有这样的几个选项:
- MSS
- SACK
- 时间戳
- 窗口缩放
这几个的选项, 会在后续进行解释
TCP的连接管理
TCP是面向连接的单播协议, 我们提到它的连接管理能够想到什么? 三路握手建立建立连接, 四路分组断开连接
三路握手建立连接
三路握手的基本流程是这样的:
- 客户端发起连接, 传送
[SYN, Seq = ISN(c)]
报文, 表示发起连接, 且通告对端窗口大小 - 服务端回复
[SYN + ACK, Seq = ISN(s), ACK = ISN(c) + 1]
,表示收到客户端请求, 同时发送自己的确认号, 其中窗口通告也告诉对端, 自己本端的窗口大小 - 客户端,回复
[ACK, Seq = ISN(c) + 1, ACK = ISN(s) + 1]
,表示收到对端的SYN分节, 连接建立完成
上面的流程便可以完成TCP的三路握手建立连接了. 来聊几个问题:
- Q: 为什么建立连接需要三次分组交换? A: 因为网络交互是分组交换,
A -> B
,B -> A
的路径可能完全不同. 所以必须互相收到ACK
才行 - Q: 序列号值怎么改变的? A: 发起连接的时候, 各自设置ISN, 之后互相对端确认, 注意:
SYN
会消耗序列号, ACK则不会 - Q: 建立连接的时候, 都要交换什么数据? A: 在连接建立的时候, 会进行窗口大小的交换, 即窗口通告, 以及MSS
- Q: ISN为什么会是随机的? A: 我们对于一条连接的标识是五元组, 在一条连接断开后, 立马重新连接, 就有可能出现非同一条连接的持续情况, 而且, 可能会造成安全问题, 所以一般采用复杂的算法: 基于时钟和结合哈希, 进行Seq计算, 保证安全性
四路分组断开连接
四路分组断开连接 任意一端发起断开都可以, 建立连接后, 两端是对等, 这里仅仅以客户端举例
- 客户端发送
[FIN, Seq = K, ACK = L]
发送FIN分节表示断开连接, 同时确认上一个数据包 - 服务端发送
[ACK, Seq = L, ACK = K + 1]
, 收到FIN,ACK表示确认到对端的请求,同时ACK + 1 - 服务端发送
[FIN + ACK, Seq = L, ACK = K + 1]
, 发送服务端的断开连接请求, 因为ACK
不消耗序列号, 所以这两个报文段的Seq是相等的, 因为没有收到对端新发送过来的报文段, 所以ACK
一样的 - 客户端发送
[ACK, Seq = K, ACK = L + 1]
, 客户端最后确认服务端的请求, 完全断开连接
上面的流程就是四路分组断开连接的过程了. 同样, 来聊几个问题:
- Q: 四路分组交换, 为什么要四次? 连接只要三次 A: 之所以需要四路分组交换, 因为TCP是全双工的协议, 两端可以各自断开连接, 所以需要四次, 网络环境紧张时, 可以是三此分组交换断开连接
- Q: 我们平时进行学习的时候, 都是
FIN -> ACK -> FIN -> ACK
的交换, 这里怎么是[FIN + ACK]
? A: 其实就是一个细节的问题, 这里详细展示出, 其中的ACK
, 建立连接后, 基本上正常的数据交互中, 都是会有ACK的, 用来进行确认机制, 同时进行窗口通告 - Q: 我们每次之后的发送
ACK确认
会提高效率吗, 因为其中包含的信息并不多. A: 不会, 发送ACK
和完整的TCP报文段的开销是一致的, 因为确认号是TCP头部的一部分
TCP半关闭
我们讲TCP是一个全双工的协议. 那么它到底体现在哪里呢? TCP可以两端分开关闭, 仅仅关闭其中一端, 不会影响另外一端的使用情况. 半关闭没什么更好说的, 看图就完事了
同时打开, 同时关闭.
Emmm, 这两种情况十分十分罕见, 出现的机率很小. 要求是: 在一端发送SYN/FIN
, 还未到达对端的时候, 另外一端同时发送了SYN/FIN
. 这样就发生了同时关连接/同时关闭
其中需要注意的点在于: 因为一端还未发送到, 所以Seq的变化和正常情况下不一致 其他基本没什么区别, 只要知道有这种现象存在就可以了
TCP选项
我们上面提到过TCP选项的问题. 下面介绍几个常用的TCP选项
MSS (maxinum segment size)
MSS即最大段大小, 它表示我们所能接收的最大报文段, 也是发送时最大的报文段. 与窗口大小进行辨析: 窗口大小是协商的结果, 是动态滑动的. MSS不是协商的结果, 它是固定的. 表示此次连接中, 我们所能接收的最大报文段大小
SACK
用于在报文遗失时, 或者丢失时, 进行重传的措施
窗口缩放选项
此选项只能用在SYN
报文中, 提供一个比例因子, 进行滑动窗口的左移. 最大可到1GB 在海量数据传输中及极其有用.
TCP状态转换
上图即为TCP的状态转换图. 我们对于TCP的状态装换过程中, 这两个状态着重进行分析:
-
CLOSE_WAIT: 此状态的产生自服务端: 接收
FIN
, 发送ACK
之后. 服务端会出现大量CLOSE_WAIT
的状态有很多原因: 比如未使用close(2)
造成的大量CLOSE_WAIT
状态, 导致资源占用. -
TIME_WAIT: 此状态产生自客户端: 在发送最后一个ACK之后, 进入
TIME_WAIT
状态.此状态会持续2MSL时间大小
为什么会有TIME_WAIT状态? 主要是两个方面的原因:
- 实现可靠的连接终止 因为可能会有最后一个
ACK
丢失的情况, 所以本端要保持状态, 2MSL保证有足够的时间重发ACK - 让老的重复报文段在网络中过期失效, 保证建立新的连接时不再接收他们.
我们使用SO_LINGER
可以直接取消TIME_WAIT
状态, 不过,不建议这么做, TIME_WAIT状态是我们的朋友