带有神秘缓冲的插座

时间:2013-11-02 02:52:58

标签: python sockets recv

我正在构建一个基于python的界面,用于从仪器上通过TCP提取数据。数据流作为特定事件发生,并且时间不稳定:我获得数据突发然后缓慢。它们是小数据包,因此为简单起见假设它们是完整的数据包。

以下是我从套接字获得的行为:

  • 发送事件#1:socket.recv返回事件#1
  • 发送事件#2:socket.recv返回事件#2
  • 快速发送事件#3-50:socket.recv仅返回事件#3-30(返回27次)
  • 慢慢发送Event#51:socket returns.recv event#31
  • 慢慢发送事件#52:socket returns.recv event#32

没有数据丢失。但是显然有一个填充的缓冲区,套接字现在返回旧数据。但是不应该recv只是继续返回,直到缓冲区为空?相反,它只在收到新数据包时返回,尽管已经建立了数据包缓冲区。怪异!

这是代码的本质(这是针对非阻塞的,我也使用recv进行了阻塞 - 相同的结果)。为简单起见,我删除了所有数据包重组的东西。我已经仔细地将它追溯到插座,所以我知道这不应该受到责备。

class mysocket:
    def __init__(self,ip,port):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect((ip,port))
        self.keepConn = True
        self.socket.setblocking(0)
        threading.Thread(target = self.rcvThread).start()
        threading.Thread(target = self.parseThread).start()

    def rcvThread(self):
        while self.keepConn:
            readable,writable,inError = select([self.socket],[self.socket],[],.1)
            if readable:
               packet = self.socket.recv(4096)
               self.recvqueue.put_nowait(packet)
            try:
               xmitmsg = self.sendqueue.get_nowait()
            except Queue.Empty:
               pass
            else:
               if writable:
                   self.socket.send(xmitmsg)

    def parseThread(self,rest = .1):
        while self.keepConn:
            try:
                output = self.recvqueue.get_nowait()
                eventnumber = struct.unpack('<H',output[:2]
                print eventnumber
            except Queue.Empty:
                sleep(rest)

为什么我不能让套接字将所有数据转储到它的缓冲区中?我永远无法赶上!这个太奇怪了。有人有指针吗?

我是一个业余爱好者,但我真的完成了我的作业,我完全感到困惑。

1 个答案:

答案 0 :(得分:3)

packet = self.socket.recv(4096)
self.recvqueue.put_nowait(packet)

TCP是基于流的协议,而不是基于消息的协议。它不保留消息边界。这意味着您不能指望每条消息都有一个recv()次呼叫。如果以突发方式发送数据,Nagle's algorithm会将数据合并为一个TCP数据包。

您的代码假定每个recv()调用返回一个“数据包”,并且解析线程打印每个“数据包”中的第一个数字。但recv()不返回数据包,它返回TCP流中的数据块。这些块可以包含一条消息或多条消息,甚至包含部分消息。无法保证前两个字节始终是事件编号。

通常,从TCP连接读取数据涉及多次调用recv()并将您获得的数据存储在缓冲区中。收到整条消息后,从缓冲区中删除适当数量的字节并处理它们。

如果您有可变长度的消息,那么您需要自己跟踪消息边界。 TCP不像UDP那样为你做这件事。这意味着将包含消息长度的标头添加到每条消息的前面。

try:
   xmitmsg = self.sendqueue.get_nowait()
except Queue.Empty:
   pass
else:
   if writable:
       self.socket.send(xmitmsg)

另一方面,看起来这段代码有一个bug。无论套接字是否可写,它都会从sendqueue中删除消息。如果套接字不可写,它将默默地丢弃消息。