文章目录
- 概要&序論
- 一、16位窗口大小与流量控制
- 1.1 16位窗口大小有什么用
- 1.2 流量控制的原理与本质
- 1.3 流量控制对效率与可靠性的提升
- 二、6个标志位
- 2.1 标志位的本质是什么
- 2.2 为什么要有标志位!面试拉开差距的问题
- 2.3ACK标志位
- 2.4PSH标志位
- 2.5SYN标志位与三次握手
- 2.6FIN标志位与四次挥手
- 2.7RST标志位与连接重置
- 2.8 URG标志位与紧急指针
- 2.8.1 TCP的按序到达与“插队”需求
- 2.8.2 紧急指针的本质:偏移量与单字节数据
- 2.8.3 带外数据与接口接收方式
概要&序論
Hello大家好,我是此方。上一篇我们讲解了TCP的可靠性和TCP通信的一般过程以及32位序号和32位确认序号。本文将详细介绍16位窗口大小与流量控制,以及6个标志位。
一、16位窗口大小与流量控制
1.1 16位窗口大小有什么用
朴素的理解:首先,一台机器接收报文的能力,由这台机器接收缓冲区“剩余空间的大小”决定!
我们可以把“接收缓冲区“剩余空间的大小””填入16位窗口大小。发给对端,对端就知道了,对端接收报文能力的大小,于是调整自己发送报文的速度,或者直接阻塞不发送。
为什么一定要有这个?报文发送到那里,如果对端的接收缓冲区满了,你发出的报文就会被丢弃!浪费了资源。虽然可以重发但是浪费了资源。
1.2 流量控制的原理与本质
这种根据对端接收能力来动态调整发送报文速度的机制,我们称之为流量控制。
要解决报文丢弃和低效发送的问题,核心在于让发送端尽早得知对端的接收能力。那么发送端如何尽早得知对方的接收能力?答案就是通过获取对方接收缓冲区中剩余空间的大小。
在TCP通信流程中,接收缓冲区通常分为被使用的部分和没有被使用的部分。当发送方将数据发送到对端的传输层(如TCP/UDP层)时,数据会暂存在接收缓冲区中,等待上层应用层进行读取。若上层读取较慢,被使用的缓冲区空间就会变大,没有被使用的剩余空间就会变小。
1.3 流量控制对效率与可靠性的提升
很多人在谈论TCP的时候,更多的是关注它的可靠性,但是不能只关注它的可靠性,TCP在效率方面也做了很多考量。
我讲这个想说的是:很多人在谈论TCP的时候,更多的关注它的可靠性,但是不能只关注它的可靠性。TCP在效率方面也做了很多考量!
二、6个标志位
2.1 标志位的本质是什么
先说结论,标志位:本质就是报头中的比特位!!
在TCP报头结构中,这些标志位并不是单独占用了几字节的空间,而是通过C/C++中的位段(bit-field)语法来实现的。像xxx: x这种语法就是标准的位段表达方式。(这玩意儿挺冷门的,我在C语言结构体那篇文章讲过)
我们直接看一下 Linux 内核中struct tcphdr的源码定义:
structtcphdr{__be16 source;__be16 dest;__be32 seq;__be32 ack_seq;#ifdefined(__LITTLE_ENDIAN_BITFIELD)__u16 res1:4,doff:4,fin:1,syn:1,rst:1,psh:1,ack:1,urg:1,ece:1,cwr:1;#elifdefined(__BIG_ENDIAN_BITFIELD)__u16 doff:4,res1:4,cwr:1,ece:1,urg:1,ack:1,psh:1,rst:1,syn:1,fin:1;#else#error"Adjust your <asm/byteorder.h> defines"#endif__be16 window;__sum16 check;__be16 urg_ptr;};源码中利用条件编译(#if defined)来判断当前是大端机还是小端机,以此来调整位段的排列顺序。可以看到,无论是fin:1、syn:1还是ack:1,它们本质上都只占用 1 个比特位,用于标记特定的状态或控制信息。
2.2 为什么要有标志位!面试拉开差距的问题
我们在理解了标志位本质是位段之后,紧接着就需要思考一个更核心的问题:TCP报头里为什么一定要设计这些标志位呢?
对于接收方(Server端)来说,在整个通信过程中收到的 TCP 报文,一定存在不同的类型。
- 可能有申请建立连接的报文。
- 可能有普通的传输数据报文。
- 可能有表示断开连接的报文。
- 可能有对前一个报文做出回应的应答报文。
针对不同报文类型,接收方要有不同的做法!
那么接收方如何区分不同的报文类型呢?于是我们就必须要在 TCP 报头中设计用于表示报文类型的字段,这就是为什么我们要有标志位!
2.3ACK标志位
ACK:确认号是否有效,表明报文是一个应答报文
ACK标志位几乎是被常设为1 ,为什么?因为我们的报文要么是应答要么是应答+数据。
2.4PSH标志位
PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走。
我方发送数据给对方,并要求对方操作系统提示对方应用层立即读取数据。至于对方应用层如何读取何时读取,这个不归我管!
在 SSH 远程终端场景下,你在本地敲下字母并回车时,操作系统会发送带 PSH=1 的 TCP 包;远端协议栈收到后不再等待缓冲区达到低水位线,而是立刻唤醒 SSH 进程将数据读走,以此保证命令行交互的高实时性与即时回显。
科普补充:缓冲区没有到达低水位线的时候操作系统不会[主动呼叫]应用层读取数据。
2.5SYN标志位与三次握手
客户端和服务器正常通信之前要进行三次握手,三次握手就是建立连接的过程,建立连接就是谋求共识的过程。
当 SYN 标志位设为 1 时,说明我发送这个报文是一个建立连接的报文。同步标志位:连接建立,握手过程使用的标志位。
需要注意的是,前两次握手,不能携带数据,只有TCP报头!因为三次握手没有完成!但在三次握手的时候,已经可以进行双方接受能力的协商了!
2.6FIN标志位与四次挥手
客户端和服务器断开连接的时候要进行四次挥手。FIN 的作用是通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段。
面试会考:为什么要四次挥手?是因为客户端和服务器之间建立的是各自的发送缓冲区到对方的接收缓冲区的单向信道。一共两条构成全双工。必须全部断开,而四次挥手是谋求共识的过程。
2.7RST标志位与连接重置
建立连接,一定要,一定会成功吗??三次握手不一定成功,但是三次握手一定会成功。(有点绕,你接下来就会看懂了)
先说结论,RST:对方要求重新建立连接,我们把携带RST标识的称为复位报文段。
对客户端而言,它知不知道服务端有没有接收到最后一条应答?当然无从知道!
于是对于客户端来说,其实第三次ACK发出的时候,就代表客户端第三次握手成功。(①第一个时间点记住)
客户端只看接收和发送的次数判断三次握手是否成功。服务器也一样!服务器在客户端发来的第二次ACK到达时候就认为:第三次握手成功(②第二个时间点记住),也只看服务器端接收和发送的次数。
我们不害怕前两次握手失败,但是第三次握手失败有问题,因为第三次握手没有应答。
于是我们知道了:“客户端认为自己建立连接之时刻(收发达到三次之时①第一个时间)”与“服务器认为自己建立连接之时刻(收发达到三次之时②第二个时间)”,存在时间差!连接建立是否成功认知不一致。
于是这个时候,客户端认为建立连接成功,开始给服务器发送消息,服务器必然会出现大大的问号,接下来干什么?发送数据??在服务器看来,连接并没有建立成功,于是向客户端发送RST!——好,我认为我把RST的由来讲的比较清楚了。
上面这种情况太过极端,但是他很好理解,我们推其一般就是:通信的过程中,连接出现任何问题,都可以进行重置!
2.8 URG标志位与紧急指针
讲这个之前先说一下——URG不常用。紧急指针也不常用,但是出于文章的完整性,我还是稍微讲一讲。
2.8.1 TCP的按序到达与“插队”需求
如何理解紧急指针+URG?
tcp保证可靠性的 -> 序号 -> 按序到达。我们的报文会按序到达。我们的接收缓冲区是一种字节流式的接收队列,报文会一个个按照序号排队被接收。
如果我们有数据,想被优先读取,优先处理,就需要紧急指针+URG。什么情况需要我们的报文优先处理?
举个网盘传输数据的例子:
客户端上传各种数据时,突然我要给服务器发送一个报文:要求我的服务器停止/暂停上传数据!我这个命令要让对面的网盘服务器立即执行!我不能跟别的数据一样在后面排队!我要插队!
2.8.2 紧急指针的本质:偏移量与单字节数据
紧急指针的意思当前报文的有效载荷中,特定偏移量处,有紧急数据。它不是一个void*,是一个偏移量!
紧急数据,只有一个字节,说明我们可以将它拿来做状态码设置,来表示不同的控制命令。
只有紧急指针所指向的那一个字节需要被“紧急处理”,而不是整个带有有效紧急指针的报文中的所有普通载荷数据都变成紧急数据。
2.8.3 带外数据与接口接收方式
一般来说紧急数据,并不属于常规数据,这种数据我们称之为带外数据。
对于带外数据的接收,我们的recv接口提供了专门的选项。MSG_OOB(out-of-band data)。
我的板书: