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)