2013-02-14 72 views
2

我编写了一个多线程的游戏服务器应用程序,它可以使用NIO处理多个同时连接。不幸的是,当第一个用户连接时,即使该用户没有真正发送或接收任何数据,该服务器也会在一个内核上生成全部CPU负载。使用NIO避免CPU使用率过高

下面是我的网络处理线程的代码(缩写为可读性的基本部分)。类ClientHandler是我自己的类,为游戏机制做网络抽象。以下示例中的所有其他课程均为java.nio

正如你所看到的,它使用了一个while(true)循环。我的理论是,当一个密钥可写时,selector.select()将立即返回,并调用clientHandler.writeToChannel()。但是,当处理程序返回而没有写入任何内容时,密钥将保持可写。然后选择立即再次调用并立即返回。所以我忙得不可开交。

有没有办法设计网络处理循环的方式,只要没有数据要发送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(); 
     } 
    } 
} 
+0

我不确定在通道是*可写的时候,select()/ NIO是否有用于等待 - 操作系统网络缓冲区应该能够处理这个问题。如果你的瓶颈是数据是否可以写入,你应该等待。 (也就是我想你的'ClientHandler's) – millimoose 2013-02-14 18:24:58

+0

我会考虑使用一个框架来支持像netty或mina这样的NIO。这些大部分的错误已经解决了。如果连接数少于1000,则可以使用阻塞IO或NIO。 – 2013-02-14 18:42:46

回答

4

我没有看到任何理由为什么阅读和写作必须发生在相同的选择器。我会在一个线程中使用一个选择器进行读取/接受操作,并且在新数据到达之前它将一直被阻塞。

然后,使用单独的线程和选择器进行写入。您提到您正在使用缓存来存储消息,然后才能将消息发送到可写频道。在实践中,通道不可写的唯一时间是内核缓冲区已满,因此很少不可写。实现这一点的一个好方法是创建一个专用的写入器线程,给出消息并进入休眠状态;在发送新消息时可以使用interrupt(),也可以在阻塞队列上使用take()。每当新消息到达时,它将解除阻止,在所有可写密钥上执行select()并发送任何未决消息;只有在极少数情况下,由于通道不可写,所以消息必须留在缓存中。

4
SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); 

问题在这里。 SocketChannel几乎总是可写的,除非套接字发送缓冲区已满。因此,他们通常不会注册OP_WRITE:,否则您的选择器循环会旋转。他们只应如此登记,如果:

  1. 有东西可写,并
  2. 事先write()已经回到零。
+0

那么这是否意味着我可以根据需要注销并取消注册OP_WRITE? – 2015-06-11 10:42:13

+0

@bot_bot当然。 – EJP 2015-08-24 06:04:52