Friday, February 10, 2012

Netty - очередное чудо асихнронной событийности в сети

Раз уж я так увлекся исследованиями асинхронными событийно-ориентированными сетевыми платформами на Java, то не могу не упомянуть о Netty. Как заявлено на сайте Netty - это асинхронный событийный фреймворк для сетевых приложений
Не будем утруждать себя чтением доки (хотя иногда это очень стоит делать), а сразу реализуем такой же примерчик, какой мы делали для демонстрации node.js
Как обычно, создаем мавеновский проект. Вот pom.xml:

Обрати внимание на то, что здесь добавлен Мавен репозиторий JBoss
Создаем класс самого сервера:
Чуть подробнее о классах, которые он использует:

ChannelFactory это фабрика для создания и управления Channel'ами и их ресурсами. Channel в Netty - это аналог Channel'ов из пакета java.nio.channels с возможностью асинхронного вызова чтения/записи.
NioServerSocketChannelFactory - это реализация ChannelFactory. Конструктор принимает 2 Executor'a : bossExecutor и workerExecutor. Boss потоки обслуживают открытые серверные Channel's (ServerSocketChannel). Worker потоки осуществляют неблокирующее чтение/запись для одного или нескольких ченелов. Т.е. если, например на сервере открыто 2 порта 80 и 443, то их обслуживать будут 2 Boss потока. Как только сервер примет клиентское соединение, то boss передаст принятый Channel одному из worker потоков.
ServerBootstrap - это вспомогательный класс для инициализации сервера. Можно и без него, но прийдется много кода писать
Дальше мы конфигурируем ChannelPipelineFactory. Как только сервер приймет новое соединение, то будет создан новый ChannelPipeline этой фабрикой.
Вот наша реализация ChannelPipeLineFactory

Немного о том, что такое ChannelPipeline. Как только создан новый Channel, то с ним ассоциируется новый экземпляр pipeline. Pipeline содержит набор обработчиков событий, которые вызываются при получении сообщения или записи. При этом принята метафора, что все сообщения на чтение обрабатываются восходящими обработчиками (Upstream Handler), а на запись - нисходящими (Donwstream Handlers).
Например, если клиент подключился к серверу и послал GET запрос, то вызовутся Upstream обработчики, то есть те, которые реализуют интерфейс ChannelUpstreamHandler. Когда мы сформировали ответ и отдаем его клиенту вызовутся обработчики, которые реализуют интерфейс ChannelDownstreamHandler. Причем Upstream вызовутся в порядке, котором их определили при добавлении, а Downstream - в обратном направлении.
В нашем случае при получении сообщения вызовется сначала HttpRequestDecoder, а затем наш HelloHander
Обратите внимание на то, что здесь идет 2 раза запись в Channel. При chunked ответе содержимое обьекта response автоматически станет пустым. Поэтому Hello пришлось записывать отдельным блоком и дальше создавать наши асинхронные таски с помощью AsyncExecutor
Поскольку вся запись/чтение в ченел асинхронные, то для того, чтобы сделать действие действительно после того, как запись произойдет нужно зарегистрировать листенера, который вызовется после физической записи блока в поток ввода-вывода
Еще один интересный момент - если у нас чанкед ответ, то HTTP спецификация просит последний блок (chunk) делать пустым
Запустим приложение и дадим команду из командкой строки curl -i http://localhost:8081. В результате выведутся http headers, за ними Hello и World 0, а затем с интервалом в 2 секунды - World N.

Вывод.Судя по архитектуре фреймворк должен быть устойчив к большому наплыву пользователей (масштабируемость). Очень легковесный, jar файл занимает 800К. Сложная в разработке и понимании (одна метафора с событиями чего стоит). Отлично подошла бы для специфических задач, например для веб серверов, которые передают/принимают большие потоки данных, или для систем, где большое количество пользователей делают однотипные операции (AJAX чаты, месенджеры и т.п.). Медиа стриминг, говорят, тоже отлично решается с помощью этого фреймворка.

2 comments:

  1. Спасибо, очень понятный пример:)

    ReplyDelete
  2. Anonymous10:36 AM

    Спасибо мужик

    ReplyDelete