【Linux网络】传输层协议UDP
在绝大多数情况下,操作系统不允许两个进程绑定到同一个端口号。如果你尝试这样做,第二个进程在调用 bind() 时会失败,并得到一个类似 “Address already in use” 的错误。在TCP/IP协议中,用 “源IP”,“源端口号”,“目的IP”,“目的端口号”,“协议号” 这样一个五元组来标识一个通信(可以通过netstat -n查看);一个进程可以创建多个网络套接字,并将每个套接字
1. 传输层
“负责数据能够从发送端传输到接收端” 是传输层最核心、最根本的任务。
- 网络层 负责的是 “主机到主机” 的通信(比如,你的电脑到一台遥远的服务器)。它只关心把数据包送到目标IP地址。
- 传输层 则更进一步,负责 “进程到进程” 或 “应用到应用” 的通信。你的电脑上可能同时运行着浏览器、微信、音乐播放器等多个程序,它们都在通过网络收发数据。传输层要确保浏览器的数据交给服务器的Web服务,而不是别的。
如何实现?—— 通过端口号
1.1 再谈端口号
传输层使用 端口号 来标识主机上的不同应用程序。
- 发送端:知道目标服务器的IP地址和目标应用程序的目标端口号(如Web服务通常是80/443)。
- 接收端:通过数据包中的目标端口号,就知道该把这个数据交给哪个应用程序处理。

在这里插入图片描述
在TCP/IP协议中,用 “源IP”,“源端口号”,“目的IP”,“目的端口号”,“协议号” 这样一个五元组来标识一个通信(可以通过netstat -n查看);

在这里插入图片描述
1.2 端口号范围划分
- 0 - 1023:知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议,他们的端口号都是固定的.
- 1024 - 65535:操作系统动态分配的端口号,客户端程序的端口号,就是由操作系统从这个范围分配的.
1.3 认识知名端口号(Well-Know Port Number)
有些服务器是非常常用的,为了使用方便,人们约定一些常用的服务器,都是用以下这些固定的端口号:
- ssh服务器,使用22端口
- ftp服务器,使用21端口
- telnet服务器,使用23端口
- http服务器,使用80端口
- https服务器,使用443端口
执行下面的命令,可以看到知名端口号
代码语言:javascript
AI代码解释
cat /etc/services
我们自己写一个程序使用端口号时,要避开这些知名端口号
1.4 两个问题
下面两个问题其实我们在之前的文章中就已经提过,下面我们再来提一下
问题一:一个进程是否可以bind多个端口号?
答案:可以。
一个进程可以创建多个网络套接字,并将每个套接字绑定到不同的端口号上。这是非常常见的技术。
工作原理:
- 每个 socket 是一个独立的通信端点。
- 一个进程可以调用多次 socket() 系统调用,创建多个套接字描述符。
- 然后,对每个套接字描述符调用 bind(),并指定不同的端口号。
应用场景举例:
- FTP 服务器:通常使用两个端口。
- 端口 21 用于控制连接(传输命令)。
- 另一个随机或指定的端口(如 20)用于数据连接(传输文件内容)。
- 自定义服务:一个后台进程可能同时提供管理接口(绑定端口 9000)和用户数据接口(绑定端口 8080)。
- 负载监听:一个进程可以同时监听多个端口,以处理不同类型的请求。
问题二:一个端口号是否可以被多个进程bind?
答案:通常情况下不行,但在特定条件下可以。
这是一个更复杂的问题,我们需要分情况讨论。
情况 A:默认情况 —— 不允许
在绝大多数情况下,操作系统不允许两个进程绑定到同一个端口号。如果你尝试这样做,第二个进程在调用 bind() 时会失败,并得到一个类似 “Address already in use” 的错误。
原因:
- 当数据包到达时,操作系统需要通过 IP地址 + 端口号 + 协议(TCP/UDP) 这个三元组来唯一确定应该将数据交付给哪个进程的哪个套接字。
- 如果两个进程绑定了同一个端口,操作系统将无法做出唯一决策,导致数据混乱。
情况 B:特殊情况 —— 允许
在某些特定技术手段下,可以实现多个进程绑定同一个端口。
- SO_REUSEADDR / SO_REUSEPORT 套接字选项 这是最常见的实现方式。通过设置套接字选项,可以允许多个套接字绑定到相同的地址和端口。
- SO_REUSEADDR:主要用于解决“TIME_WAIT”状态下的端口快速重用问题。在某些系统(如 Windows)和特定条件下,它也能允许多个绑定。
- SO_REUSEPORT(Linux 3.9+ 引入):明确设计用于允许多个进程(或线程)绑定到完全相同的 IP 地址和端口号。操作系统内核会在内核层面进行负载均衡,将传入的连接均匀地分配给这些监听了同一端口的进程。
- 应用场景:多进程网络服务器(如 Nginx)可以使用 SO_REUSEPORT 来实现,让多个 worker 进程同时监听 80 端口,提高性能并避免“惊群”问题。
- 多播 / 组播 在组播中,多个进程可以加入同一个组播组,并绑定到同一个端口来接收发送到该组播地址的数据包。这是一种一对多的通信模式。
- 不同的传输协议 一个端口号可以同时被一个 TCP 进程和一个 UDP 进程绑定。因为 (IP, Port, TCP) 和 (IP, Port, UDP) 被视为两个完全不同的端点。
- 例如,一个 DNS 服务器进程可以同时在 TCP 53 端口和 UDP 53 端口上提供服务。
- 绑定到不同的 IP 地址 如果一台机器有多个 IP 地址(例如,多个网卡或配置了多个虚拟 IP),那么:
- 进程 A 可以绑定到 (IP地址1, 端口80)
- 进程 B 可以绑定到 (IP地址2, 端口80) 这是完全允许的,因为它们仍然是两个不同的端点。
2. UDP协议
2.1 UDP协议端格式

在这里插入图片描述
UDP首部长度:固定为8字节,包含4个16位字段。
各字段详细作用解析
- 16位源端口号
- 作用:标识发送数据包的应用程序或进程。
- 详解:这个字段告诉接收方,数据是来自发送方设备的哪个“门牌号”(端口)。接收方在回复数据时,就可以将数据发送到这个源端口号,从而确保回复能准确送达最初发送数据的那个应用程序。此字段是可选的,如果发送方不需要接收回复,可以将其置为0。
- 16位目的端口号
- 作用:标识接收数据包的目标应用程序或服务。
- 详解:这是最关键字段之一,告诉网络设备(如路由器、交换机)和操作系统,这个数据包最终应该交给哪个正在“监听”的应用程序。例如,目的端口号是53,设备就知道这个包是发给DNS服务的;是123,则是发给NTP(时间同步)服务的。
- 16位UDP长度
- 作用:指示整个UDP数据包的总长度。
- 详解:这个长度包括了UDP首部(8字节)和数据部分。因为是16位(2字节),所以最大可以表示 2^16 - 1 = 65535字节。这意味着一个UDP数据包的最大长度是64KB。接收方通过这个字段可以知道应该从网络层接收多少数据。
- 16位UDP检验和
- 作用:用于检测UDP首部和数据在传输过程中是否发生错误(如比特翻转)。
- 详解:
- 发送方会根据UDP伪首部(一个包含IP地址等信息的结构)、UDP首部和数据计算出一个校验值,并填入此字段。
- 接收方会进行同样的计算,如果计算结果与接收到的检验和不匹配,则表明数据在传输中已损坏,接收方会静默地丢弃这个包,而不会要求重传。
- 注意:这个字段在IPv4中是可选的(如果不用可置为0),但在IPv6中是强制使用的。它为UDP提供了一层最基本的可靠性保障。
- 数据
- 作用:承载实际要传输的应用层信息。
- 详解:这是UDP协议最终要运送的“货物”,比如DNS查询内容、语音通话的音频数据、视频流数据等。数据字段的长度是可变的,最大为 65535 - 8 = 65527字节(减去8字节的首部)。
所以udp报文的报头和有效载荷怎么分离呢?
具体分离步骤:
- 定位报头的开始: UDP报文是从网络层(IP层)交付上来的。你拿到的是一个完整的UDP报文(即 IP 数据包中的数据部分)。这个报文的起始位置就是UDP报头的开始。
- 截取固定长度的报头: UDP报头的长度是固定不变的 8个字节(64位)。所以,你只需要: UDP报头 = UDP报文的前8个字节
- 解析报头字段以获取信息: 将这8个字节的报头按图片所示的结构进行解析,可以获得关键信息:
- 前2个字节:16位源端口号。
- 紧接着的2个字节:16位目的端口号。
- 再接着的2个字节:16位UDP长度。这个字段至关重要,它指明了整个UDP报文(报头+数据)的总长度。
- 分离有效载荷: 知道了总长度(假设为 L),又知道报头固定长度为 8字节,那么有效载荷的长度就是 L - 8字节。 因此,有效载荷的起始位置是第9个字节,结束位置是第 L个字节。 有效载荷 = UDP报文的第9个字节 到 第L个字节
举例说明
- 假设你收到一个UDP报文,其“16位UDP长度”字段的值经解析后为 1028(单位是字节)。
- 分离报头:直接取前8个字节。这8个字节就包含了所有的控制信息(端口号、长度、校验和)。
- 计算数据长度:有效载荷长度 = 1028(总长度) - 8(报头长度) = 1020字节。
- 分离有效载荷:从第9个字节开始,连续取1020个字节,这部分就是发送方应用程序真正要发送的数据。
2.2 UDP的特点
UDP 核心特点详解
- 无连接 就像寄信,不需要先打电话确认
- 没有握手过程:不需要像 TCP 那样进行三次握手建立连接
- 直接发送:知道目标地址(IP)和门牌号(端口)就直接发送数据
- 无状态:服务器不会维护与客户端的连接状态信息
- 优势:开销小,速度快,适合短平快的通信
- 不可靠 就像寄平信,不保证对方一定能收到
- 无确认机制:发送后不知道对方是否成功接收
- 无重传机制:如果数据丢失,不会自动重新发送
- 无错误通知:网络故障导致发送失败,应用层不会收到任何错误报告
- 不保证顺序:后发送的数据包可能先到达
- 影响:应用层需要自己处理可靠性问题(如果需要的话)
- 面向数据报 就像寄送包裹,每个都是独立完整的
- 数据有明确边界:每个 UDP 数据报都是一个完整的消息单元
- 一次性读写:应用层每次 sendto() 发送一个完整的数据报,每次 recvfrom() 接收一个完整的数据报
- 大小限制:每个 UDP 数据报最大约 64KB(包含头部)
对比 TCP 的流式传输:
TCP 像水管流水,没有明确边界
UDP 像邮寄包裹,每个包裹都是独立的
2.3 UDP的缓冲区
UDP缓冲区详解
- 发送缓冲区
- 无真正的发送缓冲区:UDP协议本身并不维护一个发送缓冲区。当应用程序调用sendto发送数据时,数据会被直接封装成UDP数据报,然后交给网络层(IP层)处理。
- 可能因网络层拥堵而阻塞:虽然UDP本身没有发送缓冲区,但在数据交给网络层后,如果网络层(例如,由于出口队列已满)无法立即发送,则可能会阻塞后续的发送操作。但是,UDP不会像TCP那样进行重传或拥塞控制,它只是尽可能快地发送。
- 接收缓冲区
- 存在接收缓冲区:UDP协议在内核中维护了一个接收缓冲区,用于存储接收到的UDP数据报。每个UDP socket都有自己独立的接收缓冲区。
- 不保证顺序:由于UDP数据报是独立传输的,可能因为网络路径不同而导致到达顺序与发送顺序不一致。接收缓冲区内数据报的顺序是不确定的,应用程序必须能够处理乱序到达的数据。
- 缓冲区大小有限:接收缓冲区的大小是有限的。当缓冲区满时,新到达的UDP数据报会被丢弃,并且不会通知发送方。因此,如果应用程序不能及时读取数据,就会导致丢包。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)