Phi*_*ipp 2 java network-programming cpu-usage
我编写了一个多线程游戏服务器应用程序,它使用NIO处理多个同时连接.不幸的是,只要第一个用户连接,该服务器就会在一个核心上生成完整的CPU负载,即使该用户实际上没有发送或接收任何数据.
下面是我的网络处理线程的代码(缩写为可读性的基本部分).该类ClientHandler是我自己的类,它为游戏机制进行网络抽象.以下示例中的所有其他类都来自java.nio.
如您所见,它使用while(true)循环.我的理论是,当一个密钥可写时,selector.select()将立即返回并被clientHandler.writeToChannel()调用.但是当处理程序返回而没有写任何东西时,密钥将保持可写状态.然后立即再次调用select并立即返回.所以我忙着旋转.
有没有一种方法可以设置网络处理循环,只要没有clientHandlers发送的数据就可以休眠?请注意,低延迟对于我的用例至关重要,因此当没有处理程序拥有数据时,我不能让它睡眠任意数量的ms.
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
server.socket().bind(new InetSocketAddress(port));
Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
// wait for connections
while(true)
{
// Wait for next set of client connections
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> i = keys.iterator();
while (i.hasNext()) {
SelectionKey key = i.next();
i.remove();
if (key.isAcceptable()) {
SocketChannel clientChannel = server.accept();
clientChannel.configureBlocking(false);
clientChannel.socket().setTcpNoDelay(true);
clientChannel.socket().setTrafficClass(IPTOS_LOWDELAY);
SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
ClientHandler clientHanlder = new ClientHandler(clientChannel);
clientKey.attach(clientHandler);
}
if (key.isReadable()) {
// get connection handler for this key and tell it to process data
ClientHandler clientHandler = (ClientHandler) key.attachment();
clientHandler.readFromChannel();
}
if (key.isWritable()) {
// get connection handler and tell it to send any data it has cached
ClientHandler clientHandler = (ClientHandler) key.attachment();
clientHandler.writeToChannel();
}
if (!key.isValid()) {
ClientHandler clientHandler = (ClientHandler) key.attachment();
clientHandler.disconnect();
}
}
}
Run Code Online (Sandbox Code Playgroud)
SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
Run Code Online (Sandbox Code Playgroud)
问题出在这里.除非套接字发送缓冲区已满,否则SocketChannel几乎总是可写的.因此,他们通常不应该注册,OP_WRITE:否则您的选择器循环将旋转.如果符合以下条件,他们应该如此注
write()已返回零.我没有看到任何理由为什么读取和写入必须使用相同的选择器.我会在一个线程中使用一个选择器进行读/接受操作,它将一直阻塞,直到新数据到达.
然后,使用单独的线程和选择器进行写入.您提到在使用可写通道发送消息之前使用缓存来存储消息.在实践中,通道不可写的唯一时间是内核的缓冲区已满,因此它很少是不可写的.实现这一点的一个好方法是拥有一个专用的编写器线程,该线程被赋予消息并且正在休眠; 它可以在interrupt()应该发送新消息时使用,也可以take()在阻塞队列上使用.每当有新消息到达时,它将解锁,select()对所有可写密钥执行操作并发送任何待处理消息; 仅在极少数情况下,由于通道不可写,因此消息必须保留在缓存中.
| 归档时间: |
|
| 查看次数: |
3189 次 |
| 最近记录: |