web-dev-qa-db-ja.com

Python socket receive-着信パケットのサイズは常に異なる

TCPサーバーにSocketServerモジュールを使用しています。着信パケットのサイズが常に異なるため、ここでrecv()関数に関する問題が発生しています。 recv(1024)(より大きな値で試しました)を指定した場合、パケットの長さが短くなるため(考えられる)、2回または3回のリクエストの後にスタックします。タイムアウト。

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

クライアントが同じ送信元ポートを介して複数のリクエストを送信したが、サーバーがスタックした場合、どんな助けでも大歓迎です、ありがとう!

36
n00bie

ネットワークはalways予測不能です。 TCPは、このランダムな動作の多くをあなたのためになくします。1つの素晴らしいことはTCPは、バイトが同じ順序で到着することを保証します。 !notが同じように切り刻まれて到着することを保証します単純にcannot接続の一方の端からの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
_
43
Larry Hastings

Larry Hastingsの答えには、ソケットに関する一般的なアドバイスがいくつかありますが、Pythonソケットモジュールでのrecv(bufsize)メソッドの動作に関係するいくつかの間違いがあります。

それで、明確にするために、これは助けを求めてこれを探している他の人にとって混乱するかもしれないので:

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

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

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

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

正確なクライアントコードとテストプロトコルの説明を投稿できる場合は、さらにお手伝いさせていただきます。

126
Hans L

または、recv(x_bytes, socket.MSG_WAITALL)を使用することもできます。これは、Unixでのみ動作するようで、正確にx_bytesを返します。

15
henrietta

それがTCPの性質です。プロトコルはパケットを満たし(下位層はIPパケット)、それらを送信します。 MTU(最大転送単位)をある程度制御できます。

つまり、TCPの「ペイロードの描写」が定義されている場所の上に乗るプロトコルを考案する必要があります。「ペイロードの描写」では、これは、「すべてのNULL終了文字列」と同じくらい簡単です。

2
jldupont

正確な理由がコードが凍結される理由はnotであることに注意してくださいrequest.recv()のバッファサイズが大きすぎます。以下に説明します socket.recv(buffer_size)のバッファサイズを意味する

このコードは、empty TCP message(この空のメッセージを印刷する場合、 _b''_)と表示されます:

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

そして、空のTCPメッセージ。socket.send(b'')が単に勝ちましたnowayがあります)動作しません。

どうして?空のメッセージはsocket.close()と入力したときにのみ送信されるため、接続を閉じない限りスクリプトはループします。 Hans Lが指摘しているように、いくつかの メッセージを終了する良い方法 があります。

1
Qback

これは古いことは知っていますが、これが誰かの助けになることを願っています。

通常のpythonソケットの使用

# tcp_echo_server.py
import socket

ADDRESS = ''
PORT = 54321

connections = []
Host = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Host.setblocking(0)
Host.bind((ADDRESS, PORT))
Host.listen(10)  # 10 is how many clients it accepts

def close_socket(connection):
    try:
        connection.shutdown(socket.SHUT_RDWR)
    except:
        pass
    try:
        connection.close()
    except:
        pass

def read():
    for i in reversed(range(len(connections))):
        try:
            data, sender = connections[i][0].recvfrom(1500)
            return data
        except (BlockingIOError, socket.timeout, OSError):
            pass
        except (ConnectionResetError, ConnectionAbortedError):
            close_socket(connections[i][0])
            connections.pop(i)
    return b''  # return empty if no data found

def write(data):
    for i in reversed(range(len(connections))):
        try:
            connections[i][0].sendto(data, connections[i][1])
        except (BlockingIOError, socket.timeout, OSError):
            pass
        except (ConnectionResetError, ConnectionAbortedError):
            close_socket(connections[i][0])
            connections.pop(i)

# Run the main loop
while True:
    try:
        con, addr = Host.accept()
        connections.append((con, addr))
    except BlockingIOError:
        pass

    data = read()
    if data != b'':
        print(data)
        write(b'ECHO: ' + data)
        if data == b"exit":
            break

# Close the sockets
for i in reversed(range(len(connections))):
    close_socket(connections[i][0])
    connections.pop(i)
close_socket(Host)

クライアントは似ています

# tcp_client.py
import socket

ADDRESS = "localhost"
PORT = 54321

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ADDRESS, PORT))
s.setblocking(0)

def close_socket(connection):
    try:
        connection.shutdown(socket.SHUT_RDWR)
    except:
        pass
    try:
        connection.close()
    except:
        pass

def read():
    """Read data and return the read bytes."""
    try:
        data, sender = s.recvfrom(1500)
        return data
    except (BlockingIOError, socket.timeout, AttributeError, OSError):
        return b''
    except (ConnectionResetError, ConnectionAbortedError, AttributeError):
        close_socket(s)
        return b''

def write(data):
    try:
        s.sendto(data, (ADDRESS, PORT))
    except (ConnectionResetError, ConnectionAbortedError):
        close_socket(s)

while True:
    msg = input("Enter a message: ")
    write(msg.encode('utf-8'))

    data = read()
    if data != b"":
        print("Message Received:", data)

    if msg == "exit":
        break

close_socket(s)
0
justengel