参考资料 :小林Coding、阿秀、代码随想录
一、TCP报文段首部
TCP 虽然是面向字节流的,但 TCP 传送的数据单元却是报文段。
一个 TCP 报文段分为首部和数据两部分,TCP 报文段首部的前 20 个字节是固定的,后面有 4n 字节是根据需要而增加的选项 (n 是整数)。
名称 | 含义 | |
---|---|---|
1 | 源端口和目的端口 |
|
2 | 序号(序列号) |
|
3 | 数据偏移 (首部长度) |
|
4 | 确认号 |
|
5 | URG(URGENT) |
|
6 | ACK(Acknowledgment) |
|
7 | PSH (Push) |
|
8 | RST (Reset) |
|
9 | SYN (Synchronize) |
|
10 | FIN (Finish) |
|
11 | Window 窗口 |
|
12 | Checksum 校验和 |
|
13 | Urgent Pointer 紧急指针 |
|
14 | 选项字段 |
|
二、TCP流量控制
1. 利用滑动窗口实现流量控制
流量控制 (flow control) 是为了控制发送方的发送速率,既要让接收方来得及接收,也不要使网络发生拥塞。利用滑动窗口机制可以很方便地在 TCP 连接上实现流量控制。
1.1 什么是窗口?
TCP每发送⼀个数据,都需要⼀次应答,然后继续发送,为每个数据包都进行确认应答,缺点是:数据往返时间越长,网络吞吐量越低。为解决这个问题,TCP 引⼊窗口概念(首部Window字段)。即使在往返时间较长的情况下,它也不会降低网络通信效率。窗口大小是指无需等待确认应答,可以继续发送数据的最大值。
窗口的实现是操作系统开辟的⼀个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。
如上图, 图中的 ACK 600 确认应答报文丢失,可以通过下一个确认应答进行确认,只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答。
通常窗口的大小是由接收方的窗口大小来决定的。发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。
1.2 发送方滑动窗口
- #1 是已发送并收到 ACK确认的数据:1~31 字节
- #2 是已发送但未收到 ACK确认的数据:32~45 字节
- #3 是未发送但总大小在接收方处理范围内(接收方还有空间):46~51字节
- #4 是未发送但总大小超过接收方处理范围(接收方没有空间):52字节以后
发送方将数据全部都发送后,可用窗口的大小就为 0 了,表明可用窗口耗尽,在没收到 ACK 确认之前是无法继续发送数据了。
若收到之前发送数据的 ACK 确认应答后,如果发送窗口的大小没有变化,则滑动窗口往右边移动 k 个字节,因为有 k 个字节的数据被应答确认,接下来的 k 字节又变成了可用窗口。
1.3 接收方滑动窗口
- #1 + #2 是已成功接收并确认的数据(等待应用进程读取);
- #3 是未收到数据但可以接收的数据;
- #4 未收到数据并不可以接收的数据;
1.4 窗口关闭:
TCP中采用滑动窗口进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据,发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为0时,发送方一般不能再发送数据报。两种情况除外:
- 发送紧急数据;
- 发送方可以发送一个1字节的数据报通知接受方重新声明它希望接收的下一字节以及发送方滑动窗口大小。
举个🌰:
如上图,B 向 A 发送了零窗口的报文段后不久,B 的接收缓存又有了一些存储空间。于是 B 向 A 发送了 rwnd = 400 的报文段。但这个报文段在传送过程中丢失了。A 一直等待收到 B 发送的非零窗口的通知,而 B 也一直等待 A 发送的数据。如果没有其他措施,这种互相等待的死锁局面将一直延续下去。
为了解决这个问题,TCP 为每一个连接设有一个持续计时器 (persistence timer)。只要 TCP 连接的一方收到对方的零窗口通知,就启动该持续计时器。若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带 1 字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。
- 若窗口仍然是零,则收到这个报文段的一方就重新设置持续计时器。
- 若窗口不是零,则死锁的僵局就可以打破了。
2. 糊涂窗口综合症
可以用不同的机制来控制 TCP 报文段的发送时机:
- 第一种机制是 TCP 维持一个变量,它等于最大报文段长度 MSS。只要缓存中存放的数据达到 MSS 字节时,就组装成一个 TCP 报文段发送出去。
- 第二种机制是由发送方的应用进程指明要求发送报文段,即 TCP 支持的推送 (push) 操作。
- 第三种机制是发送方的一个计时器期限到了,这时就把当前已有的缓存数据装入报文段(但长度不能超过 MSS)发送出去。
糊涂窗口综合症是指每次仅发送一个字节或很少几个字节的数据时,有效数据传输效率变得很低的现象。
2.1 发送方糊涂窗口综合症
发送方 TCP 每次接收到一字节的数据后就发送。这样,发送一个字节需要形成 41 字节长的 IP 数据报,效率很低。
解决方法:使用 Nagle 算法
Negle算法伪代码:
if 有数据要发送 {
if 可用窗口大小 >= MSS and 可发送的数据 >= MSS {
立刻发送MSS大小的数据
} else {
if 有未确认的数据 {
将数据放入缓存等待接收ACK
} else {
立刻发送数据
}
}
}
- 若发送应用进程把要发送的数据逐个字节地送到 TCP 的发送缓存,则发送方就把第一个数据字节先发送出去,把后面到达的数据字节都缓存起来。
- 当发送方收到对第一个数据字符的确认后,再把发送缓存中的所有数据组装成一个报文段发送出去,同时继续对随后到达的数据进行缓存。
- 只有在收到对前一个报文段的确认后才继续发送下一个报文段。
- 当到达的数据已达到发送窗口大小的一半或已达到报文段的最大长度时,就立即发送一个报文段。
2.2 接收方糊涂窗口综合症
当接收方的 TCP 缓冲区已满,接收方会向发送方发送窗口大小为 0 的报文。
若此时接收方的应用进程以交互方式每次只读取一个字节,于是接收方又发送窗口大小为一个字节的更新报文,发送方应邀发送一个字节的数据(发送的 IP 数据报是 41 字节长),于是接收窗口又满了,如此循环往复。
解决方法:
让接收方等待一段时间,使得接收缓存已有足够空间容纳一个最长的报文段,或者等到接收缓存已有一半空闲的空间。只要出现这两种情况之一,接收方就发出确认报文,并向发送方通知当前的窗口大小。