Python-UDP編程
1、UDP編程:
測試命令:
windows:
netstat -anp udp | findstr 9999
Linux: 發給服務器數據
echo ‘233‘ | nc -u 127.0.0.1 9999
2、UDP服務器端編程:
UDP服務器端編程流程:(從圖中可以看到,服務器端只需要一個socket)
-
-
- 創建socket 對象,socket.SOCK_DGRAM
- 綁定IP 和 Port, bind() 方法
- 傳輸數據:
- 接受數據:socket.recvfrom(bufsize [, flags] ) ,獲取一個二元組()
- 發送數據:socket.sendto(string, address) 發給某地址某信息
- 釋放資源
-
1 import socket 2 3 socket = socket.socket(type=socket.SOCK_DGRAM) 4 5 ip = ‘127.0.0.1‘ 6 port = 9999 7 8 laddr = ip, port 9 10 socket.bind(laddr) # 服務器端正式啟動 11 12 data, raddr = socket.recvfrom(1024) #沒有接收到,會處於阻塞狀態 13 14 socket.sendto(data, raddr) 15 16 socket.close()
UDP客戶端編程流程:
註意:UDP是無連接協議,多以可以只有任何一端,例如客戶端數據發往服務端,服務器端存在與否無所謂。
UDP編程中bind、connect、send、sendto、recv、recfrom方法使用
UDP的socket對象創建後,是麽有占用本地地址額端口的
註意:
1、UDP 創建socket後,不能直接recv,recvfrom,,只有知道了本地地址和端口,服務器端才能知道數據應該發給你
2、send 和 connect 搭配使用。
心跳機制:
加一個ack機制 和 心跳 hearbeat
心跳,就是一端定時的發往另一端的信息,一般每次數據越少越好,心跳時間間隔約定好就行。
ack 即響應,一端收到另一端的消息後返回的確認消息
心跳機制:
- 一般來說是客戶端定時發往服務器端的,服務器端並不需要ACK 回復客戶端,只需要記錄該客戶端還活著就行(比如群聊,如果客戶端不活著,就沒必要回復)
- 如果是服務器端定時發往客戶端的,一般需要 客戶端ack 響應,表示活著,,如果沒有收到ack的客戶端,服務器端移除其信息,這種實現比較復雜,較少使用。
- 也就是雙向都發送心跳,很少使用
UDP實現群聊:
server端:
1 import socket 2 import threading 3 import logging 4 import datetime 5 6 7 FORMAT = ‘%(asctime)s %(thread)s %(threadName)s %(message)s‘ 8 logging.basicConfig(format=FORMAT, level=logging.INFO) 9 10 class ChatServer: 11 def __init__(self, ip=‘127.0.0.1‘, port=9999, interval=10): 12 self.laddr = ip, port 13 self.event = threading.Event() 14 self.sock = socket.socket(type=socket.SOCK_DGRAM) 15 self.clients = {} 16 self.interval = interval 17 18 def start(self): 19 self.sock.bind(self.laddr) 20 21 threading.Thread(target=self.recv, name=‘recvive‘).start() 22 23 def recv(self): 24 while not self.event.is_set(): 25 data, raddr = self.sock.recvfrom(1024) 26 localkeys = set() 27 logging.info(data) 28 29 # 心跳信息 30 if data.strip() == b‘^hb^‘: 31 self.clients[raddr] = datetime.datetime.now().timestamp() 32 continue 33 34 if data.strip() == b‘quit‘: 35 # 若一個客戶端剛進來,還沒有加到字典中,直接pop,會報KerryError 36 # if raddr in self.clients.keys(): 37 self.clients.pop(raddr, None) 38 continue 39 self.clients[raddr] = datetime.datetime.now().timestamp() 40 41 # z再次發送數據的時間,如果之後沒有再次進入,這次的時間就作為下次比較時間,所以這次的時間可以認為是最新的心跳信息 42 current = datetime.datetime.now().timestamp() 43 msg = ‘{}--{}‘.format(data.decode(), ‘******‘).encode() 44 45 # 過期的就不在發數據給客戶端了,並且剔除掉 46 #z 字典不能再遍歷的時候,刪除內容 47 for r, t in self.clients.items(): 48 if current - t > self.interval: 49 localkeys.add(r) 50 self.sock.sendto(msg, r) 51 for r in localkeys: 52 self.clients.pop(r) 53 54 def stop(self): 55 self.sock.close() 56 self.event.set() 57 58 59 def main(): 60 cs = ChatServer() 61 cs.start() 62 63 while True: 64 cmd = input(">>>") 65 if cmd == ‘quit‘: 66 cs.stop() 67 break 68 logging.info(threading.enumerate()) 69 70 71 if __name__ == "__main__": 72 main()群聊server端
client端:
1 import socket 2 import threading 3 import logging 4 import datetime 5 6 7 FORMAT = ‘%(asctime)s %(thread)s %(threadName)s %(message)s‘ 8 logging.basicConfig(format=FORMAT, level=logging.INFO) 9 10 class ChatClient: 11 def __init__(self, ip=‘127.0.0.1‘, port=9999, interval=3): 12 self.raddr = ip, port 13 self.event = threading.Event() 14 self.sock = socket.socket(type=socket.SOCK_DGRAM) 15 self.interval = interval 16 17 def start(self): 18 self.sock.connect(self.raddr) 19 20 threading.Thread(target=self.recv, name=‘c-recv‘).start() 21 22 # 每隔interval 就發送一次心跳信息 23 threading.Thread(target=self.hearbeat, name=‘ht‘,daemon=True).start() 24 25 def recv(self): 26 while not self.event.is_set(): 27 data, raddr = self.sock.recvfrom(1024) 28 print(data) 29 print(raddr) 30 def hearbeat(self): 31 while not self.event.wait(self.interval): 32 self.send(‘^hb^‘) 33 34 35 36 def send(self, msg): 37 self.sock.send(msg.encode()) 38 39 def stop(self): 40 self.sock.close() 41 self.event.set() 42 43 44 def main(): 45 cc = ChatClient() 46 cc.start() 47 48 while True: 49 cmd = input(‘>>‘) 50 if cmd == ‘quit‘: 51 cc.stop() 52 break 53 cc.send(cmd) 54 logging.info(threading.enumerate()) 55 56 57 58 if __name__ == "__main__": 59 main()群聊client端
註意:
如果是如上圖所示,是一個解釋器進程,建立兩個對象,但是這兩個對象分別在不同的線程中跑。
如果直接運行兩次 client,就是兩個客戶端進程
UDP 協議的應用:
UDP 是無連接的,它是基於以下假設:
網絡足夠好
消息不會丟包
包不會亂序
但是,即使在局域網,也不能保證不丟包,而且包到達的不一定有序。
應用場景:
視頻,音頻傳輸,一般來說丟一些包。
Python-UDP編程