WSA 发送到多线程 iocp 服务器中的所有连接的套接字

Posted

技术标签:

【中文标题】WSA 发送到多线程 iocp 服务器中的所有连接的套接字【英文标题】:WSAsend to all connected socket in multithreaded iocp server 【发布时间】:2013-11-04 01:51:06 【问题描述】:

我在 IOCP 服务器上工作(重叠 I/O、4 个线程、CreateIoCompletionPortGetQueuedCompletionStatusWSASend 等)。我还创建了一个自动重置事件,并将句柄放在每个异步 I/O 操作的 OVERLAPPED 结构中。

问题是:如何正确发送缓冲区所有连接的套接字?每个套接字都存储在上下文信息结构的链表中。

我不确定下面的方法是否可行?

...
DWORD WINAPI WorkerThread()  // 1 of 4 workthread
...
GetQueuedCompletionStatus(...);
...
PPER_SOCKET_CONTEXT  pTmp1, pTmp2;
pTmp1 = g_pCtxtList; // start of linked list with sockets
EnterCriticalSection(&g_CriticalSection);
while( pTmp1 ) 
  
pTmp2 = pTmp1->pCtxtBack;
         WaitForSingleObject(pTmp1->pIOContext->Overlapped.hEvent,infinite);                
          // if there is any pending wsasend on this socket,wait to completed, so
          // we can post another wsasend using the same overlapped structure
          // and buffer
           WSASend(pTmp1->Socket,...,&(pTmp1->pIOContext->Overlapped), NULL);
       pTmp1 = pTmp2;
 
LeaveCriticalSection(&g_CriticalSection);
...

如果另一个线程也尝试同时做同样的工作会发生什么? 在所有线程中使用 GQCS 和等待函数是个好主意吗? 对于多线程 iocp 服务器中所有客户端的任何关于 wsasends 的线索将不胜感激。 谢谢

【问题讨论】:

@Len Holgate - 你为什么删除你的答案?它比我的好,(因为我没有正确阅读 OP 问题)。我补充说 refCount 需要自动递减,但基本上,你的答案应该有效。放回去!! 当/如果他取消删除它时,请支持 Len Holgate 的回答 - 这比我的要好。 马丁,对不起,我删除了,因为我认为我没有阅读过 OP 问题(基于我在您的回答中再次阅读它:)) 【参考方案1】:

我不确定我是否理解其中的一些内容。 IOCP 通常不使用 OVL 结构中的 hEvent 字段。 I/O 完成通过将完成消息排队到“完成端口”(即队列)来发出信号。您似乎将 hEvent 字段用于一些“不寻常”的额外信号来管理单个发送数据缓冲区和 OVL 块。

显然,我没有从您的帖子中了解全部内容,但在我看来,您在 tx 方面为自己做了大量工作,并且序列化发送会扼杀性能:)

您是否必须使用相同的 OVL/缓冲区对象进行连续发送?我通常做的是为每次发送使用不同的 OVL/缓冲区,然后立即将其排队。内核将按顺序发送缓冲区并为每个缓冲区返回一个完成消息。一个套接字上的多个 IOCP tx 请求没有问题——这就是 OVL 块的用途——在内核堆栈中将它们链接在一起。

有多个 IOCP 接收未完成的套接字请求的问题 - 可能会发生两个池线程同时获取同一套接字的完成数据包,因此可能导致无序处理。 “正确”修复该问题需要在每个发出的 rx 缓冲区/OVL 对象中增加序列号,在每个套接字对象中设置临界区和缓冲区列表,以“保存”无序缓冲区,直到所有更早已经处理的。我怀疑许多 IOCP 服务器只是通过一次只接收一个 rx IOCP 请求来回避这个问题(可能以牺牲性能为代价)。

如果以这种方式通过大量缓冲区可能会有点费力,如果它们被不断地构造和销毁,所以我通常不会打扰,只是在启动时创建几千个并推送它们,(好的,指针给他们),放到生产者-消费者“池队列”上,当需要 tx 或 rx 时将它们弹出并再次将它们推回。在 tx 的情况下,当 IOCP 池线程之一接收到发送完成消息时,就会发生这种情况。在 rx 的情况下,当池线程(或其他一些线程已将对象通过池线程排队)处理它并且不再需要它时,就会发生这种情况。

啊..您想将完全相同的内容发送到套接字列表 - 就像聊天服务器类型一样。

