【Linux】UDP报头和TCP报头

传输层,负责数据能够从发送端传输接收端,是两个进程间的通信,通过源端口号和目的端口号就能标明是哪两个进程在通信。

🐼UDP报头

我们在研究任何协议时,一定要研究两个问题:1.报文和有效载荷如何分离的?2.有效载荷如何交付给上层的?任何协议都是如何,UDP和后面的TCP也不例外!

这是UDP协议格式:

16 位 UDP 长度, 表示整个数据报(UDP 首部+UDP 数据)的最大长度;
如果校验和出错, 就会直接丢弃

回答几个问题:

✅UDP如何将有效载荷和报头分离开?

因为报头长度是固定的,就是8个字节。将8个字节取出来,剩下的就是有效载荷!

✅如何交付给上层的?

分离之后通过报头信息的源端口号和目的端口号,确认应该交付给那个进程

✅为什么UDP是面向数据报?

因为UDP不考虑粘包问题!只要长度不超过UDP报文的最大长度,就直接将这个报文发送出去,只会发一次。是一个独立的包,而TCP就不是,他没有明确的边界,数据就像流水一样,可以发多次,也就是多个报文

✅UDP协议可靠吗?

不可靠,没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方,UDP 协议层也不会给应用层返回任何错误信息

✅UDP特点

UDP协议时无连接的,知道对端的 IP 和端口号就直接进行传输, 不需要建立连接,所以UDP协议快,但是无连接就会导致一旦报文丢失,不会重传,这是缺点吗?TCP协议就一定好吗?不是!这是UDP的特点,正是因为UDP无连接,所以快,也正是因为TCP有连接,但有可靠性,所以慢!这是特点,不是缺点

UDP既能读又能写,支持全双工!

✅基于UDP的应用层协议,比如DHCP动态主机配置协议,DNS域名解析协议...

✅再理解什么是报文

在OS系统中,一定有很多报文,因为报文有自已的状态啊,比如待发送,已发送,还正在拼接...

不同的网络协议栈,一定存在多个报文,这么多报文,os一定要管理,os使用一个sk_buff的结构体来管理报文。

sk_buff通过head,data,tail,end就能定位整个报文大小,控制报头,有效载荷

✅为什么UDP不需要有发送缓冲区,而TCP必须要有发送缓冲区呢?

因为UDP发了就是发了,他并不关心对方的接收缓冲区是不是满了,如果满了他就会丢包,此时你也是无感的,UDP不保证可靠性,但是TCP需要保证可靠性啊!!比如说对方如果来不及把数据拿到上层处理导致对方接受缓冲区快满了,TCP会意识到对方接受能力不行(后面会说),就会减缓发送的速度,但是我们上层用户是无感的啊!你可能会一直往缓冲区里写!所以此时数据就会被存储在发送缓冲区里了!!当然TCP也不会任由这种情况发展,他会通过某种方式要求对方赶紧清空自己的接收缓冲区进行接收!


🐼TCP报头

🗾TCP 协议段格式

下面我们先看个别几个字段,剩下的字段可以根据标题来定位。

✅TCP协议时如何实现分离和分用的。

分用这里不多说,源端口和目的端口。如何分离的呢?首先,TCP报头大小是固定的20字节+选项,这个选项多大呢?首先我们先取到前20个字节,因为前20个字节的报头是固定的,在前20个字节里,取到一个字段,叫做4位首部长度,它是整个报头的大小,可是它仅仅是4位啊,最多表示15个字节?TCP规定4位首部长度*4才是整个报头的大小,所以TCP报头最大是15*4 = 60个字节,而前20个字节时固定的,剩下40个字节时可选的。所以,如果一个标准的TCP报头(没有选项),4位首部长度应该就是20,就是20/4 = 5 = 0101。

我们既然知道了整个报头大小,就可以将报文的报头和有效载荷分离开!

✅TCP报文中字段中,怎么没有报文总长度或者有效载荷的长度?

