AcceptEx() 同步完成?

AcceptEx() synchronous completion?

我正在使用 IO 完成端口和 AcceptEx(),同时学习服务器,并且正在研究 Len Holgate 的免费服务器框架来执行此操作。他有以下代码:

// Basically calls AcceptEx() via a previously obtained function pointer
if (!CMSWinSock::AcceptEx(
      m_listeningSocket,
      pSocket->m_socket,
      reinterpret_cast<void*>(const_cast<BYTE*>(pBuffer->GetBuffer())),
      bufferSize,
      sizeOfAddress,
      sizeOfAddress,
      &bytesReceived,
      pBuffer))
   {
      const DWORD lastError = ::WSAGetLastError();

      if (ERROR_IO_PENDING != lastError)
      {
         Output(_T("CSocketServerEx::Accept() - AcceptEx: ") + GetLastErrorMessage(lastError));

         pSocket->Release();           
         pBuffer->Release();
      }
   }
   else
   {
      // Accept completed synchronously. We need to marshal the data recieved over to the 
      // worker thread ourselves...

      m_iocp.PostStatus((ULONG_PTR)m_listeningSocket, bytesReceived, pBuffer);
   }

我对 "Accept completed synchronously" 其他情况感到困惑。我已经多次尝试让这个代码路径被命中(通过在我发出 AcceptEx 之前暂停代码,连接,然后恢复代码),但是每当我尝试调用总是失败并显示 ERROR_IO_PENDING然后我收到通知包。此外,我已阅读 this MS knowledgebase article(我可能误解了)其中指出

Additionally, if a Winsock2 I/O call returns SUCCESS or IO_PENDING, it is guaranteed that a completion packet will be queued to the IOCP when the I/O completes

但是,我认为这不适用于 AcceptEx(),因为参数 lpdwBytesReceived

AcceptEx() 状态的 dox

This parameter is set only if the operation completes synchronously.

所以它似乎 可以 同步完成...谁能告诉我 如何 AcceptEx() 可以同步完成(即如何在我的服务器中复制它?)

Additionally, if a Winsock2 I/O call returns SUCCESS or ERROR_IO_PENDING, it is guaranteed that a completion packet will be queued to the IOCP when the I/O completes

这适用于 any I/O 如果完成端口与文件相关联的请求。但是从 windows vista 开始,这也取决于从 notification mode 设置的文件句柄。

但需要先从本机视图开始查看。

默认情况下,如果 FILE_SKIP_COMPLETION_PORT_ON_SUCCESS 未设置,存在 3 种情况 returned NTSTATUS status :

  1. NT_SUCCESS(status)status >= 0 - 将完成
  2. NT_ERROR(status)status >= 0xc0000000 - 不会完成
  3. NT_WARNING(status) or status < 0xc0000000 - 不清楚 - 如果这个 来自 I/O 经理的错误(比如 - STATUS_DATATYPE_MISALIGNMENT - 将 没有完成)。如果这个错误来自驱动程序(比如 STATUS_NO_MORE_FILES - 即将完成)。
在这种情况下,

win32 层通常会单独检查 STATUS_PENDING 和 return ERROR_IO_PENDING(但存在例外情况,例如 ReadDirectoryChangesW)。否则万一 NT_ERROR(status) api return 失败并设置错误代码。否则 return 成功。可见 NT_WARNING(status) 的情况视为成功,但在这种情况下,如果 I/O 管理器出错,则不会完成。如果参数不正确,I/O 通常 return 来自 NT_ERROR(status) 范围的错误。只有我知道的情况(对于异步 api)- STATUS_DATATYPE_MISALIGNMENT 可以被 return 编辑以防错误对齐的缓冲区,当 I/O 管理器对缓冲区对齐有特殊的了解时。在 NtNotifyChangeDirectoryFileReadDirectoryChangesW 用于 win32)或 NtQueryDirectoryFile(没有对应的 win32 api)。所以只有我知道什么时候不会完成的情况,当 win32 return 成功时 - 使用未对齐的 lpBuffer 调用 ReadDirectoryChangesW (它必须是 DWORD-aligned ) - 在这种情况下 I/O 管理器只是 return STATUS_DATATYPE_MISALIGNMENT 但 win32 层将其解释为成功代码并且 return 为真。但在这种情况下不会完成。然而,这种情况很少见,您可能需要为此使用错误的对齐结构。所以一般来说是的:

默认情况下如果I/O调用returns SUCCESSERROR_IO_PENDING将排队一个完成条目到端口。 (我尝试描述的特殊例外情况

如果我们在文件对象上设置 FILE_SKIP_COMPLETION_PORT_ON_SUCCESS(注意这是每个文件对象而不是每个文件句柄 - 文档不完全在这里)一切都会变得更加简单和高效 - 完成条目将排队到端口- 当且仅当 I/O 请求 return STATUS_PENDING 时。 ERROR_IO_PENDING 从 win32 视图(除了 ReadDirectoryChangesW 可能是其他 api ?),其中 win32 层只是丢失了 return 代码信息)

However, I am thinking this doesn't apply to AcceptEx()

你错了。这,我怎么说,适用于任何 io 请求。 "This parameter is set only if the operation completes synchronously." - 那又怎样?


如果查看代码片段,可以清楚地看到代码假设 - 如果 AcceptEx 同步完成并且没有发生错误 - 将没有 io 完成。或 SetFileCompletionNotificationModes(m_listeningSocket, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) 调用或代码错误 - 在这种情况下将是 io 完成而不需要 m_iocp.PostStatus - 这是致命错误。但是我怀疑代码使用了 FILE_SKIP_COMPLETION_PORT_ON_SUCCESS - 所以它错了。但从未出现错误,因为 AcceptEx 的驱动程序端实现(下划线 ioctl)从不 return STATUS_SUCCESS:它检查参数 - 如果错误 - 只是 return 一些错误,否则总是 return STATUS_PENDING。结果,对于异步套接字 AcceptEx 永远不会 return true 并且代码永远不会跳转到错误的其他情况。但无论如何代码是错误的。我也认为设计不是最好的 - 如果我们确定不会完成 - 最好直接调用完成例程 returned 错误代码而不是 Release() (这将在完成例程中完成)或 PostStatus - 为什么 post?! - 直接打电话。


how AcceptEx() can complete synchronously

非常简单 - 如果 m_listeningSocket 同步 文件对象的句柄。但是在这种情况下,您不能将 IOCP 绑定到文件(它只能在异步文件对象的情况下绑定)。


关于lpdwBytesReceived参数-系统复制IO_STATUS_BLOCKInformation成员或者如果想要OVERLAPPED.InternalHigh,以防操作完成即可。如果未决 returned - 此数据根本没有准备好且未填充。你得到了由 io 完成 return 的实际字节数