>netstat -anp tcp | find "9998"?? #win下
#echo "123abc" | nc -u 127.0.0.1 9998
應用:
無連接協(xié)議,基于如下假設:網(wǎng)絡足夠好、消息不會丟包、包不會亂序;
1、音頻、視頻傳輸,一般丟些包,問題不大,最多丟些圖像、聽不清說話,再次說話即可;
2、海量采集數(shù)據(jù),如傳感器發(fā)來的數(shù)據(jù),丟幾十、幾百條數(shù)據(jù)也沒關系;
3、DNS協(xié)議,數(shù)據(jù)內容小,一個包就能查詢到結果,不存在亂序、丟包,重新請求解析;
注:
即使在LAN,也不能保證不丟包,且包的到達不一定有序;
一般來說,UDP性能優(yōu)于TCP,但可靠性要求高的場合還是選擇用TCP;
QUIC,quick udp internet connection,google,是谷歌制定的一種基于UDP的低時延的互聯(lián)網(wǎng)傳輸層協(xié)議。在2016年11月國際互聯(lián)網(wǎng)工程任務組(IETF)召開了第一次QUIC工作組會議,受到了業(yè)界的廣泛關注。這也意味著QUIC開始了它的標準化過程,成為新一代傳輸層協(xié)議;
創(chuàng)建socket對象,sock=socket.socket(type=socket.SOCK_DGRAM);
綁定ip和port,bind()方法;
傳輸數(shù)據(jù):
recvfrom(bufsize[,flags]),接收數(shù)據(jù),獲取一個二元組(string,address);
sendto(string,address),發(fā)送數(shù)據(jù),發(fā)送某信息給某地址;
釋放資源;
例:
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主動退出后要清此數(shù)據(jù)結構
def start(self):
self.sock.bind(self.addr)
threading.Thread(target=self._recv, name='recv').start()
def stop(self):
for c in self.clients:?? #業(yè)務中udp的server關閉時不會通知client
self.sock.sendto(b'end', c)
self.sock.close()?? #udp的socket關閉很快,不會有很多垃圾
self.event.set()
def _recv(self):?? #_recv中使用多線程場景,在一對多情況下,server發(fā)送消息和接收消息出現(xiàn)不匹配時,用另一線程單獨處理發(fā)送數(shù)據(jù),否則接收和發(fā)送是同步,只有等發(fā)送完才能繼續(xù)再次接收
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機制;
心跳即一端定時發(fā)往另一端信息,一般每次發(fā)的數(shù)據(jù)越少越好,心跳時間間隔約定好就行,ack響應,一端收到另一端的消息后返回的信息;
心跳包設計:
c主動,一般由client發(fā)hb-->server,server并不需要發(fā)ack-->client,只需要記錄client還活著就行;
s主動,server發(fā)hb掃一遍client,一般需要client發(fā)ack響應來表示活著,server沒收到ack就斷開與client連接,server移除其信息,這種實現(xiàn)較為復雜,用的少;
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()
創(chuàng)建socket對象,socket.socket(type=socket.SOCK_DGRAM);
發(fā)送數(shù)據(jù):sendto(string,address),發(fā)送某信息給某地址;
釋放資源;
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(),只不過不知道誰發(fā)的消息了
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:
注:此代碼有問題,在發(fā)送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')
另外有需要云服務器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內外云服務器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務器、裸金屬服務器、高防服務器、香港服務器、美國服務器、虛擬主機、免備案服務器”等云主機租用服務以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應用場景需求。