Pythonネットワークプログラミング

Pythonは、ネットワークサービスへの2つのレベルのアクセスを提供します。 :

socket

次のようにソケットクラスのヘルプを表示します

import socket  #ソケットモジュールをインポートします
>>> help(socket.socket)

初期化機能に焦点を当てます。

__ init__(self, family=<AddressFamily.AF_INET:2>, type=<SocketKind.SOCK_STREAM:1>, proto=0, fileno=None)

以下はTCPチャットルームとUDPチャットルームです

TCPチャットルーム##

アウトラインデザイン###

複数の接続を処理する

受け入れスレッドを開くと、受け入れ操作の実行がブロックを開始し、クライアント接続があると、データ受信処理のために別のスレッドrecvが開かれます。その後、受け入れスレッドはブロックを続け、後続のクライアント接続を待ちます。

ブロッキング処理

サーバーがクライアント接続を処理するとき、2つの障害があります。

したがって、別々の処理のために2つのスレッドを開く必要があります。そうしないと、メインスレッドがブロックされます。

アクティブなクライアント切断の処理

クライアントがアクティブに切断しているときにサーバーに通知がない場合、サーバーに保存されているクライアント接続はクリーンアップされないため、不合理です。したがって、クライアントがアクティブに切断すると、アプリケーションレイヤーで、クライアントが起動する前にサーバーに / quitコマンドを送信する必要があり、サーバーがソケットを閉じることに同意します。

TCPチャットルーム-サーバー###

チャットルームのサーバー側は主にポートを監視し、クライアント側からの接続を処理し、すべてのクライアント側にデータを配信します

コード

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()

TCPチャットルーム-クライアント###

チャットルームのクライアント側は、主に接続を開始し、サーバー側に接続し、サーバー側からブロードキャストおよび配信されたメッセージを受け入れます。

コード

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チャットルーム##

アウトラインデザイン###

ブロッキング処理

UDPサーバーがクライアントのメッセージを受信すると、 socket.recvfrom(1024)メソッドを使用してクライアントのアドレス情報を保存します。このメソッドは現在のスレッドをブロックするため、別の処理のためにスレッドを開く必要があります。

アクティブなクライアント切断の処理

UDPクライアントがアクティブに閉じられた後、サーバーはクライアントが閉じられたことを検出できません。次の2つの方法を使用できます。

  1. TCPが合意された終了命令を採用する方法と類似している場合、クライアントは終了命令を送信した後にcloseメソッドを呼び出し、サーバーは受信した命令に従ってクライアント辞書内の対応するクライアントを削除します。
  2. クライアントを介して定期的にハートビートをサーバーに送信することもできます。サーバーはハートビートを使用して、クライアントプロセスが動作しているかどうかを判断します。

UDPチャットルーム-サーバー###

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チャットルーム-クライアント###

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()

SocketServer

TODO(Flowsnow):チャットルームプログラムのTcpChatServerとUdpChatServerを書き直します

付録1:TCPとUDPの本質的な違い

別紙2:参考資料

  1. socketserver — A framework for network servers

Recommended Posts

Pythonネットワークプログラミング
詳細なPythonIOプログラミング
PythonGUIインターフェイスプログラミング
Python機能プログラミングについて話す
GooglePythonプログラミングスタイルガイド
一般的に使用されるPython3スクリプトプログラミング.md
Pythonオブジェクト指向プログラミングの分析
XTUプログラミングPythonトレーニング3
ブラックハットプログラミングアプリケーションPython2
Python1のブラックハットプログラミングアプリケーション
pythonオブジェクト指向プログラミングを理解する方法
Pythonの古典的なプログラミングの質問:文字列の置換
Pythonマルチスレッド
Python CookBook
Python FAQ
Python3辞書
python(you-get)
Python文字列
Pythonの基本
Python記述子
Pythonの基本2
プログラミングの基礎なしでpythonを学ぶことはできますか
Python exec
Pythonノート
Python3タプル
CentOS + Python3.6 +
Python Advanced(1)
Python IO
Pythonマルチスレッド
Pythonツールチェーン
Python3リスト
Pythonマルチタスク-日常
Pythonの概要
pythonの紹介
Pythonアナリティック
Pythonの基本
07.Python3関数
Pythonの基本3
Pythonマルチタスクスレッド
Python関数
Pythonマルチプロセスプログラミングの一般的な方法の分析
python sys.stdout
python演算子
Pythonエントリ-3
Centos 7.5 python3.6
Python文字列
pythonキューキュー
Pythonの基本4
Pythonの基本5