概念 | 解释 |
---|---|
传输层 TCP 复用 | 发送方的部分应用进程的报文在传输层使用 TCP 协议进行封装 |
传输层 UDP 复用 | 发送方的部分应用进程的报文在传输层使用 UDP 协议进行封装 |
网际层 IP 复用 | 发送方不同协议的数据都可以封装成 IP 数据报(IP 协议字段,TCP 为 6,UDP 为 17) |
网际层 IP 分用 | 接收方的网际层在去除首部后把数据交付给上层相应的协议(TCP 或 UDP) |
传输层 TCP 分用 | 接收方的传输层使用 TCP 协议去除首部后把数据交付给目的应用进程 |
传输层 UDP 分用 | 接收方的传输层使用 UDP 协议去除首部后把数据交付给目的应用进程 |
端口号 | 类型 | 解释 |
---|---|---|
0 ~ 1023 | 熟知端口号 | 由 IANA 分配给 TCP/IP 体系结构应用层中最重要的一些应用协议 |
1024 ~ 49151 | 登记端口号 | 为没有熟知端口号的应用程序使用。要使用这类端口号,必须在 IANA 进行登记,以防止重复 |
49152 ~ 65535 | 短暂端口号 | 仅在客户端使用,由客户进程在运行时动态选择,通信结束后会被系统收回,以便给其他客户进程使用 |
FTP | SMTP | DNS | DHCP | HTTP | BGP | HTTPS | RIP |
---|---|---|---|---|---|---|---|
21/20 | 25 | 53 | 67/68 | 80 | 179 | 443 | 520 |
【注】OSPF 不使用传输层协议,所以没有对应的端口号。但 OSPF 的 IP 协议字段值为 89。
【注】重点关注 seq、ack、ACK、SYN、FIN 这几个位。
假设有客户机 A 和服务器 B 已经建立了 TCP 连接。
(1)现 A 向 B 发送 TCP 确认报文段:
首部 | 数据部分 |
---|---|
seq=201, ack=800, ACK=1 | 100B(seq=201~300) |
seq=201
:指 A 的数据部分是从序号 201 开始的,数据部分总长 100B,因此数据部分的最后一个序号是 300。ack=800
:指 A 期望 B 发来的下一个报文段的数据部分序号是从 800 开始的。ACK=1
:连接建立后所有传送的报文段都必须置 1。(2)然后 B 向 A 发送 TCP 确认报文段:
首部 | 数据部分 |
---|---|
seq=800, ack=301, ACK=1 | 200B(seq=800~999) |
seq=800
:指 B 的数据部分是从序号 800 开始的,数据部分总长 200B,因此数据部分的最后一个序号是 999。ack=301
:指 B 期望 A 发来的下一个报文段的数据部分序号是从 301 开始的,同时确认 A 的报文段(seq=201~300)已收到。ACK=1
:连接建立后所有传送的报文段都必须置 1。(3)若 A 向 B 发送 3 个 TCP 确认报文段,其中第 2 个报文段丢失:
首部 | 数据部分 |
---|---|
seq=201, ack=x, ACK=1 | 100B(seq=201~300) |
seq=301, ack=x, ACK=1 | 100B(seq=301~400)(丢失) |
seq=401, ack=x, ACK=1 | 100B(seq=401~500) |
则 B 仅正确接收第 1 个和第 3 个报文段。此时 B 向 A 发送的 TCP 报文段,应对已正确接收且按序到达的最后一个 TCP 报文段确认,这就是累积确认(更详细的解释见第 4 节内容):
首部 | 数据部分 |
---|---|
seq=x, ack=301, ACK=1 | 200B(seq=x~x+199) |
TCP 连接有三个阶段:连接建立、数据传送(上面已述)、连接释放。
假设有客户机 A 和服务器 B 准备建立 TCP 连接。
服务器 B 进程处于 LISTEN(监听)状态,等待客户机 A 的连接请求。
客户机 A 向服务器 B 发送 TCP 连接请求报文段:
首部 | 数据部分 |
---|---|
seq=x, ack=0, SYN=1, ACK=0 | SYN 报文段不能携带数据 |
seq=x
:随机选取一个初始序号 x,作为客户机 A 的 TCP 报文段数据部分的初始序号。ack=0
:由于 ACK=0,所以 ack 字段无效。SYN=1
:TCP 连接请求报文段中的同步标志位 SYN 的值必须设置为 1。ACK=0
:当 SYN=1,ACK=0 时,表明这是一个 TCP 连接请求报文段。此时,客户机 A 进入 SYN-SENT(同步已发送)状态。
【注】TCP 规定同步标志位 SYN 被设置为 1 的报文段(例如 TCP 连接请求报文段和 TCP 连接请求确认报文段)不能携带数据,但要消耗掉一个序号。
服务器 B 收到 TCP 连接请求报文段后,向客户机 A 发送 TCP 连接请求确认报文段:
首部 | 数据部分 |
---|---|
seq=y, ack=x+1, SYN=1, ACK=1 | SYN 报文段不能携带数据 |
seq=y
:随机选取一个初始序号 y,作为服务器 B 的 TCP 报文段数据部分的初始序号。ack=x+1
:服务器 B 期望收到客户机 A 发来的下一个报文段的数据部分序号是从 x+1 开始的,同时确认客户机 A 的报文段(seq=x)已收到。SYN=1
:TCP 连接请求确认报文段中的同步标志位 SYN 的值必须设置为 1。ACK=1
:当 SYN=1,ACK=1 时,表明这是一个 TCP 连接请求确认报文段。此时,服务器 B 进入 SYN-RCVD(同步已接收)状态。
客户机 A 收到 TCP 连接请求确认报文段后,向服务器 B 发送 TCP 确认报文段:
首部 | 数据部分 |
---|---|
seq=x+1, ack=y+1, SYN=0, ACK=1 | TCP 确认报文段可以携带数据 |
seq=x+1
:客户机 A 的 TCP 报文段数据部分的序号为 x+1。ack=y+1
:客户机 A 期望收到服务器 B 发来的下一个报文段的数据部分序号是从 y+1 开始的,同时确认服务器 B 的报文段(seq=y)已收到。SYN=0
:TCP 确认报文段中的同步标志位 SYN 的值必须设置为 0。ACK=1
:当 SYN=0,ACK=1 时,表明这是一个普通的 TCP 确认报文段。此时,客户机 A 进入 ESTABLISHED(连接已建立)状态。
【注】TCP 规定普通的 TCP 确认报文段可以携带数据,但如果不携带数据,则不消耗序号。如果该报文段不携带数据,则客户机 A 要发送的下一个数据报文段的序号仍为 x+1。
服务器 B 收到 TCP 确认报文段后,也进入 ESTABLISHED(连接已建立)状态。至此 TCP 连接已建立。
【总结】TCP 建立连接的过程:
客户机:“我有话要跟你讲,不知可不可以?”
服务器:“可以,你讲吧!”
客户机:“好的!blablabla”
假设有客户机 A 和服务器 B 准备释放 TCP 连接。
客户机 A 进程和服务器 B 进程处于 ESTABLISHED(连接已建立)状态。
客户机 A 向服务器 B 发送 TCP 连接释放报文段:
首部 | 数据部分 |
---|---|
seq=u, ack=v, FIN=1, ACK=1 | FIN 报文段可携带也可不携带数据 |
seq=u
:客户机 A 的 TCP 报文段数据部分的序号为 u,它等于客户机 A 之前已传送数据部分的最后一个字节的序号加 1。ack=v
:ack 字段的值为 v,它等于客户机 A 之前已收到数据的最后一个字节的序号加 1。FIN=1
:TCP 连接释放报文段中的同步标志位 SYN 的值必须设置为 1。ACK=1
:当 FIN=1,ACK=1 时,表明这是一个 TCP 连接释放报文段。【注】TCP 规定,终止标志位 FIN 等于 1 的 TCP 报文段即使不携带数据,也要消耗掉一个序号。
此时,客户机 A 进入 FIN-WAIT-1(终止等待 1)状态。
服务器 B 收到 TCP 连接释放报文段后,向客户机 A 发送 TCP 确认报文段:
首部 | 数据部分 |
---|---|
seq=v, ack=u+1, FIN=0, ACK=1 | TCP 确认报文段可以携带数据 |
seq=v
:服务器 B 的 TCP 报文段数据部分的序号为 v。ack=u+1
:服务器 B 期望收到客户机 A 发来的下一个报文段的数据部分序号是从 u+1 开始的,同时确认客户机 A 的连接释放报文段(seq=u)已收到。FIN=0
:TCP 确认报文段中的同步标志位 SYN 的值必须设置为 0。ACK=1
:当 FIN=0,ACK=1 时,表明这是一个普通的 TCP 确认报文段。此时,服务器 B 进入 CLOSE-WAIT(关闭等待)状态。客户机 A 已经没有数据要发送了。但服务器 B 如果还有数据要发送,客户机 A 仍要接收,即从服务器 B 到客户机 A 这个方向的连接并未关闭,这称为半关闭状态,该状态可能会持续一段时间。
服务器 B 向客户机 A 发送若干个 TCP 确认报文段,直到发送最后一次报文段,即 TCP 连接释放报文段:
首部 | 数据部分 |
---|---|
seq=w, ack=u+1, FIN=1, ACK=1 | FIN 报文段可携带也可不携带数据 |
seq=w
:经过发送若干个 TCP 确认报文段,服务器 B 的 TCP 报文段数据部分的序号变为 w。ack=u+1
:服务器 B 期望收到客户机 A 发来的下一个报文段的数据部分序号是从 u+1 开始的,同时重复确认客户机 A 的连接释放报文段(seq=u)已收到。FIN=1
:TCP 连接释放报文段中的同步标志位 SYN 的值必须设置为 1。ACK=1
:当 FIN=1,ACK=1 时,表明这是一个 TCP 连接释放报文段。此时,服务器 B 进入 LAST-ACK(最后确认)状态。
客户机 A 收到 TCP 连接释放报文段后,向服务器 B 发送 TCP 确认报文段:
首部 | 数据部分 |
---|---|
seq=u+1, ack=w+1, FIN=0, ACK=1 | TCP 确认报文段可以携带数据 |
seq=u+1
:客户机 A 的 TCP 报文段数据部分的序号为 u+1。ack=w+1
:客户机 A 期望收到服务器 B 发来的下一个报文段的数据部分序号是从 w+1 开始的,同时确认服务器 B 的连接释放报文段(seq=w)已收到。FIN=0
:TCP 确认报文段中的同步标志位 SYN 的值必须设置为 0。ACK=1
:当 FIN=0,ACK=1 时,表明这是一个普通的 TCP 确认报文段。此时,客户机 A 进入 TIME-WAIT(时间等待)状态,服务器 B 收到 TCP 确认报文段后进入CLOSED(连接关闭)状态。但是 TCP 连接仍未释放,必须经过 2MSL(最长报文段寿命,Maximum Segment Lifetime)的时间后,客户机 A 才能进入 CLOSED(连接关闭)状态。
【总结】TCP 释放连接的过程:
客户机:“我准备走了。”
服务器:“等一下,我还有一些话没说完。blablabla”
服务器:“blablabla”
服务器:“blablabla,我说完了,你可以走了。”
客户机:“好的!那我走了!”
TCP 协议提供一种基于滑动窗口协议的流量控制机制。TCP 使用了校验、序号、确认和重传等机制来达到可靠传输的目的,在下面的过程中将体现这一特点。
假设有发送方 A 和接收方 B 已建立 TCP 连接,不考虑 TCP 的拥塞控制。再假定 A 只给 B 发送数据,B 对 A 进行流量控制。
在 A 和 B 建立 TCP 连接后,B 告诉 A:“我的接收窗口 rwnd=500”,因此 A 将自己的发送窗口 swnd 也设置为 500。窗口大小是以最大报文段 MSS 为单位的,一般将 MSS 设置为 1B,所以 “swnd=500”意思是发送窗口的大小为 500B。
A 的发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | √ | √ | √ | √ | |||
A 已发送 | ||||||||
B 已确认 | ||||||||
B 已收到 |
A 发送数据:seq=1,seq=101,seq=201,seq=301,发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | √ | √ | √ | √ | |||
A 已发送 | √ | √ | √ | √ | ||||
B 已确认 | ||||||||
B 已收到 |
但是很不巧,seq=201 丢失了,B 只收到 seq=1,seq=101,seq=301。其中 seq=301 是失序报文段,但 B 不会丢弃它。TCP 作如下规定:每接收到一个失序报文段,就要发送一次冗余 ACK,指明下一个期待的报文数据序号。
很明显,现在 B 期望收到 seq=201 的报文,因此 seq=301 是不会被确认的,这被称为累积确认。现在 B 的接收窗口已经接受了 200B 的数据,还有 300B 未接收,于是发送 ACK=1,ack=201,rwnd=300 的冗余报文给 A。
此时发送方 A 的 swnd 调整为 300,swnd 滑动到首个未确认的数据位置。发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | √ | √ | |||||
A 已发送 | √ | √ | √ | √ | ||||
B 已确认 | √ | √ | ||||||
B 已收到 | √ | √ | √ |
发送方 A 继续发送 seq=401,对于接收方 B 来说,seq=401 依然是失序报文段,继续发送 ACK=1,ack=201,rwnd=300 的冗余报文给 A。此时发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | √ | √ | |||||
A 已发送 | √ | √ | √ | √ | √ | |||
B 已确认 | √ | √ | ||||||
B 已收到 | √ | √ | √ | √ |
发送方 A 也没有对 seq=201 坐视不理,实质上从发出 seq=201 开始,重传计时器也开始计数了,A 发现计时器超时但仍未收到来自 B 的确认,于是进行超时重传。
【注】其实,当发送方 A 连续收到三个冗余 ACK 后,就可以立即重新发送 seq=201,而不必等待计时器超时,这被称为快重传算法,在下一节“拥塞控制”将提到。
接收方 B 收到 seq=201 后,由于网络流量原因,接收窗口需减小到 100B,于是返回 ACK=1,ack=501,rwnd=100 的报文。此时发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | |||||||
A 已发送 | √ | √ | √ | √ | √ | |||
B 已确认 | √ | √ | √ | √ | √ | |||
B 已收到 | √ | √ | √ | √ | √ |
发送方 A 发送 seq=501,此时发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | |||||||
A 已发送 | √ | √ | √ | √ | √ | √ | ||
B 已确认 | √ | √ | √ | √ | √ | |||
B 已收到 | √ | √ | √ | √ | √ |
接收方 B 收到 seq=501 后,返回 ACK=1,ack=601,rwnd=0 的报文,表示 B 不再接收任何数据。此时发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | ||||||||
A 已发送 | √ | √ | √ | √ | √ | √ | ||
B 已确认 | √ | √ | √ | √ | √ | √ | ||
B 已收到 | √ | √ | √ | √ | √ | √ |
由于发送方 A 的 swnd=0,因此 A 不能发送任何数据了。接收方 B 必须发送一个非零窗口通知,以告知发送方 A:“你可以开始发送大小为 xxx 的窗口了。”所以,A 一直在等待这个通知,只要收到通知,将恢复传输过程。
然而可能会出现这样一种情况:B 发出的通知丢失,A 只能无限等待下去。为了打破由于非零窗口通知报文段丢失而引起的双方互相等待的死锁局面,TCP 为每一个连接都设有一个持续计时器:
【注】TCP 规定:即使接收窗口值为 0,也必须接受零窗口探测报文段、确认报文段以及携带有紧急数据的报文段。
设置一个慢开始门限阈值 ssthresh,初始值为 16。根据发送方的拥塞窗口 cwnd 的大小执行不同的算法:
慢开始算法:从 cwnd=1 开始,每经过一个传输轮次(即往返时延 RTT)指数规律增长,cwnd=2,cwnd=4,cwnd=8,当 cwnd = ssthresh = 16 时,改用拥塞避免算法。
拥塞避免算法:每经过一个传输轮次(即往返时延 RTT),cwnd 加 1,即线性规律增长。只要发送方判断网络出现拥塞,则令 ssthresh = cwnd / 2。然后令 cwnd=1,重新执行慢算法。
【注】传输轮次和往返时延的区别:
- 传输轮次:发送一批报文段并收到它们的确认的时间。
- 往返时延 RTT:开始发送一批报文段到开始发送下一批报文段的时间。
例如有以下传输过程:
传输轮次 | cwnd | 发送的 TCP 数据部分的序号 | 算法 | 备注 |
---|---|---|---|---|
1 | 1 | 0 号 | 慢开始 | 初始时,cwnd = 1,ssthresh = 16 |
2 | 2 | 1 ~ 2 号 | 慢开始 | |
3 | 4 | 3 ~ 6 号 | 慢开始 | |
4 | 8 | 7 ~ 14 号 | 慢开始 | |
5 | 16 | 15 ~ 30 号 | 拥塞避免 | cwnd = ssthresh = 16 |
6 | 17 | 31 ~ 47 号 | 拥塞避免 | |
7 | 18 | 48 ~ 64 号 | 拥塞避免 | |
… | … | … | ||
13 | 24 | 171 ~ 194 号 | 拥塞避免 | 重传计时器发生超时,说明网络拥塞,ssthresh = cwnd/2 = 12 |
14 | 1 | 195 号 | 慢开始 | cwnd 重新设置为 1 |
15 | 2 | 196 ~ 197 号 | 慢开始 | |
16 | 4 | 198 ~ 201 号 | 慢开始 | |
17 | 8 | 202 ~ 209 号 | 慢开始 | |
18 | 12 | 210 ~ 221 号 | 拥塞避免 | cwnd = 16 > ssthresh = 12,改用拥塞避免算法 |
19 | 13 | 222 ~ 234 号 | 拥塞避免 | |
… | … | … |
慢开始和拥塞避免算法的实现过程如下图:
【注】慢开始和拥塞避免的含义:
- “慢开始”是指一开始向网络注入的报文段少,而并不是指拥塞窗口 cwnd 的值增长速度慢。
- “拥塞避免”也并非指完全能够避免拥塞,而是指在拥塞避免阶段将 cwnd 值控制为按线性规律增长,使网络比较不容易出现拥塞。
快重传和快恢复是对慢开始和拥塞避免算法的改进。根据发送方的拥塞窗口 cwnd 的大小执行不同的算法:
快重传算法:当发送方连续接收到三个冗余 ACK 报文时,直接重传对方尚未收到的报文段,而不必等待该报文段的重传计时器超时。
快恢复算法:当发送方连续接收到三个冗余 ACK 报文时,令 ssthresh = cwnd / 2,然后 cwnd 从该 ssthresh 开始线性增加。
例如有以下传输过程:
传输轮次 | cwnd | 发送的 TCP 数据部分的序号 | 算法 | 备注 |
---|---|---|---|---|
1 | 1 | 0 号 | 慢开始 | 初始时,cwnd = 1,ssthresh = 16 |
2 | 2 | 1 ~ 2 号 | 慢开始 | |
3 | 4 | 3 ~ 6 号 | 慢开始 | |
4 | 8 | 7 ~ 14 号 | 慢开始 | |
5 | 16 | 15 ~ 30 号 | 拥塞避免 | cwnd = ssthresh = 16 |
6 | 17 | 31 ~ 47 号 | 拥塞避免 | |
7 | 18 | 48 ~ 64 号 | 拥塞避免 | |
… | … | … | ||
13 | 24 | 171 ~ 194 号 | 拥塞避免 | 发送方连续收到三个冗余 ACK 报文,说明网络拥塞,ssthresh = cwnd/2 = 12 |
14 | 12 | 195 ~ 206 号 | 拥塞避免 | cwnd 设置为 ssthresh = 12 |
15 | 13 | 207 ~ 219 号 | 拥塞避免 | |
16 | 14 | 220 ~ 233 号 | 拥塞避免 | |
17 | 15 | 234 ~ 248 号 | 拥塞避免 | |
18 | 16 | 249 ~ 264 号 | 拥塞避免 | |
19 | 17 | 265 ~ 281 号 | 拥塞避免 | |
… | … | … |
快重传和快恢复算法的实现过程如下图:
需要注意,发送方的发送窗口由接收方的接收窗口和发送方的拥塞窗口两者的最小值所决定,即swnd = min(rwnd, cwnd)
。