1. 程式人生 > >chapter12.2、socket程式設計的UDP程式設計

chapter12.2、socket程式設計的UDP程式設計

socket的UDP程式設計和socketserver模組   UDP程式設計   UDP服務端流程
  1. UDP服務端
  2. 建立socket物件。socket.SOCK_DGRAM
  3. 繫結IP和Port,bind()方法
  4. 傳輸資料
    • 接收資料,socket.recvfrom(bufsize[, flags]),獲得一個二元組(string, address)
    • 傳送資料,socket.sendto(string, address) 發給某地址某資訊
  5. 釋放資源
import socket
import threading
import logging
import 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.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()
chat_udp_server

 

UDP客戶端程式設計流程
  1. 建立socket物件。socket.SOCK_DGRAM
  2. 傳送資料,socket.sendto(string, address) 發給某地址某資訊
  3. 接收資料,socket.recvfrom(bufsize[, flags]),獲得一個二元組(string, address)
  4. 釋放資源 
每種協議的埠都有65535個 無連線協議使用connect 只是提供了遠端地址,不是建立連線 無連線協議,可以只有任何一端 UDP的socket物件建立後,不佔用本地地址和埠    
bind方法  可以指定本地地址和埠laddr,會立即佔用
connect方法 可以立即佔用本地地址和埠laddr,填充遠端地址和埠raddr
sendto方法   可以立即佔用本地地址和埠laddr,並把資料發往指定遠端。只有有了本地繫結埠,sendto就可以向任何遠端傳送資料
send方法  需要和connect方法配合,可以使用已經從本地埠把資料發往raddr指定的遠端
recv方法  要求一定要在佔用了本地埠後,返回接收的資料
recvfrom方法 要求一定要佔用了本地埠後,返回接收的資料和對端地址的二元組
        心跳機制 心跳包越小越好 TCP也使用心跳包,隔一段時間可能會假死,心跳包是一種保活機制 可以使用這種方法:   伺服器發往客戶端,判斷是否有回覆,設定次數內沒有回覆,就判斷客戶端下線,用的較少 也可以使用,   客戶端定時傳送心跳包到服務端。記錄最後一次通訊的時間的方法,服務端知道客戶端或者就ok,不需要響應   使用心跳包實現伺服器
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協議。

udp53埠,dns伺服器的埠