【问题标题】:Java NIO: Relationship between OP_ACCEPT and OP_READ?Java NIO:OP_ACCEPT 和 OP_READ 之间的关系?
【发布时间】:2009-07-28 00:04:25
【问题描述】:

我正在为我的项目重新编写核心 NIO 服务器网络代码,并试图弄清楚何时应该“存储”连接信息以供将来使用。例如,一旦客户端以通常的方式连接,我就为该连接的客户端存储并关联 SocketChannel 对象,以便我可以随时向该客户端写入数据。通常我使用客户端的 IP 地址(包括端口)作为映射到 SocketChannel 对象的 HashMap 中的键。这样,我可以轻松地查找他们的 IP 地址并通过该 SocketChannel 向他们异步发送数据。

这可能不是最好的方法,但它确实有效,而且项目太大而无法更改其基本网络代码,尽管我会考虑建议。然而,我的主要问题是:

我应该在什么时候“存储” SocketChannel 以备将来使用?一旦连接被接受(通过 OP_ACCEPT),我一直在存储对 SocketChannel 的引用。我觉得这是一种有效的方法,因为我可以假设当 OP_READ 事件到来时映射条目已经存在。否则,每次 OP_READ 发生时,我都需要对 HashMap 进行计算量很大的检查,很明显与 OP_ACCEPT 相比,客户端会发生更多这些情况。我想,我担心可能有一些连接被接受(OP_ACCEPT)但从未发送任何数据(OP_READ)。这可能是由于防火墙问题或客户端或网络适配器出现故障。我认为这可能会导致“僵尸”连接不活跃但也永远不会收到关闭消息。

我重写网络代码的部分原因是,在极少数情况下,我的客户端连接已进入奇怪状态。我在想我处理 OP_ACCEPT 与 OP_READ 的方式,包括我用来假设连接“有效”并且可以存储的信息,可能是错误的。

很抱歉,我的问题没有更具体,我只是在寻找最好、最有效的方法来确定 SocketChannel 是否真正有效,以便我可以存储对它的引用。非常感谢您的帮助!

【问题讨论】:

    标签: java sockets nio


    【解决方案1】:

    如果您使用选择器和非阻塞 IO,那么您可能需要考虑让 NIO 自己跟踪通道与其有状态数据之间的关联。调用 SelectionKey.register() 时,可以使用三参数形式传入一个“附件”。在未来的每一点,SelectionKey 将始终返回您提供的附件对象。 (这显然是受到操作系统级 API 中“void *user_data”类型参数的启发。)

    该附件与密钥保持一致,因此它是保存状态数据的方便位置。好消息是,从通道到键到附件的所有映射都已经由 NIO 处理,因此您可以减少簿记。簿记(如 Map 查找)在 IO 响应器循环内部确实会受到伤害。

    作为一项附加功能,您还可以稍后更改附件,因此如果您在协议的不同阶段需要不同的状态对象,您也可以在 SelectionKey 上跟踪。

    关于你发现你的连接的奇怪状态,使用 NIO 和选择器可能会咬你的一些微妙之处。例如,一旦一个 SelectionKey 发出它已准备好读取的信号,它将在下次其他线程调用 select() 时继续准备好读取。因此,很容易导致多个线程尝试读取套接字。另一方面,如果您在读取时尝试取消注册要读取的密钥,那么您最终可能会遇到线程错误,因为 SelectionKeys 及其兴趣操作只能由线程操作实际上调用 select()。所以,总的来说,这个 API 有一些尖锐的边缘,要正确处理所有的状态是很棘手的。

    哦,还有另一种可能性,取决于谁先关闭套接字,在您明确询问之前,您可能会或可能不会注意到已关闭的套接字。我不记得我脑海中的确切细节,但它是这样的:客户端半关闭它的套接字末端,这 not 表示选择键上的任何准备好的操作,所以socketchannel永远不会被读取。这会在客户端留下一堆处于 TIME_WAIT 状态的套接字。

    作为最后的建议,如果您正在做异步 IO,那么我绝对推荐“面向模式的软件架构”(POSA)系列中的几本书。第 2 卷涉及很多 IO 模式。 (例如,NIO 非常适合第 2 卷中的 Reactor 模式。它解决了我上面提到的一堆状态处理问题。)第 4 卷包含了这些模式,并将它们嵌入到更广泛的分布式系统环境中。这两本书都是非常宝贵的资源。

    【讨论】:

    • 哇,感谢您提供大量(和质量)的帮助!我现在正在尽我所能研究这些东西。 -- 我认为你的意思是 SocketChannel.register(),而不是 SelectionKey.register(),对吧? -- 看来我可以通过只为我的 NIO 使用一个线程来避免一些线程问题,对吧? -- 定期检查应该关闭的候选人的 TIME_WAIT 状态的套接字是否合理? (你提到的半封闭问题)非常感谢!
    • 1) 是的,register() 在 SelectableChannel(SocketChannel 扩展)上。这就是我的意思。 2)您可以只使用一个线程进行选择,然后将通道关闭以进行读/写操作。如果您正在处理许多并发连接,您会发现一个线程本身无法跟上。这是棘手的交接。 3)定期寻找半封闭的套接字对我有用。
    【解决方案2】:

    另一种方法可能是查看现有的 NIO 套接字框架,可能的候选者是:

    【讨论】:

      猜你喜欢
      • 2015-09-08
      • 1970-01-01
      • 1970-01-01
      • 2011-04-28
      • 2021-01-19
      • 2011-09-06
      • 2016-06-10
      • 2013-02-03
      • 2019-07-10
      相关资源
      最近更新 更多