非阻塞TCP缓冲区问题

时间:2010-06-12 14:36:34

标签: c++ sockets winsock nonblocking

我想我遇到了问题。我有两个相互连接的TCP应用程序,它们使用winsock I / O完成端口来发送/接收数据(非阻塞套接字)。

一切正常,直到数据传输爆发。发件人开始发送错误/格式错误的数据。

我在堆栈上分配我正在发送的缓冲区,如果我理解正确,这样做是错误的,因为这些缓冲区应该保持原样,直到我收到IOCP的“写完成”通知为止。

以此为例:

void some_function()
{
    char cBuff[1024];

    // filling cBuff with some data

    WSASend(...); // sending cBuff, non-blocking mode

    // filling cBuff with other data

    WSASend(...); // again, sending cBuff

    // ..... and so forth!
}

如果我理解正确,这些WSASend()调用中的每一个都应该有自己唯一的缓冲区,并且只有在发送完成后才能重用该缓冲区。
正确的吗?

现在,我可以实施哪些策略来维护大量此类缓冲区,我应该如何处理它们,如何避免性能损失等等? 并且,如果我要使用缓冲区,这意味着我应该将要从源缓冲区发送的数据复制到临时缓冲区,因此,我将每个套接字上的SO_SNDBUF设置为零,因此系统不会重新复制我的内容已复制。你跟我在一起吗?如果我不清楚,请告诉我。

3 个答案:

答案 0 :(得分:3)

认真看看boost::asio。异步IO是它的专长(正如其名称所暗示的那样。)它是非常成熟的库,现在从1.35开始位于Boost。许多人在生产中将它用于非常密集的网络。文档中有大量examples

有一件事是肯定的 - 它非常认真地与buffers合作。

编辑:

处理突发输入的基本思路是排队

  • 创建三个预先分配的缓冲区链表 - 一个用于 free 缓冲区,一个用于待处理(已接收)数据,一个用于待发送数据。
  • 每次需要发送内容时 - 从 free 列表中取一个缓冲区(如果空闲列表为空,则分配一个新缓冲区),填入数据,将其放到 to-be -sent 列表。
  • 每次需要接收内容时 - 如上所述从 free 列表中取一个缓冲区,将其提供给IO接收例程。
  • 定期从待发送队列中取出缓冲区,将其移交给发送例程。
  • 发送完成(内联或异步) - 将它们放回 free 列表。
  • 在接收完成时 - 将缓冲区放到待处理列表中。
  • 让您的“业务”例程从待处理列表中删除缓冲区。

然后,突发将填充该输入队列,直到您能够处理它们为止。您可能希望限制队列大小以避免吹过所有内存。

答案 1 :(得分:1)

我不认为在第一次发送完成之前进行第二次发送是个好主意。

同样,我认为在发送完成之前更改缓冲区并不是一个好主意。

我倾向于将数据存储在某种队列中。一个线程可以继续向队列添加数据。第二个线程可以循环工作。发送并等待它完成。如果有更多数据再发送,否则等待更多数据。

你需要一个关键部分(或者某些部分)来很好地共享线程之间的队列,并且可能是一个事件或信号量,以便发送线程在没有数据就绪的情况下等待。

答案 2 :(得分:0)

  

现在,我可以实施哪些策略来维护大量此类缓冲区,我应该如何处理它们,如何避免性能损失等等?

如果不了解您的具体设计,很难知道答案。一般来说,我会避免维护你自己的“缓冲区”,而是使用操作系统内置的缓冲区 - 堆。

但无论如何,在一般情况下我会做的是向代码的调用者公开一个接口,该接口反映了WSASend为重叠i / o做的事情。例如,假设您提供了一个发送特定结构的接口:

struct Foo
{
   int x;
   int y;
};

// foo will be consumed by SendFoo, and deallocated, don't use it after this call
void SendFoo(Foo* foo);

我会要求SendFoo的用户使用new分配一个Foo实例,并告诉他们在调用SendFoo后,内存不再被其代码“拥有”,因此他们不应该使用它。

您可以通过一些小技巧进一步强制执行此操作:

// After this operation the resultant foo ptr will no longer point to
// memory passed to SendFoo
void SendFoo(Foo*& foo);

这允许SendFoo的主体将内存的地址发送到WSASend,但是将传入的指针修改为NULL,从而切断调用者代码与其内存之间的链接。当然,你无法真正知道调用者使用该地址做了什么,他们可能在其他地方有一个副本。

此接口还强制每个WSASend将使用单个内存块。您正试图在两个WSASend调用之间共享一个缓冲区,而不仅仅是危险区域。