一、UDP

五元组:源IP,目标IP,源端口号,目标端口号,以及协议号。

IP地址在网络层,端口号在传输层,IP首部还会带一个协议号,决定将数据交给传输层中的哪个协议(TCP还是UDP),我们通过五元组来识别一个通信。

我们先来看看UDP报头!!

这是我们的UDP报头,报头就是固定8字节的,上面的源端口号,目的端口号,UDP长度,UDP检验和都是2字节,加起来8字节。

问题1:如何分离?

        因为报头是8字节的定长报头,所以我们完全可以直接读取8字节进行分离

问题2:如何分用?

        主机收到 UDP 包,靠目的端口号,把数据包交给对应应用程序,注意UDP是有边界的报文,一次 sendto 发多少,对端 recvfrom 就读到多少,不会像 TCP 那样粘包,每个长度,交付上层,都是确定的!!

问题3:为什么端口号是16位?

        端口号字段就是 16 位,范围 0‑65535,内核协议栈规定,所以端口用 16 位存储,协议本质其实就是结构体。

我们来看一下UDP的发送流程:序列化为字节流!

  1. 应用层字符串 "你好",调用sendto()系统调用。
  2. 内核构造 UDP 头部(8 字节),把头部 + 用户数据拼接成 UDP 报文。
  3. 下交给 IP 层,加上 IP 头部,再交给链路层。
  4. 在网络上传输的是连续字节流

接收流程:

  1. 网卡收到网络上的连续字节流,产生硬件中断,将链路层帧送入内核缓冲区。
  2. 内核剥离链路层帧头帧尾,取出 IP 数据包交给 IP 层;IP 层剥离 IP 头部,识别协议号 17 (UDP),取出完整 UDP 报文交付 UDP 模块。
  3. 内核解析 8 字节 UDP 头部,通过目的端口号做分用,将完整 UDP 报文存入对应 socket 的接收缓冲区,同时保存发送方源 IP、源端口信息。
  4. 应用程序调用recvfrom()系统调用,内核把报文中的有效数据拷贝到用户空间缓冲区,完成字节流的反序列化,返回应用数据与对端地址。

                                                       

操作系统未来在做协议交换时,交换的就是我们对应的报头,也就是我们的结构体内部的变量,因为底层都是c实现,协议又一致,在应用层体现的就是直接交换结构体变量即可,因为双方交换的都是协议拟定结构体变量,所以两个人的操作系统可以完全不同,但是网络部分必须完全一样,因为网络有标准,Tcp标准,你对应的报头是多大,整体是多大,他连大小都给你规定出来了,这就避免了做夸张的内存对齐,他连里面的每一个区域占了多少比特位都给你规定好了,双方源代码一样,锁定的结构类型一样,所以双方就直接采用以交换我们的未来结构体变量的对象,也就是二进制流直接交换就可以了,所以A主机定义了一个结构体报头,直接发给对方,然后对方拿着这个报头直接做解析,立马就能识别到source,dst,len,check,其实就是利用数据变量本身的特点做的二进制流的序列化与反序列化,这样做快,因为无中间软件层,最终协议的本质就是结构体!!

应用层的实现与内核不一样,应用层追求高扩展高可用,效率上可做妥协,但是os所有人都要用,必须保证自己所定的协议必须是所有人能够尽快把它用起来,效率是第一考量!

UDP的特点:

无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接

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

面向数据报:应用层给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并!!

UDP的缓冲区:

我们先来谈谈TCP的缓冲区

其实我们的数据调用write写到发送缓冲区,在拷贝到对方的接收缓冲区,然后对方调用read读取接受缓冲区里的数据,其实写入发送缓冲区的过程是一个生产消费模型,当发送缓冲区满的时候,write就会阻塞,当消费一部分之后,再生产!
UDP其实没有真正意义上的缓冲区!

        结构可以有,但是一般都不用,因为把UDP的数据缓存起来没有意义,因为你不保证可靠性,丢了重传就可以,但这里衍生出一个问题!

        对于操作系统来讲,他要把缓冲区里的数据发出去,在他发之前他并不清楚这个报文会不会丢,所以它把数据发出去了,这个数据包在网络里了,如果有缓冲区,这个报文发送到缓冲区里,原来在缓冲区里的报文要不要被清理掉呢?答案是不会被清理,因为你把数据发出去了,在发之前你并不清楚这个报文最终会不会丢,假如说丢了,你就要重传,可是你重传的时候,数据在哪呢?缓冲区里找不到了啊!所以不需要清理。因为假如说缓冲区里的数据丢了,我们直接重传就可以。因为我们TCP的可靠性大多数都是围绕发送缓冲区展开的,而我们的UDP又不关心对应的可靠性,追去的就是速度,直接交就完了,所以不需要真正意义上的发送缓冲区!那为什么UDP要有对应的接受缓冲区呢?因为在网络进行通信的时候,上层要做大量的工作,上层是非常忙的,那么我们读一个报文,上层在处理期间,我们应该让我们的操作系统具备收取UDP报文的能力,所以人家提供一个接受缓冲区不过分,目的就是为了保证上层忙的时候,操作系统底层也继续可以缓存一部分对应的报文,但这个UDP的接收缓冲区不能保证收到的UDP报文和发送的UDP报文顺序一致,满了的时候,再到它的UDP报文就会被丢弃,对于UDP来讲,他就是巨简单我们对应传输层的一个协议,可以给上层提供最简单的面向数据报的通信服务。

UDP的使用事项:

UDP协议首部中有一个16位的最大长度,也就是说一个UDP能传输的数据最大长度是64k(包含UDP首部的8字节),然而64K在当今的互联网环境下,非常小了,如果我们需要传输的数据超过64K,就需要在应用层手动的分包,多次发送,并在接收端手动拼装。

以UDP作为载体谈一谈关于报文的理解:

我们来列几个问题:

1、如果应用层正在进行报文的解析,处理,会不会影响OS从网络中读取报文?为什么?

                网卡一旦有数据,网卡会向操作系统特定的CPU针脚去触发数据中断,这个硬件中断属于外设的数据中断,外设也在不断向CPU触发时钟中断,来促使进程被调度起来,所以CPU是不断被执行的,要么执行调度,要么在执行代码,在响应着时钟中断的同时,也在响应着外部的硬件中断,总之,不管报文的解析和读取报文谁前谁后,操作系统既可以把前者做了,又可以把后者做了,本质都是在响应外部中断,只不过你占点时间,我占点时间。假如说我做了一个服务器,客户端:服务端=n:1,操作系统能够同时处理那么多的报文,这就意味着在OS内部,一定可能会同时存在大量的报文,而OS就必须管理这些报文,就是先描述后组织!!

我们想要发你好这个报文,需要的是在传输层有一个sk_buff的结构,然后有一块对应的缓冲区,我们只考虑两个指针,一个是data,一个是tail,首先在操作系统中构建一个UDP报文,然后在操作系统中将UDP报文拷贝进缓冲区中,此时data指针往低地址移,让data-=sizeof(struct udphdr),流出来8个字节空间,然后再struct udp_hdr*(data)->....填写对应的属性,到时候交给网络层,什么乱七八糟的数据不用传了,只需要传这个结构对象就可以,报文往下走是这个sk_buff往下走。每个层都有队列,你的报文在哪个层,对应你就在哪个队列里,而接受过程也类似!!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