在Python中为套接字IO编写字符串缓冲区的最佳方法是什么?

时间:2014-04-17 08:56:11

标签: python sockets buffer

我有一个运行非阻塞事件循环的线程,其他线程可以将字符串放在缓冲区上,以便事件循环写入套接字。

我想在缓冲区中累积字符串,这样就可以通过一次调用send来发送多个小字符串。

从概念上讲,缓冲区需要做3件事。

  1. 从缓冲区的开头获取一大块数据
  2. 将数据添加到缓冲区的末尾
  3. 释放缓冲区的前n个字节
  4. 我考虑了一些事情。

    • StringIO非常适合在最后编写字符串,但在写入套接字后无法释放数据。
    • collections.deque个字节。内存效率很低。
    • array.array很容易追加字符串。复制切片以读取/转储数据。

    我目前的代码看起来像这样,但我对复制和锁定并不十分满意。

    from array import array
    from threading import Condition
    
    class SendBuffer(object):
    
        def __init__(self, max_size):
            self.mark = 0
            self.buf = array('c')
            self.max_size = max_size
            self.full = Condition()
    
        def __len__(self):
            with self.full:
                return len(self.buf) - self.mark
    
    
        def write(self, data):
            with self.full:
                while len(self) >= self.max_size:
                    # wait until data is written
                    self.full.wait()
    
                self.buf.fromstring(data)
    
        def _peek(self):
            return buffer(self.buf, self.mark)
    
        def _written(self, n):
            self.mark += n
            self.full.notify_all()
    
            if self.mark >= len(self.buf):
                self.mark = 0
                self.buf = array('c')
            elif self.mark >= self.max_size:
                self.buf = self.buf[self.mark:]
                self.mark = 0
    
        def to_sock(self, sock):
            with self.full:
                data = self._peek()
                if data:
                    n = sock.send(data)
                    self._written(n)
    

1 个答案:

答案 0 :(得分:1)

您的问题是您的缓冲区(如StringIO)只能附加到其中。当您完成处理时,请执行以下操作,而不是追加到最后并从前面删除:

  • 获取两个缓冲区。
  • 在任何一个时刻,所有的写入都将转到其中一个(结束),读者将从另一个读取(如果缓冲区大于想要的,则使用索引保持位置可以写入一个send()call)。
  • 当阅读器读完一个缓冲区时,缓冲区被清除(*)并且它们交换角色。

让我们考虑一些案例:

  • 读取器超过了写入器:每次写入后立即读取相同大小的读取,并且缓冲区交换位置。每次写入都会立即作为单个数据包发出。

  • 读取器和写入器完全同步,或者与一些抖动足够接近:多个小写入累积到写入缓冲区直到读取器完成,然后它们以与网络一样大的块发送出去将采取。

  • 作者超越了读者。当读取器忙于处理读缓冲区时,写缓冲区将填满。读者仍然会发送网络所需的大块,但是你需要以某种方式限制编写器(通常通过设置最大缓冲区大小)并调整它们以避免占用无限量的内存。

请记住,缓冲区只是防止抖动导致停顿的一种方法。他们无法帮助抵御不匹配的生产者/消费者速度。实际上,您的缓冲区将持续满或不断为空。

(*)清除一个StringIO对象显然不是微不足道的,谷歌一点点。您可能想要创建一个新对象而不是清除,但如果您有许多上下文切换,这可能会导致大量垃圾需要GC。相反,您也可以考虑使用数组和index变量的组合构建自己的可清除缓冲区,在这种情况下,清除将降至index = 0