UDP 概述
用户数据报协议(User Datagram Protocol, UDP)只是做了传输层协议能够做的最少工作,除了复用、分用功能及少量的差错检测外,它几乎没有对 IP 增加别的东西,因此与 IP 一样,提供的也是尽力而为的交付服务(报文段可能丢失、非按需到达)。
虽然 UDP 不能提供可靠数据传输服务,但是很许多应用更适合使用 UDP,原因如下:
- UDP 是无连接的:UDP 发送方和接收方之间不需要握手,可以减少延迟,这就是 DNS 运行在 UDP 而不是 TCP 的主要原因;
- 实现简单:无需维护连接状态;
- 报文段首部开销少,UDP 报文段首部为 8 字节,而 TCP 报文段首部为 20 字节;
- 没有拥塞控制,应用可以更好地控制发送时间和速率。
虽然 UDP 不能提供可靠的数据传输服务,但是使用 UDP 的应用是可以实现可靠数据传输的,这可以通过在应用程序自身中建立可靠性机制来完成,但这增加了应用开发难度。
UDP 报文段格式
UDP 报文段的首部有 4 个字段,每个字段由两个字节组成:通过端口号可以使目的主机将应用数据交给运行在目的端系统中的相应进程(多路分用);长度字段指示了在 UDP 报文段中的字节数(首部加数据);校验和字段用于检测 UDP 报文段在传输中是否发生错误。
应用层数据占用 UDP 报文段的数据字段。
UDP 校验和
发送方的 UDP 将报文段中的内容视为 16 比特的整数,进行校验和计算:计算所有整数的和,进位加在和的后面(溢出回卷),将得到的值按位求返,得到校验和,将校验和放入报文段的校验和字段。
接收方将所有的 16 比特的整数(包括校验和)加在一起,如果该报文段中没有引入差错,则显然在接收方处该和将是 1111111111111111
(虽然和为 1111111111111111
也可能有错误);如果这些比特中存在 0,那么在传输过程中肯定出现了差错。
虽然 UDP 提供差错检测,但它对差错恢复无能为力。
UDP Socket 编程
应用程序开发者在研发阶段最先做的一个决定是,应用程序是运行在 TCP 上还是 UDP 上。应用程序开发者可以控制应用层一侧的所有东西,但是基本无法控制传输层一侧。
由于 UDP 是无连接的,因此使用 UDP 套接字的两个进程进行通信时,在发送进程将数据分组推出 Socket 之前,需要将目的地址附在该分组之上,目的地址包括目的主机的 IP 地址和目的套接字的端口号。
下面是一个简单的 CS 架构的使用 UDP 实现的简单应用程序,实现了小写字母转大写字母的功能。
客户端应用程序代码如下:
from socket import *
serverName = 'hostname' # 服务器的 IP 地址(域名)
serverPort = 12000 # 服务器相应应用进程的端口号
clientSocket = socket(AF_INET, SOCK_DGRAM) # 建立客户端 Socket,指定 Socket 类型(UDP),端口号由操作系统分配
message = raw_input('Input lowercase sentence:') # 用键盘输入一行放入 message 中
clientSocket.sendto(message, (serverName, serverPort)) # 使用套接字发送消息,需要附加上目的端口号和目的 IP 地址!!!
modifiedMessage, serverAddress = clientSocket.recvfrom(2048) # 从服务器接收返回消息,和服务器的地址
print modifiedMessage
clientSocket.close()
服务器端应用程序代码如下:
from socket import *
serverPort 12000
serverSocket = socket(AF_INET, SOCK_DGRAM) # 创建服务器端 UDP Socket
serverSocket.bind(('', serverPort)) # 服务器端 Socket 绑定 12000 端口号
print "The server is ready to receive"
while true:
message, clientAddress = serverPort.recvfrom(2048) # 提取消息,客户的 IP 和端口号
modifiedMessage = message.upper()
serverSocket.sendto(modifiedMessage, clientAddress) # 同样需要指定数据分组的目的地址(指客户的地址)
UDP Socket 编程时,使用 socket 发送消息时,应用进程需要手动添加上所发送数据分组的目的 IP 地址和目的端口号。但是源地址也附在了分组上,尽管这是自动而不是显示地由代码完成的。