Python套接字接收 - 传入的数据包总是有不同的大小

n00*_*bie 37 python networking timeout recv

我正在使用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()
Run Code Online (Sandbox Code Playgroud)

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

Han*_*s L 128

Larry Hastings的答案对套接字有一些很好的一般性建议,但是有一些错误,因为它与recv(bufsize)Python套接字模块中的方法有关.

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

  1. recv(bufsize)方法的bufsize参数不是可选的.如果你打电话recv()(没有参数),你会收到错误.
  2. 缓冲区recv(bufsize)最大尺寸.如果可用的更少,recv将很乐意返回更少的字节.

有关详细信息,请参阅文档

现在,如果您从客户端接收数据并想知道何时收到所有数据,那么您可能不得不将其添加到您的协议中 - 正如Larry所说.有关确定消息结束的策略,请参阅此配方.

正如该配方指出的那样,对于某些协议,客户端只需在完成发送数据时断开连接.在这些情况下,你的while True循环应该工作正常.如果客户端没有断开连接,您需要找出一些方式来表示您的内容长度,分隔您的消息或实现超时.

如果您可以发布确切的客户端代码和测试协议的说明,我将很乐意尝试进一步提供帮助.

  • 我发现最好的方法是弄清楚消息/文件/数据中的字节数,然后在消息之前发送消息/文件/数据的长度作为标头,并使用诸如“:”之类的分隔符。`recv`直到通过检测`:`得到消息的长度,然后`recv`根据标题明确需要什么。如果是文件,则一次循环到文件的`recv`块,同时确保将`recv`的大小除以2直到最后一个字节(如果'total bytes%2!= 0')。我使用这种方法来传输大文件(价值GB),并且非常适合进度条。 (2认同)

Lar*_*ngs 42

网络总是不可预测的.TCP使很多这种随机行为消失了.TCP做的一件好事:它保证字节以相同的顺序到达.但!它不能保证它们会以同样的方式被切断.你根本不能假设来自连接一端的每个send()都会在远端产生一个具有完全相同字节数的recv().

当你说socket.recv(x),你说'在你从套接字读取x字节之前不要返回'.这称为"阻止I/O":您将阻止(等待)直到您的请求被填满.如果协议中的每条消息都是1024字节,那么调用socket.recv(1024)将非常有效.但听起来并非如此.如果您的消息是固定数量的字节,只需将该号码传递给socket.recv()您,您就完成了.

但是如果你的消息长度不一样怎么办?您需要做的第一件事是:socket.recv()使用显式数字停止呼叫.改变这个:

data = self.request.recv(1024)
Run Code Online (Sandbox Code Playgroud)

对此:

data = self.request.recv()
Run Code Online (Sandbox Code Playgroud)

意味着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
Run Code Online (Sandbox Code Playgroud)

  • Hans L在下面的注释中是正确的,在python request.recv()中,如果是强制参数,则不是有效的调用bufsize.理想情况下,应删除或编辑此答案.http://docs.python.org/library/socket.html (34认同)
  • 令人惊讶的是,51 名投票的人并不知道这不起作用,更糟糕的是 OP 将此答案标记为正确...... (12认同)
  • `类型错误:recv() 至少需要 1 个参数(给定 0 个)` (8认同)
  • 您不能在没有任何参数的情况下调用socket.recv()。如果尝试,则返回TypeError:recv()至少接受1个参数(给定0)。 (3认同)
  • *如果协议中的每条消息恰好是 1024 字节,则调用 socket.recv(1024) 会很好...*也不是这样。 (2认同)

小智 17

您也可以使用recv(x_bytes, socket.MSG_WAITALL),它似乎仅适用于Unix,并且将完全返回x_bytes.