没有了有效载荷长度,那么如何将整个数据提取到呢?答案是TCP不需要,因为TCP是面向字节流的,无需关心数据有多长,直接交付即可,所以不关心数据有多长,只需要提取到报头即可,因为TCP可以自已控制!


🗾TCP核心机制---确认应答机制

在理解TCP报头其他字段之前,我们必须先理解确定应答机制

如何理解确认应答机制?

举个例子,假设client给server发送了一条消息,client知道自已发的这个msg被server接受了还是丢包了?不知道!因为没人告诉他!也就是最新消息,永远没有办法确认到被对方是否收到,这是客观事实!

但是如果此时server给了client一条reply,表示我收到了。那么我们是不是一定敢肯定,client给server发的前一条msg,一定发送成功了?是的!所以,只要收到应答,我们就敢100%保证,历史报文被对方收到了!所以确定应答是对历史报文的100%的可靠的保证!

**细节:

  1. 消息 vs 应答:消息 ≠ 应答,包含了用户信息的内容,应答就是一个单纯的应答**

2. 不是要保证应答的可靠性,我们要保证的是消息的可靠性

3. 应答方,不确定应答对方是否收到,但是发送方,有没有收到对方的应答是能确认的。

4. 收到应答,本质为了保证历史消息的可靠性!


🗾TCP常规通信模式

基于确认应答机制,我们来看看双方的通信方式:(下面暂时不考虑丢包情况)

第一种是同步的,串行的发送方式。第二种发送模式依旧是确认应答机制,但是由于并行的发送,不就是异步的吗,效率就高了!


🗾TCP协议字段---32位序号&&确认序号

但是这种发送模式,client发送的顺序和server接受的顺序,是一致的吗?不一致,因为有的报文早跑,但是路由器经过的多,导致晚到!所以可能是乱序的,所以如何保证可靠性,是按序到达的呢?所以就要给每个报文编一个序号。比如在发送时,client就将这个报文编号为100,下一个编号为200...可是,client发的报文,我怎么知道我的报文被对方收到了呢?确认应答机制!

在TCP协议字段中又有一个字段为确认序号:ack_seq,它就表明,该数字之前所有的报文,我已经全部收到了,下次,你就从ack_seq开始发吧!

如何理解该数字之前的所有报文?首先,这是TCP规定的字段,这就是协议,就是结构体嘛!

其次,我们看这个图:

client还没等确认,就直接从200开始发,然后没等确认,就发了300,400。然后才收到了server的回复,这个确认就表明了100前的所有报文,server已经收到了!为什么可以这么发?client暂时无需等待server的应答就可以继续发,后面我们来分享一下滑动窗口

所以ack_seq是一种累计确认,它表明了ack_seq之前的所有报文,已经全部收到了,不是对1个报文做确认,是对历史所有报文做确认。

来思考一个问题,来回发的是什么,是一个数字吗?NO,是TCP协议!是一个TCP完整报头+有效载荷的,在TCP报头中写入了相应字段的数据!比如ack_seq。所以我们往后理解发送,一定要从TCP整个报文的视角来理解发送,不能单单聚焦在某个字段上!!!

我们下面开展的很多话题就是基于序号和确认序号的!

没想到吧,序号竟然和按需到达和确认有关


🗾捎带应答机制

序号和确认序号能同时存在吗,也就是一个报文报文能有两个序号吗?

举个例子:现在client给server从100的报文开始发送,然后server收到了,给client发送了一个确认的报文(没有有效载荷),可是,现在服务器,自已也要把数据比如视频了,推送给client,如果这样发送不就要发两次吗?不就占用了网络资源。如果我们在一个TCP报文中即表明了确认序号,又表明自已发报文的序号从哪里开始,可以做到吗?当然可以,就是修改结构体一个字段而已,但是就少发送了一个报文!我们把这种机制,叫做捎带应答机制:也就是一个报文,即是对上一个报文的确认(有ack_seq),也是一个数据报文(自已有自已的序号seq),两个序号同时存在!

