Статьи

Создание WebSocket-Chat-приложения с Jetty и Glassfish

В этой статье описывается, как создать простое приложение для чата HTML5, используя WebSockets для подключения к серверной части Java. Я решил включить Jetty 8 и пример GlassFish 3.1, чтобы продемонстрировать современные подходы к реализации WebSocket на стороне сервера. Хотя сервлет можно легко перенести из одного контейнера сервлета в другой, реализация WebSocket не может (на данный момент). Даже с одним поставщиком серверов у меня были проблемы с запуском приложения с разными версиями сервера. Я начал свои тесты с Jetty 7.2. Позже я обновил свои зависимости Maven до Jetty 7.4, и некоторые интерфейсы уже изменились! Чтобы избежать проблем с этой статьей, я решил использовать последнюю доступную реализацию Jetty, которая в настоящее время является Jetty 8 M3. Для моих тестов GlassFish я нашел полезный ресурспогрузиться в реализацию GlassFish WebSocket. Я приложил исходный код обоих примеров, чтобы вы могли легко протестировать его самостоятельно и использовать для дальнейшего изучения Java WebSockets.

Что будет делать это приложение чата:

Приложение чата, которое мы создаем, максимально простое. У нас есть одно окно с двумя полями ввода (1 и 3) и одним полем вывода сообщения (2). В первом поле ввода сверху (1) вы можете зарегистрировать имя вашего чата. Поле вывода в середине (2) используется для отображения входящих сообщений, отправленных всеми подключенными пользователями. Поле ввода внизу (3) — это поле ввода вашего сообщения, в которое вы можете ввести свое сообщение. Так что это в основном все приложение на стороне клиента.

Чтобы установить соединение WebSocket, мы просто создаем экземпляр WebSocket на стороне клиента, который указывает на расположение нашего сервера Java WebSocket.

 var ws;
 $(document).ready(
function() {
ws = new WebSocket("ws://localhost:8080/../WebSocketChat");
ws.onopen = function(event) {
}
ws.onmessage = function(event) {
var $textarea = $('#messages');
$textarea.val($textarea.val() + event.data + "\n");
}
ws.onclose = function(event) {
}
});
Listing 1

Префикс URL «ws» определяет соединение WebSocket по умолчанию. Чтобы открыть безопасный, используется префикс «wss» . API WebSocket определяет в основном три метода обратного вызова: onopen, onmessage и onclose. Аргумент события, передаваемый методами, содержит поле данных с полезной нагрузкой сервера.

Чтобы передать сообщение на сервер, мы определяем небольшую функцию, подобную этой:

function sendMessage() {
var message = $('#username').val() + ":" + $('#message').val();
ws.send(message);
$('#message').val('');
}
Listing 2

Здесь мы просто объединяем имя пользователя и сообщение и вызываем метод «отправки» WebSocket для передачи сообщения на сервер, который затем передает его всем подключенным пользователям.

Бэкэнд

Итак, что нам нужно на стороне сервера, чтобы создать работающее приложение? Во-первых, нам нужен сервлет для подключения вашего клиента и, во-вторых, один сокет для каждого клиента, подключенного к вашему чату. Протокол WebSocket определяет HTTP-подобное рукопожатие, которое позволяет серверу интерпретировать рукопожатие как HTTP и переключаться на протокол WebSocket. Как только сервлет связывает вас с WebSocket, весь трафик чата может обрабатываться через это дуплексное соединение.

Реализация Причала

Как упоминалось ранее, API сервера в настоящее время отличается на каждом сервере. Для сервера Jetty вы создаете WebSocketServlet, чтобы иметь указанную точку входа, с которой ваш WebSocket связывается с клиентом.

 public class WebSocketChatServlet extends WebSocketServlet {
 public final Set users = new CopyOnWriteArraySet();

protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
}

@Override
public WebSocket doWebSocketConnect(HttpServletRequest arg0, String arg1) {
return new ChatWebSocket(users);
}
 }
    Listing 3

Как видно из листинга 3, метод doGet ничего не реализует. Вместо этого мы используем «doWebSocket», чтобы вернуть экземпляр «ChatWebSocket», ссылающийся на набор со всеми текущими пользователями в чате.

Теперь один единственный WebSocket связан с вашим клиентом. После вызова метода «doWebSocket» все последующие операции в нашем чате будут выполняться с нашим вновь созданным объектом «ChatWebSocket» .

Набор пользователей, который мы передали «ChatWebSocket», содержит всех подключенных пользователей в нашем простом приложении чата. Когда мы получим клиентское сообщение, мы повторим этот набор и уведомим каждого зарегистрированного пользователя.

Итак, давайте посмотрим на ChatWebSocket:

 public class ChatWebSocket implements OnTextMessage {
private Connection connection;
private Set users;

public ChatWebSocket(Set users ) {
this.users = users;
}
public void onMessage(String data) {
for (ChatWebSocket user : users) {
try {
user.connection.sendMessage(data);
} catch (Exception e) {
}
}

}
@Override
public void onOpen(Connection connection) {
this.connection = connection;
users.add(this);
}
@Override
public void onClose(int closeCode, String message) {
users.remove(this);
       }
 }
Listing 4