好的。那么一个缓冲区和多个OVL块呢?我没有尝试过,但不明白为什么它不起作用。在单个缓冲区对象中,保留在“发送到所有客户端”循环中发送的重叠发送请求的原子引用计数。当您在完成数据包中恢复缓冲区时,将 refCount 递减至零,并在降至 0 时删除/重新池化缓冲区。

我认为这应该可行,(?)。

【讨论】:

,感谢您快速而有价值的回复。仔细阅读您的帖子后,我上面的方法似乎是幼稚和不恰当的。 “您必须使用相同的 OVL/缓冲区对象进行连续发送吗?”不,我没有。但是在阅读了很多关于 iocp 的文章之后,我仍然不知道如何处理这个问题。 “通过很多缓冲区......”,“好的。那么一个缓冲区和多个 OVL 块怎么样?......”我认为这两个建议会带来一些亮点,我将在此基础上进行另一种方法,谢谢。 每次发送或接收都必须有一个唯一的 OVERLAPPED 结构,该结构从您发出发送/接收到完成时一直处于“使用中”。完成后,您可以将 OVERLAPPED 重用于另一个操作 - 它是“每个操作数据”。【参考方案2】:

正如 Martin 所说,这将表现得非常糟糕,并且可能会破坏使用在整个发送到所有连接期间锁定的套接字列表的任何东西的性能。您没有说这是 UDP 还是 TCP,但如果是 TCP,请注意您现在正在将服务器性能的控制权交给客户端,因为慢速客户端连接上的 TCP 流控制可能会导致写入完成延迟(请参阅here)- 我假设您正在使用写完成来触发事件?

我假设您的实际要求是您希望避免在服务器上复制数据并分配多个缓冲区,每个连接一个缓冲区是由于内存限制或因为您已经分析了内存副本并发现它很昂贵。

我处理这个问题的方法是拥有一个引用计数缓冲区和一个“缓冲区句柄”,它只是一个稍微扩展的重叠结构,它引用您的单个数据缓冲区并提供您需要的 WSABUF。然后,您可以使用唯一的“缓冲区句柄”向每个连接发出“即发即忘”写入,所有这些都引用单个底层缓冲区。一旦所有写入完成,缓冲区上的引用计数就会减少到零并进行清理——正如 Martin 所说,清理工作最好通过将缓冲区放入池中以供以后重用来实现。

注意:我不确定我是否真的理解你想要做什么(所以我最初删除了我的答案),如果我没有关注,请告诉我,我会调整...

【讨论】:

,thx 快速回答,看起来我对 MSDN 中的这些准则的使用不当:“在所有异步 I/O 操作到文件对象已完成。” “一个常见的错误是在之前的异步操作完成之前重用一个 OVERLAPPED 结构。你应该为每个请求使用一个单独的结构。”在这种情况下,OVL 和 WFSO 中的 Hevent 是完全没有必要的。无论如何,我会尝试根据您的建议进行修改,谢谢。 +1 - 这确实是一个非常好的答案。正如 Len 所建议的,由多个 OVL 块寻址的单个 refCounted 缓冲区是我满足您要求的方式。它应该可以工作 - 我唯一没有尝试过的预订,但我相当确定它会正常工作。 马丁,它工作得很好;多年来一直在使用它。无论如何,我所有的缓冲区都是引用计数的,因此让“缓冲区句柄”为每个连接提供唯一的 OVERLAPPED 和 WSABUF 非常容易。如果您有兴趣,请参阅serverframework.com/ServerFramework/latest/Docs/…。 好的,谢谢您的信息!我不使用 refCounted 缓冲区(或缓冲区数组),因为我的服务器没有“聊天”广播功能。现在我知道它有效,也许我会添加它:) 引用计数对我来说很方便,但我希望减少它的使用,因为它不能很好地与 NUMA 一起使用(性能方面)。

以上是关于WSA 发送到多线程 iocp 服务器中的所有连接的套接字的主要内容,如果未能解决你的问题,请参考以下文章

发送后如何正确关闭套接字(使用 IOCP)?

多线程 IOCP 客户端问题

我可以在 IOCP 中使用 accept() 吗?

IOCP 服务器并使用单个 wsasend 发送数据

分享我写的IOCP:源码+思路(转载)

IOCP实现高并发以及与传统socke编程的对比