2013-02-14 3 views
2

Я написал многопоточное приложение для серверных игр, которое обрабатывает несколько одновременных подключений, используя NIO. К сожалению, этот сервер генерирует полную загрузку ЦП на одном ядре, как только первый пользователь подключается, даже когда этот пользователь фактически не отправляет или не получает никаких данных.Избегайте использования большого количества процессоров с помощью NIO

Ниже приведен код моей сетевой обработки (сокращенно важные части для удобства чтения). Класс ClientHandler - это мой собственный класс, который выполняет сетевую абстракцию для игровой механики. Все остальные классы в приведенном ниже примере - от java.nio.

Как вы можете видеть, он использует петлю while(true). Моя теория заключается в том, что когда ключ доступен для записи, selector.select() будет немедленно возвращаться и вызывается clientHandler.writeToChannel(). Но когда обработчик вернется, ничего не написав, ключ останется доступным для записи. Затем select вызывается снова немедленно и немедленно возвращается. Поэтому я занялся спиной.

Есть ли способ сконструировать цикл обработки сети таким образом, чтобы он спал, пока нет данных для отправки клиентомHandlers? Обратите внимание, что низкая латентность имеет решающее значение для моего случая использования, поэтому я не могу просто позволить ей спятить произвольное количество мс, когда у обработчиков нет данных.

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 - это все, что полезно ждать, когда каналы записываются * - сетевой буфер OS должен быть в состоянии позаботиться об этом. Если ваше узкое место в том, доступны ли данные для записи, вам следует подождать. (Например, на вашем «ClientHandler», я полагаю.) – millimoose

+0

Я бы подумал об использовании фреймворка для поддержки NIO, такого как netty или mina. Большинство из них уже решены. Если у вас менее 1000 соединений, вы можете использовать блокировку ввода-вывода или NIO. –

ответ

4

Я не вижу причин, по которым чтение и запись должны происходить с одним и тем же селектором. Я бы использовал один селектор в потоке для операций чтения/принятия и всегда будет блокироваться до тех пор, пока не появятся новые данные.

Затем используйте отдельный поток и селектор для записи. Вы упомянули, что используете кеш для хранения сообщений до их отправки на записываемых каналах. На практике единственный раз, когда канал не был доступен для записи, - это если буфер ядра заполнен, поэтому он редко будет недоступен для записи. Хорошим способом реализации этого было бы иметь выделенный поток писем, которому предоставляются сообщения, и спать; это может быть либо interrupt() ed при отправке новых сообщений, либо с использованием take() в очереди блокировки. Всякий раз, когда приходит новое сообщение, он разблокирует, выполняет select() на всех доступных для записи ключах и отправляет любые ожидающие сообщения сообщения; только в редких случаях сообщение должно оставаться в кеше, поскольку канал недоступен для записи.

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

Проблема здесь. SocketChannels почти всегда доступны для записи, если буфер отправки сокета не заполнен. Ergo, они, как правило, не должны регистрироваться для OP_WRITE:, иначе ваша петля выбора будет вращаться. Они только должны быть зарегистрированы таким образом, если:

  1. есть что-то писать, и
  2. предшествующее write() вернулся к нулю.
+0

Значит ли это, что я могу resister и отменить регистрацию OP_WRITE по мере необходимости? –

+0

@bot_bot Конечно. – EJP

Смежные вопросы