4

我正在为我的项目重新编写核心 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 是否真正有效,以便我可以存储对它的引用。非常感谢您的帮助!

4

2 回答 2

4

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

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

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

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

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

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

于 2009-07-28T01:24:21.947 回答
0

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

于 2009-07-28T16:58:11.510 回答