
Как и обещал, привожу описание производительного игрового сервера на Netty, который использую в своих проектах.
Все описанное ниже не является истинной в последней инстанции, а всего лишь опыт применения технологий в реальных проектах.
Начнем с начала.
Перед нами стоит задача сделать игровой сервер.
Каковы основные задачи игрового сервера?
Если коротко, то:
- Получить пакет от клиента
- Обработать этот пакет (расшифровка, десериализация и т.д. )
- Просчитать игровую ситуацию
- Разослать клиентам изменения игровой ситуации
…
Работу с БД и прочими вещами сейчас рассматривать не будем. Сосредоточимся на сетевой части.
Чем в этом деле нам может помочь Netty?
Netty это сетевая библиотека, которая возьмет на себя непосредственно работу с сокетами. Подключение, отключение клиентов. Прием, отправка и фрагментация пакетов. Т.е. всю низкоуровневую работу с сокетами netty возьмет на себя.
Как netty нам поможет?
Netty реализует очень удобную архитектуру. Например она позволяет подключить несколько обработчиков входящих данных. Т.е. в первом обработчике мы разделяем входящий поток данных на пакеты, а во втором уже обрабатываем эти пакеты. При этом можно гибко управлять настройками самой библиотеки, выделять ей необходимое число потоков или памяти и т.д…
Общая архитектура при использовании Netty может выглядеть так:

