简体   繁体   English

非阻塞TCP缓冲区问题

[英]Non-blocking TCP buffer issues

I think I'm in a problem. 我想我遇到了问题。 I have two TCP apps connected to each other which use winsock I/O completion ports to send/receive data (non-blocking sockets). 我有两个相互连接的TCP应用程序,它们使用winsock I / O完成端口来发送/接收数据(非阻塞套接字)。

Everything works just fine until there's a data transfer burst. 一切正常,直到数据传输爆发。 The sender starts sending incorrect/malformed data. 发件人开始发送错误/格式错误的数据。

I allocate the buffers I'm sending on the stack, and if I understand correctly, that's a wrong to do, because these buffers should remain as I sent them until I get the "write complete" notification from IOCP. 我分配了我正在堆栈上发送的缓冲区,如果我理解正确,这是错误的,因为这些缓冲区应该保留,因为我发送它们直到我从IOCP获得“写完成”通知。

Take this for example: 以此为例:

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!
}

If I understand correctly, each of these WSASend() calls should have its own unique buffer, and that buffer can be reused only when the send completes. 如果我理解正确,这些WSASend()调用中的每一个都应该有自己唯一的缓冲区,并且只有在发送完成时才能重用该缓冲区。
Correct? 正确?

Now, what strategies can I implement in order to maintain a big sack of such buffers, how should I handle them, how can I avoid performance penalty, etc'? 现在,我可以实施哪些策略来维护大量的缓冲区,我应该如何处理它们,如何避免性能损失等等?
And, if I am to use buffers that means I should copy the data to be sent from the source buffer to the temporary one, thus, I'd set SO_SNDBUF on each socket to zero, so the system will not re-copy what I already copied. 并且,如果我要使用缓冲区,这意味着我应该将要从源缓冲区发送的数据复制到临时缓冲区,因此,我将每个套接字上的SO_SNDBUF设置为零,因此系统不会重新复制我的内容已复制。 Are you with me? 你跟我在一起吗? Please let me know if I wasn't clear. 如果我不清楚,请告诉我。

Take a serious look at boost::asio . 仔细看看boost::asio Asynchronous IO is its specialty (just as the name suggests.) It's pretty mature library by now being in Boost since 1.35. 异步IO是它的专长(顾名思义。)它是非常成熟的库,现在从1.35开始在Boost中 Many people use it in production for very intensive networking. 许多人在生产中将它用于非常密集的网络。 There's a wealth of examples in the documentation. 文档中有大量示例

One thing for sure - it take working with buffers very seriously. 有一点可以肯定 - 它非常重视缓冲区

Edit: 编辑:

Basic idea to handling bursts of input is queuing . 处理突发输入的基本思想是排队

  • Create, say, three linked lists of pre-allocated buffers - one is for free buffers, one for to-be-processed (received) data, one for to-be-sent data. 例如,创建预先分配的缓冲区的三个链接列表 - 一个用于空闲缓冲区,一个用于待处理 (接收)数据,一个用于待发送数据。
  • Every time you need to send something - take a buffer off the free list (allocate a new one if free list is empty), fill with data, put it onto to-be-sent list. 每次你需要发送一些东西 - 从空闲列表中取一个缓冲区(如果空闲列表为空,则分配一个缓冲区),填入数据,将其放入待发送列表中。
  • Every time you need to receive something - take a buffer off the free list as above, give it to IO receive routine. 每次你需要收到一些东西 - 从上面的空闲列表中取出一个缓冲区,将它交给IO接收程序。
  • Periodically take buffers off to-be-sent queue, hand them off to send routine. 定期将缓冲区从待发送队列中取出,将其关闭以发送例程。
  • On send completion (inline or asynchronous) - put them back onto free list. 在发送完成(内联或异步) - 将它们放回到空闲列表中。
  • On receive completion - put buffer onto to-be-processed list. 在接收完成时 - 将缓冲区放入待处理列表。
  • Have your "business" routine take buffers off to-be-processed list. 让您的“业务”例程将缓冲区从待处理列表中删除。

The bursts will then fill that input queue until you are able to process them. 然后,突发将填充该输入队列,直到您能够处理它们为止。 You might want to limit the queue size to avoid blowing through all the memory. 您可能希望限制队列大小以避免吹过所有内存。

I don't think it is a good idea to do a second send before the first send is finished. 我不认为在第一次发送结束之前做第二次发送是个好主意。

Similarly, I don't think it is a good idea to change the buffer before the send is finished. 同样,我认为在发送完成之前更改缓冲区并不是一个好主意。

I would be inclined to store the data in some sort of queue. 我倾向于将数据存储在某种队列中。 One thread can keep adding data to the queue. 一个线程可以继续向队列添加数据。 The second thread can work in a loop. 第二个线程可以循环工作。 Do a send and wait for it to finish. 发送并等待它完成。 If there is more data do another send, else wait for more data. 如果有更多数据执行另一次发送,则等待更多数据。

You would need a critical section (or some such) to nicely share the queue between the threads and possibly an event or a semaphore for the sending thread to wait on if there is no data ready. 您需要一个关键部分(或某些部分)来很好地共享线程之间的队列,并且可能是一个事件或信号量,以便发送线程在没有数据就绪时等待。

Now, what strategies can I implement in order to maintain a big sack of such buffers, how should I handle them, how can I avoid performance penalty, etc'? 现在,我可以实施哪些策略来维护大量的缓冲区,我应该如何处理它们,如何避免性能损失等等?

It's difficult to know the answer without knowing more about your specific design. 在不了解您的具体设计的情况下,很难知道答案。 In general I'd avoid maintaining your own "sack of buffers" and instead use the OS's built in sack of buffers - the heap. 一般来说,我会避免维护你自己的“缓冲区”,而是使用操作系统内置的缓冲区 - 堆。

But in any case, what I would do in the general case is expose an interface to the callers of your code that mirror what WSASend is doing for overlapped i/o. 但无论如何,在一般情况下我会做的是向代码的调用者公开一个接口,该接口反映了WSASend为重叠i / o做的事情。 For example, suppose you are providing an interface to send a specific struct: 例如,假设您提供了一个发送特定结构的接口:

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);

I would require users of SendFoo allocate a Foo instance with new, and tell them that after calling SendFoo the memory is no longer "owned" by their code and they therefore shouldn't use it. 我会要求SendFoo的用户使用new分配一个Foo实例,并告诉他们在调用SendFoo后,内存不再由其代码“拥有”,因此他们不应该使用它。

You can enforce this even further with a little trickery: 您可以通过一些小技巧进一步强制执行此操作:

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

This allows the body of SendFoo to send the address of the memory down to WSASend, but modify the passed in pointer to NULL, severing the link between the caller's code and their memory. 这允许SendFoo的主体将内存的地址发送到WSASend,但是将传入的指针修改为NULL,从而切断调用者代码与其内存之间的链接。 Of course, you can't really know what the caller is doing with that address, they may have a copy elsewhere. 当然,你无法真正知道调用者正在使用该地址做什么,他们可能在其他地方有一个副本。

This interface also enforces that a single block of memory will be used with each WSASend. 此接口还强制每个WSASend将使用单个内存块。 You are really treading into more than dangerous territory trying to share one buffer between two WSASend calls. 您正试图在两个WSASend调用之间共享一个缓冲区,而不仅仅是在进入危险区域。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM