UDP服務端&客戶端編程
''' udp編程 創建socket對象,socket.SOCK_DGRAM 綁定ip和port,bind()方法 傳輸數據 1.接收數據,socket.recvfrom(bufsize[,flags]),獲得一個2元祖(string,address) 2.發送數據,socket.sendto(string,address) ,發送給某地址信息 釋放資源 ''' import socket server = socket.socket(type=socket.SOCK_DGRAM) server.bind(('0.0.0.0',9999)) data = server.recv(1024) #阻塞等待數據 data = server.recvfrom(1024) #阻塞等待數據(value,(ip,port)) server.sendto(b'hello',('127.0.0.1',10000)) server.close() ''' udp客戶端編程流程 創建socket對象,socket.SOCK_DGRAM 發送數據,socket.sendto(string,address)發送給某地址信息 接收數據,socket.recvfrom(bufsize[,flags]),獲取一個2元祖(string,address) 釋放資源 ''' client = socket.socket(type=socket.SOCK_DGRAM) raddr = ('127.0.0.1',10000) client.connect(raddr) client.sendto(b'hello',raddr) data = client.recv(1024) #阻塞等待數據 data = client.recvfrom(1024)#阻塞等待數據,(value,(ip,port)) client.close()
注意:udp時無連接協議,所以可以只有任何一端,例如客戶端數據發往服務端,服務端存在與否不重要 udp的socket對象創建后,時沒有占用本地地址和端口的 bind() 可以指定本地地址和端口laddr,會立即占用 connect() 可以立即占用本地地址和端口,填充遠端地址和端口raddr sendto() 可以立即占用本地地址和端口,並把數據發往指定遠端,只有有了本地綁定端口,sendto就可以向任何遠端發送數據 send() 需要和connect()配合使用,可以使用已經從本地端口把數據發往raddr指定的遠端 recv() 要求一定要在占用可本地端口后,返回接收的數據 recvfrom() 要求一定要占用了本地端口后,返回接收數據和對端地址的二元組
udp聊天server
import threading import socket import logging FORMAT = '%(asctime)s,%(threadName)s %(thread)d,%(message)s' logging.basicConfig(level=logging.INFO,format=FORMAT) class ChatUDPServer: def __init__(self,ip='127.0.0.1',port=9999): self.addr = (ip,port) self.sock = socket.socket(type=socket.SOCK_DGRAM) self.event = threading.Event() self.clients = set() def start(self): self.sock.bind(self.addr) threading.Thread(target=self.receive,name='receive').start() def receive(self): while not self.event.is_set(): data,raddr= self.sock.recvfrom(1024) print(data) if data.strip() == b'quit': if raddr in self.clients: self.clients.remove(raddr) logging.info('remove leave clients') # self.sock.close() 面向無連接的 所以每天udp產生的時候不需要close continue self.clients.add(raddr) for i in self.clients: self.sock.sendto('ack {}'.format(data).encode(),i) def stop(self): for i in self.clients: self.sock.sendto(b'bye bye',i) self.sock.close() self.event.set() def main(): cs = ChatUDPServer() cs.start() while True: cmd = input("please set stop command>>>>>>") if cmd == 'quit': cs.stop() break logging.info(cs.clients) logging.info(threading.enumerate()) if __name__ == '__main__': main()
心跳機制:客戶端定時往服務端發送的,服務端不需要ack回復,只記錄客戶端存活
class ChatUDPServer: def __init__(self,ip='127.0.0.1',port=9999,interval=10): self.addr = (ip,port) self.sock = socket.socket(type=socket.SOCK_DGRAM) self.event = threading.Event() self.clients = {} self.interval = interval def start(self): self.sock.bind(self.addr) threading.Thread(target=self.receive,name='receive').start() def receive(self): while not self.event.is_set(): localset = set() #迭代字典時不能操作字典,把超時的放在集合里面 data,raddr= self.sock.recvfrom(1024) current = datetime.datetime.now().timestamp() #return float if data.strip == b'^hb^': #從client接收到指定的字符串,做判斷 print('~~~~~~~~',raddr) self.clients[raddr] = current continue elif data.strip() == b'quit': if raddr in self.clients: self.clients.pop(raddr,None) logging.info('remove leave clients') # self.sock.close() 面向無連接的 所以不需要close continue self.clients[raddr] = current for c,stamp in self.clients.items(): if current - stamp > self.interval: localset.add(c) else: self.sock.sendto('ack {}'.format(data).encode(), i) for i in localset: localset.pop(i) def stop(self): for i in self.clients: self.sock.sendto(b'bye bye',i) self.sock.close() self.event.set()
client端的更改
def start(self): self.sock.connect(self.addr) self.sock.sendto(b'hello server',self.addr) threading.Thread(target=self.reveive,name='receive').start() threading.Thread(target=self._sendb,name="heartbeat",daemon=True).start()
#daemon 隨着主線程退出而退出,不用程序員關注線程退出的問題 def _sendb(self): while True: self.sock.sendto(b'^hb^',self.addr)
