C/C++ Linux网络编程14 - 传输层TCP协议详解(保证可靠传输)

上篇文章:C/C++ Linux网络编程13 - 传输层TCP协议详解(面向字节流和有连接)-CSDN博客

代码仓库:橘子真甜 (yzc-YZC) - Gitee.com

上篇文章中,我们详细叙述了TCP的面向字节流和连接机制。TCP还有一个特性是可靠传输

目录

[一. TCP报头字段说明](#一. TCP报头字段说明)

[1.1 序列号和确认序列号](#1.1 序列号和确认序列号)

[1.2 16位窗口大小](#1.2 16位窗口大小)

[1.3 标记位和紧急指针](#1.3 标记位和紧急指针)

[二. 确认应答机制](#二. 确认应答机制)

[三. 超时重传机制](#三. 超时重传机制)

[四. 流量控制机制](#四. 流量控制机制)

[五. 滑动窗口机制](#五. 滑动窗口机制)

[六. 拥塞控制机制](#六. 拥塞控制机制)

[6.1 慢启动](#6.1 慢启动)

[6.2 拥塞避免](#6.2 拥塞避免)

[6.3 快重传和快恢复](#6.3 快重传和快恢复)

[七. 总结](#七. 总结)


一. TCP报头字段说明

上篇文章中,我们简要提到了TCP报头的格式。

这里介绍一个各个字段的作用。

源端口,目的端口首部长,检验和就不过多介绍。功能和UDP的类似,可以看前两篇文章

1.1 序列号和确认序列号

由于TCP是面向字节流的,如何保证收到的数据的先后顺序是一致的呢?TCP头部的确认序列号就是用于识别某段字节流数据有没有被收到。

比如32位确认序列号是 1234,则表示对方将1234之前的所有数据都收到了。

而32位序号是发送方发送数据使用的,用于标记发送字节流的位置。

总结一下就是:

32位序号:发送方用于标记自己发送数据的位置,是对方报文的确认序号。

可以用于:确保数据顺序,去重,标记字节位置

32位确认信号:接收方接收报文后,若ack = 1234 表示 1234之前的数据都接收完毕了

可以用于:确保数据顺序接收(方便确认和重传),流量控制,支持滑动窗口

1.2 16位窗口大小

讨论一下下面的场景:

发送方发送数据快于接收方:**会导致接收方接收缓冲区写满,需要额外的时间来处理缓冲区。**这样就会导致双方通信的效率降低。

发送方发送数据小于接收方:**会导致对方没有数据处理,浪费时间。**也会导致双方的通信收到影响。

所以我们需要控制双方发送接收数据的速度,而16位窗口大小就是用于控制接收数据的速度的。如果对方发送的窗口过大,我就发送更多数据,如果对方发送的窗口较少,我就极少发送的数据。

通信双方通过交换窗口大小来**获取对方接收数据的能力,**从而调整发送数据的速度。16位窗口大小是流量控制的基础。

1.3 标记位和紧急指针

TCP报文是有不同类型的,通过标记位来区分不同的TCP报文。

SYN:表示这个报文是用于握手请求的。

FIN:表示这个报文是用于挥手请求的。

ACK:表示这个报文是一个确认报文

PSH:push的意思,如果对方接收缓冲区满了就会发送PSH报文催促对方上层快点取走数据。当然也能用于告诉对方立即交付数据

对方看到psh报文后可以立即将数据推送到上层,当然也能直接忽略

RST:复位报文,要求对方重新建立连接。比如我们的连接异常断开了,重新连上后发现对方还在向我发送数据。此时就会发送RST报文告诉对方连接异常断开,需要重新建立连接

URG:紧急报文,TCP的数据报文是按顺序接收的。如果有紧急数据需要发送咋办(比如需要快速判断对方的状态)?

将URG置1就表示有紧急数据发送,对方发现一个紧急报文后就使用报头的紧急指针获取数据中的紧急数据。这个数据也称为带外数据。

接收方什么时候去读取这个紧急指针的数据呢?应用层recv或者send的最后一个参数标志位就是用于读取发送带外数据

二. 确认应答机制

确认应答机制是基于序列号和确认号实现的。当接收对方是数据X之后,我就需要向对方发送确认序号X+1表示之前的X数据都接收到来。

三. 超时重传机制

超时重传机制是TCP用于处理网络丢包问题的。**如果发送一个TCP数据报之后,如果一段时间内没有接收到对方的响应。**就会默认这个数据丢失了,发送方就会重新发送一个报文。

有两种情况:

1 发送方发送的包丢失了,接收方没有收到

2 接收方接收了发送方的包,但是接收方返回的响应丢失了

两种情况发送方都会进行重传。

思考一下:

1 tcp怎么知道自己发送的数据有没有丢失?

tcp其实并不知道自己的发送数据有没有丢失,只是一段时间内没有收到响应就会进行重传。(即便这个报文还在网络中)

2 既如此,对方不会收到多个相同的报文吗?对方如何处理?

通过序列号来进行去重。这样就能保证同一个数据只会接收一次

3 TCP如何维持自己发送的数据用于重传呢?

维持在发送缓冲区,通过滑动窗口机制处理

4 TCP如何确认超时重传的时间?如果对应一直不应答呢?

由于网络传输的速度是有波动的,如果重传过快,会浪费时间,重传过慢,会导致对方迟迟接收不到数据。所以TCP以500ms,2*500ms依次向对方重传,如果长时间(2MSL)没有收到对方的回应,就会关闭连接。

四. 流量控制机制

TCP报头就介绍了,TCP通过16位窗口来进行流量控制。根据对方发送过来的窗口大小来调整发送数据的数量。从而提高双方通信的效率。

通信双方在三次握手时候其实就第一次交换了互相的窗口大小,后续会定时的发送**窗口探测报文用于确定双方接收数据的能力从而调整发送数据的多少。**当接收缓冲区的数据被上层取走之后就会更新自己的窗口大小。

TCP首部后面的40字节选项中,包含一个字段M,实际窗口大小是窗口字段左移M位

五. 滑动窗口机制

TCP发送报文的时候并不是一个一个发送的,而是连续发送很多报文,连续处理应答。既如此,TCP如何并发处理这些报文?(即处理确认应答,超时重传,流量控制

观察上面的滑动窗口。

已确认用于: 确认应答

只要接收到对方的应答,snd.una指针就会向右移动

已发送未确认用于: 超时重传

如果发现snd.una和snd.nxt中有数据迟迟没有应答,就会进行重传

可发送用于: 流量控制

我方只要数据,snd.nxt就会向右移动。接收到对方发送的窗口大小后就会移动snd.wnd表示对方的窗口更大,我也能发送更多数据。

所以说:滑动窗户是确认应答,超时重传,流量控制的基础。

在内核中,滑动窗口本质是一个环形结构,肯定有人放数据,有人拿数据,所以不会出现越界问题,越界了取模即可

六. 拥塞控制机制

拥塞控制是用于控制网络通信效率的机制。比如我们client发送了10000个报文,丢包了10个,这是正常的。假如丢掉了99990个呢?明显是网络出现了问题,此时就要执行拥塞控制用于保证整个网络通信的效率。

6.1 慢启动

慢启动是指:通信前期,先少量发送数据,然后根据网络状态发送数据。并且以指数增长。TCP中有一个拥塞窗口cwnd。每当收到一个ack之后,这个cwnd就会加1。每一次传输轮次cwnd就会翻倍。

通过慢启动,我们就能够尽快让网络恢复正常的通信。

注意:网络有一个拥塞窗口,根据对方16位窗口会有一个对方接受能力的窗口。滑动窗口 = min(拥塞窗口,对端发送的窗口)。

6.2 拥塞避免

由于慢启动是指数增长的,若干轮次后cwnd值会很大,导致所有进程发送数据过快。从而导致网络拥塞。为了避免cwnd快速增长导致网络阻塞,需要进行拥塞避免。

**当cwnd拥塞窗口到达我们设置的阈值ssthresh就会执行拥塞避免算法。**此时每一个轮次不再是2倍增长了,而是线性增长。

这样就能避免拥塞窗口增加过快导致网络堵塞

6.3 快重传和快恢复

当接收方收到三个重复的ack之后,表示这个网络已经很拥塞了(对方由于超时进行重传,重传了3个ack都遗留在网络中,然后连续到达对方,你说是不是非常堵塞)。此时就需要执行快重传和快恢复。

即将cwnd进行乘法减少,一般是*1/2并且设置好新的阈值ssthresh,然后继续执行拥塞避免。这样cwnd就会变小,整个网络的发送速度就会变小,让网络尽快恢复

这样我们就能实现网络通信的同时保证整个网络传输的效率。

七. 总结

TCP通过确认应答机制保证数据被对方顺序接收不重复,通过超时重传机制保证数据不会丢失,通过流量控制保证双方能够以最高效率收发数据,通过滑动窗口机制来支撑前三大机制,通过拥塞控制来保证双方通信的同时确保整个网络通信的效率

相关推荐
小云小白7 小时前
Bash /dev/tcp、nc 与 nmap:端口检测的定位与取舍
linux·端口检测
Lenyiin8 小时前
Linux 项目托管 `git`
linux·运维·服务器·git·lenyiin
真正的醒悟15 小时前
图解网络34
网络
徐子元竟然被占了!!16 小时前
Linux-systemctl
linux·数据库·oracle
_w_z_j_19 小时前
Linux----mmap
linux
程序员zgh20 小时前
Linux系统常用命令集合
linux·运维·服务器·c语言·开发语言·c++
IT·小灰灰20 小时前
告别“翻墙“烦恼:DMXAPI让Gemini-3-pro-thinking调用快如闪电
网络·人工智能·python·深度学习·云计算
Bigan(安)20 小时前
【奶茶Beta专项】【LVGL9.4源码分析】09-core-obj_class对象类系统
linux·c语言·mcu·arm·unix