使用java选择器唯一标识客户端
我正在使用java.nio编写一个套接字服务器。因为我需要我的服务器使用0个线程,所以我使用的是使用java选择器唯一标识客户端,java,sockets,nio,Java,Sockets,Nio,我正在使用java.nio编写一个套接字服务器。因为我需要我的服务器使用0个线程,所以我使用的是java.nio.channels.Selector。我的代码如下所示 while (iterator.hasNext()) { SelectionKey key = (SelectionKey) iterator.next(); iterator.remove(); if (!key.isValid()) { continue; } if (key.
java.nio.channels.Selector
。我的代码如下所示
while (iterator.hasNext()) {
SelectionKey key = (SelectionKey) iterator.next();
iterator.remove();
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) { // Accept client connections
this.acceptClient(key);
} else if (key.isReadable()) { // Read from client
this.read(key);
} else if (key.isWritable()) {
this.write(key);
}
}
private void acceptClient(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = serverChannel.accept();
channel.configureBlocking(false);
SocketAddress clientAddress= channel.getRemoteAddress();
//clients is a Hashmap
clients.put(clientAddress, new Client());
clientConnected(clientAddress.toString());
System.out.println("Connected to: " + clientAddress);
channel.register(this.selector, SelectionKey.OP_READ);
}
正如您所看到的,我正在为每个接受的客户机创建一个新的客户机对象。我需要做的是,相关的客户端对象处理自己的读写。
我的方法是用地址唯一地标识客户机,并将其转发给相关的客户机对象。
我认为使用客户地址来唯一标识客户不是一个好方法。处理此问题的最佳方法是什么?使用
选择器注册频道时:
channel.register(this.selector, SelectionKey.OP_READ);
它将返回一个SelectionKey
,您可以在以后从选择器中选择时使用该键
使用该键填充IdentityHashMap
,以便可以将IO定向到正确的客户端
实例。正如EJP在回答中指出的,如果没有正确清理,这将泄漏选择键。如果您不想清理这些内容,那么也可以使用WeakHashMap
,但这样您就依赖于选择器的隐式行为而不是显式行为
EJP建议使用附件可能是最好的选择。虽然我可以想象一个更复杂的场景,您可能需要维护额外的附件,并且可能需要将附件重构为封装,但成本很高。当您使用选择器注册频道时:
channel.register(this.selector, SelectionKey.OP_READ);
它将返回一个SelectionKey
,您可以在以后从选择器中选择时使用该键
使用该键填充IdentityHashMap
,以便可以将IO定向到正确的客户端
实例。正如EJP在回答中指出的,如果没有正确清理,这将泄漏选择键。如果您不想清理这些内容,那么也可以使用WeakHashMap
,但这样您就依赖于选择器的隐式行为而不是显式行为
EJP建议使用附件可能是最好的选择。虽然我可以想象一个更复杂的场景,在这个场景中,您可能需要维护额外的附件,并且可能将附件重构为封装,但成本很高
我认为使用客户地址来唯一标识客户不是一个好方法
没什么问题。TCP/IP的语义保证每个接受的套接字都有不同的远程SocketAddress
但是你不需要它,也不需要地图。只需将客户端
保存为选择键
的附件即可。这样,当您关闭频道时,客户端
将随着选择键
自动消失
相反,如其他地方所建议的,更改为IdentityHashMap
会让您有机会泄漏SelectionKey
,从而泄漏其频道
和客户端
我认为使用客户地址来唯一标识客户不是一个好方法
没什么问题。TCP/IP的语义保证每个接受的套接字都有不同的远程SocketAddress
但是你不需要它,也不需要地图。只需将客户端
保存为选择键
的附件即可。这样,当您关闭频道时,客户端
将随着选择键
自动消失
相比之下,按照其他地方的建议更改为IdentityHashMap
会让您有机会泄漏SelectionKey
,因此它的频道和客户端也会泄漏。为什么要使用IdentityHashMap
,键的hashCode方法没有很好地定义,而且定义的状态似乎可能与其他键重叠,因此我不相信hashcodes对于每个SelectionKey都是唯一的(即使src表示hashCode()的默认实现,它依赖于地址),这在Javadoc中定义得很好,即都等于()
和hashCode()
是从对象继承的。我不理解“定义的状态似乎可能与其他键重叠”。它们(当前)是从对象继承的,并且对象对equals和hashcode必须遵守的内容有严格的要求。然而,关于对象的实现细节仍然模糊不清。通常,hashcode和equals会根据对象的内部状态进行更改;SelectionKey指定内部状态,包括准备就绪的内容,该状态在SelectionKey上随时间而变化。由于SelectionKey类没有明确声明hashcode和equals结果不会随时间而改变,因此我认为依赖对象的模糊实现是不安全的。定义良好的是,选择器将返回对同一SelectionKey实例的引用,因此,使用IdenityHashMap与显式定义的行为(忽略隐式定义的行为)非常吻合。为什么要使用IdentityHashMap
,键的hashCode方法没有很好地定义,而且定义的状态似乎可能与其他键重叠,因此我不相信hashcodes对于每个SelectionKey都是唯一的(即使src表示hashCode()的默认实现,它依赖于地址),这在Javadoc中定义得很好,即都等于()
和hashCode()
是从对象继承的。我不理解“定义的状态似乎可能与其他键重叠”。它们(当前)是从对象继承的,并且对象对equals和hashcode必须遵守的内容有严格的要求。然而,关于对象的实现细节仍然模糊不清。通常是hashcode和e