У нас получается 3 обработчика:
1. Connection handler — это обработчик который отвечает за подключения/отключения клиентов. Здесь будет происходить проверка возможности подключения клиентов (черные, белые списки, IP фильтры и т.д.) а так же корректное отключение клиентов с закрытием всех используемых ресурсов.
2. Frame handler — это обработчик разделяющий поток данных на отдельные пакеты.
Для удобства работы примем, что пакет состоит из двух частей. 1-заголовок, в котором описана длинна пакета, и 2-непосредственно данные пакета.
3. Packet handler — это уже обработчик игровых сообщений. Здесь мы будем получать данные из пакета и дальше их обрабатывать.
Разберем каждую часть в коде.
Connection handler
public class ConnectionHandler extends SimpleChannelHandler { @Override public void channelOpen( ChannelHandlerContext ctx, ChannelStateEvent e ) throws Exception { if( необходимые условия фильтрации подключения не выполнены ) { // закроем подключение e.getChannel().close(); } } @Override public void channelClosed( ChannelHandlerContext ctx, ChannelStateEvent e ) throws Exception { // Здесь, при закрытии подключения, прописываем закрытие всех связанных ресурсов для корректного завершения. } @Override public void exceptionCaught( ChannelHandlerContext ctx, ExceptionEvent e ) throws Exception { // Обработка возникающих ошибок log.error( "message: {}", e.getCause().getMessage() ); } }
Frame handler.
Здесь используем реплей декодер с двумя состояниями. В одном читаем длину пакета, в другом сами данные.
public class FrameHandler extends ReplayingDecoder<DecoderState> { public enum DecoderState { READ_LENGTH, READ_CONTENT; } private int length; public ProtocolPacketFramer() { super( DecoderState.READ_LENGTH ); } @Override protected Object decode( ChannelHandlerContext chc, Channel chnl, ChannelBuffer cb, DecoderState state ) throws Exception { switch ( state ) { case READ_LENGTH: length = cb.readInt(); checkpoint( DecoderState.READ_CONTENT ); case READ_CONTENT: ChannelBuffer frame = cb.readBytes( length ); checkpoint( DecoderState.READ_LENGTH ); return frame; default: throw new Error( "Shouldn't reach here." ); } } }
Packet handler
public class ProtocolPacketHandler extends SimpleChannelHandler { @Override public void messageReceived( ChannelHandlerContext ctx, MessageEvent e ) throws Exception { // получим сообщение byte[] msg = ((ChannelBuffer)e.getMessage()).array(); // Соберем его для отправки обработчику Packet packet = new Packet(); packet.setData( msg ); packet.setSender( session ); // Поставим пакет в очередь обработки сесссии session.addReadPacketQueue( packet ); // Поставим сессию в очередь обработки логики Server.getReader().addSessionToProcess( session ); } }
Как видно, обработчик получился очень простой и быстрый. Packet это мой класс в котором содержится вся необходимая информация для обработки его игровой логикой. Он очень простой и его реализация не составит труда.
Это самый главный обработчик. В принципе можно практически всю логику описать в нем. Нюанс здесь в том, что в нем нельзя использовать блокирующие элементы и длительные по времени операции, например подключение к базе данных. Это как минимум затормозит всю работу. Поэтому мы архитектурно разделим наш сервер на 2 части. Первая это чисто TCP сервер, основная задача которого как можно быстрее принять пакет от клиента и как можно быстрее отослать пакет клиенту. Вторая это непосредственно обработчик игровой логики. В принципе, по такой схеме можно сделать не только игровой сервер. Ведь логика обработки пакетов может быть любой.
Прелесть такой архитектуры еще и в том, что TCP сервер и обработчики можно разнести по разным машинам (например с помощью Akka акторов) получив кластер для расчетов игровых данных. Таким образом получаем следующую схему работы сервера. TCP часть на Netty старается как можно быстрее принять пакеты от клиента и отправить их обработчику игровой логики, при этом в обработчике создается очередь из них.
Схематично весь процесс выглядит так.

Таким образом получаем довольно гибкую структуру. Пока н��грузки маленькие можно держать все на одном физическом сервере. При возрастании нагрузки можно TCP сервер на Netty выделить в отдельную машину. У которой хватит производительности для обслуживания нескольких физических серверов с игровой логикой.
Обработка сообщений происходит следующим образом. У нас есть Session это объект который хранит информацию связанную с подключенным клиентом, в нем так же хранятся 2 очереди, пришедших пакетов и готовых к отправке. Packet это объект хранящий информацию о сообщении полученном от клиента. Netty при получении пакета, добавляет его в очередь сессии на обработку и затем отправляет саму сессию на обработку игровой логике. Обработчик игровой логики берет сессию из очереди, потом берет пакет из очереди сессии и обрабатывает его согласно своей логике. И так в несколько потоков. Получается что пакеты обрабатываются последовательно по мере получения. И один клиент не будет тормозить остальных. Ух… вот завернул-то )) Если что, спрашивайте в каментах, поясню.
Вот картинка которая возможно понятнее будет.

Обработчик игровой логики.
public final class ReadQueueHandler implements Runnable { private final BlockingQueue<Session> sessionQueue; private final ExecutorService threadPool; private int threadPoolSize; public ReadQueueHandler( int threadPoolSize ) { this.threadPoolSize = threadPoolSize; this.threadPool = Executors.newFixedThreadPool( threadPoolSize ); this.sessionQueue = new LinkedBlockingQueue(); initThreadPool(); } private void initThreadPool() { for ( int i = 0; i < this.threadPoolSize; i++ ) { this.threadPool.execute( this ); } } // добавление сесси в очередь на обработку public void addSessionToProcess( Session session ) { if ( session != null ) { this.sessionQueue.add( session ); } } @Override public void run() { while ( isActive ) { // Получаем следующую сессию для обработки Session session = (Session) this.sessionQueue.take(); // Здесь происходит обработка игровых сообщений // получаем пакет packet = session.getReadPacketQueue().take(); // далее получаем и обрабатываем данные из него data = packet.getData(); } } }
Тут тоже ничего сложного нет. Создает пул потоков. Добавляем в очередь сессии на обработку и в обработчике реализуем свою игровую логику. Здесь просто надо соблюсти баланс между скоростью TCP сервера и скоростью обработчиков игровой логики. Чтобы очередь не заполнялась быстрее чем обрабатывалась. Netty очень быстрая библиотека. Так что все зависит от реализации вашей игровой гейм логики.
В качестве протокола я использую protobuf. Очень быстрая и удобная бинарная сериализация. Сделана и используется гуглом, что говорит о проверенности библиотеки на больших проектах )
С такой архитектурой, на моем нетбуке AMD 1.4 ГГц (lenovo edge 13), обрабатывается порядка 18-20к сообщений в секунду. Что в общем неплохо.
P.S. Любые вопросы — велкам.
