【问题标题】:Avoiding high CPU usage with NIO使用 NIO 避免高 CPU 使用率
【发布时间】:2013-02-14 18:10:11
【问题描述】:

我编写了一个多线程游戏服务器应用程序,它使用NIO 处理多个同时连接。不幸的是,一旦第一个用户连接,该服务器就会在一个核心上产生完整的 CPU 负载,即使该用户实际上并未发送或接收任何数据。

下面是我的网络处理线程的代码(为了便于阅读,缩写为基本部分)。 ClientHandler 类是我自己的类,它为游戏机制进行网络抽象。以下示例中的所有其他类均来自java.nio

如您所见,它使用while(true) 循环。我的理论是,当密钥可写时,selector.select() 将立即返回并调用clientHandler.writeToChannel()。但是当处理程序返回而不写任何东西时,密钥将保持可写状态。然后立即再次调用 select 并立即返回。所以我忙着旋转。

有没有办法设计网络处理循环,只要 clientHandlers 没有数据要发送,它就会休眠?请注意,低延迟对我的用例至关重要,所以当没有处理程序有数据时,我不能让它休眠任意数量的毫秒。

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();
        }
    }
}

【问题讨论】:

  • 我不确定select()/NIO 在频道可写时等待是否有用 - 操作系统网络缓冲区应该能够处理这个问题.如果您的瓶颈是数据是否可以写入,您应该等待。 (即我想在你的ClientHandlers 上。)
  • 我会考虑使用像 netty 或 mina 这样的框架来支持 NIO。这些已经解决了大部分错误。如果您的连接数少于 1000,则可以使用阻塞 IO 或 NIO。

标签: java network-programming cpu-usage


【解决方案1】:
SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

问题就在这里。 SocketChannel 几乎总是可写的,除非套接字发送缓冲区已满。因此,它们通常不应注册为OP_WRITE:,否则您的选择器循环将旋转。只有在以下情况下才应如此注册:

  1. 有东西要写,而且
  2. 之前的write() 已返回零。

【讨论】:

  • 那么这是否意味着我可以根据需要抵抗和注销 OP_WRITE?
  • @mal 当然。这就是你必须做的事情。
  • 好的,这个问题很模糊,我会开新帖详细stackoverflow.com/questions/67697853/…
【解决方案2】:

我看不出为什么必须使用同一个选择器进行读取和写入。我会在线程中使用一个选择器来进行读取/接受操作,它会一直阻塞,直到新数据到达。

然后,使用单独的线程和选择器进行写入。您提到您正在使用缓存来存储消息,然后再将它们发送到可写通道上。实际上,通道不可写的唯一情况是内核的缓冲区已满,因此它很少不可写。实现这一点的一个好方法是有一个专用的编写器线程,它被赋予消息并处于休眠状态;当应该发送新消息时,它可以是interrupt()ed,也可以在阻塞队列上使用take()。每当有新消息到达时,它将解除阻塞,对所有可写键执行select() 并发送任何未决消息;仅在极少数情况下,消息必须保留在缓存中,因为通道不可写。

【讨论】:

  • 为什么要使用专用的编写器线程?它不会阻塞,因此您不需要单独的线程。
猜你喜欢
  • 2014-03-23
  • 1970-01-01
  • 2015-02-28
  • 2020-02-13
  • 2021-12-09
  • 2014-07-16
  • 2014-08-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多