UDP是面向无链接的协议,使用UDP协议时,不须要创建链接,只须要知道对方的IP地址和端口号,就能够直接发数据包
。可是,能不能到达就不知道了。虽然用UDP传输数据不可靠,但它的优势是和TCP比,速度快,对于不要求可靠到达的数据,就可使用UDP协议。编程
咱们先来了解一下,python的socket的通信流程:
服务器
服务端:网络
一个UDP端口只能被绑定一次
客户端:socket
咱们能够看到UDP不须要维护一个链接,因此比较简单tcp
使用udp编程和使用tcp编程用于类似的步骤,而由于udp的特性,它的服务端不须要监听端口,而且客户端也不须要事先链接服务端。根据上图,以及创建服务端的流程,我门来捋一下服务端的逻辑到代码的步骤:函数
socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # socke.AF_INET 指的是使用 IPv4 # socket.SOCK_STREAM 指定使用面向数据报的UDP协议
socket.bind(('127.0.0.1',999)) # 小于1024的端口只有管理员才能够指定
data, client_info = sock.recvfrom(1024) # 返回一个元组,数据和客户端的地址,由于UDP没有链接,因此只能经过提取消息的发送的源地址,才能在应答时指定对方地址
sock.sendto('data'.encode(),('127.0.0.1',999)) # bytes格式 # 第二个参数为客户端地址
sock.close()
完成的代码:性能
import socket server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 指定socket的协议,UDP使用的是SOCK_DGRAM server.bind(('127.0.0.1', 9999)) # 绑定端口 print('UDP Server is Starting...') data, addr = server.recvfrom(1024) # 接受(包含数据以及客户端的地址) print('Received from {}'.format(addr)) server.sendto('hello,{}'.format(addr).encode('utf-8'), addr) # 应答,格式为(应答的数据,客户端的IP和Port元组)
为何要使用recvfrom/sendto?大数据
sendto因为没有socket的特性,因此应答时也须要传递client的地址和端口ui
socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # socke.AF_INET 指的是使用 IPv4 # socket.SOCK_STREAM 指定使用面向数据报的UDP协议
socket.connect(('127.0.0.1',999)) # UDP不会建立链接,因此这里仅仅是在socket上添加了本段/对端的地址而已,并不会发起链接
data, client_info = sock.recv(1024) # 返回一个元组,数据和客户端的地址,由于UDP没有链接,因此只能经过提取消息的发送的源地址,才能在应答时指定对方地址
sock.sendto('data'.encode(),('127.0.0.1',999)) # bytes格式 # 第二个参数为客户端地址
sock.close()
为何connect是可选的?
import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) print(client) # <socket.socket fd=140, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0> client.connect(('127.0.0.1', 9999)) print(client) # <socket.socket fd=140, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 51859), raddr=('127.0.0.1', 9999)>
完整的代码:
import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 指定socket的协议,UDP使用的是SOCK_DGRAM client.sendto('hello world'.encode('utf-8'), ('127.0.0.1', 9999)) # 发送数据,格式为(发送的数据,服务端的IP和Port元组) print(client.recv(1024).decode('utf-8')) # 一样使用recv来接受服务端的应答数据
UDP的使用与TCP相似,可是不须要创建链接。此外,服务器绑定UDP端口和TCP端口互不冲突,也就是说,UDP的9999端口与TCP的9999端口能够各自绑定。
服务器端套接字:
函数 | 描述 |
---|---|
s.bind() |
绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。 |
客户端套接字:
函数 | 描述 |
---|---|
s.connect() |
初始化UDP链接对象的,本段/对端地址。 |
s.connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 |
公共用途的套接字函数:
函数 | 描述 |
---|---|
s.recv() |
接收TCP/UDP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其余信息,一般能够忽略。 |
s.send() |
发送TCP/UDP数据,将string中的数据发送到链接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。 |
s.recvfrom() |
接收UDP数据,与recv()相似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。 |
s.sendto() |
发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。 |
s.close() |
关闭套接字 |
s.fileno() | 返回套接字的文件描述符。 |
s.setblocking(flag) |
若是flag为0,则将套接字设为非阻塞模式,不然将套接字设为阻塞模式(默认值)。非阻塞模式下,若是调用recv()没有发现任何数据,或send()调用没法当即发送数据,那么将引发socket.error异常。 |
s.makefile() |
建立一个与该套接字相关连的文件 |
下面来模仿上一篇TCP版本的聊天室的结构来建立一个UDP版本的聊天室
服务端:
import socket import threading import datetime import logging FORMAT = '%(asctime)s %(message)s' logging.basicConfig(level=logging.INFO, format=FORMAT) class ChatUDPServer: def __init__(self, ip, port): self.ip = ip self.port = port self.event = threading.Event() self.clients = {} self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) def start(self): self.sock.bind((self.ip, self.port)) threading.Thread(target=self.recv, name='start').start() def recv(self): while not self.event.is_set(): # 待清理的列表 clean = set() # 远程主机关闭链接时,这里会触发异常。不知道为啥 try: data, client_addr = self.sock.recvfrom(1024) except ConnectionResetError: continue if data.upper() == 'quit' or data == b'': self.clients.pop(client_addr) logging.info(client_addr, 'is down') continue # 心跳包,内容越小越好 if data.lower() == b'@im@': self.clients[client_addr] = datetime.datetime.now().timestamp() continue logging.info('{}:{} {}'.format(*client_addr, data.decode())) self.clients[client_addr] = datetime.datetime.now().timestamp() msg = "{}:{} {}".format(*client_addr, data.decode()).encode() current = datetime.datetime.now().timestamp() for client, date in self.clients.items(): # 若是10s内没有发送心跳包,则进行清理 if current - date > 10: clean.add(client) else: self.sock.sendto(msg, client) # 清理超时链接 for client in clean: self.clients.pop(client) def stop(self): self.event.set() self.sock.close() if __name__ == '__main__': cus = ChatUDPServer('127.0.0.1', 9999) cus.start() while True: cmd = input('>>>>: ').strip() if cmd.lower() == 'quit': cus.stop() break else: print(threading.enumerate())
客户端:
import socket import threading import logging FORMAT = '%(asctime)s %(message)s' logging.basicConfig(level=logging.INFO, format=FORMAT) class ChatUDPClient: """ self.ip: 服务端地址 self.port:服务端端口 self.socket:建立一个socket对象,用于socket通讯 self.event:建立一个事件对象,用于控制连接循环 """ def __init__(self, ip, port): self.ip = ip self.port = port self.socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) self.event = threading.Event() def connect(self): self.socket.connect((self.ip, self.port)) threading.Thread(target=self.recv, name='recv',daemon=True).start() threading.Thread(target=self._heart,name='heart',daemon=True).start() def _heart(self): while not self.event.wait(5): data = '@im@' self.send(data) def recv(self): while not self.event.is_set(): # 某些服务端强制关闭时,会出b'',这里进行判断 try: data = self.socket.recv(1024) if data == b'': self.event.set() logging.info('{}:{} is down'.format(self.ip, self.port)) break logging.info(data.decode()) # 有些服务端在关闭时不会触发b'',这里会直接提示异常,这里进行捕捉 except (ConnectionResetError,OSError): self.event.set() logging.info('{}:{} is down'.format(self.ip, self.port)) def send(self, msg): self.socket.send(msg.encode()) def stop(self): self.send('quit') self.socket.close() if __name__ == '__main__': ctc = ChatUDPClient('127.0.0.1', 9999) ctc.connect() while True: info = input('>>>>:').strip() if not info: continue if info.lower() == 'quit': logging.info('bye bye') ctc.stop() break if not ctc.event.is_set(): ctc.send(info) else: logging.info('Server is down...') break
UDP是无链接协议,它基于如下假设:
可是,即便在局域网,也不能保证不丢包,并且包的到达不必定有序。
应用场景:
通常来讲,UDP性能优于TCP,可是可靠性要求高的场合仍是要选择TCP协议。