chapter12.2、socket程式設計的UDP程式設計
阿新 • • 發佈:2018-11-04
socket的UDP程式設計和socketserver模組
UDP程式設計
UDP服務端流程
心跳機制
心跳包越小越好
TCP也使用心跳包,隔一段時間可能會假死,心跳包是一種保活機制
可以使用這種方法:
伺服器發往客戶端,判斷是否有回覆,設定次數內沒有回覆,就判斷客戶端下線,用的較少
也可以使用,
客戶端定時傳送心跳包到服務端。記錄最後一次通訊的時間的方法,服務端知道客戶端或者就ok,不需要響應
使用心跳包實現伺服器
- UDP服務端
- 建立socket物件。socket.SOCK_DGRAM
- 繫結IP和Port,bind()方法
- 傳輸資料
- 接收資料,socket.recvfrom(bufsize[, flags]),獲得一個二元組(string, address)
- 傳送資料,socket.sendto(string, address) 發給某地址某資訊
- 釋放資源
import socket import threading import logging import datetime FORMATchat_udp_server= "%(asctime)s %(threadName)s %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT) class ChatUdpServer: def __init__(self,ip="127.0.0.1",port=9999,interval=10): self.sock = socket.socket(type=socket.SOCK_DGRAM) self.addr = ip,port self.event = threading.Event() self.clients= {} self.interval = interval def start(self): self.sock.bind(self.addr) threading.Thread(target=self.recv,name='recv').start() def recv(self): while not self.event.is_set(): data,raddr = self.sock.recvfrom(1024) logging.info(data) localkeys= set() if data.strip() == b"^hb^": self.clients[raddr] = datetime.datetime.now().timestamp() continue if data.strip() == b"quit": if raddr in self.clients: self.clients.pop(raddr) logging.info(("quit__+_++++")) continue self.clients[raddr] = datetime.datetime.now().timestamp() #add client current = datetime.datetime.now().timestamp() msg = "{} . form {}:{}".format(data.decode(),*raddr).encode() for r,ts in self.clients.items(): if current - ts > self.interval: localkeys.add(r) else: self.sock.sendto(msg,r) for k in localkeys: self.clients.pop(k) def stop(self): self.sock.close() self.event.set() def main(): cs = ChatUdpServer() cs.start() while True: cmd = input() if cmd.strip() == "quit": logging.info("quit") break logging.info(threading.enumerate()) print(cs.clients) if __name__ == '__main__': main()
UDP客戶端程式設計流程
- 建立socket物件。socket.SOCK_DGRAM
- 傳送資料,socket.sendto(string, address) 發給某地址某資訊
- 接收資料,socket.recvfrom(bufsize[, flags]),獲得一個二元組(string, address)
- 釋放資源
bind方法 | 可以指定本地地址和埠laddr,會立即佔用 |
connect方法 | 可以立即佔用本地地址和埠laddr,填充遠端地址和埠raddr |
sendto方法 | 可以立即佔用本地地址和埠laddr,並把資料發往指定遠端。只有有了本地繫結埠,sendto就可以向任何遠端傳送資料 |
send方法 | 需要和connect方法配合,可以使用已經從本地埠把資料發往raddr指定的遠端 |
recv方法 | 要求一定要在佔用了本地埠後,返回接收的資料 |
recvfrom方法 | 要求一定要佔用了本地埠後,返回接收的資料和對端地址的二元組 |
import threading import socket import logging,datetime FORMAT = "%(asctime)s %(threadName)s %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT) class ChatUdpServer: def __init__(self,ip="127.0.0.1",port=9999,interval=10): self.sock = socket.socket(type=socket.SOCK_DGRAM) self.event = threading.Event() self.clients = {} self.addr = ip,port self.interval = interval #超時移除對應的客戶端,超時時間,預設10s def start(self): self.sock.bind(self.addr) threading.Thread(target=self.recv,name="recv").start() def recv(self): logging.info("start now") while not self.event.is_set(): clientset = set() try: data,raddr = self.sock.recvfrom(1024) except Exception as e: logging.error(e) break if data.strip() == b"^hb^": self.clients[raddr] = datetime.datetime.now().timestamp() #logging.info("hb^^^^^^^^^^^") continue if data.strip() == b"quit" or b"": self.clients.pop(raddr) logging.info("{} logout".format(raddr)) continue self.clients[raddr] = datetime.datetime.now().timestamp() logging.info(data) current = datetime.datetime.now().timestamp() for r,t in self.clients.items(): if current - t > self.interval: clientset.add(r) else: self.sock.sendto(data,r) for i in clientset: self.clients.pop(i) def stop(self): for c in self.clients.keys(): self.sock.sendto(b"bye",c) self.sock.close() self.event.set() def main(): cs = ChatUdpServer() cs.start() while True: cmd = input(">>>") if cmd.strip() == "quit": cs.stop() threading.Event().wait(3) break logging.info(cs.clients) if __name__ == '__main__': main()chat_udp_server
使用心跳包實現客戶端
import socket import threading import logging FORMAT = "%(asctime)s %(threadName)s %(message)s" logging.basicConfig(level=logging.INFO, format=FORMAT) class ChatUdpClient: def __init__(self, ip="127.0.0.1", port=9999): self.sock = socket.socket(type=socket.SOCK_DGRAM) self.raddr = ip, port self.event = threading.Event() def start(self): self.sock.connect(self.raddr) threading.Thread(target=self._heratbeat, name="hb", daemon=True).start() threading.Thread(target=self.recv, name="recv").start() print(self.sock) def recv(self): while not self.event.is_set(): try: data, raddr = self.sock.recvfrom(1024) except Exception as e: logging.error(e) break logging.info(data) logging.info(raddr) def _heratbeat(self): while not self.event.wait(5): self.send("^hb^") def send(self, msg): msg = "{}".format(msg).encode() self.sock.sendto(msg, self.raddr) def stop(self): self.sock.close() self.event.set() def main(): cc = ChatUdpClient() cc.start() while True: cmd = input() if cmd.strip() == "quit": cc.stop() logging.info("quit") threading.Event().wait(3) break if cmd == "th": logging.info(threading.enumerate()) continue cc.send(cmd) if __name__ == '__main__': main()chat_udp_client
UDP協議應用 UDP是無連線協議,基於以下假設 區域網路由好,訊息不會丟失,包不會亂序 但是,實際上都不一定可以保證 應用場景:
視訊、音訊傳輸,一般來說,丟些包,問題不大,最多丟些影象、聽不清話語,可以重新發話語來解決。
海量採集資料,例如感測器發來的資料,丟幾十、幾百條資料也沒有關係。
DNS協議,資料內容小,一個包就能查詢到結果,不存在亂序,丟包,重新請求解析。
一般來說,UDP效能優於TCP,但是可靠性要求高的場合的還是要選擇TCP協議。