2013-02-14 174 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