【计算机网络】传输层UDP协议
🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:计算机网络
🌹往期回顾🌹: 【计算机网络】应用层协议Http——构建Http服务服务器
🔖流水不争,争的是滔滔不息
- 一、UDP协议
- 二、UDP特点
- 面向数据报
- 三、UDP缓冲区
- 四、基于UDP的应用层协议
- 五、报文理解
一、UDP协议
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,提供简单的、不可靠的数据传输服务。与TCP不同,UDP不保证数据包的顺序、可靠性或流量控制,但具有低延迟和高效率的特点,适用于实时性要求高但允许少量丢包的应用场景。
UDP协议端格式
UDP 报文其实很简单,它的格式是 “定长报头 + 不定长数据”。如图上面8字节是报头大小,下面数据是有效载荷。UDP报头是定长报头,所以每个报文交付给上层报头都是确定的。UDP报文分离的时候,直接把定长的报头和有效载荷分离就好了,分用根据目的端口号去完成。UDP报文和报文之间都有有边界的,所以不用像TCP传输那样还要自定义协议区区分报头。16位源端口号是表示报文是从哪发的,16位目的端口号是表示报文要发到哪里。端口号设计为16位的,是因为内核协议是16位的。报头是固定的,每个报文的有效载荷能和报头分明的区分,且报文和报文之间有边界,这就是用户数据报最大的特点。16 位 UDP 长度, 表示整个数据报(UDP 首部+UDP 数据)的最大长度;如果校验和出错, 就会直接丢弃;
为什么说UDP简单?
UDP和TCP有一个最大的不同:UDP是有“边界”的协议,每一个UDP报文本身就是独立的一块
内核收到一个UDP报文,直接把8字节头部剥掉,剩下的数据送到应用层就行。
每一个recvfrom()调用,只会收到一个完整的UDP报文(不会拆开,也不会合并)。
不像TCP:TCP是“流”,没有边界,一个包可能拆成多个recv才收完,或者多个包粘在一起。所以TCP应用层必须“自己想办法分包”,比如用自定义协议。
二、UDP特点
UDP 传输的过程类似于寄信。
- 无连接: 知道对端的 IP 和端口号就直接进行传输, 不需要建立连接;
- 不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方,UDP 协议层也不会给应用层返回任何错误信息;
- 面向数据报: 不能够灵活的控制读写数据的次数和数量;
面向数据报
应用层交给 UDP 多长的报文, UDP 原样发送, 既不会拆分, 也不会合并;
用 UDP 传输 100 个字节的数据:如果发送端调用一次 sendto, 发送 100 个字节, 那么接收端也必须调用对应的一次 recvfrom, 接收 100 个字节; 而不能循环调用 10 次 recvfrom, 每次接收 10 个字节。
三、UDP缓冲区
UDP 没有真正意义上的 发送缓冲区. 调用 sendto 会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作;
UDP 具有接收缓冲区. 但是这个接收缓冲区不能保证收到的 UDP 报的顺序和发送 UDP 报的顺序一致; 如果缓冲区满了, 再到达的 UDP 数据就会被丢弃;
UDP 的 socket 既能读, 也能写, 这个概念叫做 全双工
因为UDP没有发送缓冲区,所以如果报文在网络传输的过程中丢包了,UDP 本身是“无连接 + 不可靠传输协议”,它不会确认你发出去了没,也不会重传,如果在网络中丢了,那就真的丢了,服务器一无所知。
如果应用层正在进行报文的解析,不会影响OS从网络中读取报文
UDP使用注意事项
我们注意到, UDP 协议首部中有一个 16 位的最大长度. 也就是说一个 UDP 能传输的数据最大长度是 64K(包含 UDP 首部).。
然而 64K 在当今的互联网环境下, 是一个非常小的数字,如果我们需要传输的数据超过 64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装。
四、基于UDP的应用层协议
- DNS(Domain Name System)
DNS用于将域名解析为IP地址。由于查询通常只需一次请求-响应,UDP的轻量特性使其成为首选。默认使用端口53。 - DHCP(Dynamic Host Configuration Protocol)
DHCP用于自动分配IP地址。UDP的广播和多播支持使其适合局域网内的地址分配。客户端使用端口68,服务器使用端口67。 - SNMP(Simple Network Management Protocol)
SNMP用于网络设备监控和管理。UDP的简单性适合频繁的监控数据交换。默认使用端口161(查询)和162(陷阱)。 - TFTP(Trivial File Transfer Protocol)
TFTP是简单的文件传输协议,常用于网络引导或设备固件更新。使用UDP端口69,实现比FTP更轻量。 - QUIC(Quick UDP Internet Connections)
QUIC是Google开发的传输协议,基于UDP实现类似TCP的可靠性,但减少握手延迟。HTTP/3基于QUIC,提升Web性能。 - VoIP(Voice over IP)
语音通话协议如RTP(Real-time Transport Protocol)通常基于UDP。实时性要求高,少量丢包对语音质量影响较小。
五、报文理解
Linux sk_buff源码
struct sk_buff {union {struct {struct sk_buff *next;struct sk_buff *prev;union {struct net_device *dev;unsigned long dev_scratch;};};struct rb_node rbnode; // 用于红黑树};union {struct sock *sk;int ip_defrag_offset;};char cb[48]; // 控制缓冲区,协议层私有数据unsigned long _skb_refdst;unsigned int len, data_len;__u16 mac_len, hdr_len;__u16 queue_mapping;__u8 cloned:1, nohdr:1, fclone:2, peeked:1, head_frag:1;__u8 pfmemalloc:1, pp_recycle:1;__u16 tc_index; // 流量控制索引__u16 transport_header;__u16 network_header;__u16 mac_header;__u32 headers_end[0];void (*destructor)(struct sk_buff *skb);struct nf_conntrack *nfct;unsigned long nfct_reasm;__u32 secmark;unsigned int mark;__u16 vlan_proto;__u16 vlan_tci;union {__u32 inner_network_header;__u32 inner_network_header_offset;};__u32 inner_transport_header;__be16 protocol;__u16 transport_header_was;__u8 encapsulation:1;// 更多字段...unsigned char *head, *data, *tail, *end;
};
在操作系统内部,可能会同时存在大量的报文,操作系统必须管理这些报文。引入sk_buff(Socket Buffer)是 Linux 内核网络协议栈中 收发数据的核心结构体,每当有网络数据要处理,内核就会分配一个 sk_buff。
skb->head, skb->data, skb->tail, skb->end
是sk_buff内的指针。
- skb->head:指向整个缓冲区的起始位置(分配的整块内存)
- skb->data:指向有效数据的起始位置(比如 IP 头部开始)
- skb->tail:指向有效数据的结尾(可以向后追加数据)
- skb->end:指向缓冲区结尾(表示最多能写到哪)
所谓封装和解包,本质就是移动data指针在缓冲区中指向。
我们把data和tail想成一个"动态窗口",当我们的报完往下层协议封装就要往前移动data指针,封装报头,当往后移动tail指针的时候是添加新的数据。
发送过程:从上层到下层
调用send()或者write()发送数据,数据经历了用户态->内核态->网络层多次加工封装。
发送数据,数据进入内核socket buffer,内核分配sk_buff,并把数据填进去(通常从skb->tail开始)。进去TCP层,加上TCP报头skb->tail往前移动数据跟在TCP头后面,设置TCP序号、校验和等。进入IP层。加上IP层报头,设置源IP、目的IP等。进入数据链路层加MAC头,设置目标MAC、源MAC等。最后整个数据包都集中在skb->data和skb->tail之间->DMA拷贝给网卡发送过去。
接收过程:从底层到上层
网卡收到数据,触发中断。驱动将数据拷贝到sk_buff。skb->data指向以太网头部。一层一层"剥皮"去掉报头。最后skb->data指向payload(应用层数据)上交给socket buffer,再wake up用户进程接收。
sk_buff 就是内核网络协议栈用来“托运数据”的容器,skb->data 是快递包裹的“当前开箱点”,从 TCP 到 IP 到 MAC,每一层往前预留一段头,然后下发出去。