Linux(传输层二继续讲TCP)
文章目录
- 0. 前言
- 1. 流量控制
- 2. 滑动窗口
- 2-1 基础
- 2-2 重传
- 3. 拥塞控制
- 4. 延迟应答
- 5. 捎带应答
- 6. 面向字节流
- 7. 粘包问题
- 8. TCP异常情况
- 9. TCP小结
- 10. 基于TCP应用层协议
- 11. TCP/UDP对比
0. 前言
上一章我们主讲了TCP,本章我们继续
链接:https://blog.csdn.net/Dingyuan0/article/details/129415670?spm=1001.2014.3001.5502
1. 流量控制
- 主要保证数据的可靠性
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应。
因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);
- 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;
- 窗口大小字段越大, 说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
- 发送端接受到这个窗口之后, 就会减慢自己的发送速度;
- 如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端。
(图片来自于相关教材资料)
- 接收端如何把窗口大小告诉发送端呢? 回忆我们的TCP首部中, 有一个16位窗口字段, 就是存放了窗口大小信息;
- 那么问题来了, 16位数字最大表示65535, 那么TCP窗口最大就是65535字节么?
- 实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是 窗口字段的值左移 M 位;
2. 滑动窗口
- 主要提高传输效率
2-1 基础
确认应答策略:对每一个发送的数据段, 都要给一个ACK确认应答;收到ACK后再发送下一个数据段;这样做有一个比较大的缺点, 就是性能较差,尤其是数据往返的时间较长的时候。
(图片来源于相关教材资料)
既然这样一发一收的方式性能较低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时
间重叠在一起了)。
-
窗口大小指的是无需等待确认应答而可以继续发送数据的最大值. 下图的窗口大小就是4000个字节(四个段).
-
发送前四个段的时候, 不需要等待任何ACK, 直接发送;
-
收到第一个ACK后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推;
-
操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答;
-
只有确认应答过的数据, 才能从缓冲区删掉;窗口越大, 则网络的吞吐率就越高
(图片来源于相关教材资料)
我们如何理解TCP的发送缓存区,我们可以把缓冲区看成一个数组;而数组的下标是序号;而滑动缓冲区就是发送缓冲区的一部分。
滑动窗户的起始地址有传过来的确认序号决定的,大小是有拥塞窗口和对方窗口大小共同决定的。
2-2 重传
情况一: 数据包已经抵达, ACK被丢了
- 这种情况下, 部分ACK丢了并不要紧, 因为可以通过后续的ACK进行确认。
情况二: 数据包就直接丢了
(图片来源于相关教材资料)
- 当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 "我想要的是 1001"一样;
- 如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
- 这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中。
例如:滑动窗口是1000~5000;而1000 ~ 2000 和 3000 ~ 4000数据包丢了,发送端只能收到其它的ACK,此时发送端的窗口起始位置还是1000;直到收到1000 ~ 2000的ACK窗口起始才会改变。
- 这种机制被称为 “高速重发控制”(也叫 “快重传”)
3. 拥塞控制
滑动窗口的大小 = min(拥塞窗口, 对方窗口大小【接受能力—剩余空间】)
- 虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题。
- 因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能引起雪上加霜。
- TCP引入慢启动机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据。
相关图片:
- 发送开始的时候, 定义拥塞窗口大小为1;
- 每次收到一个ACK应答, 拥塞窗口加1;
- 每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;
刚开始拥塞窗口增长速度, 是指数级别的,“慢启动” 只是指初使时慢, 但是增长速度非常快;为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍。
- 此处引入一个叫做慢启动的阈值;
- 当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长;
- 当TCP开始启动的时候, 慢启动阈值等于窗口最大值;
- 在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1;
就好像谈恋爱一样,刚开始处于热恋期;感情温度升的非常快,之后感觉升温就慢下来了;最后可能还吵了一架,导致感情瞬间到达极小点,如何男生不断的示好,感情也重新开始升温,可是感情的最后点变低了。(当然也可能直接崩了)
TCP拥塞控制这样的过程, 就好像热恋的感觉。
小结:
- 少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;
- 当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;
- 拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案。
4. 延迟应答
如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小。
- 假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
- 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
- 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
- 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M。
注意:
- 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
那么所有的包都可以延迟应答么? ??肯定也不是;
- 数量限制: 每隔N个包就应答一次;
- 时间限制: 超过最大延迟时间就应答一次
具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2, 超时时间取200ms。
5. 捎带应答
在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 “一发一收” 的. 意味着客户端给服务器说了 “How are you”, 服务器也会给客户端回一个 “Fine, thank you”;那么这个时候ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起回给客户端。
6. 面向字节流
创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区。
- 调用write时, 数据会先写入发送缓冲区中;
- 如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
- 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
- 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
- 然后应用程序可以调用read从接收缓冲区拿数据;
- 另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个概念叫做全双工。
由于缓冲区的存在, TCP程序的读和写不需要一一匹配,。
例如:
- 写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;
- 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次;
类似我们写过的网络计算机中TCP协议
链接:https://blog.csdn.net/Dingyuan0/article/details/129249383?spm=1001.2014.3001.5502
7. 粘包问题
例如:早上我们吃一笼饺子,我们会发现,有时候饺子粘在一起,我们用筷子夹的时候容易只加半个。
- 首先要明确, 粘包问题中的 “包” , 是指的应用层的数据包;
- 在TCP的协议头中, 没有如同UDP一样的 “报文长度” 这样的字段, 但是有一个序号这样的字段;
- 站在传输层的角度, TCP是一个一个报文过来的. 按照序号排好序放在缓冲区中;
- 站在应用层的角度, 看到的只是一串连续的字节数据;
- 那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包。
那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界。
- 对于定长的包, 保证每次都按固定大小读取即可; 例如上面的Request结构, 是固定大小的, 那么就从缓冲
区从头开始按sizeof(Request)依次读取即可; - 对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;
- 对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可);
对于UDP协议来说, 是否也存在 “粘包问题” 呢?(没有,报文是定长的)
- 对于UDP, 如果还没有上层交付数据, UDP的报文长度仍然在. 同时, UDP是一个一个把数据交付给应用层.,就有很明确的数据边界。
- 站在应用层的站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收. 不会出现"半个"的情况。
8. TCP异常情况
- 进程终止: 进程终止会释放文件描述符, 仍然可以发送FIN,它和正常关闭没有什么区别。
- 机器重启: 它和进程终止的情况相同。
- 机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset, 即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放。
- 另外, 应用层的某些协议, 也有一些这样的检测机制. 例如HTTP长连接中, 也会定期检测对方的状态. 例如QQ, 在QQ断线之后, 也会定期尝试重新连接。
9. TCP小结
可靠性:
- 校验和出错
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
提高性能:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
其他:
- 定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等)
10. 基于TCP应用层协议
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
当然,也包括你自己写TCP程序时自定义的应用层协议。
11. TCP/UDP对比
- TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景;
- UDP用于对高速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输等. 另外UDP可以用于广播。