【网络原理】从零开始深入理解TCP的各项特性和机制.(二)

本篇博客给大家带来的是TCP/IP原理的知识点,重点以TCP为主,接续上篇.
🐎文章专栏: JavaEE初阶
🚀若有问题 评论区见
❤ 欢迎大家点赞 评论 收藏 分享
如果你不知道分享给谁,那就分享给薯条.
你们的支持是我不断创作的动力 .

王子,公主请阅🚀

  • 要开心
  • [1. TCP传输过程的重要机制.](#1. TCP传输过程的重要机制.)
    • [1.1 滑动窗口](#1.1 滑动窗口)
    • [1.2 流量控制](#1.2 流量控制)
    • [1.3 拥塞控制](#1.3 拥塞控制)
    • [1.4 延迟应答](#1.4 延迟应答)
    • [1.5 捎带应答](#1.5 捎带应答)
    • [1.6 面向字节流](#1.6 面向字节流)
    • [1.7 粘包问题](#1.7 粘包问题)
    • [1.8 异常情况](#1.8 异常情况)
  • [2. TCP和UDP的对比](#2. TCP和UDP的对比)

要开心

要快乐

顺便进步

1. TCP传输过程的重要机制.

1.1 滑动窗口

Ⅰ 什么是滑动窗口?

上篇讲的前三个机制(确认应答,超时重传,连接管理)都是在保证 tcp 的可靠传输. 前三个机制在保证tcp可靠传输的同时,也影响了传输效率,传输变慢. 滑动窗口机制则是为了减小这种影响.

确认应答机制, 对每一个发送的数据段, 都要给一个ACK确认应答. 收到ACK后再发送下一个数据段. 这样做有一个比较大的缺点, 就是性能较差. 尤其是数据往返的时间较长的时候. 如果能够一次发送多条数据,就可以大大的提高性能.

① 一次发送多条数据, 也不是想发多少就发多少的,一次发的数量存在上限,我们把一次最多能发的数据条数称为窗口大小. 上图的窗口大小就是4000个字节.

② 如上图,主机A向主机B批量发送了四份数据, B返回给A一个ACK,A的窗口就往后移动一位.

Ⅱ 在传输过程中丢包了该如何解决?

① 数据包已经抵达,ACK丢包了.

这种情况下, 部分ACK丢了并不要紧, 因为可以通过后续的ACK进行确认;
如果1001这个ACK丢了,但是2001ACK正常传输, 2001成功传输意味着 2001之前的数据都已经确认传输成功了. 也就涵盖了1001的情况.

② 数据包丢了

如上图所示, 当1-1000数据包成功发送而1001-2000数据包丢了之后. 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 "我想要的是1001" 一样;
如果发送端主机连续三次收到了同样⼀个 "1001" 这样的应答, 就会将对应的数据 1001 - 2000数据包重新发送;
这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了,因为2001 - 7000的数据包,接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;
上述这种机制被称为 "高速重发控制"(也叫 "快重传")

如果通信双方,传输数据的量比较小,也不频繁,就仍然是普通的确认应答和普通的超时重传.
如果通信双方,传输数据量更大,也比较频繁, 就会进入到滑动窗口模式,按照快速重传的方式处理.

在滑动窗口机制中,窗口越大,传输的效率就越高. 但是窗口的设置也不是越大越好.
如果传输的速度太快,就可能会使接收方处理不过来了.此时,接收方也会出现丢包,发送方还得重传.
不要忘记TCP的初心是可靠性, 一定是在可靠性的基础上,提高传输效率的.

1.2 流量控制

接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应.
因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制.

① 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段, 通过ACK端通知发送端;

如上图,接收主机B 接收缓冲区总空间是 4000, 收到 1000 数据后还剩 3000,于是就把 3000 放到应答报文ACK中告诉发送方.

② 发送端接受到这个窗口之后, 就会减慢自己的发送速度;

③ 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;

④ 如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探
测数据段, 使接收端把窗口大小告诉发送端.

窗口探测包不携带具体的业务数据.只是为了查询当前接收方这边的接收缓冲区剩余空间.

剩余空间越大,意味着消费速度越快, 处理能力就越强,剩余空间越小,消费速度越慢,处理能力就越弱. 处理能力取决于B的应用程序代码是怎么写的.

上述过程类似于前面讲过的生产者消费者模型.

A给B发送数据,数据优先到达TCP socket对象上的接收缓冲区, 主机B 这边的应用程序调用read方法把数据从接收缓冲区中读出来, 该数据就从接收缓冲区删除了. 此过程中:
生产者: A
消费者: B
阻塞队列: B的接收缓冲区.

1.3 拥塞控制

流量控制考虑的是接受方的处理能力, 而拥塞控制考虑的是通信过程中间节点的情况.
由于中间节点,结构更复杂,更难以直接的进行量化因此就可以使用"实验"的方式,来找到个合适的值.

虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题. 因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的.
TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;

让 A 先按照比较低的速度先发送数据(小的窗口)如果数据传输过程非常顺利,没有丢包,再尝试使用更大的窗口,更高的速度进行发送.
随着窗口大小不停的增大,达到一定程度,可能中间节点就会出现问题了.此时这个节点就可能会出现丢包.发送方发现丢包了,就把窗口大小调整小,此时如果发现还是继续丢包,继续缩小,如果不丢包了,就继续尝试窗口变大.
这个过程中,发送方不停的调整窗口大小,逐渐达成"动态平衡".

拥塞控制, 归根结底是TCP协议想要尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.

1.4 延迟应答

如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小.

假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
但实际上可能接收端处理数据的速度很快, 10ms之内就把500K数据从接收缓冲区消费掉了;
在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;

窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下,尽量提高传输效率;

有延时应答机制才促成了四次挥手能够三次挥完.

1.5 捎带应答

捎带应答能够进一步提高效率.
在延迟应答的基础上, 我们发现, 很多情况下客户端服务器在应用层也是 "一发一收" 的.
意味着客户端给服务器说了 "How are you", 服务器也会给客户端回一个 "Fine, thank you";
那么这个时候ACK就可以搭顺风车, 和服务器回应的 "Fine, thank you" 一起回给客户端

1.6 面向字节流

创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区.
调用write时, 数据会先写入发送缓冲区中;如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
然后应用程序可以调用read从接收缓冲区拿数据;
另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据,也可以写数据. 这个概念叫做 全双工.

由于缓冲区的存在, TCP程序的读和写不需要一 一匹配, 例如:
① 写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;
② 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次;

1.7 粘包问题

Ⅰ 什么是粘包问题?

如上图所示, 接收缓冲区中有三个应用层数据包, 这三个数据包以字节的形式紧紧挨在一起.
接收端B, 读取数据的时候不知道缓冲区中的数据从哪到哪是一个完整的应用数据包.

Ⅱ 粘包问题该如何解决?
避免粘包问题归根结底就一句话,明确两个包之间的边界.

① 对于定长的包, 保证每次都按固定大小读取即可; 例如上面的request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(request)依次读取即可;

② 对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;

③ 对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证
分隔符不和正文冲突即可);

Ⅲ UDP协议是否也存在粘包问题呢?

显然不存在

① 对于UDP, 如果还没有上层交付数据, UDP的报文长度仍然在. 同时, UDP是一个一个把数据交付给应用层. 就有很明确的数据边界.

② 站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收到. 不会出现收到"半个"的情况.

1.8 异常情况

① 进程终止(正常还是异常都一样)

进程终止会释放文件描述符表(相当于调用了socket.close()), 仍然可以发送FIN. 和正常关闭的四次挥手没有什么区别.

② 主机关机

进行关机的时候,就是会先触发强制终止进程操作.(相当于①的情况). 如果在系统关闭之前,对端返回的 ACK 和 FIN 到了,此时系统还是可以返回 ACK,进行正常的四次挥手的.
如果系统已经关闭了,ACK 和 FIN 迟到了,无法进行后续ACK 的响应,站在对端的角度,对端以为是自己的 FIN 丢包了,重传 FIN,重传几次都没有响应,自然就会放弃连接.

③ 主机非正常掉电/网线断开

这种情况是 一瞬间的事情,来不及终止进程,也来不及发送 FIN, 主机直接就停机了.

第一种情况:
如果对端是在发送数据(接收方掉电),发送的数据就会一直等待ACK, 触发超时重传.触发 TCP 连接重置功能.发起"复位报文段".如果 复位报文段 发过去之后,也没有效果,此时就会释放连接.

如上图所示: 上述红圈内的报文段即为复位报文段.

第二种情况:
如果对端是在接收数据(发送方掉电),对端还在等待数据到达, 等了半天没消息,此时无法区分时对端丢包了,还是对端挂了.
好在 TCP 中提供了 心跳包机制.
接收方也会周期性地给发送方发起一个特殊的, 不携带业务数据的数据包. 并且期望对方返回一个应答,如果对方多次没有返回应答, 就视为对方挂了,此时就可以单方面释放连接了.

TCP 的心跳机制在日常开发中非常常见,经常能碰到这种心跳机制.(尤其是在分布式系统). 后续用到的心跳,都是在应用程序中,自助实现的(秒级或者毫秒级),而不是直接使用 tcp 的心跳. 因为TCP的心跳机制周期比较长.

2. TCP和UDP的对比

Ⅰ 各自优势

① TCP的优势是可靠传输,适用于大部分场景.例如:文件传输,重要状态更新等场景.
UDP的优势是传输效率高,使用于"可靠性不敏感,但性能敏感" 的场景.例如同一机房(同一局域网),视频传输,广播.

有一种特殊的场景,需要把数据发给局域网的所有机器,这个情况就是广播.

② TCP没有长度限制, 如果要传输比较大的数据包, TCP 更优先(UDP有64KB的限制).
如果要进行广播传输,优先考虑UDP,因为UDP天然支持广播, TCP不支持(需要额外写代码实现).

Ⅱ 用UDP实现可靠传输(经典面试题)

参考TCP的可靠性机制, 在应⽤层实现类似的逻辑;
① 引入序列号, 保证数据顺序;
② 引入确认应答, 确保对端收到了数据;
③ 引入超时重传, 如果隔一段时间没有应答, 就重发数据;
④ 引入滑动窗口, 一次多传几个数据包,在确保可靠性的基础上提高传输效率.
⑤ ...

本篇博客到这里就结束啦, 感谢观看 ❤❤❤
🐎期待与你的下一次相遇😊😊😊

相关推荐
席万里12 分钟前
Go语言企业级项目使用dlv调试
服务器·开发语言·golang
IT运维爱好者1 小时前
Ubuntu 22.04.4操作系统初始化详细配置
linux·运维·服务器·ubuntu
樂5021 小时前
关于 Web 服务器的五个案例
linux·服务器·经验分享
H1346948902 小时前
服务器异地备份,服务器异地备份有哪些方法?
运维·服务器
SQingL2 小时前
解决SSLError: [SSL: DECRYPTION_FAILED_OR_BAD_RECORD_MAC] decryption faile的问题
服务器·网络协议·ssl
山猪打不过家猪2 小时前
(六)RestAPI 毛子(外部导入打卡/游标分页/Refit/Http resilience/批量提交/Quartz后台任务/Hateoas Driven)
网络·缓存
Lonwayne2 小时前
Web服务器技术选型指南:主流方案、核心对比与策略选择
运维·服务器·前端·程序那些事
法迪3 小时前
Linux电源管理(2)_常规的电源管理的基本概念和软件架构
linux·运维·服务器·功耗
weixin138233951793 小时前
EN18031测试,EN18031认证,EN18031报告解读
网络
JhonKI3 小时前
【Linux网络】构建与优化HTTP请求处理 - HttpRequest从理解到实现
linux·网络·http