根据MSDN:
hEvent:如果在没有I/O完成例程(操作的lpCompletionRoutine参数设置为null)的情况下发出重叠I/O操作,则此参数应包含WSAEVENT对象的有效句柄或为null.
当我使用IOCP时,当我调用WSASend()或WSARecv()时,我将NULL传递给它们的最后一个参数(即lpCompletionRoutine):
WSASend(pIoRequest->GetSocket(), pIoRequest->GetWsaBuffer(), 1, NULL, pIoRequest->GetFlags(), pIoRequest, NULL);
WSARecv(pIoRequest->GetSocket(), pIoRequest->GetWsaBuffer(), 1, NULL, &(pIoRequest->GetFlags()), pIoRequest, NULL);
Run Code Online (Sandbox Code Playgroud)
我的"每个I/O数据"类(pIoRequest)看起来像:
class IoRequest : public WSAOVERLAPPED
{
public:
IoRequest()
{
...
SecureZeroMemory(this, sizeof(WSAOVERLAPPED));
hEvent = WSACreateEvent(); // A
}
...
void ResetForNextIoRequest()
{
WSACloseEvent(hEvent); // B
SecureZeroMemory(this, sizeof(WSAOVERLAPPED));
hEvent = WSACreateEvent(); // C
...
}
...
DWORD& GetFlags() { return m_dwFlags; }
...
private:
...
DWORD m_dwFlags;
...
};
Run Code Online (Sandbox Code Playgroud)
即使我注释掉上面标记为A,B和C的行,它似乎对我的程序的行为没有任何影响.
那么如何决定何时调用WSACreateEvent()或者只是将hEvent设置为NULL?
我想使用工作线程池和IO完成端口编写服务器.服务器应该在多个客户端之间处理和转发消息."每个客户端"数据位于ClientContext类中.使用工作线程交换此类实例之间的数据.我认为这是一个典型的场景.
但是,我有两个IO完成端口的问题.
(1)第一个问题是服务器基本上从客户端接收数据,但我不知道是否收到了完整的消息.事实上,WSAGetLastError()始终返回WSARecv()仍处于挂起状态.我试图用WaitForMultipleObjects()等待事件OVERLAPPED.hEvent.但是,它永远阻止,即WSARecv()永远不会在我的程序中完成.我的目标是在进一步处理开始之前完全确定已收到整条消息.我的消息在其标题中有一个"消息长度"字段,但我真的没有看到如何将它与IOCP函数参数一起使用.
(2)如果WSARecv()在下面的代码片段中被注释掉,程序仍会接收数据.那是什么意思?这是否意味着我根本不需要调用WSARecv()?我无法通过这些IO完成端口获得确定性行为.谢谢你的帮助!
while(WaitForSingleObject(module_com->m_shutdown_event, 0)!= WAIT_OBJECT_0)
{
dequeue_result = GetQueuedCompletionStatus(module_com->m_h_io_completion_port,
&transfered_bytes,
(LPDWORD)&lp_completion_key,
&p_ol,
INFINITE);
if (lp_completion_key == NULL)
{
//Shutting down
break;
}
//Get client context
current_context = (ClientContext *)lp_completion_key;
//IOCP error
if(dequeue_result == FALSE)
{
//... do some error handling...
}
else
{
// 'per client' data
thread_state = current_context->GetState();
wsa_recv_buf = current_context->GetWSABUFPtr();
// 'per call' data
this_overlapped = current_context->GetOVERLAPPEDPtr();
}
while(thread_state != STATE_DONE)
{
switch(thread_state)
{
case STATE_INIT:
//Check if completion packet has been posted by internal …Run Code Online (Sandbox Code Playgroud) 我最近在Windows上知道了这个名为IOCP的东西,我开始搜索有关它的更多信息,但我找不到任何最新版本(大多数示例都在代码项目上差不多5年)并且没有太多的指南或者教程.任何人都可以通过在线教程或示例项目(您编写并可以共享或其他开源项目)的形式推荐任何有关它的最新资源,甚至可以推荐一本关于它的书籍,因为如果它听起来好像我打算使用它它广泛,所以我会投资它.
谢谢.
我有经典的 IOCP 回调,它使 i/o 待处理请求出队、处理它们并取消分配它们,如下所示:
struct MyIoRequest { OVERLAPPED o; /* ... other params ... */ };
bool is_iocp_active = true;
DWORD WINAPI WorkerProc(LPVOID lpParam)
{
ULONG_PTR dwKey;
DWORD dwTrans;
LPOVERLAPPED io_req;
while(is_iocp_active)
{
GetQueuedCompletionStatus((HANDLE)lpParam, &dwTrans, &dwKey, (LPOVERLAPPED*)&io_req, WSA_INFINITE);
// NOTE, i could use GetQueuedCompletionStatusEx() here ^ and set it in the
// alertable state TRUE, so i can wake up the thread with an ACP request from another thread!
printf("dequeued an i/o request\n");
// [ process i/o request ] …Run Code Online (Sandbox Code Playgroud) 我正在寻找某种方法,当套接字变为可读/可写时(即下一个发送/接收将立即完成),在 I/O 完成端口上获取信号。基本上我想要一个重叠版本的WSASelect.
(是的,我知道对于许多应用程序来说,这是不必要的,您可以继续发出重叠的send调用。但在其他应用程序中,您希望延迟生成要发送的消息,直到最后一刻可能,如此处所讨论的。在这些情况下(a) 等待套接字可写,(b) 生成下一条消息,(c) 发送下一条消息,这很有用。)
到目前为止,我能想到的最好的解决方案是生成一个线程来调用selectthen PostQueuedCompletionStatus,这很糟糕并且不是特别可扩展......有没有更好的方法?
我正在开发一个多协议套接字服务器,在我第一次尝试时,我把它作为事件驱动,因为这是我所知道的最好方法,但是通过使用这种方法,我无法找到将应用程序特定数据链接到套接字的有效方法所以在每个事件中我都必须执行搜索才能将套接字链接到其上下文.经过一些研究后,我发现IO完成端口是一个更好的工作方式,所以经过大量阅读后,我终于能够重新编写代码,以便在IOCP方法下工作.
但经过一些进一步的研究后,我发现这篇文章(请阅读:"接受连接")建议通过在另一个线程上处理FD_ACCEPT事件来将接受操作与I/O进程分离,他还建议将此作为预防的手段恶意攻击...解释确实有意义,但作者没有考虑到在这种方法下没有办法(至少AFAIK)将套接字与其上下文数据相关联,因此在侦听的服务器上应用此建议绑定到多个地址的几个端口和每个处理不同协议的端口,必然涉及每个FD_ACCEPT事件的搜索操作,这可能(或可能不)打败解除接受的原始提议......这是我迁移到的原因首先完成港口.
所以..我想知道是否有2个完成端口,一个用于接受操作,一个用于I/O过程可以被认为是一般的热门,但特别是关于性能......或者最好只有一个?
更新:
经过一些实验,我发现,通过使用2个IOCP(在分离的线程上)将接受过程与I/O过程分离是根本不可行的,并且由于这个事实,不能获得效率提升.因此,即使他没有明确提及它,作者也是正确的,解耦这两个过程的唯一可行方法是通过处理FD_ACCEPT事件所有暗示,但如果他在他的陈述中也是正确的,那么唯一在我的情况下使其可行的方法是找到一种有效的方法将每个套接字链接到其上下文数据,或者换句话说; 无需搜索,所以原始问题仍然存在.
MSDN声明"对于Winsock应用程序,一旦调用WSASend函数,系统就拥有这些缓冲区,应用程序可能无法访问它们."
在服务器应用程序中,这是否意味着如果我想向多个客户端广播消息,我不能使用保存数据的单个缓冲区并使用该缓冲区在每个套接字上调用WSASend?
CreateIoCompletionPort()用于将套接字与完成端口相关联.但是,当这个套接字关闭时,我需要从完成端口"取消关联"它.我怎样才能做到这一点?
到目前为止,我发现的所有示例要么只能读取或写入,要么都是 10000 行的野兽,我什至不知道从哪里开始了解它们是如何工作的。
为了测试我的代码,我将浏览器指向我的服务器并发送了一个简单的 http 请求。结果令人困惑。
例如,在某一时刻,GetQueuedCompletionStatus 返回,WSARecv 表示它读取了我发送的 http 响应的字节数,尽管此响应应该(并且确实)最终到达客户端,并且 recvbuffer 甚至没有填充这些字节。
另外,我不明白一旦其他浏览器关闭连接,何时释放缓冲区,因为 GetQueuedCompletionStatus 在我调用 closesocket 后不断返回几次。
此外,我不知道 GetQueuedCompletionStatus 返回后何时有数据要读取或数据要写入。我可以尝试两者,看看哪个失败,但这似乎很粗鲁。
为了揭示我对 IOCP 可能存在的任何误解,我编写了一些伪代码来表达我认为我的代码的作用:
main {
create server socket
create io completion port
while true {
accept client socket
create completion port for client socket
create recv buffer and send buffer for client
call WSARecv once with 0 bytes for whatever reason
}
}
worker thread {
while true {
wait until GetQueuedCompletionStatus returns
do something if that failed, not …Run Code Online (Sandbox Code Playgroud) 我正在为消息传递库编写 IO 核心,并考虑使用 libuv 与在 linux 上使用原始 epoll 和在 Windows 上使用 IOCP(最终是其他的,solaris 事件等)。我喜欢 libuv 的可移植性,我正在关注性能。
epoll 和 IOCP 允许多个线程直接等待 IO 事件,由内核进行调度。尽管我没有任何数字,但可能比用户空间调度更有效。
libuv(根据我的阅读)有一个线程安全的事件循环,但我可以实现一个领导者-跟随者线程池。我的意思是一个线程(一次)是等待事件的“领导者”。当领导者收到事件时,它发出信号,追随者应该接替领导者。前领导者处理事件,然后成为追随者。
我的希望是,假设 libuv 得到有效实现,其性能应该接近原始多线程 epoll/IOCP。我会自己测量,但我想听听有经验的人的意见。