【Linux网络】传输层协议UDP

上三层,放在一起也可以统称为应用层,我们已经介绍过了,下面我们就来一层一层往下介绍,这篇我们就来介绍传输层最重要的两个协议之一UDP,下一篇介绍TCP

文章目录

  • [1. 传输层](#1. 传输层)
    • [1.1 再谈端口号](#1.1 再谈端口号)
    • [1.2 端口号范围划分](#1.2 端口号范围划分)
    • [1.3 认识知名端口号(Well-Know Port Number)](#1.3 认识知名端口号(Well-Know Port Number))
    • [1.4 两个问题](#1.4 两个问题)
  • [2. UDP协议](#2. UDP协议)
    • [2.1 UDP协议端格式](#2.1 UDP协议端格式)
    • [2.2 UDP的特点](#2.2 UDP的特点)
    • [2.3 UDP的缓冲区](#2.3 UDP的缓冲区)
    • [2.4 UDP使用注意事项](#2.4 UDP使用注意事项)
    • [2.5 基于UDP的应用层协议](#2.5 基于UDP的应用层协议)

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端口

执行下面的命令,可以看到知名端口号

bash 复制代码
cat /etc/services

我们自己写一个程序使用端口号时,要避开这些知名端口号


1.4 两个问题

下面两个问题其实我们在之前的文章中就已经提过,下面我们再来提一下

问题一:一个进程是否可以bind多个端口号?

答案:可以。

一个进程可以创建多个网络套接字,并将每个套接字绑定到不同的端口号上。这是非常常见的技术。

工作原理:

  • 每个 socket 是一个独立的通信端点。

  • 一个进程可以调用多次 socket() 系统调用,创建多个套接字描述符。

  • 然后,对每个套接字描述符调用 bind(),并指定不同的端口号。

应用场景举例:

  1. FTP 服务器:通常使用两个端口。

    • 端口 21 用于控制连接(传输命令)。

    • 另一个随机或指定的端口(如 20)用于数据连接(传输文件内容)。

  2. 自定义服务:一个后台进程可能同时提供管理接口(绑定端口 9000)和用户数据接口(绑定端口 8080)。

  3. 负载监听:一个进程可以同时监听多个端口,以处理不同类型的请求。

问题二:一个端口号是否可以被多个进程bind?

答案:通常情况下不行,但在特定条件下可以。

这是一个更复杂的问题,我们需要分情况讨论。

情况 A:默认情况 ------ 不允许

在绝大多数情况下,操作系统不允许两个进程绑定到同一个端口号。如果你尝试这样做,第二个进程在调用 bind() 时会失败,并得到一个类似 "Address already in use" 的错误。

原因:

  • 当数据包到达时,操作系统需要通过 IP地址 + 端口号 + 协议(TCP/UDP) 这个三元组来唯一确定应该将数据交付给哪个进程的哪个套接字。

  • 如果两个进程绑定了同一个端口,操作系统将无法做出唯一决策,导致数据混乱。

情况 B:特殊情况 ------ 允许

在某些特定技术手段下,可以实现多个进程绑定同一个端口。

  1. SO_REUSEADDR / SO_REUSEPORT 套接字选项

    这是最常见的实现方式。通过设置套接字选项,可以允许多个套接字绑定到相同的地址和端口。

    • SO_REUSEADDR:主要用于解决"TIME_WAIT"状态下的端口快速重用问题。在某些系统(如 Windows)和特定条件下,它也能允许多个绑定。

    • SO_REUSEPORT(Linux 3.9+ 引入):明确设计用于允许多个进程(或线程)绑定到完全相同的 IP 地址和端口号。操作系统内核会在内核层面进行负载均衡,将传入的连接均匀地分配给这些监听了同一端口的进程。

    • 应用场景:多进程网络服务器(如 Nginx)可以使用 SO_REUSEPORT 来实现,让多个 worker 进程同时监听 80 端口,提高性能并避免"惊群"问题。

  2. 多播 / 组播

    在组播中,多个进程可以加入同一个组播组,并绑定到同一个端口来接收发送到该组播地址的数据包。这是一种一对多的通信模式。

  3. 不同的传输协议

    一个端口号可以同时被一个 TCP 进程和一个 UDP 进程绑定。因为 (IP, Port, TCP) 和 (IP, Port, UDP) 被视为两个完全不同的端点。

  • 例如,一个 DNS 服务器进程可以同时在 TCP 53 端口和 UDP 53 端口上提供服务。
  1. 绑定到不同的 IP 地址
    如果一台机器有多个 IP 地址(例如,多个网卡或配置了多个虚拟 IP),那么:
  • 进程 A 可以绑定到 (IP地址1, 端口80)

  • 进程 B 可以绑定到 (IP地址2, 端口80)

    这是完全允许的,因为它们仍然是两个不同的端点。


2. UDP协议

2.1 UDP协议端格式


UDP首部长度:固定为8字节,包含4个16位字段。

各字段详细作用解析

  1. 16位源端口号
  • 作用:标识发送数据包的应用程序或进程。

  • 详解:这个字段告诉接收方,数据是来自发送方设备的哪个"门牌号"(端口)。接收方在回复数据时,就可以将数据发送到这个源端口号,从而确保回复能准确送达最初发送数据的那个应用程序。此字段是可选的,如果发送方不需要接收回复,可以将其置为0。

  1. 16位目的端口号
  • 作用:标识接收数据包的目标应用程序或服务。

  • 详解:这是最关键字段之一,告诉网络设备(如路由器、交换机)和操作系统,这个数据包最终应该交给哪个正在"监听"的应用程序。例如,目的端口号是53,设备就知道这个包是发给DNS服务的;是123,则是发给NTP(时间同步)服务的。

  1. 16位UDP长度
  • 作用:指示整个UDP数据包的总长度。

  • 详解:这个长度包括了UDP首部(8字节)和数据部分。因为是16位(2字节),所以最大可以表示 2^16 - 1 = 65535字节。这意味着一个UDP数据包的最大长度是64KB。接收方通过这个字段可以知道应该从网络层接收多少数据。

  1. 16位UDP检验和
  • 作用:用于检测UDP首部和数据在传输过程中是否发生错误(如比特翻转)。

  • 详解:

    • 发送方会根据UDP伪首部(一个包含IP地址等信息的结构)、UDP首部和数据计算出一个校验值,并填入此字段。

    • 接收方会进行同样的计算,如果计算结果与接收到的检验和不匹配,则表明数据在传输中已损坏,接收方会静默地丢弃这个包,而不会要求重传。

    • 注意:这个字段在IPv4中是可选的(如果不用可置为0),但在IPv6中是强制使用的。它为UDP提供了一层最基本的可靠性保障。

  1. 数据
  • 作用:承载实际要传输的应用层信息。

  • 详解:这是UDP协议最终要运送的"货物",比如DNS查询内容、语音通话的音频数据、视频流数据等。数据字段的长度是可变的,最大为 65535 - 8 = 65527字节(减去8字节的首部)。

所以udp报文的报头和有效载荷怎么分离呢?

具体分离步骤

  1. 定位报头的开始:

    UDP报文是从网络层(IP层)交付上来的。你拿到的是一个完整的UDP报文(即 IP 数据包中的数据部分)。这个报文的起始位置就是UDP报头的开始。

  2. 截取固定长度的报头:

    UDP报头的长度是固定不变的 8个字节(64位)。所以,你只需要:
    UDP报头 = UDP报文的前8个字节

  3. 解析报头字段以获取信息:

    将这8个字节的报头按图片所示的结构进行解析,可以获得关键信息:

    • 前2个字节:16位源端口号。

    • 紧接着的2个字节:16位目的端口号。

    • 再接着的2个字节:16位UDP长度。这个字段至关重要,它指明了整个UDP报文(报头+数据)的总长度。

  4. 分离有效载荷:

    知道了总长度(假设为 L),又知道报头固定长度为 8字节,那么有效载荷的长度就是 L - 8字节。

    因此,有效载荷的起始位置是第9个字节,结束位置是第 L个字节。
    有效载荷 = UDP报文的第9个字节 到 第L个字节

举例说明

  1. 假设你收到一个UDP报文,其"16位UDP长度"字段的值经解析后为 1028(单位是字节)。

  2. 分离报头:直接取前8个字节。这8个字节就包含了所有的控制信息(端口号、长度、校验和)。

  3. 计算数据长度:有效载荷长度 = 1028(总长度) - 8(报头长度) = 1020字节。

  4. 分离有效载荷:从第9个字节开始,连续取1020个字节,这部分就是发送方应用程序真正要发送的数据。


2.2 UDP的特点

UDP 核心特点详解

  1. 无连接

    就像寄信,不需要先打电话确认

    • 没有握手过程:不需要像 TCP 那样进行三次握手建立连接

    • 直接发送:知道目标地址(IP)和门牌号(端口)就直接发送数据

    • 无状态:服务器不会维护与客户端的连接状态信息

    • 优势:开销小,速度快,适合短平快的通信

  2. 不可靠

    就像寄平信,不保证对方一定能收到

    • 无确认机制:发送后不知道对方是否成功接收

    • 无重传机制:如果数据丢失,不会自动重新发送

    • 无错误通知:网络故障导致发送失败,应用层不会收到任何错误报告

    • 不保证顺序:后发送的数据包可能先到达

    • 影响:应用层需要自己处理可靠性问题(如果需要的话)

  3. 面向数据报

    就像寄送包裹,每个都是独立完整的

    • 数据有明确边界:每个 UDP 数据报都是一个完整的消息单元

    • 一次性读写:应用层每次 sendto() 发送一个完整的数据报,每次 recvfrom() 接收一个完整的数据报

    • 大小限制:每个 UDP 数据报最大约 64KB(包含头部)

对比 TCP 的流式传输:

TCP 像水管流水,没有明确边界

UDP 像邮寄包裹,每个包裹都是独立的


2.3 UDP的缓冲区

UDP缓冲区详解

  1. 发送缓冲区
  • 无真正的发送缓冲区:UDP协议本身并不维护一个发送缓冲区。当应用程序调用sendto发送数据时,数据会被直接封装成UDP数据报,然后交给网络层(IP层)处理。

  • 可能因网络层拥堵而阻塞:虽然UDP本身没有发送缓冲区,但在数据交给网络层后,如果网络层(例如,由于出口队列已满)无法立即发送,则可能会阻塞后续的发送操作。但是,UDP不会像TCP那样进行重传或拥塞控制,它只是尽可能快地发送。

  1. 接收缓冲区
  • 存在接收缓冲区:UDP协议在内核中维护了一个接收缓冲区,用于存储接收到的UDP数据报。每个UDP socket都有自己独立的接收缓冲区。

  • 不保证顺序:由于UDP数据报是独立传输的,可能因为网络路径不同而导致到达顺序与发送顺序不一致。接收缓冲区内数据报的顺序是不确定的,应用程序必须能够处理乱序到达的数据。

  • 缓冲区大小有限:接收缓冲区的大小是有限的。当缓冲区满时,新到达的UDP数据报会被丢弃,并且不会通知发送方。因此,如果应用程序不能及时读取数据,就会导致丢包。

  1. 全双工
  • UDP socket是全双工的:一个UDP socket可以同时进行读和写操作。这意味着同一个socket既可以接收数据也可以发送数据,而不需要像TCP那样建立连接后才能通信。

UDP数据报的丢失

由于UDP的不可靠性,数据报可能会因为以下原因丢失:

  1. 接收缓冲区满:如果应用程序读取速度跟不上接收速度,导致缓冲区满,新到的数据报被丢弃。

  2. 网络拥堵:路由器或交换机的队列满,数据报被丢弃。

  3. 校验和错误:如果UDP数据报在传输过程中发生错误,校验和不匹配,数据报会被丢弃。

应用层处理

由于UDP不提供可靠性,如果应用需要可靠性,则必须在应用层实现:

  • 序列号:用于检测丢失和乱序。

  • 确认和重传:确保数据到达。

  • 流量控制:防止发送方发送过快导致接收方无法处理。

为什么udp不需要发送缓冲区呢?

我们可以从UDP的工作方式来理解。

  1. UDP的发送过程:
  • 当应用程序调用sendto发送UDP数据报时,UDP不会对数据进行缓存,而是立即将数据封装成IP数据报并发送到网络。

  • 如果应用程序发送数据的速度超过了网络接口的处理能力,UDP不会像TCP那样将数据缓存在发送缓冲区中,而是直接丢弃数据并返回错误(或者取决于具体实现,可能不会返回错误,但不会保留数据)。

  1. UDP的无连接和不可靠特性:
  • 由于UDP不需要保证可靠性,因此它不需要维护发送缓冲区来存储已发送但未确认的数据(因为不需要重传)。

  • 同时,UDP是无连接的,所以它不需要维护连接状态,包括发送缓冲区。

  1. TCP的对比:
  • TCP是面向连接的、可靠的协议。它使用发送缓冲区来存储已发送但尚未被确认的数据,以便在超时或收到重复确认时进行重传。

  • TCP还需要实现流量控制和拥塞控制,这些都需要缓冲区来配合。

  1. UDP的简单性:
  • UDP的设计目标就是简单高效。省略发送缓冲区减少了UDP的实现复杂性和内存开销。
  1. 实际影响:
  • 由于没有发送缓冲区,应用程序调用sendto时,数据会立即被送出(或者因为某些原因被丢弃),而不会在UDP层被缓存。这意味着如果网络条件不好,数据包可能会被大量丢弃,而应用程序可能无法及时得知。

因此,UDP不需要发送缓冲区是因为其无连接和不可靠的特性所决定的。它不需要重传,也不需要流量控制,所以没有必要缓存发送的数据。


2.4 UDP使用注意事项

我们注意到,UDP协议首部中有一个16位的最大长度。也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部)。

然而64K在当今的互联网环境下,是一个非常小的数字。

如果我们需要传输的数据超过64K,就需要在应用层手动分包,多次发送,并在接收端手动拼装。

但是,我们也要注意,网络环境是复杂的,我们并不能保证发送的多个包都会按照顺序到达,甚至可能丢包。

因此,在应用层实现分包和组装时,需要考虑以下问题:

  1. 如何分组?每个分组应该包含哪些信息?

  2. 如何确保接收端能够正确组装?(例如,需要包含序列号、总包数、当前包序号等)

  3. 如何处理丢包和乱序?

  4. 是否需要进行重传?如何重传?

下面是一个简单的示例,说明如何在应用层实现分包和组装:

假设我们要传输一个大型数据,我们将其分成多个包,每个包包含:

  • 序列号(用于标识同一个大数据的传输,接收端通过序列号来区分不同的大数据)

  • 总包数

  • 当前包序号(从0开始)

  • 数据

注意:由于UDP包最大为64K,我们每个包的实际数据部分需要减去我们添加的头部信息(序列号、总包数、当前包序号等)。

但是,实际上我们通常不会让UDP包接近64K,因为超过MTU(通常1500字节)会导致IP分片,而IP分片会降低传输效率和增加丢包率。

因此,我们通常会在应用层将数据分成更小的包,比如每个包1KB左右,避免IP分片。

步骤

发送端

  1. 将数据分成多个小块,每个小块加上我们自定义的协议头(序列号、总包数、当前包序号)。

  2. 依次发送这些包。

接收端

  1. 接收包,根据序列号区分不同的大数据,将同一个序列号的包收集起来。

  2. 根据当前包序号和总包数,判断是否接收完整。

  3. 如果接收完整,则按照包序号排序,然后组合成原始数据。

但是,这种方法可能会遇到丢包和乱序的问题。我们可以通过以下方式改进:

  • 接收端收到包后,可以发送一个确认包(ACK)给发送端,告知收到了哪些包。发送端可以根据ACK决定重传哪些包。

  • 或者,我们可以使用前向纠错(FEC)的方式,发送一些冗余包,使得接收端在丢失部分包的情况下也能恢复数据。

由于UDP本身不保证可靠性,所以我们需要在应用层实现这些机制。这就是为什么在需要可靠传输时,我们通常使用TCP,或者使用基于UDP的可靠传输协议(如QUIC)的原因。


2.5 基于UDP的应用层协议

  • NFS:网络文件系统
  • TFTP:简单文件传输协议
  • DHCP:动态主机配置协议
  • BOOTP:启动协议(用于无盘设备启动)
  • DNS:域名解析协议

当然, 也包括你自己写UDP程序时自定义的应用层协议;

相关推荐
ycydynq2 小时前
python html 解析的一些写法
linux·python·html
知识分享小能手2 小时前
openEuler入门学习教程,从入门到精通,openEuler 24.03 中的 Vim 编辑器 —— 全面知识点详解(7)
linux·vim·openeuler
汤愈韬2 小时前
STP协议概述、STP工作原理、STP拓扑计算
网络·网络安全
jenchoi4133 小时前
【2025-11-15】软件供应链安全日报:最新漏洞预警与投毒预警情报汇总
前端·网络·安全·网络安全·npm·node.js
LCG元3 小时前
Linux 性能监控三板斧:top/vmstat/iostat 快速入门
linux
LCG元3 小时前
实战案例:服务器磁盘空间告急,如何快速定位和清理"大文件"
linux
以琦琦为中心3 小时前
很好!从 `fdisk -l` 输出可以看到您的磁盘确实是600GB,但只有29.5GB被分配给根分区 `/dev/sda3`。现在我来帮您扩展这个分区。
linux·ubuntu
wc_xue_fei_le3 小时前
11.11DNS主从服务器
linux·服务器·前端
用户31187945592183 小时前
申威SW64系统安装docker-ce-19.03.14.rpm详细教程(附安装包)
linux