вторник, 8 мая 2012 г.

Netty\Java - асинхронный событийно ориентированный сетевой фреймворк

Заметка по Netty\Java.

Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.

Netty - асинхронный событийно ориентированный сетевой фреймворк для быстрой разработки высоконагруженных клиент-серверных приложений.



Это замечательный фреймворк, но, к сожалению, он плохо документирован.
Мне пришлось потратить около четырёх дней, чтобы разобраться как он устроен. И в итоге, я разобрался только тогда, когда скачал готовый пример клиента-сервера с авторизацей (handshake).

Чтобы не потерять понимание движка, ниже описываю основные, важные для меня, моменты работы движка Netty и привожу ссылки на ресурсы, которые когда то позволили мне лучше понять его структуру и работу.

Чтобы разобраться в этом движке вам надо внимательно изучить guide и api движка и почитать примеры, найденные в интернет.

Netty

// официальный сайт
http://netty.io
// пример сервера с авторизацией
http://biasedbit.com/handshaking-tutorial-with-netty/
// очень полезная информация по Netty
http://habrahabr.ru/post/136456/

// --------------------------
Описание фреймворка Netty\Java

Для сервера создаётся 1 Channel, который биндуется к соответствующему порту. Для каждого нового подключённого клиента, сервер создаёт его собственный Channel.

Разумно иметь в сервере static ChannelGroup group, чтобы складывать туда Channel всех подключённых пользователей. (Указатель group передаём в Channel подключённого пользователя через один из Handler-ов, которые создаются для ChannelPipeline подключённого пользователя)


// --------------------------
О работе Channel.

С Channel связана 1 ChannelPipeline, которая имеет несколько Handler. Handler - это обработчик события.
В одном ChannelPipeline может быть несколько Handler-ов. (Заметка: также один и тот же Handler может быть зарегистрирован в нескольких разных ChannelPipeline, но мы этим не пользуемся)

ChannelPipeline - это своего рода труба, по которой движутся события (ChannelEvent), а в трубе сидят Handler-ы, которые по очереди обрабатывают пробегающее событие.

ChannelPipeline характеризуется направлением движения события: Upstream или Downstream. Таким образом, каждый Handler ассоциирован с соответствующим направлением, или с обоими сразу.

Направление Upstream - это из сети в вашу программу.
Направление Downstream - это из программы в сеть.
(наглядное изображение из документации: http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelPipeline.html )

Handler может обрабатывать либо входящие, либо исходящие, либо и те и другие сообщения. Чтобы ваш Handler обрабатывал только входящие события (Upstream), то вам нужно наследоваться от класса SimpleChannelUpstreamHandler.
Чтобы Handler обрабатывал только исходящие события (Downstream), то нужно наследоваться от класса SimpleChannelDownstreamHandler.
Если же ваш Handler должен обслуживать, как входящие, так и исходящие события, то он должен наследоваться от SimpleChannelHandler.


Очень важно понять, что по ChannelPipeline движутся ChannelEvent (события канала). Это значит, что любой ваш Handler может сгенерировать (создать операцией new) ваше собственное событие (MyChannelEvent implements ChannelEvent) и пустить его либо вверх, либо вниз по ChannelPipeline.

Чтобы отправить событие вверх, надо вызвать ctx.sendUpstream(ChannelEvent e);
Чтобы отправить событие вниз, надо вызвать ctx.sendDownstream(ChannelEvent e);
Где ctx - ChannelHandlerContext.
Также, вспомогательный инструмент по проталкиванию событий можно найти в классе Channels.


// --------------------------
Работа Handler.

Когда в Handler приходит поднимающееся событие, самым первым вызвается метод
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)

Когда в Handler приходит опускающееся событие, самым первым вызывается метод
public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)

Когда запускается Handler, будет всегда вызывается либо handleUpstream(), либо handleDownstream метод, в зависимости от направления входящего события.
Внутри этих методов надо после выполнения своих действий, если таковы имеются, выполнить super.handleUpstream(ctx, e) или super.handleDownstream(ctx, e) соответственно, чтобы Handler вызвал другие свои методы (к пример messageReceived() ).

Таким образом, когда в Handler попадает любое событие (ChannelEvent), всегда выполняется два метода:
1. handleUpstream() или handleDownstream() в зависимости от движения события;
2. и один из другим методов, соответствующий именно этому ChannelEvent. (Например метод messageReceived(), или channelConnected() )

Событие ChannelEvent продвигается далее по своему направлению, когда вызывается super.messageReceived(ctx, e) из метода messageReceived() для класса SimpleChannelUpstreamHandler и super.writeRequested(ctx, e) из метода writeRequested() для класса SimpleChannelDownstreamHandler.


// --------------------------
О ChannelPipeline.

ChannelPipeline может удалять любой из своих Handler-ов. Например, после успешной авторизации, можно удалить MyHandshakeHandler extends SimpleChannelHandler.


// --------------------------
Об идеи авторизации.

Есть идея, что обработчик MyHandshakeHandler, установленный на стороне сервера, будет накапливать ChannelEvent, идущие от сервера в сеть, и когда клиент авторизуется, MyHandshakeHandler протолкнёт всю очередь накопленных событий в направлении Downstream, чтобы они отправились к клиенту. Если же авторизация не пройдена клиентом, то соединение с клиентом закрывается и вся очередь событий от сервера будет потеряна.


// --------------------------
Предположения.

Кажется, один и тот же объект Handler может быть вызвать в нескольких потоках в случае, когда по одному и тому же ChannelPipeline текут два сообщения. Тогда это объясняет, для чего требуется синхронизация в методе messageReceived в ServerHandshakeHandler классе в примере с авторизацией.

1 комментарий: