54

TCP サーバーに SocketServer モジュールを使用しています。recv()着信パケットのサイズは常に異なるため、ここで関数でいくつかの問題が発生しています。指定するrecv(1024)と(大きい値と小さい値で試しました)、パケット長が小さくすると(私は思う)、サーバーはタイムアウトまでスタックします。

class Test(SocketServer.BaseRequestHandler):

def handle(self):

   print "From:", self.client_address

   while True:    

     data = self.request.recv(1024)
     if not data: break

     if data[4] == "\x20":              
       self.request.sendall("hello")
     if data[4] == "\x21":
       self.request.sendall("bye")
     else:
       print "unknow packet"
   self.request.close()
   print "Disconnected", self.client_address

launch = SocketServer.ThreadingTCPServer(('', int(sys.argv[1])),Test)

launch.allow_reuse_address= True;

launch.serve_forever()

クライアントが同じ送信元ポートを介して複数のリクエストを送信しても、サーバーが動かなくなった場合は、助けていただければ幸いです。

4

7 に答える 7

164

recv(bufsize)Larry Hastings による回答には、ソケットに関する優れた一般的なアドバイスがありますが、メソッドが Python ソケット モジュールでどのように機能するかに関連するため、いくつかの間違いがあります。

したがって、明確にするために、これは助けを求める他の人にとって混乱を招く可能性があるためです。

  1. メソッドの bufsize パラメータrecv(bufsize)はオプションではありません。recv()(パラメータなしで)呼び出すとエラーが発生します。
  2. bufferlen inrecv(bufsize)最大サイズです。使用可能なバイト数が少ない場合、recv は喜んでより少ないバイト数を返します。

詳細については、ドキュメントを参照してください。

現在、クライアントからデータを受信して​​いて、すべてのデータをいつ受信したかを知りたい場合は、Larry が示唆しているように、おそらくそれをプロトコルに追加する必要があります。メッセージの終わりを決定するための戦略については、このレシピを参照してください。

そのレシピが指摘しているように、一部のプロトコルでは、データの送信が完了すると、クライアントは単純に切断されます。そのような場合、while Trueループは正常に機能するはずです。クライアントが切断されない場合は、コンテンツの長さを通知する方法、メッセージを区切る方法、またはタイムアウトを実装する方法を見つける必要があります。

正確なクライアント コードとテスト プロトコルの説明を投稿していただければ、さらにお役に立てれば幸いです。

于 2009-11-27T05:43:38.603 に答える
40

ネットワークは常に予測不可能です。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
于 2009-11-11T16:02:59.103 に答える
20

recv(x_bytes, socket.MSG_WAITALL)代わりに、Unix でのみ動作するようで、正確に を返す を使用することもできますx_bytes

于 2009-12-02T04:40:21.577 に答える
3

これが TCP の性質です。プロトコルはパケット (下位層は IP パケット) を埋めて送信します。MTU (Maximum Transfer Unit) をある程度制御できます。

言い換えれば、「ペイロードの描写」が定義されているTCPの上に乗るプロトコルを考案する必要があります。「ペイロードの描写」とは、プロトコルがサポートするメッセージの単位を抽出する方法を意味します。これは、「すべての NULL 終了文字列」と同じくらい簡単です。

于 2009-11-10T15:38:33.923 に答える
2

データの最初の 4 バイトを常にデータ サイズとして送信してから、完全なデータを一度に読み取ることができます。クライアント側とサーバー側の両方で以下の関数を使用して、データを送受信します。

def send_data(conn, data):
    serialized_data = pickle.dumps(data)
    conn.sendall(struct.pack('>I', len(serialized_data)))
    conn.sendall(serialized_data)


def receive_data(conn):
    data_size = struct.unpack('>I', conn.recv(4))[0]
    received_payload = b""
    reamining_payload_size = data_size
    while reamining_payload_size != 0:
        received_payload += conn.recv(reamining_payload_size)
        reamining_payload_size = data_size - len(received_payload)
    data = pickle.loads(received_payload)

    return data

サンプル プログラムはhttps://github.com/vijendra1125/Python-Socket-Programming.gitにあります。

于 2020-08-22T05:49:22.930 に答える