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協議。