Python socket 接收 - 来自包的大小总是不同

60 投票
7 回答
201043 浏览
提问于 2025-04-15 15:51

我正在使用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()

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

7 个回答

22

你也可以用 recv(x_bytes, socket.MSG_WAITALL) 这个方法,它好像只在Unix系统上有效,并且会准确返回 x_bytes 指定的字节数。

174

Larry Hastings的回答提供了一些关于套接字的很好的建议,但在Python的socket模块中,recv(bufsize)方法的工作原理上有几个错误。

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

  1. recv(bufsize)方法中的bufsize参数是必需的。 如果你调用recv()(没有参数),会出现错误。
  2. recv(bufsize)中的bufferlen是一个最大大小。如果可用的数据少于这个大小,recv会返回更少的字节。

详细信息请查看文档

现在,如果你正在从客户端接收数据,并想知道何时接收完所有数据,你可能需要在你的协议中添加一些内容——正如Larry所建议的那样。可以参考这个方案,了解如何判断消息结束。

正如那个方案所指出的,对于某些协议,客户端在发送完数据后会直接断开连接。在这种情况下,你的while True循环应该可以正常工作。如果客户端没有断开连接,你需要想办法来标记内容的长度、分隔消息,或者实现超时机制。

如果你能提供你的具体客户端代码和测试协议的描述,我很乐意进一步帮助你。

37

注意:正如评论中提到的,Python中不允许在调用recv()时不传参数,因此这个回答可以忽略。

原始回答:


网络是永远不可预测的。TCP协议能帮你处理很多这种随机行为。TCP有一个很棒的功能:它保证数据包会按顺序到达。但!它并不保证数据包会以相同的方式被拆分。你不能假设从连接的一端发送的每一个send()都会在另一端对应一个recv(),并且字节数完全相同。

当你说socket.recv(x)时,你是在说“在从socket读取到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()随时返回任意数量的数据。下面是一个如何做到这一点的例子。我尝试用伪代码或注释来告诉你该怎么做,但不太清晰。所以我用长度前缀作为以冒号结束的数字字符串明确写了出来。给你:

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

撰写回答