【问题标题】:Python socket receive - incoming packets always have a different sizePython套接字接收 - 传入的数据包总是有不同的大小
【发布时间】:2010-12-15 02:17:46
【问题描述】:

我将 SocketServer 模块用于 TCP 服务器。 我在这里遇到了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()

如果客户端通过同一个源端口发送多个请求,但服务器卡住了,任何帮助将不胜感激,谢谢!

【问题讨论】:

    标签: python networking timeout recv


    【解决方案1】:

    请注意,您的代码被冻结的确切原因不是不是,因为您设置了过高的 request.recv() 缓冲区大小。这里解释一下What means buffer size in socket.recv(buffer_size)

    此代码将一直有效,直到它收到 空 TCP 消息(如果您要打印此空消息,它将显示 b''):

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

    请注意,无法发送空 TCP 消息。 socket.send(b'') 根本行不通。

    为什么?因为只有在您键入socket.close() 时才会发送空消息,所以只要您不关闭连接,您的脚本就会循环。 正如 Hans L 指出的,这里有一些 good methods to end message

    编辑:

    问题

    所以你真正的问题是你没有任何适当的方法来结束你的网络消息。因此,您的程序将一直等待,直到客户端结束连接或发生超时。

    关键字解决方案

    一种解决方案是在接收到的数据中查找特殊关键字,当您找到特殊关键字时,您无需等待连接关闭,而是中断循环并继续您的程序。一种更高级的方法是将您的消息包含在一个特殊的标签中,例如<message>hello world</message>.

    头解决方案

    另一种方法是首先发送一个始终相同(固定)长度的标头消息。在此消息中,您发送消息的剩余时间(正文)信息,因此您的程序将知道它究竟应该将什么放入 self.request.recv 以及何时中断循环。

    这些问题是我们使用例如HTTP。它已经是精心设计的协议,可以为我们解决所有这些低级问题。

    【讨论】:

      【解决方案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找到示例程序

      【讨论】:

        【解决方案3】:

        您也可以使用 recv(x_bytes, socket.MSG_WAITALL),它似乎只在 Unix 上有效,并且将准确返回 x_bytes

        【讨论】:

          【解决方案4】:

          我知道这是旧的,但我希望这对某人有所帮助。

          使用常规 python 套接字我发现您可以使用 sendto 和 recvfrom 在数据包中发送和接收信息

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

          【讨论】:

            【解决方案5】:

            Larry Hastings 的回答对套接字有一些很好的一般性建议,但在 Python 套接字模块中recv(bufsize) 方法的工作方式方面存在一些错误。

            所以,澄清一下,因为这可能会让其他寻求帮助的人感到困惑:

            1. recv(bufsize) 方法的 bufsize 参数不是可选的。如果您调用 recv()(不带参数),您会收到错误消息。
            2. recv(bufsize) 中的 bufferlen 是 最大 大小。如果可用的字节数减少,recv 将很高兴返回更少的字节。

            详情请见the documentation

            现在,如果您从客户端接收数据并想知道何时收到所有数据,您可能需要将其添加到您的协议中——正如 Larry 建议的那样。有关确定消息结束的策略,请参阅this recipe

            正如该配方所指出的,对于某些协议,客户端在完成发送数据后会简单地断开连接。在这些情况下,您的 while True 循环应该可以正常工作。如果客户端确实没有断开连接,您将需要找出某种方法来指示您的内容长度、分隔消息或实现超时。

            如果您能发布您的确切客户端代码和您的测试协议的描述,我很乐意提供进一步的帮助。

            【讨论】:

            • 我找到的最好的方法是计算出消息/文件/数据中的字节数,然后在消息之前发送消息/文件/数据的长度,作为标头,使用像: 这样的分隔符。 recv 直到您通过检测 : 获得消息的长度,然后 recv 根据标头明确需要什么。如果它是一个文件,则一次循环到文件的recv 块,同时确保在最后一个字节之前保持recv 的大小可被 2 整除(如果是total bytes % 2 != 0)。我使用这种方法来传输大文件(GB 值),它非常适合进度条。
            • 我测试了recv(bufsize),它发送的数据也更少。但我的问题是python如何理解这是结束?!由于 tcp 是一个流,服务器可以检测到流中数据的结尾吗?
            【解决方案6】:

            网络总是不可预测。 TCP 使很多这种随机行为消失了。 TCP 做了一件奇妙的事情:它保证字节将以相同的顺序到达。但!它确实保证它们会以同样的方式切碎到达。您只是不能假设来自连接一端的每个 send() 都会在远端产生完全相同的一个 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:' )。第二种方法通常效率较低,但在 Python 中更容易。

            将其添加到协议后,您需要更改代码以随时处理 recv() 返回任意数量的数据。这是如何执行此操作的示例。我试着把它写成伪代码,或者用 cmets 来告诉你该怎么做,但不是很清楚。所以我明确地使用长度前缀作为以冒号结尾的数字字符串来编写它。给你:

            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
            

            【讨论】:

            • Hans L 在下面的评论中是正确的,在 python request.recv() 中,如果是强制参数,则不是有效的 bufsize 调用。理想情况下,应删除或编辑此答案。 docs.python.org/library/socket.html
            • 如果你的协议中的每条消息都是 1024 字节,那么调用 socket.recv(1024) 会很好......也不正确。
            • 你不能在没有任何参数的情况下调用 socket.recv()。如果你尝试,TypeError: recv() takes at least 1 argument (0 given) 会返回。
            • TypeError: recv() takes at least 1 argument (0 given)
            • 令人惊讶的是,51 位支持此问题的人不知道这不起作用,更糟糕的是 OP 将此答案标记为正确...
            【解决方案7】:

            这就是 TCP 的本质:协议填充数据包(较低层是 IP 数据包)并发送它们。您可以对 MTU(最大传输单元)进行一定程度的控制。

            换句话说:您必须设计一个基于 TCP 的协议,其中定义了您的“有效负载描述”。 “有效负载描述”是指您提取协议支持的消息单元的方式。这可以像“每个以 NULL 结尾的字符串”一样简单。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2016-05-26
              • 2020-09-24
              • 1970-01-01
              • 2021-04-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多