// 
//     Licensed to the Apache Software Foundation (ASF) under one
//     or more contributor license agreements.  See the NOTICE file
//     distributed with this work for additional information
//     regarding copyright ownership.  The ASF licenses this file
//     to you under the Apache License, Version 2.0 (the
//     "License"); you may not use this file except in compliance
//     with the License.  You may obtain a copy of the License at
// 
//       http://www.apache.org/licenses/LICENSE-2.0
// 
//     Unless required by applicable law or agreed to in writing,
//     software distributed under the License is distributed on an
//     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//     KIND, either express or implied.  See the License for the
//     specific language governing permissions and limitations
//     under the License.
//

= Использование WebSocket API в веб-приложении
:jbake-type: tutorial
:jbake-tags: tutorials 
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: Использование WebSocket API в веб-приложении - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, Использование WebSocket API в веб-приложении

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

Из этого учебного курса вы узнаете о том, как создать веб-приложение, использующее интерфейс Java API for WebSocket (link:http://www.jcp.org/en/jsr/detail?id=356[+JSR 356+]) для двустороннего обмена данными между браузерными клиентами и сервером приложений. Интерфейс Java API for WebSocket обеспечивает возможность создания компонентов Java WebSocket, инициализации и перехвата событий WebSocket, а также создания и использования текстовых и двоичных сообщений WebSocket. В этом учебном курсе также приведен пример использования интерфейса Java API for JSON Processing (link:http://jcp.org/en/jsr/detail?id=353[+JSR 353+]) для создания и использования компонентов JSON. Интерфейсы Java API for WebSocket и Java API for JSON Processing являются компонентами платформы Java EE 7 (link:http://jcp.org/en/jsr/detail?id=342[+JSR 342+]).

Приложение содержит интерфейсы WebSocket для терминала, декодера и кодера, веб-страницу и ряд файлов JavaScript, запускаемых в браузере клиента при загрузке страницы или вызываемых формой на веб-странице. Развертывание приложения осуществляется на экземпляре GlassFish Server Open Source Edition 4, эталонной версии реализации технологии Java EE 7.

NOTE:  Этот курс основывается на статье блога link:https://blogs.oracle.com/arungupta/entry/collaborative_whiteboard_using_websocket_in[+ Collaborative Whiteboard using WebSocket in GlassFish 4 - Text/JSON and Binary/ArrayBuffer Data Transfer (TOTD #189) +] и других статьях блога link:http://blog.arungupta.me/[+Arun Gupta+]. Рекомендуем посетить этот блог и ознакомиться с другими интересными статьями о работе с WebSocket API и GlassFish 4.

Также рекомендуем посмотреть видеоролик link:maven-websocketapi-screencast.html[+Использование WebSocket API в веб-приложении+].

*Упражнения по темам руководства*

* <<Exercise_1,Создание проекта веб-приложения>>
* <<createendpoint,Создание терминала WebSocket>>
* <<createendpoint1,Создание терминала>>
* <<createendpoint2,Инициализация сеанса WebSocket>>
* <<createendpoint3,Тестирование терминала>>
* <<createwhiteboard,Создание интерактивной доски>>
* <<createwhiteboard1,Добавление полотна>>
* <<createwhiteboard2,Создание POJO>>
* <<createwhiteboard3,Создание класса координат>>
* <<createwhiteboard6,Генерация строки JSON>>
* <<createwhiteboard4,Реализация интерфейсов кодера и декодера>>
* <<createwhiteboard5,Запуск приложения>>
* <<sendbinary,Отправка двоичных данных на терминал>>

*Для работы с этим учебным курсом требуется следующее программное обеспечение и ресурсы.*

|===
|Программное обеспечение или ресурс |Требуемая версия 

|link:https://netbeans.org/downloads/index.html[+IDE NetBeans+] |Версия Java EE 7.3.1, 7.4, 8.0 

|link:http://www.oracle.com/technetwork/java/javase/downloads/index.html[+Комплект для разработчика на языке Java (JDK)+] |версия 7 или 8 

|link:https://glassfish.java.net/[+GlassFish Server Open Source Edition 3.1.2.2+] |4 
|===

NOTE:  GlassFish 4 входит в состав загружаемого комплекта Java EE для NetBeans IDE.

*Предпосылки*

Предполагается, что читатель обладает базовыми знаниями по следующим технологиям или опытом программирования с их использованием:

* Программирование на Java
* Программирование на JavaScript/HTML
* IDE NetBeans

Перед изучением этого учебного курса можно ознакомиться со следующей документацией:

* link:http://wiki.netbeans.org/MavenBestPractices[+Испытанные приемы для Apache Maven в IDE NetBeans+]
* link:http://books.sonatype.com/mvnref-book/reference/introduction.html[+Глава 1. Введение в Apache Maven+] (из книги link:http://books.sonatype.com/mvnref-book/reference/index.html[+Maven: The Complete Reference +])

Можно загрузить link:https://netbeans.org/projects/samples/downloads/download/Samples/JavaEE/WhiteboardApp.zip[+готовый проект в виде архива ZIP+].


== Создание проекта веб-приложения

Цель этого упражнения - создать проект веб-приложения с помощью мастера создания проектов в IDE. При создании проекта в качестве версии Java EE необходимо указать Java EE 7, а в качестве сервера приложений - GlassFish 4. GlassFish 4 является эталонной реализацией платформы Java EE 7. Для создания приложения по инструкциям, приведенным в этом учебном курсе, вам потребуется сервер приложений с поддержкой Java EE 7, зарегистрированный в IDE.

1. Выберите Файл > Создать проект в главном меню (в Windows можно использовать сочетание клавиш Ctrl-Shift-N; в Mac - сочетание клавиш ⌘-Shift-N).
2. Выберите в категории Maven пункт "Веб-приложение". Нажмите 'Далее'.
3. В поле 'Имя проекта' введите *WhiteboardApp* и укажите местоположение проекта.
4. В поле 'ИД группы' введите *org.sample*. Нажмите 'Далее'.
5. В списке 'Сервер' выберите *GlassFish Server 4.0*.
6. В списке 'Версия Java EE' выберите *Java EE 7 Web*. Нажмите 'Готово'.

image::images/websocket-newproject.png[title="Версии сервера и Java EE в мастере создания проектов"]

При нажатии кнопки "Завершить" проект будет создан в среде IDE, который откроется в окне "Проекты".


== Создание терминала WebSocket

Этот раздел содержит инструкции по созданию класса терминала WebSocket и файла JavaScript. Класс терминала WebSocket содержит несколько базовых методов, которые выполняются при открытии сеанса. Затем необходимо создать файл JavaScript, который запускает процесс квитирования с сервером при загрузке страницы. После этого останется только запустить приложение и проверить подключение.

Дополнительные сведения об использовании API-интерфейсов и аннотаций WebSocket см. в описании пакета link:https://javaee-spec.java.net/nonav/javadocs/javax/websocket/package-summary.html[+ javax.websocket+].


=== Создание терминала

В этом упражнении показано, как создать класс терминала WebSocket с помощью мастера IDE.

1. В окне 'Проекты' щелкните правой кнопкой мыши узел 'Исходные пакеты' и выберите 'Создать > Другие'.
2. В категории 'Веб' выберите 'Терминал WebSocket'. Нажмите 'Далее'.
3. В поле 'Имя класса' введите *MyWhiteboard*.
4. В списке 'Пакет' выберите  ``org.sample.whiteboardapp`` .
5. В поле 'WebSocket URI' введите */whiteboardendpoint*. Нажмите 'Готово'.

image::images/websocket-newendpoint.png[title="Терминал WebSocket в мастере создания файлов"]

При нажатии кнопки 'Готово' среда IDE создает класс терминала WebSocket и открывает файл в редакторе исходного кода. При просмотре файла в редакторе вы увидите, что среда IDE сгенерировала несколько аннотаций, которые входят в состав API-интерфейса WebSocket. Класс имеет аннотацию  ``link:https://javaee-spec.java.net/nonav/javadocs/javax/websocket/server/ServerEndpoint.html[+@ServerEndpoint+]`` , указывающую на его принадлежность к классам терминала, а в качестве параметра аннотации указан WebSocket URI. Среда IDE также создает стандартный метод  ``onMessage``  с аннотацией  ``link:https://javaee-spec.java.net/nonav/javadocs/javax/websocket/OnMessage.html[+@OnMessage+]`` . Метод с аннотацией  ``@OnMessage``  вызывается каждый раз, когда клиент получает сообщение WebSocket.


[source,java]
----

@ServerEndpoint("/whiteboardendpoint")
public class MyWhiteboard {

    @OnMessage
    public String onMessage(String message) {
        return null;
    }
    
}
----


. Добавьте в класс следующее поле (выделено *полужирным шрифтом*).

[source,java]
----

@ServerEndpoint("/whiteboardendpoint")
public class MyWhiteboard {
    *private static Set<Session> peers = Collections.synchronizedSet(new HashSet<Session>());*

    @OnMessage
    public String onMessage(String message) {
        return null;
    }
}
----


. Добавьте методы  ``onOpen``  и  ``onClose`` .

[source,java]
----

    @OnOpen
    public void onOpen (Session peer) {
        peers.add(peer);
    }

    @OnClose
    public void onClose (Session peer) {
        peers.remove(peer);
    }
----

Методы  ``onOpen``  и  ``onClose``  имеют аннотации API-интерфейса WebSocket:  ``link:https://javaee-spec.java.net/nonav/javadocs/javax/websocket/OnOpen.html[+@OnOpen+]``  и  ``link:https://javaee-spec.java.net/nonav/javadocs/javax/websocket/OnClose.html[+@OnClose+]`` . Метод с аннотацией  ``@OnOpen``  вызывается при открытии сеанса WebSocket. В этом примере аннотированный метод  ``onOpen``  добавляет браузерного клиента в группу одноранговых узлов текущего сеанса, а метод  ``onClose``  удаляет клиента из этой группы.

Создайте методы, используя подсказки и автозавершение кода в редакторе исходного кода. Щелкните значок подсказки в левом поле рядом с объявлением класса (или поместите указатель мыши на объявление класса и нажмите Alt-Enter), затем выберите этот метод в раскрывающемся меню. Для создания кода метода можно использовать автозавершение кода.

image::images/websocket-endpoint-hint.png[title="Подсказка к коду в редакторе исходного кода"]



. Щелкните правой кнопкой мыши в редакторе и выберите 'Исправить операторы импорта' (Alt-Shift-I; ⌘-Shift-I для Mac). Сохраните изменения.

В результате в файл будут добавлены операторы импорта для классов в  ``javax.websocket`` .

Терминал создан. Теперь необходимо создать файл JavaScript для инициализации сеанса WebSocket.

 


=== Инициализация сеанса WebSocket

Этот раздел содержит инструкции по созданию файла JavaScript для инициализации сеанса WebSocket. Браузерный клиент подключается к сеансу, используя HTTP-запрос для квитирования с сервером по протоколу TCP. В файле JavaScript необходимо указать  ``wsURI``  терминала и объявить WebSocket. Схема  ``wsURI``  является составным элементом протокола WebSocket и указывает путь к терминалу для приложения.

1. Щелкните правой кнопкой мыши узел проекта в окне "Проекты" и выберите "New > Other"(Создать > Другое).
2. Откройте мастер создания файлов и выберите 'Файл JavaScript' в категории 'Веб'. Нажмите 'Далее'.
3. В поле 'Имя файла JavaScript' введите *websocket*. Нажмите 'Готово'.
4. Добавьте в файл JavaScript следующие элементы.

[source,javascript]
----

var wsUri = "ws://" + document.location.host + document.location.pathname + "whiteboardendpoint";
var websocket = new WebSocket(wsUri);

websocket.onerror = function(evt) { onError(evt) };

function onError(evt) {
    writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
----

Этот сценарий инициализирует квитирование сеанса с сервером, когда браузер загружает файл  ``websocket.js`` .



. Откройте файл  ``index.html``  и добавьте следующий код (выделен *полужирным шрифтом*) в самый нижний сегмент файла, чтобы по завершении загрузки страницы загружался файл  ``websocket.js`` .

[source,html]
----

<body>
    *<h1>Collaborative Whiteboard App</h1>
        
    <script type="text/javascript" src="websocket.js"></script>*
</body>
----

Теперь можно проверить функционирование терминала WebSocket, возможность открытия сеанса и подключения клиента к сеансу.

 


=== Тестирование терминала

В этом упражнении показано, как добавить ряд простых методов в файл JavaScript, чтобы при подключении браузера к терминалу в окне браузера выводились данные  ``wsURI`` .

1. Добавьте тег  ``<div>``  (выделен *полужирным шрифтом*) в файл  ``index.html`` 

[source,html]
----

<h1>Collaborative Whiteboard App</h1>
        
*<div id="output"></div>*
<script type="text/javascript" src="websocket.js"></script>
----


. Добавьте следующее объявление и методы в файл  ``websocket.js`` . Сохраните изменения.

[source,javascript]
----

// For testing purposes
var output = document.getElementById("output");
websocket.onopen = function(evt) { onOpen(evt) };

function writeToScreen(message) {
    output.innerHTML += message + "<br>";
}

function onOpen() {
    writeToScreen("Connected to " + wsUri);
}
// End test functions
----

При загрузке страницы функции JavaScript будут выводить сообщение о том, что браузер подключен к терминалу. Эти функции можно удалить после успешной проверки функционирования терминала.



. Правой кнопкой мыши щелкните окно 'Проект' и выберите 'Выполнить'.

При запуске приложения среда IDE запускает сервер GlassFish и выполняет построение и развертывание приложения. В браузере открывается страница индекса со следующим сообщением.

image::images/websocket-browser1.png[title="Сообщение 'Подключение к терминалу установлено' в окне браузера"]

В окне браузера отображается терминал, принимающий сообщения:  ``http://localhost:8080/WhiteboardApp/whiteboardendpoint`` 


== Создание интерактивной доски

Этот раздел содержит инструкции по созданию классов и файлов JavaScript для отправки и получения текстовых сообщений JSON. Также в этом разделе показано, как создать элемент link:http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html[+HTML5 Canvas+] для рисования и отображения содержимого и HTML-форму  ``<form>``  с переключателями, с помощью которых можно выбрать форму и цвет кисти.


=== Добавление полотна на веб-страницу

В этом упражнении показано, как добавить элемент  ``canvas``  и элемент  ``form``  на страницу индекса по умолчанию. Флажки на форме определяют свойства кисти на полотне.

1. Откройте файл  ``index.html``  в редакторе исходного кода.
2. Удалите тег  ``<div>`` , добавленный перед тестированием терминала, и добавьте элементы  ``<table>``  и  ``<form>``  (выделены *полужирным шрифтом*) после открывающего тега body.

[source,html]
----

<h1>Collaborative Whiteboard App</h1>
        
    *<table>
        <tr>
            <td>
            </td>
            <td>
                <form name="inputForm">
                    

                </form>
            </td>
        </tr>
    </table>*
    <script type="text/javascript" src="websocket.js"></script>
    </body>
----


. Добавьте следующий код (выделен *полужирным шрифтом*) для элемента canvas.

[source,html]
----

        <table>
            <tr>
                <td>
                    *<canvas id="myCanvas" width="150" height="150" style="border:1px solid #000000;"></canvas>*
                </td>
----


. Добавьте элемент  ``<table>``  для создания переключателей, позволяющих выбирать цвет и форму. Сохраните изменения.

[source,html]
----

        <table>
            <tr>
                <td>
                    <canvas id="myCanvas" width="150" height="150" style="border:1px solid #000000;"></canvas>
                </td>
                <td>
                    <form name="inputForm">
                        *<table>

                            <tr>
                                <th>Color</th>
                                <td><input type="radio" name="color" value="#FF0000" checked="true">Red</td>
                                <td><input type="radio" name="color" value="#0000FF">Blue</td>
                                <td><input type="radio" name="color" value="#FF9900">Orange</td>
                                <td><input type="radio" name="color" value="#33CC33">Green</td>
                            </tr>

                            <tr>
                                <th>Shape</th>
                                <td><input type="radio" name="shape" value="square" checked="true">Square</td>
                                <td><input type="radio" name="shape" value="circle">Circle</td>
                                <td> </td>
                                <td> </td>
                            </tr>

                        </table>*
                    </form>
----

Форма, цвет и координаты любой фигуры, нарисованной на полотне, преобразуются в строковые данные в структуре JSON и отправляются в виде сообщения на терминал WebSocket.

 


=== Создание POJO

В этом упражнении показано, как создать простой компонент POJO.

1. Щелкните узел проекта правой кнопкой мыши и выберите Создать > Класс Java.
2. В поле 'Имя класса' введите *Figure* и выберите  ``org.sample.whiteboardapp``  в списке 'Пакет'. Нажмите 'Готово'.
3. В редакторе исходного кода добавьте следующие элементы (выделены *полужирным шрифтом*):

[source,java]
----

public class Figure {
    *private JsonObject json;*
}
----

При добавлении кода отобразится запрос на добавление оператора импорта для  ``javax.json.JsonObject`` . Если запрос не отображается, нажмите Alt-Enter.

Дополнительные сведения о  ``javax.json.JsonObject``  см. в описании интерфейса Java API for JSON Processing (link:http://jcp.org/en/jsr/detail?id=353[+JSR 353+]), который входит в спецификацию Java EE 7.



. Создайте операторы получения и установки для  ``json`` .

Методы получения и установки можно выбрать в раскрывающемся меню 'Вставить код' (Alt-Ins в Windows; Ctrl-I в Mac). В результате откроется диалоговое окно 'Создание методов получения и установки'. Также можно выбрать Исходный код > Вставить код в главном меню.

image::images/websocket-generategetter.png[title="Диалоговое окно 'Создание методов получения и установки'"]



. Добавьте конструктор для  ``json`` .

[source,java]
----

    public Figure(JsonObject json) {
        this.json = json;
    }
----

Конструктор можно выбрать в раскрывающемся меню 'Вставить код' (Ctrl-I).

image::images/websocket-generateconstructor.png[title="Раскрывающееся меню 'Создать конструктор'"]


. Добавьте метод  ``toString`` :

[source,java]
----

    @Override
    public String toString() {
        StringWriter writer = new StringWriter();
        Json.createWriter(writer).write(json);
        return writer.toString();
    }
----


. Щелкните правой кнопкой мыши в редакторе и выберите 'Исправить операторы импорта' (Alt-Shift-I; ⌘-Shift-I для Mac). Сохраните изменения.
 


=== Создание класса координат

Теперь необходимо создать класс координат фигур, которые пользователи будут рисовать на полотне.

1. Щелкните узел проекта правой кнопкой мыши и выберите Создать > Класс Java.
2. Откроется мастер создания классов Java. В поле 'Имя класса' введите *Coordinates* и выберите  ``org.sample.whiteboardapp``  в списке 'Пакет'. Нажмите 'Готово'.
3. В редакторе исходного кода добавьте следующий код. Сохраните изменения.

[source,java]
----

    private float x;
    private float y;

    public Coordinates() {
    }

    public Coordinates(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }
                
----

Этот класс содержит только поля для координат  ``x``  и  ``y`` , а также несколько методов получения и установки.

 


=== Создание строки JSON

В этом упражнении показано, как создать файл JavaScript, преобразующий все сведения о фигуре, которую пользователь рисует на полотне (элемент  ``canvas`` ), в структуру JSON для отправки на терминал WebSocket.

1. Щелкните узел проекта правой кнопкой мыши и выберите Создать > Файл JavaScript. В результате откроется мастер создания файлов JavaScript.
2. В поле 'Имя файла' введите *whiteboard*. Нажмите 'Готово'.

При нажатии кнопки 'Готово' среда IDE создает пустой файл JavaScript и открывает его в редакторе. Новый файл отображается в структуре узла 'Веб-страницы' в окне 'Проекты'.



. Добавьте следующий код для инициализации элемента canvas и создания прослушивателя событий.

[source,javascript]
----

var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
canvas.addEventListener("click", defineImage, false);
----

Когда пользователь нажимает на полотно (элемент  ``canvas`` ), вызывается метод  ``defineImage`` .



. Добавьте методы  ``getCurrentPos`` ,  ``defineImage``  и  ``drawImageText``  для создания структуры JSON и ее отправки на терминал ( ``sendText(json)`` ).

[source,javascript]
----

function getCurrentPos(evt) {
    var rect = canvas.getBoundingClientRect();
    return {
        x: evt.clientX - rect.left,
        y: evt.clientY - rect.top
    };
}
            
function defineImage(evt) {
    var currentPos = getCurrentPos(evt);
    
    for (i = 0; i < document.inputForm.color.length; i++) {
        if (document.inputForm.color[i].checked) {
            var color = document.inputForm.color[i];
            break;
        }
    }
            
    for (i = 0; i < document.inputForm.shape.length; i++) {
        if (document.inputForm.shape[i].checked) {
            var shape = document.inputForm.shape[i];
            break;
        }
    }
    
    var json = JSON.stringify({
        "shape": shape.value,
        "color": color.value,
        "coords": {
            "x": currentPos.x,
            "y": currentPos.y
        }
    });
    drawImageText(json);
        sendText(json);
}

function drawImageText(image) {
    console.log("drawImageText");
    var json = JSON.parse(image);
    context.fillStyle = json.color;
    switch (json.shape) {
    case "circle":
        context.beginPath();
        context.arc(json.coords.x, json.coords.y, 5, 0, 2 * Math.PI, false);
        context.fill();
        break;
    case "square":
    default:
        context.fillRect(json.coords.x, json.coords.y, 10, 10);
        break;
    }
}
----

Готовая к отправке структура JSON будет выглядеть примерно так:


[source,javascript]
----

{
 "shape": "square",
 "color": "#FF0000",
 "coords": {
 "x": 31.59999942779541,
 "y": 49.91999053955078
 }
} 
----

Теперь необходимо добавить метод  ``sendText(json)``  для отправки строковых данных JSON с помощью  ``websocket.send()`` .



. Откройте файл  ``websocket.js``  в редакторе и добавьте следующие методы для отправки JSON на терминал и рисования изображения при получении сообщения от терминала.

[source,javascript]
----

websocket.onmessage = function(evt) { onMessage(evt) };

function sendText(json) {
    console.log("sending text: " + json);
    websocket.send(json);
}
                
function onMessage(evt) {
    console.log("received: " + evt.data);
    drawImageText(evt.data);
}
----

NOTE:  Код, добавленный в файл  ``websocket.js``  для тестирования терминала, можно удалить.



. Добавьте следующую строку (выделена *полужирным шрифтом*) в нижний сегмент файла  ``index.html``  для загрузки файла  ``whiteboard.js`` .

[source,html]
----

        </table>
    <script type="text/javascript" src="websocket.js"></script>
    *<script type="text/javascript" src="whiteboard.js"></script>*
<body>
                
----
 


=== Реализация интерфейсов кодера и декодера

В этом упражнении показано, как создать классы для реализации интерфейсов декодера и кодера, которые требуются для преобразования сообщений WebSocket (JSON) в класс POJO  ``Figure``  и преобразования класса  ``Figure``  в формат строковых данных JSON, отправляемых на терминал.

Дополнительные сведения можно найти в разделе технической статьи о типах сообщений, кодерах и декодерах link:http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html[+JSR 356, Java API for WebSocket+].

1. Щелкните узел проекта правой кнопкой мыши и выберите Создать > Класс Java.
2. В поле 'Имя класса' введите *FigureEncoder* и выберите  ``org.sample.whiteboardapp``  в списке 'Пакет'. Нажмите 'Готово'.
3. В редакторе исходного кода реализуйте интерфейс кодера WebSocket. Для этого добавьте следующий код (выделен *полужирным шрифтом*):

[source,java]
----

            
public class FigureEncoder *implements Encoder.Text<Figure>* {
    
}
----


. Добавьте оператор импорта для  ``javax.websocket.Encoder``  и реализуйте абстрактные методы.

Поместите указатель мыши на объявление класса, нажмите Alt-Enter и выберите *Реализовать все абстрактные методы* в раскрывающемся меню.



. Внесите следующие изменения в созданные абстрактные методы (выделены *полужирным шрифтом*). Сохраните изменения.

[source,java]
----

    @Override
    public String encode(Figure *figure*) throws EncodeException {
        *return figure.getJson().toString();*
    }

    @Override
    public void init(EndpointConfig ec) {
        *System.out.println("init");*
    }

    @Override
    public void destroy() {
        *System.out.println("destroy");*
    }
----


. Щелкните узел проекта правой кнопкой мыши и выберите Создать > Класс Java.


. В поле 'Имя класса' введите *FigureDecoder* и выберите  ``org.sample.whiteboardapp``  в списке 'Пакет'. Нажмите 'Готово'.


. В редакторе исходного кода реализуйте интерфейс декодера WebSocket. Для этого добавьте следующий код (выделен *полужирным шрифтом*):

[source,java]
----

            
public class FigureDecoder *implements Decoder.Text<Figure>* {
    
}
----


. Добавьте оператор импорта для  ``javax.websocket.Decoder``  и реализуйте абстрактные методы.


. Внесите следующие изменения (выделены *полужирным шрифтом*) в созданные абстрактные методы.

[source,java]
----

    @Override
    public Figure decode(String *string*) throws DecodeException {
        *JsonObject jsonObject = Json.createReader(new StringReader(string)).readObject();
        return  new Figure(jsonObject);*
    }

    @Override
    public boolean willDecode(String *string*) {
        *try {
            Json.createReader(new StringReader(string)).readObject();
            return true;
        } catch (JsonException ex) {
            ex.printStackTrace();
            return false;
        }*
    
    }

    @Override
    public void init(EndpointConfig ec) {
        *System.out.println("init");*
    }

    @Override
    public void destroy() {
        *System.out.println("destroy");*
    }
----


. Исправьте операторы импорта и сохраните изменения.

Теперь необходимо внести изменения в файл  ``MyWhiteboard.java``  и указать кодер и декодер.

 


=== Запуск приложения

Скоро вы сможете запустить приложение. В этом упражнении показано, как изменить класс терминала WebSocket и указать кодер и декодер для строковых данных JSON, а также добавить метод для отправки строковых данных JSON на подключенные клиенты при получении сообщения.

1. Откройте файл  ``MyWhiteboard.java``  в редакторе.
2. Измените аннотацию  ``@ServerEndpoint``  и укажите кодер и декодер для терминала. Обратите внимание, что необходимо явно указать параметр  ``value``  для имени терминала.

[source,java]
----

@ServerEndpoint(*value=*"/whiteboardendpoint"*, encoders = {FigureEncoder.class}, decoders = {FigureDecoder.class}*)
        
----


. Удалите метод  ``onMessage`` , созданный по умолчанию.


. Добавьте метод  ``broadcastFigure``  и создайте для него аннотацию  ``@OnMessage`` .

[source,java]
----

    @OnMessage
    public void broadcastFigure(Figure figure, Session session) throws IOException, EncodeException {
        System.out.println("broadcastFigure: " + figure);
        for (Session peer : peers) {
            if (!peer.equals(session)) {
                peer.getBasicRemote().sendObject(figure);
            }
        }
    }
----


. Щелкните правой кнопкой мыши в редакторе и выберите 'Исправить операторы импорта' (Alt-Shift-I; ⌘-Shift-I для Mac). Сохраните изменения.


. В окне 'Проекты' щелкните проект правой кнопкой мыши и выберите 'Выполнить'.

При нажатии кнопки 'Выполнить' среда IDE открывает окно браузера с адресом link:http://localhost:8080/WhiteboardApp/[+http://localhost:8080/WhiteboardApp/+].

NOTE:  Может потребоваться удалить предыдущее приложение с сервера приложений или выполнить принудительную перезагрузку страницы в браузере.

Просмотрите сообщения браузера. Вы увидите, что при каждом нажатии на полотно на терминал отправляются строковые данные JSON.

image::images/websocket-onebrowser.png[title="Полотно с нарисованными фигурами в окне браузера и JSON в веб-консоли"]

Если открыть страницу с адресом  ``http://localhost:8080/WhiteboardApp/``  в другом браузере, можно видеть, что при каждом нажатии на полотно в окне одного браузера в окне другого браузера появляется новый круг или квадрат.

image::images/websocket-twobrowsers.png[title="Два браузера, отправляющие данные JSON через терминал"]


== Отправка двоичных данных на терминал

Теперь приложение может обрабатывать и отправлять строковые данные через JSON на терминал, а затем эти строковые данные будут пересылаться на подключенные клиенты. Этот раздел содержит инструкции по изменению файлов JavaScript для отправки и получения двоичных данных.

Чтобы отправлять двоичные данные на терминал, необходимо задать для свойства  ``binaryType``  компонента WebSocket значение  ``arraybuffer`` . Таким образом гарантируется, что любые двоичные данные, передаваемые посредством WebSocket, передаются посредством  ``ArrayBuffer`` . Преобразование двоичных данных осуществляется методом  ``defineImageBinary``  в файле  ``whiteboard.js`` .

1. Откройте файл  ``websocket.js``  и добавьте в него следующий код, чтобы задать для свойства  ``binaryType``  компонента WebSocket значение  ``arraybuffer`` .

[source,javascript]
----

websocket.binaryType = "arraybuffer";
----


. Добавьте следующий метод для отправки двоичных данных на терминал.

[source,javascript]
----

function sendBinary(bytes) {
    console.log("sending binary: " + Object.prototype.toString.call(bytes));
    websocket.send(bytes);
}
----


. Измените метод  ``onMessage``  и добавьте в него следующий код (выделен *полужирным шрифтом*), чтобы выбрать метод обновления полотна в соответствии с типом данных, переданных во входящем сообщении.

[source,javascript]
----

function onMessage(evt) {
    console.log("received: " + evt.data);
    *if (typeof evt.data == "string") {*
        drawImageText(evt.data);
    *} else {
        drawImageBinary(evt.data);
    }*
}
----

Метод  ``drawImageBinary``  вызывается при получении сообщения с двоичными данными.



. Откройте файл  ``whiteboard.js``  и добавьте следующие методы. Метод  ``drawImageBinary``  вызывается для обновления полотна после анализа входных двоичных данных. Метод  ``defineImageBinary``  используется для подготовки снимка полотна в двоичном формате.

[source,javascript]
----

function drawImageBinary(blob) {
    var bytes = new Uint8Array(blob);
//    console.log('drawImageBinary (bytes.length): ' + bytes.length);
    
    var imageData = context.createImageData(canvas.width, canvas.height);
    
    for (var i=8; i<imageData.data.length; i++) {
        imageData.data[i] = bytes[i];
    }
    context.putImageData(imageData, 0, 0);
    
    var img = document.createElement('img');
    img.height = canvas.height;
    img.width = canvas.width;
    img.src = canvas.toDataURL();
}
                    
function defineImageBinary() {
    var image = context.getImageData(0, 0, canvas.width, canvas.height);
    var buffer = new ArrayBuffer(image.data.length);
    var bytes = new Uint8Array(buffer);
    for (var i=0; i<bytes.length; i++) {
        bytes[i] = image.data[i];
    }
    sendBinary(buffer);
}
----

Теперь необходимо реализовать вызов метода  ``defineImageBinary`` , когда требуется сгенерировать двоичные данные с типом  ``ArrayBuffer``  и отправить их на терминал.



. Откройте файл  ``index.html``  и измените элемент  ``<table>``  так, чтобы в таблице формы появилась следующая строка.

[source,html]
----

<tr>
    <th> </th>
    <td><input type="submit" value="Send Snapshot" onclick="defineImageBinary(); return false;"></td>
    <td> </td>
    <td> </td>
    <td> </td>
</tr>
                
----

Новая строка содержит кнопку 'Отправить снимок', которая позволяет отправить двоичный снимок полотна на подключенные одноранговые узлы. При нажатии этой кнопки вызывается метод  ``defineImageBinary``  в файле  ``whiteboard.js`` .



. Откройте файл  ``MyWhiteboard.java``  и добавьте следующий метод. Этот метод используется для отправки двоичных данных на одноранговые узлы, когда на терминал поступает сообщение с двоичными данными.

[source,java]
----

@OnMessage
public void broadcastSnapshot(ByteBuffer data, Session session) throws IOException {
    System.out.println("broadcastBinary: " + data);
    for (Session peer : peers) {
        if (!peer.equals(session)) {
            peer.getBasicRemote().sendBinary(data);
        }
    }
}
----

NOTE:  Потребуется добавить оператор импорта для  ``java.nio.ByteBuffer`` .

Вы можете изменить приложение так, чтобы пользователь мог остановить отправку данных на терминал. По умолчанию все одноранговые узлы подключаются в момент открытия страницы и отправки данных из браузера на все подключенные клиенты. Вы можете добавить простой условный оператор, чтобы данные отправлялись на терминал только в том случае, если выбран этот параметр. Этот параметр не влияет на получение данных. Клиенты по-прежнему будут получать данные от терминала.

1. Измените метод  ``defineImage``  в файле  ``whiteboard.js`` , добавив в него следующий код (выделен *полужирным шрифтом*).

[source,javascript]
----

        drawImageText(json);
*    if (document.getElementById("instant").checked) {*
        sendText(json);
*    }*
}
----

Этот код условия проверяет,  ``установлен ли флажок``  для элемента с этим идентификатором



. Откройте файл  ``index.html``  и измените элемент  ``<table>`` , добавив в форму флажок.

[source,html]
----

<tr>
    <th> </th>
    <td><input type="submit" value="Send Snapshot" onclick="defineImageBinary(); return false;"></td>
    <td>*<input type="checkbox" id="instant" value="Online" checked="true">Online*</td>
    <td> </td>
    <td> </td>
</tr>
                
----

Если флажок 'Подключено' не установлен, отправка данных не осуществляется, но клиенты по-прежнему могут получать данные от терминала.

Если добавить кнопку 'Отправить снимок' и флажок 'Подключено' и снова запустить приложение, на странице индекса отобразятся новые элементы. Если открыть другой браузер и снять флажок 'Подключено', можно видеть, что при нажатии на полотно сообщение JSON не отправляется на терминал.

image::images/websocket-onebrowser-binary.png[title="Веб-консоль в окне браузера с сообщением об отправке двоичных данных"]

Если нажать кнопку 'Отправить снимок', двоичные данные будут отправлены на терминал, который будет транслировать эти данные на все подключенные клиенты.


link:/about/contact_form.html?to=3&subject=Feedback:%20Using%20the%20WebSocket%20API%20in%20a%20Web%20Application[+Отправить отзыв по этому учебному курсу+]



== См. также

Подробнее об использовании IDE NetBeans для разработки приложений Java EE см. в следующих ресурсах:

* Демонстрация: link:maven-websocketapi-screencast.html[+Использование WebSocket API в веб-приложении+]
* link:javaee-intro.html[+Введение в технологию Java EE +]
* link:javaee-gettingstarted.html[+Начало работы с приложениями Java EE+]
* link:../../trails/java-ee.html[+Учебная карта по Java EE и Java Web+]

Дополнительные сведения об использовании Java EE можно найти в link:http://download.oracle.com/javaee/6/tutorial/doc/[+Учебном курсе по Java EE+].

To send comments and suggestions, get support, and keep informed on the latest developments on the IDE NetBeans Java EE development features, link:../../../community/lists/top.html[+join the nbj2ee mailing list+].

