1. UDPプログラミング
1.1 UDPプログラミングプロセス
1.1.1 UDPサーバサイドのプログラミングプロセス
- ソケットオブジェクト、socket.SOCK_DGRAMを作成します。
- バインドIPとポート、バインド()メソッド
- データ伝送:データ受信、socket.recvfrom(bufsizeは[、フラグ])、タプル(列アドレス)を得るために、アドレスにメッセージを送信し、送信データ、socket.sendto(文字列、アドレス)
- リリースリソース
import socket
import threading
event = threading.Event()
address = '0.0.0.0', 9999
server = 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()
1.1.2 UDPクライアント・プログラミング・プロセス
- ソケットオブジェクト、socket.SOCK_DGRAMを作成します。
- 送信データ、socket.sendto(文字列、アドレス)のアドレス情報を送信します
- 受信データ、socket.recvfromタプル(列アドレス)を取得するために、([、フラグ] bufsizeは)
- リリースリソース
import socket
address = '127.0.0.1', 10001
client = socket.socket(type=socket.SOCK_DGRAM) # 数据报协议
client.connect(address) # 会解决本地address和远端地址address
print(1, client)
print(1.5, client)
ms = b'111222333444555'
# client.sendto(ms + b'~~~~~~~', ('127.0.0.1', 10000))
# 会帮你抢一个本地地址和端口(端口是临时的),没有此句,recvfrom会报错
# 可以自己玩,因为它有本地address, 它不会记录远端地址address
client.send(ms) # 必须和connect配合使用,什么都不解决
print(2, client)
data, info = client.recvfrom(1024) # 它需要本地address
print(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はコネクションレスプロトコルであり、すべての両端にのみ存在してもよく、例えば、サーバに送信され、サーバは、クライアントデータが存在するか否かの問題ではありません。注意してください
一般的に使用される1.2 UDPのプログラミング方法
プログラミングUDPの一般的な方法のためのいくつかの説明:直接またはエラーを使用してメソッドを接続する必要があります方法を送信、recvのより安全なのrecvfrom、のrecvfromがあなたに情報を送った人知っているが、それはローカルアドレスを知っている必要があります;のsendtoをつかむだろうローカルアドレスとポート(ポートは一時的なものである)、それはローカルアドレスを持っているので、それは、自分自身を再生することができ、それがリモートアドレスを記録しません。ローカルアドレスとリモートアドレスを解決接続します。
1.3 UDPプログラミンググループチャット
グループチャットサーバーコードの1.3.1 UDPバージョン
import socket
import datetime
import logging
import threading
FORMAT = "%(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())
1.3.2グループチャットクライアントコードのUDPバージョン
import socket
import logging
import threading
FORMAT = "%(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())
ハートビートメカニズム:
- クライアントがACKを必要とすることが一般的に定期的にサーバーへのクライアントは、サーバはクライアントの記録がそれにまだ生きている限り、応答しません
- サーバーがクライアントに送信タイミングされている場合は、一般的な必要性は、クライアントがackを受信しない場合、サーバはその情報を削除し、生きているACK応答でクライアントを表現します。この実装は、以下で、より複雑です。
- また、双方向のハートビート、あまり使用さてきたことができます
1.4 UDPプロトコルアプリケーション
(ユーザデータグラムプロトコル):UDPプロトコルは、以下の仮定に基づいているコネクションレスプロトコルであります
- インターネットは十分です
- メッセージの損失ではないでしょう
- パケットは順不同ではありません
しかし、ローカルエリア、それはパケット損失を保証することはできませんし、パケット到着が順序である必要はありません。
シナリオ:ビデオ、オーディオ伝送、一般的には、いくつかのパケットを失うことなく、大きな問題は、これらの画像のほとんどを失った、言葉を聞いて、言葉を解決するために再送信することができます。大規模なデータ収集、例えばために送られたセンサデータは、数十人を失った、データの何百もの問題ではありません。DNSプロトコル、小さなコンテンツデータは、パケットが結果を照会することができるであろう、いかなる障害、損失、再要求解像度がありません。一般的には、UDPの性能はTCPよりも良いですが、機会の高い信頼性要件またはTCPプロトコルを選択します。