UDP协议概述
- UDP协议——User Datagram Protocol,即用户数据包协议,UDP协议是无连接、不可靠、高效率数据传输协议。
- UDP协议优点:开销小、速度快、无连接。
- UDP协议缺点:无确认重传和排序、无流控制、无错误恢复。
- UDP协议是面向报文的传输协议,它对报文既不拆分也不合并,而是给报文添加头部后就向下交给IP层。发现错误时不要重传,只是简单地把错误的报文扔掉、或给应用程序提供警告。
- UDP协议广泛运用在强调数据传输速率的多媒体应用领域,例如网络聊天、在线视频、网络游戏、实时监控、路由协议中修改路由表等。
- TCP协议遇到丢包或错误时要求重传,因而导致传输速率大幅降低。
- UDP协议遇到丢包或错误时不要求重传,因为一个报文丢失后几秒内会有新的报文替代它,所以UDP协议传输速率比TCP协议高很多。
- 应用程序需要根据需要自定义应用层协议来处理报文丢失、错误、失序的情况。
- 编写UDP应用程序需要审慎考虑合适的报文长度:
- 局域网最大传输单元(Maximum Transmission Unit,MTU)默认是1500字节。
- UDP协议头部是8字节、IP层协议头部是20字节。
- 单条报文建议长度是1024字节,最大长度不得超过65507字节(报文加头部总长不得超过65535字节)。
- 网络环境较好时报文长度及接收缓存建议设大一些,可以减少报文接收不及时导致丢包的概率。
udp协议服务端
-
创建socket实例。
sk = socket.socket(type=socket.SOCK_DGRAM)
注意:type=socket.SOCK_DGRAM是指定关键字参数,不能“缺省type=”,该参数用来指定使用udp协议。
-
绑定ip和端口。
sk.bind(('127.0.0.1',9001))
说明:该方法有一个参数,类型是元组(元组的第一个元素是IP,第二个元素是端口)。无返回值。
-
从UDP客户端收报文。
message,addrress = sk.recvfrom(1024)
说明:该方法参数是接收消息长度。返回值是元组,元组第一项是消息,第二项是ip及端口。
-
发送报文给UDP客户端。
sk.sendto(message,address)
说明:该方法有二个参数,第一个参数是要发送的消息,必须是bytes型;第二个参数是元组(元组的第一个元素是IP,第二个元素是端口)。返回值是发送的字节数。
udp协议客户端
-
创建socket实例。
sk = socket.socket()
-
发报文给UDP服务端。
sk.sendto(message.encode(),('127.0.0.1',9001))
说明:该方法有二个参数,第一个参数是要发送的消息,必须是bytes型;第二个参数是元组(元组的第一个元素是IP,第二个元素是端口)。返回值是发送的字节数。
-
从UDP服务端收报文。
message = sk.recv(1024).decode()
说明:该方法参数是接收消息长度。返回值是消息。
案例:udp协议聊天室
-
服务端
import socket HOST = '127.0.0.1' # IP地址 PORT = 50007 # 端口 max_connect = 5 def main(): accounts = {} with socket.socket(type=socket.SOCK_DGRAM) as sk: sk.bind((HOST, PORT)) while True: msg, address = sk.recvfrom(1024) rev = msg.decode().split('|||') accounts[rev[0]] = address if rev[1] in accounts: sk.sendto(msg, accounts[rev[1]]) if __name__ == "__main__": main()
说明:这个是UDP协议简易聊天室服务端模型,服务端接收到客户端发来的消息,会记录客户端的ip和端口以及账号,并更新到字典中。遇到发往目标账户的消息在字典中时,会将信息转发给目标账户,从而实现多人聊天功能。
注意:代码目前没有缓存消息,当要发送的目标不在线时,该消息会被丢弃。 -
客户端
import socket from threading import Thread import sys HOST = '127.0.0.1' # IP地址 PORT = 50007 # 端口 class Transceiver: def __init__(self, conn, server, account: str, target: str, buffer: int = 1024) -> None: self.conn = conn # socket实例 self.server = server # 目标ip及端口 self.buffer = buffer # 缓存区大小 self.path = (account + '|||' + target + '|||').encode() # 己方账号 def send(self, message: str) -> None: """发送消息""" if type(message) is str: # 判断消息类型 message = message.encode() # 将消息转成bytes型,缺省参数是utf8编码 self.conn.sendto(self.path + message, self.server) # 发送消息 def recv(self): """接收消息""" message, _ = self.conn.recvfrom(self.buffer) message = message.decode('utf8').split('|||') return message[1], message[2] # 返回消息字符串 def main(): def send(m, message): m.send(message) def recv(m): while True: try: source, data = m.recv() print(f'收到"{source}"发来的信息:{data}') except: pass with socket.socket(type=socket.SOCK_DGRAM) as sk: account = input("请输入你的账号:") target = input("请输入你想聊天的账号:") m = Transceiver(sk, (HOST, PORT), account, target) t = Thread(target=recv, args=(m,)) t.setDaemon(True) t.start() while True: message = input('请输入你要发送的信息:') if not message: continue if message == 'q' or message == 'Q': sys.exit(0) Thread(target=send, args=(m, message)).start() if __name__ == "__main__": main()
说明:这个是udp协议简易聊天室客户端模型,客户端登录时首先输入自己的账号和对方的账号,然后进入循环,循环输入发给对方账号的消息,另开了子收获进程接受服务端转发来的消息。