Прежде всего вы можете увидеть, что «ChatWebSocket» реализует интерфейс «OnTextMessage» . Этот интерфейс расширяет интерфейс WebSocket и является одним из тех изменений, с которыми мне приходилось сталкиваться при переходе с Jetty 7.2 на Jetty 8. Вместо реализации различных методов «onMessage» вы решаете, хотите ли вы обрабатывать текстовые или двоичные сообщения. Используйте «OnBinaryMessage», когда вам нужно иметь дело с двоичными данными, и «OnTextMessage», когда вы создаете такие приложения, как это приложение чата.

Метод onOpen в строке 18 связывает текущее соединение с объектом WebSocket и добавляет этот объект в глобальный набор подключенных пользователей. Метод «onClose» просто удаляет пользователя из набора при отключении.

Ключевым методом в этом классе WebSocket является «onMessage» . Мы используем его для уведомления всех подключенных пользователей, перебирая набор пользователей и вызывая метод «connection.send ()» . Запомните код на стороне клиента, где массаж также был отправлен на сервер, вызвав этот метод. Теперь сервер отправляет сообщение каждому клиенту. Обратите внимание, что здесь не задействован опрос на стороне клиента, вместо этого у нас есть реальное полнодуплексное соединение и серверный пуш.

Реализация GlassFish

Как и реализация Jetty, GlassFish также использует сервлет для определения точки входа. Но вместо использования нового интерфейса мы используем стандартный HttpServlet и регистрируем адаптер WebSocket (ChatApplication) при первой инициализации.

public class WebSocketChatServlet extends HttpServlet {
private final ChatApplication app = new ChatApplication();
 @Override
public void init(ServletConfig config) throws ServletException {
    WebSocketEngine.getEngine().register(app);
  }
 }Listing 5

 public class ChatApplication extends WebSocketApplication {
@Override
public WebSocket createSocket(WebSocketListener... listeners)
throws IOException {
return new ChatSocket(listeners);
}

@Override
        public void onMessage(WebSocket socket, DataFrame frame) throws IOException {
final String data = frame.getTextPayload();
for (final WebSocket webSocket : getWebSockets()) {
try {
webSocket.send(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}

}Listing 6

Класс «WebSocketApplication» является адаптером, но скрывает большую часть кода, специфичного для WebSocket. Расширение этого класса означает «перезаписать создание WebSocket и обработку сообщений». Сам ChatSocket расширяет «BaseServerWebSocket» и не содержит никакого специального кода или методов для переопределения. Вместо этого «BaseServerWebSocket» делегирует все вызовы «onMessage, onOpen, onClose» адаптеру WebSockat (WebSocketApplication).

Метод «createSocket» (строка 3) в этом примере просто связывает сокет с клиентом.

Метод onMessage в строке 9 отличается от примера Jetty. Хотя мы реализовали методы WebSocket в предыдущем примере, теперь мы получаем сокет в качестве параметра. Кроме того, мы не определяем интерфейс «onTextMessage» или «onBinaryMessage» , мы просто используем фрейм данных с полезной нагрузкой.

В строке 10 вы видите, что мы вызываем «getTextPaylod», чтобы получить текстовое сообщение нашего чата. В качестве альтернативы вы можете использовать метод «getBinaryPayload», который дает вам двоичную полезную нагрузку, аналогичную интерфейсу «onBinaryMessage» в Jetty.

В листинге 4, строка 9 (Jetty WebSocket) мы использовали наш пользовательский набор. Здесь (листинг 6) нам не нужно обрабатывать самостоятельно созданный набор, потому что WebSocketApplication уже предоставляет набор всех подключенных WebSockets (листинг 6, строка 11). Возьмите этот набор и сообщите (webSocket.send (message); строка 13) всем подключенным клиентам.

Запуск примера Jetty

Запустить пример Jetty довольно просто. Перейдите в корневую папку проекта, содержащую файл pom.xml, и введите mvn clean install jetty: run . Это установит все отсутствующие зависимости, развернет веб-проект и запустит сервер Jetty. Теперь вы можете открыть приложение чата по адресу http: // localhost: 8080 / html5-webapp-jetty /.

Запуск примера GlassFish

Для запуска примера GlassFish требуются некоторые дополнительные шаги. Предоставленный файл pom.xml в корневой папке проекта создает только файл war ( сборка mvn ). Созданный файл войны можно найти в целевой папке вашего проекта. Для его запуска вам понадобится установленный GlassFish 3.1, и вы должны включить поддержку WebSocket. Для этого введите:
asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.websockets-support-enabled = true
Теперь вы можете развернуть свое приложение и протестировать его на http : // локальный: 8080 / html5-WebApp-GlassFish /.

Вывод:

WebSockets — очень перспективный подход для веб-приложений реального времени. Client API будет стандартизирован W3C и протокола WebSockets по IETF , поэтому у вас есть открытый и потенциально широкое распространение поддержки на стороне клиента и на стороне сервера. Но в настоящее время основной проблемой является отсутствие единого серверного API. Вы можете рисковать тем, что ваше приложение WebSocket не будет работать без изменений в более поздних версиях сервера. Кроме того, вы не можете переключаться с одного поставщика сервера на другого, не меняя основные части вашего приложения.