UDP连接是一个不可靠的连接,也就是说,UDP通信过程中可能出现数据包丢失的情况,或者是服务端宕机后,客户端不知道服务端状态,仍然不停的访问服务端的情况。针对这一情况,UDP客户端必须选择一个等待时间,一旦超过这个时间间隔还未收到服务端的响应,就重新发送请求,若多次请求仍未收到服务端的响应,就认为服务端宕机。而且,由于数据丢包的主要原因在于网络拥塞,因此,客户端在等待时间内未收到服务端的应答,客户端重发不应该和上次一样,这样就会造成网络拥塞加剧,鉴于这种情况,客户端采用指数退避算法,使得重发数据包频率越来越低,逐渐的环境网络的拥塞。同时,如果客户端选择的等待时间不合理,如选择200毫秒,但是由于发送的请求可能200毫秒才能到达服务端,这时客户端在等待时间内永远都收不到服务端的应答报文,这时也需要调整等待时间。
为了模拟网络拥塞或者服务器宕机出现的丢包,在服务端使用random()随机drop客户端的请求。
服务端代码:
import socket
import random
def server(ipaddr, port):
# 开启UDP服务
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((ipaddr, port))
print('Listening at {0}'.format(s.getsockname()))
while True:
data, addr = s.recvfrom(1024)
print('From {0} Data:{1}'.format(addr, data.decode()))
info = '12345'
# 一半的概率会丢失客户端的请求
if random.random() < 0.5:
print('Drop packet from {0}'.format(addr))
continue
s.sendto(info.encode(), addr)
if __name__ == '__main__':
server('127.0.0.1', 12345)
对于客户端,调用套接字的settimeout()方法,设置等待时间。若多次请求超时,就会终止接收。
客户端代码:
import socket
def client(ipaddr, port):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 设置初始的客户端等待响应时间
delay = 0.1
while True:
send_data = input('enter info:')
s.sendto(send_data.encode(), (ipaddr, port))
print('waiting up to {0} seconds for a reply'.format(delay))
s.settimeout(delay)
try:
data, addr = s.recvfrom(1024)
print('From {0} Data:{1}'.format(addr, data.decode()))
if data:
delay = 0.1
except socket.timeout:
# 每次超时后,需要动态调整等待时间,当等待时间大于2秒时,就认为服务端宕机
delay *= 2
if delay > 2.0:
print('Server down')
break
else:
continue
if __name__ == '__main__':
client('127.0.0.1', 12345)