ネットワークは常に予測不可能です。TCPを使用すると、このランダムな動作の多くがなくなります。TCPが行う素晴らしいことの1つは、バイトが同じ順序で到着することを保証することです。だが!同じように切り刻まれて到着することを保証するものではありません。接続の一方の端からのすべてのsend()が、まったく同じバイト数の遠端で正確に1つのrecv()になると 単純に想定することはできません。
あなたが言うときsocket.recv(x)
、あなたは「あなたがソケットからxバイトを読むまで戻らないでください」と言っています。これは「ブロッキングI/O」と呼ばれます。リクエストが満たされるまでブロック(待機)します。プロトコル内のすべてのメッセージが正確に1024バイトである場合、呼び出しsocket.recv(1024)
はうまく機能します。しかし、それは真実ではないように聞こえます。メッセージが固定バイト数の場合は、その数をに渡すだけでsocket.recv()
完了です。
しかし、メッセージの長さが異なる場合はどうなるでしょうか。socket.recv()
最初に行う必要があるのは、明示的な番号での通話を停止することです。これを変更する:
data = self.request.recv(1024)
これに:
data = self.request.recv()
つまりrecv()
、新しいデータを取得するたびに常に戻ります。
しかし今、あなたは新しい問題を抱えています:送信者があなたに完全なメッセージをいつ送ったかをどうやって知るのですか?答えは:あなたはしません。メッセージの長さをプロトコルの明示的な部分にする必要があります。最善の方法は次のとおりです。固定サイズの整数(socket.ntohs()
またはを使用してネットワークバイトオーダーに変換socket.ntohl()
)または文字列の後に区切り文字(「123:」など)を付けて、すべてのメッセージに長さのプレフィックスを付けます。この2番目のアプローチは効率が悪いことがよくありますが、Pythonの方が簡単です。
これをプロトコルに追加したらrecv()
、いつでも任意の量のデータを返すようにコードを変更する必要があります。これを行う方法の例を次に示します。擬似コードとして、または何をすべきかを説明するコメントを付けて書いてみましたが、あまり明確ではありませんでした。そのため、コロンで終わる数字の文字列として長さプレフィックスを使用して明示的に記述しました。どうぞ:
length = None
buffer = ""
while True:
data += self.request.recv()
if not data:
break
buffer += data
while True:
if length is None:
if ':' not in buffer:
break
# remove the length bytes from the front of buffer
# leave any remaining bytes in the buffer!
length_str, ignored, buffer = buffer.partition(':')
length = int(length_str)
if len(buffer) < length:
break
# split off the full message from the remaining bytes
# leave any remaining bytes in the buffer!
message = buffer[:length]
buffer = buffer[length:]
length = None
# PROCESS MESSAGE HERE