TCP的真实发送情况往往就是这样的!


🗾16位窗口大小

我们知道,TCP有自已的接收缓冲区和发送缓冲区,就是一块固定的开辟的内存罢了。一旦创建TCP套接字,os都会帮助我们自动开好这个空间大小。因为是固定的!所以有上限!

可是,在双方互发消息的过程中,大量的数据,可能导致对方来不及接收,这是不是就造成了丢包!浪费了网络资源和网络带宽!因为我这个数据千里迢迢来到了你这里,浪费了这么多的网络资源,却因为你的接收能力不行导致我在没有明显错误的情况下被你丢弃,我又没有错,我发的快还是坏事?那么曾经的发送不就是没有意义了的吗??

如何做?我只要知道对方此时最大的接收报文的能力的上限即可!

可是,我怎么让发送方,知道对端的当前的接受能力呢?因为我发送数据,得到应答,应答的本质是TCP报文,既然是TCP报文,在TCP报头中有一个字段,位16位窗口大小,这个字段就能标明当前自已的最大接受报文的能力上限,也就是接收缓冲区剩余空间的大小!

几个细节:

✅细节1:流量控制是双向的(如果单行发送,就只做单向流量控制

✅细节2:窗口大小填写的是谁的接受剩余空间的大小?自己的!TCP报文发给谁的?对方的,对方不就知道我的窗口剩余大小了吗?我如何得知对方的接收缓冲区大小?同理!

✅如果对方接受能力为0??不要发数据了如果对方剩余缓冲区空间特别大呢??多发数据!!

流量控制 = 可靠性 + 效率!所以这个字段就和后面的流量控制有关(后面再说),就可以根据win大小,调整发送数据速率的机制!


🗾6个标志位

首先,什么是标志位?就是结构体位段中的比特位,0,1罢了!0表示无效,1表示有效

为什么要有标志位?区分报文类型的!这点怎么理解呢?TCP报文有的报文是建立连接,有的报文是断开连接,有的报文是应答报文,所以报文是有类型的!不同的标志位就表明这个报文的行为!我们就可以在报头中设置标志位来表明报文行为~

🌻ACK

ACK:表明确认号是否有效,一般都是1,因为一般都是要确认的对方发送的报文自已有没有收到的,一般都在捎带应答机制里。


🌻SYN

SYN:当TCP首次建立请求连接时,我们把携带SYN的报文叫做同步报文段,该SYN字段置为1。

🌞初始三次握手

我们都知道TCP是面向连接的?如何做到的?其实就是通过三次握手来建立连接,反应在代码角度,就是connect时,双方TCP自动完成三次握手。所以在通信前,要经过三次握手来建立连接!

在三次握手之前,我们来想想我们现实生活中是如何建立共识的?是不是要征得双方的同意!

而三次握手本质也就是以最短的建立连接的方式来达成共识

如何进行三次握手的?如图:

所以三次握手,现在我们粗粒度的来理解一下,为什么是三次?因为达成共识的最小集就得是三次!


🌻FIN

FIN:通知对方, 本端要关闭了, 该字段设置为1.我们称携带 FIN 标识的为结束报文段

🌞初始四次挥手

想想我们现实生活中是如何分手的?是不是得征得双方的同意,四次挥手也是如此!

四次挥手过程:

所以,4次挥手,为什么是4次呢?如果server把ACK+FIN捎带应答,一同发给客户端,那不就变成了3次挥手了吗?是的!

**同理,在三次握手时,如果服务端不捎带应答,而是先发一个ACK,再发一个SYN,那不就变成了四次握手了吗?是的!**如图:

所以三次握手的本质是也是四次,只不过第二次三次往往被捎带应答了

我们可以理解为,server是一个"舔狗",在谈恋爱时(三次握手),答应的快,明明要分4次,偏偏捎带应答,心急。然后再分手时,却又想个舔狗,不舍得分手,要四次才能达成共识,分手的慢


🌻PSH

PSH:提示接收端应用程序立刻从 TCP 缓冲区把数据读走

举个例子:在客户端向服务器发送数据的过程中,如果根据服务器的回复报文发现其接收缓冲区已满,客户端可能会暂时停止发送。若一段时间后,客户端再次发送测试报文,发现服务器窗口大小(win)仍为0,客户端便会主动干预------发送一个PSH标志位设为1的报文,以此提醒服务器:"请立即将数据从TCP缓冲区中读取处理,否则我将停止后续数据发送。"

当然,不仅仅是上面这种应用场景,比如SSH这种协议,要尽快得到答复,PSH可以被设置,尽管对方有空间。

有一个细节,就是应用程序,怎么取数据,不是应用层自已决定的呢?PSH是怎么干预的呢?其实就是唤醒了这个进程,让这个进程快点交付


🌻RST

RST:对方要求重新建立连接; 我们把携带 RST 标识的称为复位报文段

首先,我们要清楚,TCP三次握手一定是100%建立成功的吗?不是。那你怎么说TCP具有可靠性?注意:是建立连接时不可靠,建立完链接才可靠!

而现在有这样一种场景:就是当client发出了最后一个ACK,他是不是自已就认为自已建立连接成功了。注意,对于client来说,他认为,只要发出和收到,就是握手,他不必等对方server收到才认为是握手成功,client也等不到。对于client来说,只要最后一个ACK发出,他就认为建立连接成功了!

而此时,最后一个ACK丢了,但是client还是认为自已建立连接成功了,于是发送数据给server,但是此时server又没有和他达成共识啊,没有三次握手成功,于是就打了一个大大的问号。并且给client发了一个RST为1的TCP报文,告诉client,你建立连接失败了,重新建立完连接,再来给我发送数据吧!

当然,上面是一种极端的场景,比如客户端没有建立连接,恶意直接发送,那么也会发送reset给它!所以,RST标志位的报文用来处理链接异常的时候,让对方进行重置的。处理各种连接异常!


🌻URG

URG:紧急指针是否有效,一但有效,就是和紧急指针配合使用的,一般这种情况非常少,因为有取代方案。

举个例子:比如我现在正在上传一个4GB的资源到云盘,可是现在由于某种原因,我不想下载了。但是如果我发常规报文,我是不是到达对端还得去后面排队去。所以如果我是紧急指针,就会被优先处理,告诉对端不要再接受ipxxx,portxxx的报文了~但是不要处理的大小是多少???

而16位紧急指针:来指向当前报文有效载荷的一个部分的偏移量,比如这次的紧急数据有多少个字节?

不过这种方案有取代方案。在传输过程中,我们可以有两条链路,一条是数据链路,主要是数据;另一条是指令链路,控制数据发送行为的指令。根据指令我们就可以控制该数据的发送行为了~


相关推荐
p***92481 小时前
【Linux】nmcli命令详解
linux·网络·php
qq_479875431 小时前
Linux 网络实验(1)
linux·网络·php
HalvmånEver1 小时前
linux:虚拟地址空间与物理地址空间(进程六)
linux·运维·服务器
倔强的石头1061 小时前
Linux 进程深度解析(一):从内核视角看懂进程的本质
linux·运维·服务器
saber_andlibert1 小时前
【docker】入门基础和镜像、容器
linux·运维·docker·容器
不想画图1 小时前
数据库概念和编译安装mysql流程
linux·数据库·mysql
小醉你真好1 小时前
18、CentOS 9 使用 1Panel 安装 Jenkins
linux·centos·jenkins
郝学胜-神的一滴1 小时前
Linux信号的概念与机制
linux·服务器·开发语言·c++·程序人生
云计算老刘1 小时前
5. MariaDB 数据库管理
linux·运维·服务器·centos