пятница, 1 февраля 2013 г.

Создание websocket-приложения на Tomcat 7

Ранее я писал о своём опыте использования технологии websocket в связывании серверного java-приложения и android-клиента. Я предлагал использовать jetty 8 и делал обычное консольное java-приложение. Но если у нас websocket является частью web-приложения с сервлетами и т.п., то такой подход уже не годится. Да и зачем тянуть в свой проект jetty, если наш старый добрый Tomcat с версии 7.0.27 уже поддерживает websocket самостоятельно?
Сегодня мы сделаем websocket-приложение с помощью одного только Tomcat и javascript на клиенте.

WebSocketServlet
Ключевым механизмом реализации websocket в Tomcat является класс org.apache.catalina.websocket.WebSocketServlet. Он расширяет обычный javax.servlet.http.HttpServlet а значит мы можем "мапить" его на url как это делаем с остальными своими сервлетами. Собственно сама наша реализация крайне проста:

import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
 
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
 
@WebServlet(name = "WsServlet", urlPatterns = {"/ws"})
public class WsServlet extends WebSocketServlet {
    @Override
    protected StreamInbound createWebSocketInbound(String s, HttpServletRequest httpServletRequest) {
        return new WsConnection();
    }
}

Мы тут реализуем метод, который возвращает объект, отвечающий за обслуживание websocket-соединения. Сам объект WsConnection выглядит так:

import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.WsOutbound;
 
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.concurrent.ArrayBlockingQueue;
 
public class WsConnection extends MessageInbound {
 
    public static ArrayBlockingQueue<WsOutbound> connections = new ArrayBlockingQueue<WsOutbound>(100);
    private WsOutbound outbound;
 
    @Override
    protected void onBinaryMessage(ByteBuffer byteBuffer) throws IOException {
    }
 
    @Override
    protected void onTextMessage(CharBuffer charBuffer) throws IOException {
        broadcast(charBuffer.toString());
    }
 
    @Override
    protected void onOpen(WsOutbound outbound) {
        this.outbound = outbound;
        connections.add(outbound);
    }
 
    @Override
    protected void onClose(int status) {
        connections.remove(this.outbound);
    }
 
    private void broadcast(String message) {
        for (WsOutbound connection : connections) {
            try {
                CharBuffer buffer = CharBuffer.wrap(message);
                connection.writeTextMessage(buffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Тут мы расширяем MessageInbound, что позволяет нам переопределяя соответствующие методы обрабатывать события установления и обрыва соединения, получения сообщения и т.п. При подключении нового клиента мы добавляем его в синхронизированную очередь, при отключении - удаляем. При получении сообщения просто рассылаем его всем подключенным клиентам. Такой вот чатик :)
На клиентской стороне можно сделать много красивостей, но мы обойдёмся минимальным количеством кода. Только чтобы убедиться в работоспособности сервера.

<html>
  <head>
    <title></title>
      <script type="text/javascript">
          var socket = new WebSocket("ws://mydomain.com:8080/ws");
 
          socket.onopen = function() {
              alert("Соединение установлено.");
          };
 
          socket.onclose = function(event) {
              if (event.wasClean) {
                  alert('Соединение закрыто');
              } else {
                  alert('Обрыв соединения');
              }
              alert('Код: ' + event.code + ' причина: ' + event.reason);
          };
          socket.onmessage = function(event) {
              var logarea = document.getElementById("log");
              logarea.value = event.data+"n"+logarea.value;
          };
          socket.onerror = function(error) {
              alert("Ошибка " + error.message);
          };
 
          function send() {
              var s = document.getElementById("in").value;
              socket.send(s);
          }
 
      </script>
  </head>
  <body>
    <input type="text" id="in" /><input type="button" onclick="send()" value="send" />
    <br/>
    <textarea id="log" rows="8" cols="20">

  

9 комментариев:

  1. Можешь дать весь проект?

    джава 1.7 ?

    ОтветитьУдалить
  2. Этот комментарий был удален администратором блога.

    ОтветитьУдалить
    Ответы
    1. Этот комментарий был удален администратором блога.

      Удалить
  3. Есть ли реализация клиента на java? Очень хотелось бы увидеть.

    ОтветитьУдалить
    Ответы
    1. https://github.com/TooTallNate/Java-WebSocket
      проверено - клиент на андроиде великолепно работает чкрез wss

      Удалить
    2. https://github.com/socketio/socket.io-client-java

      Удалить
  4. А как сделать то же самое на tomcat 8?

    ОтветитьУдалить
  5. Отличная почта. Можете ли вы написать сообщение о Webfirmframework?

    ОтветитьУдалить