複数のクライアントとエラー処理
現実世界のアプリケーションでは、サーバーは通常、複数のクライアントを同時に処理し、さまざまなエラー状態を適切に管理する必要があります。これらの考慮事項を念頭に置いて、実装を改善しましょう。
同時クライアント処理の理解
複数のクライアントを同時に処理するには、いくつかの方法があります。
- スレッディング (Threading): 各クライアント接続に対して新しいスレッドを作成します
- プロセスベース (Process-based): 各クライアントに対して新しいプロセスを生成します
- 非同期 I/O (Asynchronous I/O): イベントループを使用して、ノンブロッキング I/O を使用します
この実験では、理解と実装が比較的簡単なスレッディングベースのアプローチを実装します。
複数のクライアントに対応するためのサーバーの強化
スレッドを使用して複数のクライアントを処理するように、サーバーを変更しましょう。
- WebIDE で
server.py ファイルを開き、コードを次のように置き換えます。
import socket
import threading
def handle_client(client_socket, client_address):
"""Handle communication with a single client"""
try:
print(f"[NEW CONNECTION] {client_address} connected.")
while True:
## Receive client data
try:
data = client_socket.recv(1024)
if not data:
break ## Client disconnected
message = data.decode()
print(f"[{client_address}] {message}")
## Send response
response = f"Message '{message}' received successfully"
client_socket.send(response.encode())
except ConnectionResetError:
print(f"[{client_address}] Connection reset by client")
break
except Exception as e:
print(f"[ERROR] {e}")
finally:
## Clean up when client disconnects
client_socket.close()
print(f"[DISCONNECTED] {client_address} disconnected")
def start_server():
"""Start the server and listen for connections"""
## Server configuration
host = '127.0.0.1'
port = 12345
## Create socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
## Set socket option to reuse address
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
## Bind to host and port
server_socket.bind((host, port))
## Listen for connections
server_socket.listen(5)
print(f"[STARTING] Server is listening on {host}:{port}")
while True:
## Accept client connection
client_socket, client_address = server_socket.accept()
## Create a new thread to handle the client
client_thread = threading.Thread(
target=handle_client,
args=(client_socket, client_address)
)
client_thread.daemon = True ## Thread will close when main program exits
client_thread.start()
## Display active connections
print(f"[ACTIVE CONNECTIONS] {threading.active_count() - 1}")
except KeyboardInterrupt:
print("\n[SHUTTING DOWN] Server is shutting down...")
except Exception as e:
print(f"[ERROR] {e}")
finally:
server_socket.close()
print("[CLOSED] Server socket closed")
if __name__ == "__main__":
start_server()
- ファイルを保存します。
エラー処理によるクライアントの改善
また、より良いエラー処理でクライアントを強化しましょう。
- WebIDE で
client.py ファイルを開き、コードを次のように置き換えます。
import socket
import sys
import time
def start_client():
"""Start a client that connects to the server"""
## Server information
host = '127.0.0.1'
port = 12345
## Create socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
## Set a timeout for connection attempts (5 seconds)
client_socket.settimeout(5)
try:
## Connect to server
print(f"[CONNECTING] Connecting to server at {host}:{port}...")
client_socket.connect((host, port))
## Reset timeout to none for regular communication
client_socket.settimeout(None)
print("[CONNECTED] Connected to server")
## Communication loop
while True:
## Get user input
message = input("\nEnter message (or 'quit' to exit): ")
if message.lower() == 'quit':
print("[CLOSING] Closing connection by request...")
break
try:
## Send message
client_socket.send(message.encode())
## Wait for response
response = client_socket.recv(1024)
print(f"[RESPONSE] {response.decode()}")
except ConnectionResetError:
print("[ERROR] Connection was reset by the server")
break
except ConnectionAbortedError:
print("[ERROR] Connection was aborted")
break
except Exception as e:
print(f"[ERROR] {e}")
break
except socket.timeout:
print("[TIMEOUT] Connection attempt timed out. Is the server running?")
except ConnectionRefusedError:
print("[REFUSED] Connection refused. Make sure the server is running.")
except KeyboardInterrupt:
print("\n[INTERRUPT] Client shutting down...")
except Exception as e:
print(f"[ERROR] {e}")
finally:
## Close socket
try:
client_socket.close()
print("[DISCONNECTED] Disconnected from server")
except:
pass
if __name__ == "__main__":
start_client()
- ファイルを保存します。
強化されたコードの理解
サーバーの強化:
- 複数のクライアントを同時に処理するために、
threading モジュールを追加しました
- 各クライアント接続は、個別のスレッドで処理されるようになりました
- より具体的な例外キャッチにより、エラー処理を改善しました
- アクティブなクライアント接続の数を表示します
- スレッドは「デーモン」として設定されており、メインプログラムが終了すると自動的に閉じられます
クライアントの強化:
- サーバーが利用できない場合にハングアップを防ぐために、接続タイムアウトを追加しました
- さまざまなネットワークエラーに対する特定の例外キャッチにより、エラー処理を改善しました
- より明確なフォーマットで、より詳細なステータスメッセージを追加しました
マルチクライアントサーバーのテスト
改善されたアプリケーションをテストしましょう。
- サーバーを起動します。
python3 ~/project/socket_lab/server.py
次のように表示されるはずです。
[STARTING] Server is listening on 127.0.0.1:12345
- 新しいターミナルで、クライアントを起動します。
python3 ~/project/socket_lab/client.py
- 3 番目のターミナルで、別のクライアントを起動します。
python3 ~/project/socket_lab/client.py
- サーバーターミナルには、両方の接続が表示されます。
[NEW CONNECTION] ('127.0.0.1', 59124) connected.
[ACTIVE CONNECTIONS] 1
[NEW CONNECTION] ('127.0.0.1', 59126) connected.
[ACTIVE CONNECTIONS] 2
- 両方のクライアントからメッセージを送信し、サーバーがそれらを受信していることを確認します。
[('127.0.0.1', 59124)] Hello from client 1
[('127.0.0.1', 59126)] Hello from client 2
- 完了したら、各クライアントで「quit」と入力して切断するか、サーバーターミナルで Ctrl+C を押してサーバーをシャットダウンします。
サーバーが実行されていない場合の処理
また、サーバーが実行されていない場合に何が起こるかをテストしましょう。
-
サーバーが停止していることを確認します (実行中の場合は Ctrl+C を押します)
-
クライアントを実行してみます。
python3 ~/project/socket_lab/client.py
次のように表示されるはずです。
[CONNECTING] Connecting to server at 127.0.0.1:12345...
[REFUSED] Connection refused. Make sure the server is running.
[DISCONNECTED] Disconnected from server
クライアントは、エラーを適切に処理し、サーバーが実行されていない可能性があることをユーザーに通知するようになりました。
これらの強化により、複数のクライアントとさまざまなエラー状態を処理できる、堅牢なクライアントサーバーシステムを作成しました。これは、より複雑なネットワークアプリケーションを開発するための確固たる基盤です。