User Datagram Protocol(UDP) gaunthan Posted on May 14 2016 ? Transport Layer ? ? Computer Networking ? ## 概述 **UDP**(User Datagram Protocol,用户数据报协议)是无连接、不可靠的传输层协议。它仅在IP服务上提供进程到进程的通信,进行非常有限的差错检验。UDP是一个简单的协议,开销很小,如果进程需要通信的速度而不考虑可靠性,那么可以使用UDP。 ## 用户数据报 UDP分组称为**用户数据报**(user datagram),由两个部分组成:**头部**和**数据区**。头部固定为8字节,由4个字段组成,每个字段2字节。16位可以定义的总长度为0到65535。然而,由于UDP数据报存储在总长度为65535的IP数据报中,它的总长度要小些。  你或许疑惑为什么数据一定要16位对齐?原因是UDP校验和的计算是对16比特的字进行的,因此我们让数据保持16位对齐(必要时填充位全0的字节)就免除了在计算校验和时需要根据数据的总字节数是奇数还是偶数而做不同处理的逻辑。当然这个必要的填充工作无须由我们进行,我们只需要知道数据报在封装时需要做这样的处理。 ### 伪头部 **伪头部**(pseudo header)是IP分组的头部的一部分,其中有些字段要填入0,用户数据报封装在这个IP分组中。 #### 协议字段 伪头部的协议字段可确保这个分组是属于UDP,而不是属于其他传输层协议。因为如果一个进程可以使用UDP和TCP,则端口号可以是相同的。UDP的协议字段值是17。如果在传输过程中这个值改变,在接收端计算校验和时就可检测出来,UDP就可丢弃这个分组。这样就不会传递给错误的协议。 ### 校验和 UDP校验和提供了差错检测功能,它包含三部分:伪头部、UDP头部和从应用层来的数据。校验和可以不包括伪头部,用户数据报也可能安全完整地到达。但是,如果IP头部受损,那么它可能被提交到错误的主机。 UDP分组的发送方可以选择不计算校验和。这种情况下,在发送前,校验和字段全填0。在发送方决定计算校验和的情况下,如果碰巧结果是0,那么在发送前校验和全改为1。 #### 计算过程 发送方的UDP对报文段中的所有16比特字的和进行反码运算,求和时遇到的任何溢出都被回卷(相当于溢出时和再加上1)。得到的结果被放在UDP报文段中的校验和字段。 例如有2个16比特的数据要发送,则其计算过程如下: 0110011001100000 1101010101010101 + ------------- 0011101110110101 // 有溢出,因此补加1 0000000000000001 + ------------- 0011101110110110 // 和 ~ ------------- // 反码运算 1100010001001001 // 校验和 #### 校验过程 接收方将全部的16比特字(包括校验和)加上一起。如果该分组中没有引入差错,则显然该和将是全1的16比特字。如果这些比特之一是0,则说明分组出现了差错。 ## 服务 ### 进程到进程通信 UDP使用套接字地址提供进程到进程通信,这是IP地址和端口号的组合。 一个UDP套接字是由一个二元组来全面标识的,该二元组包含一个目的IP地址和目的端口号。因此,如果两个UDP数据报有不同的源IP地址和/或源端口号,但具有相同的目的IP地址和目的端口号,那么这两个数据报将通过相同的目的套接字被定向到相同的目的进程。 ### 无连接服务 UDP提供无连接服务,它发送出去的每一个用户数据报都是一个独立的分组,之间没有任何关系。用户数据报不进行编号,也没有像TCP协议的握手过程,这意味着每一个数据报可以沿着不同的路径传递。 无连接的一个结果就是使用UDP的进程不能够向UDP发送数据流,并期望它将这个数据流分割成许多不同的相关联的用户数据报。相反,每一个请求必须足够小以能够装入用户数据报中。只有那些发送小于65507字节(65535减去UDP头部的8字节再减去IP头部的20字节)的短报文的进程才应该使用UDP。 ### 流量控制 UDP是一个非常简单的协议,它没有流量控制(flow control),因此也没有窗口机制。如果到来的报文太多,接收方可能会溢出。缺乏流量控制意味着需要流量控制时,需要由使用UDP的进程来提供该服务。 ### 差错检测 UDP可以使用校验和以确保发送内容在传输过程中没有破损或被更改。 你可能会疑惑为什么UDP也提供了差错检测。原因是不能保证源和目的之间的所有链路都提供差错检测,即这些链路中的一条可能使用没有差错检测的协议。另外,即使报文段经链路正确地传输,当报文段存储在某台路由器的内存中时,也可能引入比特差错。在及无法确保整条链路的可靠性,又无法确保内存中的差错检测的情况下,如果端到端数据传输服务要提供差错检测,UDP就必须在端到端基础上在传输层提供差错检测。 ### 差错控制 UDP没有差错控制(error control)机制,这就表示发送方不知道报文是丢失还是重传。当接收方使用校验和检测出差错时,它就静默丢弃该数据报。缺乏差错控制意味着需要差错控制时,需要由使用UDP的进程来提供该服务。 ### 拥塞控制 由于UDP是无连接协议,它不提供拥塞控制。它假设被发送的分组很小且零星,不会在网络中造成拥塞。由于这个原因,当UDP发送量很大时,可能会抢占链路的带宽使得TCP传输饥饿。 ### 封装和解封装 当将报文从一个进程发送到另一个进程时,UDP协议就要对报文进行封装和解封装。 ### 排队 在UDP中,队列是与端口联系在一起的。在客户端,当进程启动时,它从操作系统请求一个端口号。有些实现是创建一个入队列和一个出队列并与每一个进程相关联。而有些实现只创建与每一个进程相关的入队列。 ### 多路复用和多路分解 在运行TCP/IP协议簇的主机上只有一个UDP,但可能有多个想使用UDP服务的进程。这种情况下,UDP采用多路复用和多路分解。 ## 与其他协议的比较 与无连接简单协议比较,唯一的区别在于UDP为差错检测加入了可选校验和,以在接收端发现被破坏分组。 与IP相比,UDP几乎只是添加了复用/分解功能以及少量的差错检测。实际上,如果应用程序开发人员选择UDP而不是TCP,则该应用程序差不多就是直接与IP打交道。UDP从应用进程得到数据,附加上用于多路复用/分解服务的源和目的端口号字段,以及两个其他的小字段,然而将形成的报文段交给网络层。网络层将该传输层报文封装到一个IP数据报中,然后尽力而为地尝试将此数据报交付给接收主机。如果该报文段到达接收主机,UDP使用目的端口号将报文段中的数据交付给正确的应用进程。 ## 优点 ### 关于何时、发送什么数据的应用层控制更为精细 采用UDP时,只要应用进程将数据传递给UDP,UDP就会将此数据打包进UDP报文段并立即将其传递给网络层。另一方面,TCP有一个拥塞控制机制,以便当源和目的主机间的一条或多条链路变得极度拥塞时来遏制传输层TCP发送方。TCP仍将继续重新发送数据报文段直到目的主机收到此报文并加以确认,而不管可靠交付需要用多长时间。 因为实时应用通常要求最小的发送速率,不希望过分地延迟报文段的传送,而且能容忍一些数据丢失,TCP服务模型并不是特别适合这些应用的需求。 ### 无需建立连接 TCP在开始数据传输之前要经过三次握手,UDP却不需要任何准备即可进行数据传输。因此UDP不会引入建立连接的时延。这可能是DNS运行在UDP上之而不是TCP之上的主要原因。 ### 无连接状态 TCP需要在端系统中维护连接状态。此连接状态包括接收和发送缓存、拥塞控制参数以及序号与确认号的参数。另一方面,UDP不维护连接状态,也不跟踪这些参数。因此,某些专门用于特定应用的服务器当应用程序运行在UDP之上而不是运行在TCP上时,一般都能支持更多的活跃客户。 ### 分组首部开销小 每个TCP报文段都有20字节的首部开销,而UDP仅有8字节的开销。 ## 应用方向 UDP一般应用于: * 需要简单的请求-响应通信,而较少考虑流量控制和差错控制的进程,如DNS。对于需要传送成块数据的进程(如FTP)则通常不使用UDP。 * 具有内部流量控制和差错控制机制的进程。如简单文件传输协议(TFTP)的进程就包含流量控制和差错控制。 * 多播。多播能力已嵌入到UDP软件中,但没有嵌入到TCP软件中。 * 管理进程,如SMTP。 * 某些路由选择更新协议,如路由选择信息协议(RIP)。 * 交互实时应用。 可以查看这篇文章获悉UDP协议的具体应用:[应用层协议](http://leanote.com/blog/post/58871d09ab6441616300008c)。 ## UDP Socket编程 下面是使用Python 2.7编写的示例程序,客户端向服务器发送一串字符串,服务器将其转化为大写后返回。 ### server.py ```python #!/bin/python2 from socket import * serverPort = 12000 serverSocket = socket(AF_INET, SOCK_DGRAM) serverSocket.bind(('', serverPort)) print 'The server is ready to receive.' while True: message, clientAddress = serverSocket.recvfrom(2048) modifiedMessage = message.upper() serverSocket.sendto(modifiedMessage, clientAddress) ``` ### client.py ```python #!/bin/python2 from socket import * serverName = 'localhost' serverPort = 12000 serverAddress = (serverName, serverPort) clientSocket = socket(AF_INET, SOCK_DGRAM) while True: message = raw_input('>> ') clientSocket.sendto(message, serverAddress) modifiedMessage, _ = clientSocket.recvfrom(2048) print modifiedMessage ``` ## References - JamesF.Kurose, KeithW.Ross, 库罗斯,等. 计算机网络:自顶向下方法[M]. 高等教育出版社, 2009. - 谢希仁. 计算机网络.第6版[M]. 电子工业出版社, 2013. 赏 Wechat Pay Alipay Transport Layer Shell 与进程