Pythonは、ネットワークサービスへの2つのレベルのアクセスを提供します。 :
次のようにソケットクラスのヘルプを表示します
import socket #ソケットモジュールをインポートします
>>> help(socket.socket)
初期化機能に焦点を当てます。
__ init__(self, family=<AddressFamily.AF_INET:2>, type=<SocketKind.SOCK_STREAM:1>, proto=0, fileno=None)
SOCK_STREAM
または SOCK_DGRAM
に分類されます。以下はTCPチャットルームとUDPチャットルームです
複数の接続を処理する
受け入れスレッドを開くと、受け入れ操作の実行がブロックを開始し、クライアント接続があると、データ受信処理のために別のスレッドrecvが開かれます。その後、受け入れスレッドはブロックを続け、後続のクライアント接続を待ちます。
ブロッキング処理
サーバーがクライアント接続を処理するとき、2つの障害があります。
したがって、別々の処理のために2つのスレッドを開く必要があります。そうしないと、メインスレッドがブロックされます。
アクティブなクライアント切断の処理
クライアントがアクティブに切断しているときにサーバーに通知がない場合、サーバーに保存されているクライアント接続はクリーンアップされないため、不合理です。したがって、クライアントがアクティブに切断すると、アプリケーションレイヤーで、クライアントが起動する前にサーバーに / quit
コマンドを送信する必要があり、サーバーがソケットを閉じることに同意します。
チャットルームのサーバー側は主にポートを監視し、クライアント側からの接続を処理し、すべてのクライアント側にデータを配信します
コード
import socket
import threading
classTcpChatServer:
def __init__(self, ip='192.168.110.13', port=9001):
self.ip = ip
self.port = port
self.clients ={}
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
self.event = threading.Event()
def recv(self, so, ip ,port):while not self.event.is_set():
data = so.recv(1024).decode() #受信したバイトデータバイトをutfに変換します-8フォーマット文字列
if data.strip()=='/quit': #クライアントがアクティブに切断したときの処理
so.close()
self.clients.pop((ip, port))returnfor s in self.clients.values(): #放送
s.send('{}:{}\n{}'.format(ip, port, data).encode())
def accept(self):while not self.event.is_set():
so,(ip, port)= self.sock.accept()
self.clients[(ip, port)]= so
# そうだから.recvはブロックするため、データの受信部分を処理するために別のスレッドが開かれます。このようにして、acceptは他のクライアントからのリンクを引き続き受け入れることができます
threading.Thread(target=self.recv, args=(so, ip, port), name='client-{}:{}'.format(ip, port)).start()
def start(self):
self.sock.bind((self.ip, self.port))
self.sock.listen()
t = threading.Thread(target=self.accept, daemon=True) #メインスレッドをブロックしないようにするには、acceptを処理するために別のスレッドを開きます(acceptはスレッドをブロックします)
try:
t.start()
t.join() #KeyboardInterruptが取得されるまでブロックする
except KeyboardInterrupt:
self.stop()
def stop(self):for s in self.clients.values():
s.close()
self.sock.close()
self.event.set() #すべてのループを停止します
if __name__ =='__main__':
tcp_chat_server =TcpChatServer()
tcp_chat_server.start()
チャットルームのクライアント側は、主に接続を開始し、サーバー側に接続し、サーバー側からブロードキャストおよび配信されたメッセージを受け入れます。
コード
import socket
import threading
classTcpChatClient:
def __init__(self):
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
self.event = threading.Event()
def recv(self): #クライアントは、サーバーによってブロードキャストおよび配信されるメッセージを常に受信する必要があります
while not self.event.is_set():
data = self.sock.recv(1024).decode()
data = data.strip()print(data)
def send(self): #メッセージを送る
while not self.event.is_set():
data =input()
self.sock.send(data.encode())if data.strip()=='/quit': #送信/終了したら自分自身を閉じる
self.stop()
def start(self, ip, port):
self.sock.connect((ip, port))
s = threading.Thread(target=self.send, daemon=False)
r = threading.Thread(target=self.recv, daemon=False)
s.start()
r.start()
def stop(self):
self.sock.close()
self.event.set()if __name__ =='__main__':
tcp_chat_client =TcpChatClient()
tcp_chat_client.start('192.168.110.13',9001)
ブロッキング処理
UDPサーバーがクライアントのメッセージを受信すると、 socket.recvfrom(1024)
メソッドを使用してクライアントのアドレス情報を保存します。このメソッドは現在のスレッドをブロックするため、別の処理のためにスレッドを開く必要があります。
アクティブなクライアント切断の処理
UDPクライアントがアクティブに閉じられた後、サーバーはクライアントが閉じられたことを検出できません。次の2つの方法を使用できます。
UDPサーバープログラムは、クライアントのデータの受信を待機しているスレッドを開始し、それを他のクライアントにブロードキャストして、すべての接続のハートビートがタイムアウトしたかどうかを確認します。
コード
import socket
import datetime
import threading
classUdpChatServer:
def __init__(self, ip='192.168.110.13', port=9001):
self.addr =(ip, port)
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
self.clients ={}
self.event = threading.Event()
def recv(self):while not self.event.is_set():
data, addr = self.sock.recvfrom(1024)
data = data.decode().strip()
now = datetime.datetime.now()if data =='#ping#': #ハートビートを受信するかどうかを決定します
self.clients[addr]= now #ハートビートを受信したら、クライアントアドレスを保存し、タイムスタンプを更新します
continue
disconnected =set() #一度データを受信せずにすべての壊れたリンクを判断する
for addr, timestamp in self.clients.items():if(now - timestamp).total_seconds()>10: #障害状態:2回(つまり、10秒)、クライアントはハートビートを受信せずに閉じていると判断されます
disconnected.add(addr)else:
self.sock.sendto('{}:{}\n{}'.format(addr[0], addr[1], data).encode(), addr)for addr in disconnected:
self.clients.pop(addr)
def start(self):
self.sock.bind(self.addr) #ポートをバインドした後、スレッドを開始し、常にクライアントのデータを受け入れます
t = threading.Thread(target=self.recv(), daemon=True)try:
t.start()
t.join()
except KeyboardInterrupt:
self.stop()
def stop(self):
self.event.set()
self.sock.close()if __name__ =='__main__':
udp_chat_server =UdpChatServer()
udp_chat_server.start()
UDPクライアントのメインスレッドは、ユーザーがデータを入力するのを待ってからサーバーにデータを送信すると同時に、ハートビートプロセスとサーバーからブロードキャストデータを受信するためのスレッドが開始されます。
コード
import socket
import threading
import time
classUdpChatClient:
def __init__(self, ip, port):
self.addr =(ip, port)
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
self.event = threading.Event()
def heartbeat(self): #ハートビートスレッド機能:5秒ごとにハートビートを送信します
while not self.event.wait(5):
self.sock.sendto(b'#ping#', self.addr)
def recv(self): #udpサーバーによってブロードキャストされたデータの受信を待機しています
while not self.event.is_set():
data = self.sock.recv(1024)print(data.decode())
def start(self):
threading.Thread(target=self.heartbeat, name='heartbeat', daemon=True).start()
threading.Thread(target=self.recv, name='recv', daemon=True).start()print('5秒で話してください')
time.sleep(5) #サーバーはセカンダリクライアントを保存する前にハートビートを受信する必要があるため、5秒待つ必要があります
print('話し始めてください')while not self.event.is_set():
data =input('')
data = data.strip()if data =='/quit':
self.event.set()
self.sock.close()return
self.sock.sendto(data.encode(), self.addr)if __name__ =='__main__':
udp_chat_client =UdpChatClient('192.168.110.13',9001)
udp_chat_client.start()
TODO(Flowsnow):チャットルームプログラムのTcpChatServerとUdpChatServerを書き直します
付録1:TCPとUDPの本質的な違い
udp:クライアントから送信されたすべてのデータグラムがキューに積み上げられ、サーバーがそれらを1つずつ処理します。
tcp:各クライアントとサーバーには接続チャネルがあり、クライアントに対応するデータストリームのみを処理します
別紙2:参考資料
Recommended Posts