>netstat -anp tcp | find "9998" #win下
#echo "123abc" | nc -u 127.0.0.1 9998
应用:
无连接协议,基于如下假设:网络足够好、消息不会丢包、包不会乱序;
1、音频、视频传输,一般丢些包,问题不大,最多丢些图像、听不清说话,再次说话即可;
2、海量采集数据,如传感器发来的数据,丢几十、几百条数据也没关系;
3、DNS协议,数据内容小,一个包就能查询到结果,不存在乱序、丢包,重新请求解析;
注:
即使在LAN,也不能保证不丢包,且包的到达不一定有序;
一般来说,UDP性能优于TCP,但可靠性要求高的场合还是选择用TCP;
QUIC,quick udp internet connection,google,是谷歌制定的一种基于UDP的低时延的互联网传输层协议。在2016年11月国际互联网工程任务组(IETF)召开了第一次QUIC工作组会议,受到了业界的广泛关注。这也意味着QUIC开始了它的标准化过程,成为新一代传输层协议;
创建socket对象,sock=socket.socket(type=socket.SOCK_DGRAM);
绑定ip和port,bind()方法;
传输数据:
recvfrom(bufsize[,flags]),接收数据,获取一个二元组(string,address);
sendto(string,address),发送数据,发送某信息给某地址;
释放资源;
例:
import socket
sock = socket.socket(type=socket.SOCK_DGRAM)
addr = ('127.0.0.1', 9998)
sock.bind(addr)
data, clientaddr = sock.recvfrom(1024)
print(clientaddr)
msg = 'ack: {}'.format(data.decode())
sock.sendto(msg.encode(), clientaddr)
输出:
('127.0.0.1', 9999)
例,ChatServerUdp:
ver1:
class ChatServerUdp:
def __init__(self, ip='127.0.0.1', port=9998):
self.sock = socket.socket(type=socket.SOCK_DGRAM)
self.addr = (ip, port)
self.event = threading.Event()
self.clients = set() #集合,去重,client主动退出后要清此数据结构
def start(self):
self.sock.bind(self.addr)
threading.Thread(target=self._recv, name='recv').start()
def stop(self):
for c in self.clients: #业务中udp的server关闭时不会通知client
self.sock.sendto(b'end', c)
self.sock.close() #udp的socket关闭很快,不会有很多垃圾
self.event.set()
def _recv(self): #_recv中使用多线程场景,在一对多情况下,server发送消息和接收消息出现不匹配时,用另一线程单独处理发送数据,否则接收和发送是同步,只有等发送完才能继续再次接收
while not self.event.is_set():
data, client = self.sock.recvfrom(1024)
data = data.strip().decode()
if data == 'quit':
self.clients.remove(client)
continue #关键,接收下个client的消息
self.clients.add(client)
print(self.clients)
msg = 'ack: {}'.format(data)
for c in self.clients:
self.sock.sendto(msg.encode(), c)
if __name__ == '__main__':
cs = ChatServerUdp()
cs.start()
myutils.show_threads()
例:
ver2:
增加ack和heartbeat机制;
心跳即一端定时发往另一端信息,一般每次发的数据越少越好,心跳时间间隔约定好就行,ack响应,一端收到另一端的消息后返回的信息;
心跳包设计:
c主动,一般由client发hb-->server,server并不需要发ack-->client,只需要记录client还活着就行;
s主动,server发hb扫一遍client,一般需要client发ack响应来表示活着,server没收到ack就断开与client连接,server移除其信息,这种实现较为复杂,用的少;
c-s双向,用的更少;
class ChatServerUdp:
def __init__(self, ip='127.0.0.1', port=9998, 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 stop(self):
for c in self.clients:
self.sock.sendto(b'end', c)
self.sock.close()
self.event.set()
def _recv(self):
while not self.event.is_set():
lostset = set()
data, client = self.sock.recvfrom(1024)
data = data.strip().decode()
current = datetime.datetime.now().timestamp()
if data == '^hb^' or data == 'reg':
print('hb')
self.clients[client] = current
continue
elif data == 'quit':
self.clients.pop(client, None)
logging.info('{} leaving'.format(client))
continue
self.clients[client] = current
print(self.clients)
msg = 'ack: {} {}\n{}\n'.format(*client, data)
logging.info(msg)
for c, stamp in self.clients.items():
if current - stamp > self.interval:
lostset.add(c)
else:
self.sock.sendto(msg.encode(), c)
for c in lostset:
self.clients.pop(c)
if __name__ == '__main__':
cs = ChatServerUdp()
cs.start()
myutils.show_threads()
创建socket对象,socket.socket(type=socket.SOCK_DGRAM);
发送数据:sendto(string,address),发送某信息给某地址;
释放资源;
udp客户端编程中,只能在sendto()后,才能recvfrom(),否则OSError;
例:
sock = socket.socket(type=socket.SOCK_DGRAM)
addr = ('127.0.0.1', 9998)
data = 'test_data'.encode()
sock.sendto(data, addr) #方式1,使用sendto()和recvfrom(),建议用此种方式
data, saddr = sock.recvfrom(1024) #也可用recv(),只不过不知道谁发的消息了
print(data, saddr)
sock.close()
# sock.connect(addr) #方式2,用connect()连接后才能用send()或sendto(),没有connect()连接只能用sendto();此方式可能会有问题,client-->server正常,server-->client,server上连client的端口不对了
# sock.send(data)
# data, saddr = sock.recvfrom(1024)
# print(data, saddr)
# sock.close()
例:
addr = ('127.0.0.1', 9998)
event = threading.Event()
def recv1(sock:socket.socket, event:threading.Event):
while not event.is_set():
data, saddr = sock.recvfrom(1024)
logging.info('recv: {} ; from: {}'.format(data, saddr))
sock1 = socket.socket(type=socket.SOCK_DGRAM)
sock1.sendto('udp client1 send'.encode(), addr)
threading.Thread(target=recv1, args=(sock1, event)).start() #recvfrom()必须在sendto()或connect()之后,否则OSError,即recvfrom()操作之前应该先sendto()或connect();如果用connect(),则远端必须有服务
def recv2(sock:socket.socket, event:threading.Event):
while not event.is_set():
data, saddr2 = sock.recvfrom(1024)
logging.info('recv: {} ; from: {}'.format(data, saddr2))
sock2 = socket.socket(type=socket.SOCK_DGRAM)
sock2.connect(addr)
threading.Thread(target=recv2, args=(sock2, event)).start()
threading.Event().wait(5)
sock2.sendto('udp client2 send'.encode(), addr)
event.wait(2)
sock2.send('udp client2.1 send'.encode())
while True:
if input('>>> ').strip() == 'quit':
sock1.close()
sock2.close()
event.wait(3)
break
logging.info('end')
例,ChatClientUdp:
注:此代码有问题,在发送hb后一直阻塞
class ChatClientUdp:
def __init__(self, ip='127.0.0.1', port=9998, interval=5):
self.sock = socket.socket(type=socket.SOCK_DGRAM)
self.addr = (ip, port)
self.event = threading.Event()
self.interval = interval
self.sock.connect(self.addr)
self._sendhb()
def start(self):
# self.sock.send(b'reg')
threading.Thread(target=self._sendhb, name='hb', daemon=True).start()
# threading.Thread(target=self._recv, name='recv').start()
# self._recv()
def stop(self):
self.send()
self.sock.close()
self.event.wait(2)
self.event.set()
def _sendhb(self):
while not self.event.wait(5):
self.sock.sendto(b'^hb^', self.addr)
def send(self, msg:str='quit'):
self.sock.sendto(msg.encode(), self.addr)
def _recv(self):
while not self.event.is_set():
data, addr = self.sock.recvfrom(1024)
logging.info('recv {} from {}'.format(data, addr))
cc = ChatClientUdp()
cc.start()
while True:
data = input('plz input string>>> ')
if data == 'quit':
cc.stop()
break
else:
cc.send(data)
logging.info('end')
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。