четверг, 24 мая 2012 г.

Использование websoсket в Android

Самые интересные Android-приложения, по-моему, те, которые позволяют взаимодействовать пользователям на различных устройствах. Например, сетевые игры, коммуникационные и социальные приложения. В разработке таких программ мы должны использовать широкий спектр технологий, и, соответственно, имеем большой простор для творчества, для построения интересной архитектуры.
Я уже описывал некоторые своеобразные решения для клиент-серверного взаимодействия в мобильных приложениях, но сегодня мы познакомимся, вроятно, с самым интересным инструментом для решения таких задач: с технологией websocket.
В чём её прелесть? Практически полный реалтайм, минимальные накладные расходы на передачу данных, возможность реализовать любой, даже бинарный протокол "внутри" websocket-а, и самое приятное: простота реализации и готовые библиотеки. С этим давайте и разберёмся подробнее.

Websocket на сервере

Технология websocket пока ещё не стала официальным стандартом и не удивительно, что "большие" сервера её ещё не поддерживают. Это даёт простор для развития специализированных websocket-серверов типа phpDaemon, Cowboy или Tornado. Но нам, java-программистам чужды суперскоростные и непостижимые erlang-сервера или python с php. Нам бы что-то родное, понятное и по возможности не громоздкое... Есть, к примеру, jwebsocket. Это комплекс библиотек для реализации сервеной и клиентской поддержки websocket. Вероятно, это неплохое решение для крупного проекта, но мне больше нравится использовать jetty. "Впилим" его в наше java-приложение и получим свой websocket-сервер с блекджеком и т.п. :)
Скачиваем jetty, распаковываем, и из директории lib добавляем в свой проект библиотеки:
jetty-continuation-8.1.3.v20120416.jar
jetty-io-8.1.3.v20120416.jar
jetty-util-8.1.3.v20120416.jar
servlet-api-3.0.jar
jetty-http-8.1.3.v20120416.jar
jetty-server-8.1.3.v20120416.jar
jetty-websocket-8.1.3.v20120416.jar
В вашем случае последняя часть имени файла, само собой, может отличаться.
Теперь пишем код.

import org.eclipse.jetty.server.Server;
 
public class WsProxy {
 
 public static void main(String[] args) throws Exception {
  int port = 8088;
  if (args.length>0) {
   try {
    port = Integer.parseInt(args[0]);
   } catch (Exception e) {
    e.printStackTrace();
   }
  }
 
  Server jetty = new Server(port);
  jetty.setHandler(new WsHandler());
  jetty.start();
 }
}

Как видим, чтобы запустить сервер нам потребовалось всего три стороки кода. Дальше пишем код уже в классе WsHandler:

import javax.servlet.http.HttpServletRequest;
 
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketHandler;
 
public class WsHandler extends WebSocketHandler {
 
 @Override
 public WebSocket doWebSocketConnect(HttpServletRequest req, String protokol) {
  try {
   return new WebSocket.OnTextMessage() {
 
    @Override
    public void onOpen(Connection arg0) {
     // TODO Auto-generated method stub
 
    }
 
    @Override
    public void onClose(int arg0, String arg1) {
     // TODO Auto-generated method stub
 
    }
 
    @Override
    public void onMessage(String arg0) {
     // TODO Auto-generated method stub
 
    }
   };
  } catch (Exception e) {
   e.printStackTrace();
  }
  return null;
 }
}

Вот и всё. Описываем нашу логику, которая должна работать при подключении нового устройства, отключении и получении сообщения от него. Объект Connection, полученный при подключении следует сохранить, чтобы иметь возможность послать сообщение клиенту при необходимости методом connection.sendMessage(текст сообщения); Сами объекты WebSocket сохраняем в коллекцию для организации взаимодействия между ними.

Websocket в Android

Для реализации клиентской части нашего соединения в Android-приложении также есть несколько вариантов библиотек. Подробно сравнить их характеристики, можно тут. Мне представляется, что самым важным критерием для выбора библиотеки в нашем случае будет поддержка последних версий протокола. Сам протокол ещё не стандартизован, опубликованы (и соответсвенно реализованы в библиотеках) три "черновых" версии и "предрелизная" RFC 6455. Её весьма неплохо реализует библиотека AutobahnAndroid. Она также отличается высокой производительностью за счёт использования Java NIO, хотя это и является причиной её, по-моему, единственного недостатка: отсутствия поддержки wss - защищённого websocket соединения. Впрочем, если вы не передаёте конфиденциальных данных или всё равно намерены реализовать внутренний слой шифрования самостоятельно, для вашего проекта это не проблема. Если же wss - обязательное условие, обратите внимние на Java WebSocket Client. Он хоть и не поддерживает RFC 6455, зато нормально обеспечивает соединение с wss - сервером.
Итак, добавим к себе в Android-приложение библиотеку AutobahnAndroid, а также две библиотеки, от которых она зависит: jackson-core-asl-1.8.5.jar и jackson-mapper-asl-1.8.5.jar.
Теперь метод, который установит нам соединение с сервером будет виглядеть так:

 public void connect(View v) {
  try {
   WebSocketConnection mConnection = new WebSocketConnection();
   mConnection.connect(url, new WebSocketHandler() {
             @Override
             public void onOpen() {
              System.out.println("--open");
             }
 
             @Override
             public void onTextMessage(String message) {
              System.out.println("--received message: " + message);
             }
 
             @Override
             public void onClose(int code, String reason) {
              System.out.println("--close");
             }
          });
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 
Тут в метод connect экземпляра класса WebSocketConnection мы передаём WebSocketHandler, в методах которого ловим соответствующие события и реагируем на них, как нам потребуется. Разорвать соединение можно методом  mConnection.disconnect(), а послать сообщение методом mConnection.sendTextMessage(текст сообщения). Похоже на то, что мы делали на серверной стороне, не правда ли?



4 комментария:

  1. а как прогнать WebSockets через прокси?

    ОтветитьУдалить
    Ответы
    1. Пока мне попалось только одно решение: отправлять периодически пустой пакет с сервера, чтобы прокси считал соединение активным, и не разрывал его. Возможно есть решения лучше, не знаю.

      Удалить