Вступление
Здравствуйте. Некоторое время назад я решил сделать своё web-приложение которое должно быстро обмениваться данными с сервером.
Вопрос был о том, как же сделать качественную и быструю скорость передачи данных между web-приложением и сервером.
Можно было бы использовать Flash но я не стал, т.к. моё web-приложение должно корректно запускаться на iДевайсах. Тогда мой взор упал на long-polling, проведя несколько тестов, я понял, что драгоценные секунды теряются на открытие нового соединения и ожидание ответа от сервера. Немного позже я узнал, совершенно случайно, что есть еще некий WebSocket, который как раз решил бы все мои проблемы с передачей данных.
Пост про маленькую победу над сложной задачей.
Может есть что-то готовое?
Потратив некоторое время на поиски готовых решений в гугл’е, я понял, что есть несколько способов реализации поставленной задачи при использовании сервлетов:
1) JWebSocket
2) Jetty ( Вы можете посмотреть решение тут: тык )
3) Netty
Я не буду говорить ничего о JWebSocket, потому что я не смог с ним разобраться (да и не хотелось, странный он, не верю я ему). Готовое решение на Jetty, с хабра, было бы очень кстати, если бы не устарело, по видимому разработчики решили что-то с чем-то объединить и нужные классы оказались перемещены/переименованы/удалены (нужное подчеркнуть).
Надо сказать, что Java для меня очень новый язык и после таких языков как js и php давался мне давольно легко. В общем-то это мой первый опыт использования языка, не для создания окошек с кнопочками, полями и ‘Hello World’, а для чего-то более серьёзного и сложного.
Раз нет, значит будет
Не хочу, чтобы мой первый пост был наполнен огромным количеством листинга кода, по этому остановлюсь на краткой версии с некоторыми пояснениями.
Для начала нужно создать сам сервер.
ExecutorService bossExec = new OrderedMemoryAwareThreadPoolExecutor(1, 400000000, 2000000000, 60, TimeUnit.SECONDS); ExecutorService ioExec = new OrderedMemoryAwareThreadPoolExecutor(3, 400000000, 2000000000, 60, TimeUnit.SECONDS); ServerBootstrap networkServer = new ServerBootstrap(new NioServerSocketChannelFactory(bossExec, ioExec, 3)); networkServer.setOption("connectTimeoutMillis", 10000); networkServer.setPipelineFactory(new WebSocketServerPipelineFactory()); Channel channel = networkServer.bind(new InetSocketAddress(address, port));
В документации Netty используется Executors.newCachedThreadPool(), он зачем-то поднимает аж 9 потоков выполнения, как по мне это неоправданная трата ресурса. Я решил ограничиться 3мя потоками и использовал OrderedMemoryAwareThreadPoolExecutor().
Затем необходимо обрабатывать подключения и пакеты, для этого назначаются (см. документацию как, класс WebSocketServerPipelineFactory) четыре обработчика, три из них стандартные из пакетов Netty (HttpRequestDecoder, HttpChunkAggregator, HttpResponseEncoder) и один мы должны описать самостоятельно.
Обработчик WebSocketServerHandler наследуется от класса SimpleChannelUpstreamHandler из Netty. Необходимо переопределить метод messageReceived под нужды, а нужды таковы:
1) Обрабатывать запросы по протоколу http://
2) Обрабатывать запросы по протоколу ws://
Для сего пишем следующее:
if (msg instanceof HttpRequest) { handleHttpRequest(ctx, (HttpRequest) msg); }else if (msg instanceof WebSocketFrame) { handleWebSocketFrame(ctx, (WebSocketFrame) msg); }
Метод handleHttpRequest должен корректно подключать клиентов по протоколу websocket и только через GET, а так же отбрасывать всех любопытных по http, показывая им ошибку, например "505 HTTP Version Not Supported".
sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, HTTP_VERSION_NOT_SUPPORTED));
Обрабатывать запрос на подключение ws будем примерно так:
if(req.getMethod() == GET && req.getUri().equals(WEBSOCKET_PATH) && Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION)) && WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) { WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(req), null, false); handshaker = wsFactory.newHandshaker(req); if(handshaker == null){ wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel()); }else{ handshaker.handshake(ctx.getChannel(), req).addListener(WebSocketServerHandshaker.HANDSHAKE_LISTENER); } return; } //Здесь должен быть ответ с ошибкой HTTP_VERSION_NO....
Обуславливаем это тем, что браузер присылает методом GET на наш WEBSOCKET_PATH заголовок Upgrade: websocket. Далее начинается процесс приветствия (handshake), который сам отправляет клиенту верные заголовки и в итоге переключает протокол с http на ws открывая постоянное соединение.
В документации метод handleWebSocketFrame проверяет не закрыто ли соединение, отвечает на пакет ping и проверяет текст ли пришел, после чего возвращает принятую строку назад клиенту.
Получаем строчку, которую прислал клиент:
String clietnMsg = ((TextWebSocketFrame) frame).getText();
Теперь когда установлено соединение клиента с сервером, можно слать любые сообщения с максимальной скоростью не задерживаясь на открытие соединения и ожидание ответа.
Заключение
Это была моя первая личная победа над сложной задачей реализации WebSocket сервера, хоть я использовал документацию Netty, кроме этого я так же читал другие ресурсы (в том числе хабр).
Надеюсь, что теперь любой желающий сможет сделать свой WebSocket сервер на Java с использованием Netty.
Постскриптум
У меня еще нет репорезитория в git, по этому предлагаю исходный код по старинке с моего сервера, зеркало на Народе
Интересное по теме:
— Сервер на python для websockets
— Поддержка реверс-проксирования Web Sockets в Nginx
— Двунаправленный асинхронный обмен данными в веб-приложениях
ссылка на оригинал статьи http://habrahabr.ru/post/165791/
Добавить комментарий