Java NIO:OP_ACCEPT和OP_READ之间的关系?

时间:2009-07-28 00:04:25

标签: java sockets nio

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

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

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

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

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

2 个答案:

答案 0 :(得分:4)

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

该附件与密钥保持一致,因此它是保存状态数据的便利位置。好消息是,从通道到密钥到附件的所有映射都将由NIO处理,因此您可以减少簿记。簿记 - 就像地图查找 - 在IO响应器循环中真的会受到伤害。

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

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

哦,还有一种可能性,取决于谁首先关闭套接字,你可能会或可能不会注意到一个关闭的套接字,直到你明确要求。我无法回想起我头顶的确切细节,但它是这样的:客户端半闭合它的套接字结束,这表示选择键上的任何就绪操作,所以socketchannel永远不会被读取。这可以在客户端上留下一堆处于TIME_WAIT状态的套接字。

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

答案 1 :(得分:0)

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