本文共 6248 字,大约阅读时间需要 20 分钟。
import socketimport threadingevent = threading.Event()address = '0.0.0.0', 9999server = socket.socket(type=socket.SOCK_DGRAM) # 数据报协议server.bind(address) # 只能绑定一次while not event.is_set(): data, client_info = server.recvfrom(1024) # 比recv安全,可以知道是谁发给你的 print(data) # print(server.getpeername()) # 会报错OSError msg = "{} from {}-{}".format(data.decode(), *client_info).encode() # server.send(msg) # 会报错,不知道发送给谁 server.sendto(msg, client_info) print('~' * 30)event.set()server.close()
import socketaddress = '127.0.0.1', 10001client = socket.socket(type=socket.SOCK_DGRAM) # 数据报协议client.connect(address) # 会解决本地address和远端地址addressprint(1, client)print(1.5, client)ms = b'111222333444555'# client.sendto(ms + b'~~~~~~~', ('127.0.0.1', 10000))# 会帮你抢一个本地地址和端口(端口是临时的),没有此句,recvfrom会报错# 可以自己玩,因为它有本地address, 它不会记录远端地址addressclient.send(ms) # 必须和connect配合使用,什么都不解决print(2, client)data, info = client.recvfrom(1024) # 它需要本地addressprint(data, info)# client.connect(address) # 加了这一句,send就可以用了# while True:# cmd = input(">>>").strip()# ms = cmd.encode()# # client.sendto(ms, address)# client.send(ms) # 此句必须和connect配合使用# client.sendto(ms + b'~~~~~~~', ('127.0.0.1', 10000))# client.sendto(ms + b'+++++++', ('127.0.0.1', 10001))# data, info = client.recvfrom(1024) # 比recv安全,可以知道是谁发给你的# print(data)# msg = "{} from {}".format(data.decode(), *info).encode()## print('~' * 30)client.close()
注意:UDP是无连接协议,所有可以只有任何一端,例如客户端数据发往服务端,服务端存在与否无所谓
对udp编程常用方法的几点说明:send方法必须和connect方法配合使用,否则直接报错;recvfrom比recv要安全,recvfrom知道是谁发送信息给你的,它需要知道本地的address;sendto会抢一个本地的地址和端口(端口是临时的),它可以自己玩,因为它有本地的地址,它不会记录远端地址;connect会解决本地地址和远端地址。
import socketimport datetimeimport loggingimport threadingFORMAT = "%(threadName)s %(thread)d %(message)s"logging.basicConfig(format=FORMAT, level=logging.INFO)class ChatServerUdp: # UDP群聊用的都是同一个socket,所以用字典浪费了,所有的value值都是一样的,列表可以,但是移除的话,效率低,所以考虑用集合 # 但是添加了过期删除了client的话,集合就不合适了,此时还是要用字典 def __init__(self, ip='127.0.0.1', port=9999, interval=10): # 服务端的时间间隔一般是客户端的时间间隔的2到3倍 self.sock = socket.socket(type=socket.SOCK_DGRAM) # 数据报文协议 self.address = ip, port self.event = threading.Event() # self.clients = set() self.clients = {} self.interval = interval def start(self): self.sock.bind(self.address) threading.Thread(target=self.rec, name='rec').start() def rec(self): while not self.event.is_set(): data, client_info = self.sock.recvfrom(1024) current = datetime.datetime.now().timestamp() # float # self.clients.add(client_info) if data.strip() == b'^hb^': # b'^hb^'为自己设计的 self.clients[client_info] = current logging.info('{} hb^^^^^'.format(client_info)) continue if data.strip() == b'quit': # self.clients.remove(client_info) # 注意remove相当于是按照key查找的,因为集合的值可以看做字典的key,所以比列表高效 self.clients.pop(client_info) # 客户端主动断开连接,就把该客户的ip从字典中删除 logging.info("{} leaving".format(client_info)) continue # 不能用break,因为总共只有一个线程,break了,while循环结束了 self.clients[client_info] = current # 在该位子遍历字典,删除过期的clients,比较耗时,因为如果一个都没有删除,每次都要遍历字典,会很耗时,可以考虑在发送信息时, # 遍历字典判断是否超时 logging.info(data) msg = "{} [{}:{}] {}".format(datetime.datetime.now(), *client_info, data.decode()) keys = set() for c, stamp in self.clients.items(): # 有线程安全问题,解决方法是加锁 if current - stamp < 0 or current - stamp > self.interval: # 小于0应该是时间出问题了 keys.add(c) # 不能直接self.clients.pop(c),因为字典在遍历的过程中,其长度不能改变 else: self.sock.sendto(msg.encode(), c) for c in keys: self.clients.pop(c) def stop(self): self.event.set() self.clients.clear() self.sock.close()csu = ChatServerUdp()csu.start()while True: cmd = input('>>>').strip() if cmd == 'quit': csu.stop() break logging.info(threading.enumerate())
import socketimport loggingimport threadingFORMAT = "%(threadName)s %(thread)d %(message)s"logging.basicConfig(format=FORMAT, level=logging.INFO)class ChatClientUdp: def __init__(self, ip='127.0.0.1', port=9999, interval=5): self.sock = socket.socket(type=socket.SOCK_DGRAM) self.r_address = ip, port self.event = threading.Event() self.interval = interval def start(self): self.sock.connect(self.r_address) self.send('{} hello'.format(self.sock.getsockname())) threading.Thread(target=self.heart_beat, name='heartbeat', daemon=True).start() threading.Thread(target=self.rec, name='rec').start() def heart_beat(self): while not self.event.wait(self.interval): # self.sock.send(b'^hb^') # 发送心跳包,记录最后一次发送的时间,此句比较浪费时间,换成下面的语句 self.send('^hb^') def rec(self): while not self.event.is_set(): data = self.sock.recv(1024) logging.info(data) def send(self, msg: str): self.sock.sendto(msg.encode(), self.r_address) def stop(self): self.event.set() self.send('quit') self.sock.close()ccu = ChatClientUdp()ccu.start()while True: line = input('>>>').strip() if line == 'quit': ccu.stop() break ccu.send(line) logging.info(threading.enumerate())
心跳机制:
UDP协议是无连接协议,它基于以下假设:(User Datagram Protocol)
但是,即使是局域网,也不能保证不丢包,而且包的到达不一定有序。
应用场景:视频、音频传输,一般来说,丢些包,问题不大,最多丢些图像,听不清话语,可以重新发话语来 解决。海量采集数据,例如传感器发来的数据,丢几十、几百条数据也没有关系。DNS协议,数据内容小,一个包就能查询到结果,不存在乱序,丢包,重新请求解析。一般来说,UDP性能优于TCP,但是可靠性要求高的场合还是要选择TCP协议。
转载地址:http://lpfvi.baihongyu.com/