【问题标题】:How to close a SOCKET when using IOCP?使用 IOCP 时如何关闭 SOCKET?
【发布时间】:2017-07-18 05:49:42
【问题描述】:

我有一个使用 IOCP 的服务器应用程序。我想知道关闭SOCKET 的正确方法是什么。

如果我只是简单地调用closesocket()(例如SOCKET 的句柄为12345),而这个SOCKET 有待处理的IO 操作(例如:待处理的WSARecv() 请求),那么可能会发生以下情况:

  • 我打电话给closesocket(),这将破坏SOCKET

  • 我接受另一个 SOCKET12345 相同的句柄。

  • 我使用12345 的句柄将SOCKET 的待处理WSARecv() 完成数据包加入队列。现在我假设这个完成包是针对当前SOCKET,句柄为12345,但实际上它是针对之前关闭的SOCKET(这是他使用这种方法的主要问题)。

所以这显然是一个不好的方法。

似乎正确的第二种方法如下:

  • 我将struct 实例与每个SOCKET 关联起来。 struct 具有以下成员:一个名为 number_of_pending_IO_operationsint 和一个名为 Is_SOCKET_being_closedboolean

  • 1234563将number_of_pending_IO_operations 递减1。 1234563将Is_SOCKET_being_closed 设置为true
  • 当我要发出另一个IO操作时(例如:一个WSASend()请求),我会检查Is_SOCKET_being_closed的值,如果是true,我不会发出IO操作。

  • 1234563

当然我会有竞争条件,所以我会使用临界区。

第二种方法是关闭SOCKET的正确方法,还是有更好的方法?

【问题讨论】:

  • “第二种方法” - 是的,现在你走上了正确的道路。但是,您在这里不需要关键部分,所有这些都可以通过联锁操作来完成。 InterlockedIncrement/InterlockedDecrementnumber_of_pending_IO_operations。关于Is_SOCKET_being_closed 不够好。真的这里需要停机保护(用于插座手柄)。在用户模式下没有 api,但非常简单地自己实现它。 if (AcquireRundownProtection()) { WSASend(); ReleaseRundownProtection(); }when 想要 closesocket - 启动 rundown(但不在此处等待)以及何时完成关闭句柄。
  • 请注意,您传递给 I/O 操作的 OVERLAPPED 结构可以是包含上下文信息的较大结构的第一个成员,例如,指向与套接字关联的结构的指针。这样可以节省您在表格或类似物中查找套接字句柄的时间,这反过来意味着如果方便的话,没有令人信服的理由不关闭套接字。
  • RbMm 很高兴在这里见到其他驱动程序开发人员:-)。看起来你对微过滤器有一些工作经验。实际上,我确实在同一个代码库中的用户空间中实现了破旧保护,并且正在考虑将它用于这个目的(除了其他一些目的)——并在一些测量后决定反对它。破旧保护似乎更适合引用寿命较长且它们不产生的情况(或者处理 AddRef() 的失败更容易),并且当您有明确的所有者时,因此您保证只有一次调用 BeginRundown。
  • Harry Johnson,我看不出这种方法如何单独保证在另一个线程进入 WSARecv/WSASend() 时调用 closesocket() 时的正确性。至于在另一个结构中包含 OVERLAPPED,这确实是一个非常有用的技术。

标签: c sockets winapi winsock iocp


【解决方案1】:

我的客户端-服务器应用程序中遇到了类似的问题。我认为我没有比赛,没有破败保护,也没有关键部分。不过也有一些取舍。

  • 一次只有一个 WSARecv()。多个缓冲区——也许,但只有一个 WSARecv()。 WSARecv 完成(可能是内联),数据包从完成端口弹出,我快速检查我得到了什么并发出另一个 WSARecv()
  • 我有一个 Is_SOCKET_being_closed 的标志(实际上是一个只能上升的计数器)。反击,因为我可能同时决定在两个不同的地方终止套接字。
  • 设置 Is_SOCKET_being_closed 后,我调用 shutdown(),然后调用 CancelIoEx(),这将破坏之前发出的 WSARecv()。
  • CancelIoEx 和 WSARecv() 完成之间存在竞争——我在发出 WSARecv() 之前立即通过仔细检查 Is_SOCKET_being_closed 来消除它。即使发生竞态,WSARecv() 也注定会因为 shutdown() 而失败。
  • 结构被重新计算,其中一个 ref 由接收状态机持有,并且为 ptr 的每个合法所有者(如潜在发送者)持有一个 ref。如果您有合法的参考,您可以制作一份副本,您可以将其交给其他人(这与破旧的参考不同,因为它们的 AddRef() 可能会失败)。我只会在 ref 归零时关闭socket(),因为我不能保证没有线程通过所有检查并即将发出 WSARecv/WSASend。

【讨论】:

  • 我不确定每个套接字只有一个挂起的 WSARecv() 是否会影响性能 - 我猜不会,因为在 IOCP 排队时到达的任何数据只会由 TCP 驱动程序缓冲。如果你想有多个接收待处理,你总是可以引用计数它们。但我认为不这样做的主要原因是,如果两个这样的 IOCP 被不同的线程接收,你怎么知道它们属于什么顺序? :-)
  • Harry Johnson -- 考虑您决定将 TCP 代码重用于 UDP 的情况。至于 refcounting 接收,当然,它很可能会起作用,但我还没有完全考虑清楚。
  • TCP 和 UDP 有很大的不同,您需要修改您的代码。
  • 你可能是对的,我没有使用 UDP 的经验。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-05
  • 1970-01-01
  • 2012-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多