Open3
Online Messager実装

UDPを使ったシンプルなチャットシステムの骨格(ステップ1)
server.py
import socket
import time
from threading import Thread
class UDPServer:
def __init__(self, host='localhost', port=12345):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind((host, port))
self.clients = {}
self.timeout = 30 # 30秒間非アクティブで切断
def handle_client(self):
while True:
try:
data, addr = self.sock.recvfrom(4096)
if not data:
continue
# メッセージ解析
username_len = data[0]
username = data[1:1+username_len].decode('utf-8')
message = data[1+username_len:].decode('utf-8')
# クライアント登録/更新
self.clients[addr] = (username, time.time())
# 全クライアントにリレー
for client_addr in self.clients:
if client_addr != addr:
self.sock.sendto(data, client_addr)
except Exception as e:
print(f"Error: {e}")
def check_timeout(self):
while True:
current_time = time.time()
to_delete = []
for addr, (_, last_active) in self.clients.items():
if current_time - last_active > self.timeout:
to_delete.append(addr)
for addr in to_delete:
del self.clients[addr]
time.sleep(5)
def start(self):
print("Server started...")
Thread(target=self.handle_client).start()
Thread(target=self.check_timeout).start()
if __name__ == "__main__":
server = UDPServer()
server.start()
while True: # メインスレッドをブロック
time.sleep(1)
client.py
import socket
import time
from threading import Thread
class UDPClient:
def __init__(self, server_host='localhost', server_port=12345):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.server_addr = (server_host, server_port)
self.username = input("Enter your username: ")
def send_message(self, message):
username_bytes = self.username.encode('utf-8')
msg_bytes = message.encode('utf-8')
data = bytes([len(username_bytes)]) + username_bytes + msg_bytes
self.sock.sendto(data, self.server_addr)
def receive_messages(self):
while True:
try:
data, _ = self.sock.recvfrom(4096)
username_len = data[0]
username = data[1:1+username_len].decode('utf-8')
message = data[1+username_len:].decode('utf-8')
print(f"[{username}] {message}")
except Exception as e:
print(f"Error receiving: {e}")
def start(self):
Thread(target=self.receive_messages).start()
while True:
message = input()
if message.lower() == 'exit':
break
self.send_message(message)
self.sock.close()
if __name__ == "__main__":
client = UDPClient()
client.start()

機能要件・非機能要件
機能要件
システムやソフトウェアが「何をすべきか」を具体的に示したもの。
システムが提供する機能や動作に関する要求を指す。
システムが提供する具体的な機能や動作を定義します。
(例)メッセージの送受信、ユーザー名の入力など。
- サーバーの動作
バックグラウンドでの稼働
・サーバーは常にバックグラウンドで動作し、クライアントからの接続を待ち受ける。
オフライン時の挙動
・サーバーが停止している場合、チャットサービス全体が利用できなくなる。 - 通信プロトコル
UDPソケットの使用
・サーバーとクライアントはUDP(User Datagram Protocol)を使ってメッセージをやり取りする。
メッセージサイズの制限
・1回のメッセージ送信で最大4096バイトまで処理できます。 - ユーザー名の入力
セッション開始時のユーザー名入力
・クライアントは接続時にユーザー名を入力する。
ユーザー名のサイズ制限
・ユーザー名は最大255バイト(UTF-8エンコード)。 - メッセージのフォーマット
メッセージの構造
・最初の1バイト:ユーザー名の長さ(usernamelen)。
・次のusernamelenバイト:ユーザー名。
・残りのバイト:実際のメッセージ。
・UTF-8エンコード:メッセージとユーザー名はUTF-8でエンコードされる。 - リレーシステム
メッセージのリレー
サーバーは受信したメッセージを、接続中のすべてのクライアントに転送する。
クライアントの管理
・サーバーは接続中のクライアントの情報をメモリ上に保持する。
タイムアウト処理
・一定時間メッセージを送信しないクライアントは自動的に切断される。
非機能要件
システムが「どのように動作すべきか」を定義するもの。
具体的には、パフォーマンス、信頼性、セキュリティなど、システムの品質に関する要求を指す。
システムの品質や性能に関する要求を定義する。
(例)リアルタイム性、パフォーマンス、信頼性など。
- リアルタイム性
リアルタイム通信の優先
・チャットシステムでは、データのリアルタイム性が信頼性よりも優先される。つまり、メッセージが即座に届くことが重要。 - パフォーマンス
高スループット
・システムは毎秒最低10,000パケットを処理できる必要がある。
(例)1,000人のユーザーが同時にチャットしている場合、1秒間に10メッセージを処理できること。
スケーラビリティ
・一般的なハードウェアで動作するように設計されている必要がある。 - 信頼性
UDPの特性
・UDPはコネクションレスなプロトコルであり、パケットの到達保証や順序保証がない。そのため、アプリケーションレベルでの信頼性確保が必要。
(例)タイムアウト処理や再送メカニズムの実装。 - セキュリティ
クライアントの自動切断
・一定時間メッセージを送信しないクライアントは自動的に切断される。
ユーザー情報の保護
・ユーザー名やメッセージが外部に漏れないようにする必要がある。 - ユーザビリティ
シンプルなCLIインターフェース
・クライアントはコマンドラインインターフェース(CLI)を通じてサーバーに接続し、メッセージを送受信する。

実装時の注意点
-
Pythonのバージョン:
UbuntuとMacOSでデフォルトのPythonバージョンが異なる場合があります。python3コマンドを使って、明示的にPython 3.xを使用するようにしましょう。 -
仮想環境の使用:
プロジェクトごとに仮想環境を作成し、依存ライブラリを管理することをお勧めします。
bash
python3 -m venv venv
source venv/bin/activate # Ubuntu/MacOS共通
-
ネットワーク設定:
UDPソケットを使う場合、OSのファイアウォール設定でポートがブロックされないように注意してください。
例: UbuntuやMacOSで特定のポートを開放する方法を確認しておきましょう。 -
デバッグ:
VS Codeのデバッグ機能はUbuntuでもMacOSでも同じように使えます。ブレークポイントを設定して、プログラムの動作を確認しましょう。