【Linux-传输层协议UDP】再谈端口号+UDP协议+深度理解UDP
前沿
传输层
负责数据能够从发送端传输接收端
再谈端口号
端口号(Port)标识了一个主机上进行通信的不同的应用程序
在TCP/IP协议中,用“源IP”,“源端口号”,“目的IP”,“目的端口号”,“协议号”这样一个五元组来标识一个通信(可以通过 netstat-n 查看);
端口号范围划分
端口号是一个 16 位的整数,它的取值范围是 0~65535。
-
0~1023:知名端口号。比如 HTTP,FTP,SSH 等这些广为使用的应用层协议,它们的端口号 都是固定的。
-
1024~65535:操作系统动态分配的端口号。客户端程序的端口号就是由操作系统从这个范围 分配的,允许用户手动绑定。
认识知名端口号
有些服务器是非常常用的,为了使用方便,人们约定一些常用的服务器,都是用以下这些固定的端口号:
-
ssh 服务器使用 22 端口
-
ftp 服务器使用 21 端口
-
telnet 服务器使用 23 端口
-
http 服务器使用 80 端口
-
https 服务器使用 443
执行下面的命令,可以看到知名端口号
我们自己写一个程序使用端口号时,要避开这些知名端口号
两个问题
一个进程是否可以bind多个端口号?--》可以
一个端口号是否可以被多个进程bind---〉不可以
UDP协议
UDP协议端格式
思考:如何将报头和有效载荷进行分离(封装)?如何将有效载荷进行分用
-
16位UDP长度,表示整个数据报(UDP首部+UDP数据)的最大长度
-
如果校验和出错,就会直接丢弃
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协议首部中有一个16位的最大长度.也就是说一个UDP能传输的数据最大长度是64K(包含UDP 首部).
然而 64K 在当今的互联网环境下,是一个非常小的数字.
如果我们需要传输的数据超过64K,就需要在应用层手动的分包,多次发送,并在接收端手动拼装;
基于UDP 的应用层协议
-
NFS:网络文件系统
-
TFTP:简单文件传输协议
-
DHCP:动态主机配置协议
-
BOOTP:启动协议(用于无盘设备启动)
-
DNS:域名解析协议
当然,也包括你自己写UDP 程序时自定义的应用层协议;
深刻理解UDP
1.UDP报头
操作系统内可能同时存在大量的报文,有的报文正在被向上交付,有的报文正在被向下交付;这么多的报文,所以操作系统要对报文进行各种管理--->先描述,在组织
struct udphdr
是C语言中用于描述UDP(用户数据报协议)头部的一个结构体。UDP是OSI模型中的传输层协议,它提供了无连接的数据包传送服务。以下是 struct udphdr
的一个典型定义,通常在Linux系统的头文件 <netinet/udp.h>
中可以找到:
struct udphdr {
uint16_t source; /* 源端口号 */
uint16_t dest; /* 目的端口号 */
uint16_t len; /* UDP数据报长度 */
uint16_t check; /* 校验和 */
};
2.对报文的理解
UDP封装的过程,首先我们知道协议栈有四层:应用层,传输层,网络层,数据链路层;从应用层到传输层需要对数据进行封装,那么UDP是如何一层层封装的呢
首先我们需要认识一个数据结构struct sk_buff
struct sk_buff
是Linux内核网络子系统中的一个核心数据结构,它用于表示网络层的数据包缓冲区。sk_buff
代表一个 “socket buffer”,它包含了内核处理网络数据包所需的所有信息,包括网络层和传输层头部、数据负载、元数据以及用于管理缓冲区的各种指针。
以下是 struct sk_buff
的简化版本,用于展示其主要字段:
struct sk_buff {
// 指向skb_shared_info结构的指针,该结构包含了数据包共享信息
struct skb_shared_info *shinfo;
// 指向skb_memo结构的指针,该结构用于存储skb的元数据
struct skb_memo *memo;
// 指向skb对应的网络设备结构的指针
struct net_device *dev;
// 指向skb所属的套接字的指针
struct sock *sk;
// 指向skb的传输控制块的指针
struct sk_buff *next;
struct sk_buff *prev;
// 指向skb的数据缓冲区的指针
unsigned char *head;
unsigned char *data;
unsigned char *tail;
unsigned char *end;
// skb的长度
unsigned int len;
unsigned int data_len;
// skb的类型
unsigned int type;
// skb的协议
__be16 protocol;
// skb的标记
__u32 mark;
// skb的hash值
__u32 hash;
// skb的优先级
__u32 priority;
// 其他字段...
};
字段名 | 类型 | 描述 |
---|---|---|
shinfo | 指针(struct skb_shared_info* ) | 指向skb_shared_info结构,包含数据包的共享信息,如片段信息和引用计数。 |
dev | 指针(struct net_device* ) | 指向发送或接收此数据包的网络设备结构。 |
sk | 指针(struct sock* ) | 指向数据包所属的套接字结构。 |
head | 指针(unsigned char* ) | 指向数据包缓冲区的开始。 |
data | 指针(unsigned char* ) | 指向数据包中数据的开始。 |
tail | 指针(unsigned char* ) | 指向数据包中数据的末尾。 |
end | 指针(unsigned char* ) | 指向数据包缓冲区的末尾。 |
len | unsigned int | 数据包的总长度(包括所有头部)。 |
data_len | unsigned int | 数据部分的长度(不包括头部)。 |
type | unsigned char | 数据包的类型,用于内核处理。 |
protocol | __be16 | 数据包的协议字段,通常是IP层头部中的协议字段。 |
mark | unsigned int | 数据包的标记,用于QoS和其他目的。 |
hash | unsigned int | 数据包的哈希值,用于快速查找。 |
UDP(用户数据报协议)报文的封装过程涉及到在传输层将数据打包成UDP数据报,并将其传递给网络层以发送到目的地
以下是将UDP报文封装过程:
阶段 | 操作 | 描述 | |
---|---|---|---|
应用层 | 发送数据 | 应用程序通过UDP套接字发送数据。 | |
传输层 | 创建UDP头部 | 内核创建一个UDP头部(struct udphdr )。 | |
传输层 | 填充UDP头部 | 源端口、目的端口、长度和校验和字段被填充。 | |
传输层 | 创建skb缓冲区 | 内核创建一个skb缓冲区(struct sk_buff )来管理数据。 | |
传输层 | 附加数据到skb | 应用程序数据被附加到skb缓冲区的数据部分。 | |
传输层 | 计算校验和 | 如果启用了校验和,则计算UDP伪头部、头部和数据。 | |
网络层 | 添加IP头部 | skb缓冲区被传递给网络层,并添加IP头部(struct iphdr )。 | |
网络层 | 填充IP头部 | 源IP地址、目的IP地址、协议类型(17表示UDP)等被填充。 | |
链路层 | 添加链路层头部 | skb缓冲区被传递给链路层,并添加链路层头部(例如以太网头部)。 | |
链路层 | 填充链路层头部 | 目的MAC地址、源MAC地址和类型(对于IP数据包是0x0800)等被填充。 | |
发送 | 发送数据包 | 最终的数据包(包含链路层、IP层和UDP层头部)被发送到网络。 |
应用数据
+-------------------+
| |
| 应用数据 |
| |
+-------------------+
|
| (通过UDP套接字发送)
v
UDP头部 (struct udphdr)
+-------------------+
| 源端口 | 目的端口 | 长度 | 校验和 |
+-------------------+
|
| (skb缓冲区管理)
v
IP头部 (struct iphdr)
+-------------------+
| 版本 | 头部长度 | 服务类型 | 总长度 |
| 标识 | 标志 | 片偏移 | 生存时间 |
| 协议 | 校验和 | 源IP地址 | 目的IP地址 |
+-------------------+
|
| (网络层处理)
v
链路层头部 (例如以太网头部)
+-------------------+
| 目的MAC | 源MAC | 类型 |
+-------------------+
|
| (发送到网络)
v