【计算机网络】UDP 协议详解及其网络编程应用
文章目录
- 一、引言
- 二、UDP
- 1、UDP的协议格式
- 2、UDP 报文的解包和分用
- 3、UDP面向数据报的特点
- 三、UDP输入输出
- 四、UDP网络编程
一、引言
UDP(User Datagram Protocol,用户数据报协议)是一种网络通信协议,它属于传输层的协议。是一个简单的、面向无连接的协议。UDP用于将数据从一个应用程序发送到另一个应用程序,并在此过程中不提供可靠的数据传输保障。UDP 支持一对一、一对多、多对多的交互通信。
UDP是不具有可靠性的协议。细微的处理它会交给上层的应用去完成。在使用UDP的情况下,虽然可以确保发送消息的大小(例如,发送端应用程序发送一个100字节的消息,那么接收端应用程序也会以100字节为长度接收数据。UDP中,消息长度的数据也会发送到接收端,因此在发送的消息中不需要设置一个表示消息长度或间隔的字段信息。然而,UDP不具备可靠传输。所以,发送端发出去的消息在网络传输途中一旦丢失,接收端将收不到这个消息。) ,却不能保证消息一定会到达。因此,应用有时会根据自己的需要进行重发处理。
UDP不提供复杂的控制机制,利用 IP 提供面向「无连接」的通信服务。
就是说UDP是依赖于底层的IP协议来进行数据传输。IP提供了基本的无连接数据传输服务,UDP在此基础上构建,只是增加了端口号等基本的传输控制功能,但不提供额外的可靠性或连接管理。
由于UDP面向无连接,它可以随时发送数据。再加上UDP本身的处理既简单又高效,因此经常用于以下几个方面:
- 包总量较少的通信(DNS、SNMP等)
- 视频、音频等多媒体通信(即时通信)
- 广播通信(广播、多播)
- 限定于LAN等特定网络中的应用通信
二、UDP
系统调用接口:网络套接字编程接口位于应用层和传输层之间,是操作系统提供的系统调用接口。它允许应用程序通过一组标准的接口进行网络通信,比如 socket()
、bind()
、sendto()
、recvfrom()
等函数。
内核中的UDP:UDP是由操作系统内核实现的,它属于操作系统协议栈的一部分。UDP的所有功能(如数据报的发送和接收)都是由操作系统负责的,而不是由用户应用程序编写的。
协议栈:操作系统内核提供了完整的网络协议栈,其中包括各种网络协议(如IP、TCP、UDP等)。操作系统通过这些协议栈管理网络通信的底层细节,确保网络数据能够在系统和网络之间正确传输。
网络作为操作系统的一部分:网络功能是操作系统的一部分,因为网络协议的实现和管理都在操作系统内核中完成。操作系统通过提供网络套接字接口,让应用程序可以利用这些内核级别的网络功能进行通信。
网络套接字编程接口提供了一个在应用层与操作系统之间的桥梁,使得应用程序能够使用底层的网络协议(如TCP和UDP)进行通信。HTTP等高层协议利用这些底层协议的特性来实现其功能,而UDP作为内核中的协议,由操作系统负责其实现和管理。这种分层设计使得网络编程变得更加灵活和高效。
1、UDP的协议格式
UDP数据段的长度为16位,也就是说UDP能传输的最大长度是64K(包括UDP首部字段)。
若传输的数据超过64K,就需要在应用层手动分包,多次发送,并在接收端手动拼装。
校验和(Checksum):用于检测数据在传输过程中是否被篡改或损坏。它是 UDP 首部和数据部分的 16 位字的反码和。这个字段在某些实现中可能是可选的(可以为 0),但在 IPv6 中是强制的。
UDP报头在内核中的实现:
这个结构体UDPhdr
用于表示UDP(用户数据报协议)报头,它通常用于网络编程中。下面是这个结构体中各个字段的含义:
source
:源端口号,占16位,表示发送方的端口号。dest
:目的端口号,占16位,表示接收方的端口号。len
:UDP长度,占16位,包括UDP头部和UDP数据的总长度。check
:校验和,占16位,用于检测UDP数据在传输过程中是否出现错误。
这个结构体通常用于原始套接字编程,或者在网络协议的实现中。
2、UDP 报文的解包和分用
1. 报文解包
当UDP报文从网络层传递到传输层时,解包过程会解析UDP首部并提取相应的信息:
- 接收数据报:网络层将接收到的IP数据报传递给传输层。
- 解析UDP首部:传输层解析UDP数据报的前8个字节,从而获取源端口、目的端口、报文长度和校验和。
- 校验和验证:通过计算校验和验证数据报的完整性。如果校验和不匹配,数据报会被丢弃。
- 提取数据部分:从UDP数据报中提取实际的数据部分,将其交给上层应用。
2. 分用
分用是指将接收到的数据报根据目的端口号分配给不同的应用程序。具体步骤如下:
- 检查目的端口:解析出的目的端口号用于确定数据报应该交给哪个应用程序。
- 查找对应的进程:操作系统维护一个端口号到进程的映射表,通过目的端口号查找到相应的进程。
- 传递数据:将数据报的内容传递给相应的应用程序。
UDP的报头当中只包含四个字段,每个字段的长度都是16位,总共8字节。因此UDP采用的实际上是一种定长报头,UDP在读取报文时读取完前8个字节后剩下的就都是有效载荷了。
3、UDP面向数据报的特点
UDP传输过程类似于寄信。
-
无连接性
UDP是无连接的,意味着发送方和接收方之间不需要建立或维护连接。在UDP中,每个数据报文(数据报)都是独立的,不依赖于前后的数据报。这种无连接特性使UDP特别适合需要快速传输的应用程序,如实时视频流、在线游戏等。
-
数据报独立性与无序性
在UDP中,每个数据报都是一个独立的单元。数据报之间没有顺序关系,即使发送顺序被打乱或丢失,也不会影响其他数据报的传输。应用程序必须自行处理数据报的顺序、丢失和重复等问题。而且不保证数据报按发送顺序到达接收端。每个数据报独立传输,接收端收到的数据报可能是乱序的。应用程序需要根据需求自行处理数据的顺序。
-
不可靠
没有确定机制,没有重传机制;若因为网络故障该段无法发送到对方,UDP协议层也不会给应用层返回任何错误信息。UDP没有提供重传机制。如果数据报在传输过程中丢失或损坏,UDP不会自动重传数据。应用程序必须自行处理这些问题,这使得UDP在需要高实时性但对数据可靠性要求不高的场景中非常有效。
如何理解UDP是面向数据报的
UDP(用户数据报协议)是一种无连接、面向数据报的传输层协议。它的设计使其高效且适用于需要快速传输但不需要可靠性保证的应用。
由于UDP提供的是无连接的服务,因此UDP客户与服务之间不存在任何的长期关系。举例来说,一个UDP客户可以创建一个套接字并发送数据报给一个特定的服务器,然后立即用同一个套接字发送另一个数据报给另一个服务器。同样的,一个UDP服务器可以用同一个UDP套接字从若干个不同的客户接收数据报,每个客户一个数据报。
UDP面向数据报的实现:
-
数据报传输:在UDP中,每个数据报从发送端到接收端是独立传输的。发送端将数据封装成UDP数据报,通过网络层(IP层)发送到接收端。接收端接收数据报后,提取出UDP首部信息和数据部分,然后将数据交给相应的应用程序处理。
-
报文解包:在接收端,UDP首部被解析以获取源端口、目的端口、数据长度和校验和等信息。然后,数据部分被提取出来,根据目的端口号将数据传递给相应的应用程序。
-
分用:接收端使用目的端口号来确定哪个应用程序应该接收数据报。这种方式被称为分用。操作系统会维护一个端口号到应用程序的映射表,通过查找表将数据报传递给正确的应用程序。
UDP被称为面向数据报的协议,原因如下:
- 独立的数据报:每个UDP数据报独立存在,彼此之间没有联系。这种设计意味着数据报可以独立处理和传输,无需依赖其他数据报的状态。
- 无连接和无状态:UDP不维护连接状态,也不跟踪数据报的传输状态。这使得每个数据报的传输过程独立,不需要为连接的建立、维护或终止花费额外的开销。
- 简单的首部:UDP首部非常简洁,仅包含必要的信息,使得数据报的处理和解析速度非常快。
- 不保证可靠性:UDP不保证数据报的可靠传输、不丢失、按序到达。这意味着每个数据报的处理是独立的,不依赖于前后数据报的传输状态。
这些特性使UDP非常适合需要低延迟和高效传输的应用,如视频会议、实时游戏和语音通信等。
三、UDP输入输出
下图展示了一个应用进程写数据到UDP套接字中发生的步骤。
这一端的UDP简单地给来自用户的数据报安上它的8字节首部以构成UDP数据报,然后传递给IP层。
其中虚线框表示了套接字发送缓冲区,因为它实际并不存在。任何UDP套接字都有份发送缓冲区的大小,不过它仅仅是可写到该套接字的UDP数据报大小的上限。如果一个应用进程写一个大于套接字发送缓冲区大小的数据报,内核将返回进程一个EMSGSIZE
错误。UDP是不可靠的,它不必保存应用进程数据的一个副本,因此不必像TCP一样有一个真正的发送缓冲区。
也就是说UDP没有真正意义上的发送缓冲区。调用sendto
会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作。
UDP 的发送缓冲区在数据发送成功后,数据会立即从缓冲区中移除,不像 TCP 需要等待接收方的确认。
发送缓冲区(Send Buffer)是一个内核空间中的缓冲区,专门用于临时存储应用程序准备发送的数据。它的作用是在网络协议栈中不同层次之间传递数据时,确保数据能够有序且可靠地传输。
应用进程的数据在沿协议栈向下传递时,通常被复制到某种格式的一个内核缓冲区中。当数据通过协议栈传递并成功发送后,数据链路层完成了数据的传输任务,此时该数据的副本在发送缓冲区中的存在已经没有意义。因此,内核会将发送缓冲区中的数据副本丢弃,以便释放内存资源给后续需要发送的数据。
简单来说,发送缓冲区存在的意义就是:接收方来不及接收了,我们暂时把它放在缓冲区保存,等对方来得及接收了,有接收能力了再发送给对方。
UDP套接字的接收缓冲区:由UDP给某个特定套接字排队的UDP数据报的数目受限于该套接字接收缓冲区的大小,但是这个缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致。如果缓冲区满了,再到达的UDP数据就会被丢弃。
UDP没有发送缓冲区:
- UDP是一种无连接的协议,它不提供可靠的数据传输保证。当应用程序调用
sendto
函数发送数据时,UDP协议会将数据直接传递给网络层,而不像TCP那样在发送缓冲区中排队等待确认。 - 由于UDP不进行拥塞控制,也没有重传机制,因此发送操作通常是立即返回的,即使数据报可能还没有被完全发送到网络上。
UDP的接收缓冲区:
- UDP的接收缓冲区存在于内核中,用于暂存从网络层接收到的UDP数据报。
- 由于UDP协议的性质,接收缓冲区不能保证数据报的顺序与发送顺序一致。数据报可能会因为网络延迟、路由差异等原因而以不同的顺序到达。
- 如果接收缓冲区满了,新到达的数据报可能会被丢弃。UDP协议本身不会通知发送方数据报丢失,因此需要应用程序自行处理这种情况。
应用注意事项:
- 应用程序在使用UDP时应该考虑到数据报可能会丢失、重复或乱序。
- 对于需要可靠传输的应用,开发者需要在应用层实现额外的机制,如确认、重传等。
- 应用程序还应该考虑到接收缓冲区的大小,以避免在高速网络环境中因为缓冲区溢出而丢失数据。
UDP不是全双工的,但是可以实现全双工。
UDP的设计目标是简单和低开销。全双工通信需要更多的控制信息和状态维护,这会增加复杂性,与UDP的设计目标相违背。全双工通信通常需要保持一定程度的连接状态。
全双工通信需要处理诸如流量控制、拥塞控制和数据包顺序等问题,UDP将这些责任交给了应用程序,自己则保持简单,只提供数据报文的发送和接收功能。如果UDP是全双工的,那么它需要在网络堆栈中为每个通信对维护更多的资源,例如缓冲区和状态信息。UDP的设计意图是尽量减少这种资源的使用。
注:UDP协议中使用connect
会改变原来套接字绑定的目标地址和端口号,用新绑定的地址和端口号代替,原有的绑定状态会消失。与TCP不同,UDP的connect
调用不会与目标地址进行任何形式的握手或建立连接。它仅仅是在套接字上设置了一个默认的目的地址。
UDP服务器接收数据的方式,以及是否能够同时接收来自多个客户端的数据
UDP是一种无连接的协议,它不保证数据的可靠传输,也不要求在数据传输之前建立连接。
接收数据的情况:
- 服务器使用
recvfrom
系统调用等待接收数据。 recvfrom
会阻塞当前线程,直到接收到客户端发送的数据。- 一旦接收到数据,服务器就会记录日志,并将接收到的数据处理后发送回客户端。
UDP协议支持多播和广播,这也是其能够同时接收来自多个客户端的原因之一:
- 广播:通过广播,服务器可以在本地网络中向所有客户端发送数据,所有客户端也可以通过广播向服务器发送消息。
- 多播:多播允许服务器订阅一个特定的多播组,然后任何加入这个组的客户端都可以向该组发送消息,服务器接收来自组内所有客户端的数据。
这种能力意味着UDP不仅可以通过单播与多个客户端通信,也可以通过广播和多播机制同时处理来自多个客户端的数据报。关于多个客户端同时发送数据:
- 由于UDP是无连接的,服务器不需要为每个客户端维持一个单独的连接,因此服务器可以同时处理来自多个客户端的数据。无论有多少客户端向同一个服务器地址发送数据包,服务器都可以使用相同的
recvfrom()
函数接收来自不同客户端的数据包。 - 当服务器调用
recvfrom
时,如果多个客户端几乎同时发送数据,操作系统内核的网络栈会处理这些数据报。也可能会接收到来自不同客户端的数据,这取决于内核调度和网络状况。
因此,UDP服务器能够由多个客户端同时发送数据,而不会像TCP那样需要为每个客户端建立一个独立的连接。
注意点:
- 由于UDP不保证数据的可靠传输,如果网络状况不佳或者数据报丢失,服务器可能不会收到某些客户端发送的数据。
recvfrom
每次调用只处理一个数据报,如果多个数据报同时到达,它们会在内核中的队列中排队等待处理。
也就是说,UDP服务器能够同时接收来自多个客户端的数据,因为UDP协议本身支持多播和广播,并且在单个套接字上可以接收来自不同发送者的数据报。
四、UDP网络编程
UDP协议的服务器端程序设计的流程分为套接字建立、套接字于地址进行绑定、收发数据、关闭套接字等过程,分别对应函数socket
、bind
、sendto
、recvfrom
和close
。
服务器端:建立套接字过程使用 socket
函数,建立的套接字类型为数据报套接字。地址结构与套接字文件描述符进行绑定。当绑定操作成功后,可以调用recvfrom
函数从建立的套接字接收数据或者调用 sendto
函数向建立的套接字发送络数据。当相关的处理过程结束后,调用 close
函数关闭套接字。
- 创建UDP套接字得到套接字描述符
- 设置服务器地址和侦听端口
- 绑定套接字到侦听端口
- 接收客户端发送的数据
- 处理接收到的数据
- 发送响应给客户端
- 关闭套接字
客户端:套接字建立、收发数据、关闭套接字等过程,分别对应于函数 socket
、sendto
、recvfrom
和 close
。建立套接字过程使用 socket
函数。建立套接字之后,可以调用函数sendto
向建立的套接字发送数据或者调用 recvfrom
函数从建立的套接字收网络数据。当相关的处理过程结束后,调用 close
函数关闭套接字。
- 创建UDP套接字
- 设置服务器地址和端口
- 发送数据到服务器
- 接收服务器的响应
- 关闭套接字
UDP服务器可以在使用单个进程的情况下处理所有客户。对于UDP套接字,UDP层隐含有排队发生,即每个UDP套接字都有一个接送缓冲区,到达该套接字的每个数据报都进入这个套接字接收缓冲区。这样,在进程能够读该套接字中任何已排好队的数据报之前,如果有多个数据到达该套接字,那么相继到达的数据报仅仅加到该套接字的接收缓冲区中。然而这个缓冲区的大小是有限的。
上图中仅有一个服务器进程,它仅有的单个套接字用于接收所有到达的数据报并返回所有的响应。该套接字只有一个缓冲区来存放所到达的数据报。
如果一个客户数据报丢失,客户端将永远阻塞在recvfrom
调用处,等待一个永远收不到的服务器应答。防止这种永久阻塞的方法一般是客户的recvfrom
调用设置一个超时。
客户端必须给sendto
调用指定服务器的端口号和IP地址。一般来说,客户端的IP地址和端口号都是由内核自动选择。即客户端不需要调用bind
。客户端的IP地址却可以随客户发送的每一个UDP数据报而变动。
如果客户主机是多宿的(即具有多个网络接口和多个IP地址),那么在发送UDP数据报时,客户有可能在两个目的地之间交替选择。默认情况下,如果未明确指定,则操作系统会选择默认的发送接口,这通常是系统的路由表决定的。
号和IP地址。一般来说,客户端的IP地址和端口号都是由内核自动选择。即客户端不需要调用bind
。客户端的IP地址却可以随客户发送的每一个UDP数据报而变动。
如果客户主机是多宿的(即具有多个网络接口和多个IP地址),那么在发送UDP数据报时,客户有可能在两个目的地之间交替选择。默认情况下,如果未明确指定,则操作系统会选择默认的发送接口,这通常是系统的路由表决定的。