Определение

WebSocket – это протокол компьютерной связи, предоставляющий полнодуплексные каналы связи по одному TCP-соединению.

В отличие от HTTP, WebSocket обеспечивает полнодуплексную связь. Более того, WebSocket позволяет передавать потоки сообщений поверх TCP. Только TCP обрабатывает потоки байтов без присущей концепции сообщения.

Таким образом, с помощью WebSocket вы можете осуществлять полнодуплексную связь, а подписавшись на канал, вы можете избежать опроса. Этот протокол используется во многих приложениях, и они станут будущим современных приложений.

Архитектура

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

Соединение создает канал от устройств, но для получения информации вы должны подписаться на тему (иногда подписка неявно связана с соединением).

Большое отличие от сервера REST заключается в том, что в HTTP-запросе вы отправляете запрос, и вы должны дождаться ответа, чтобы получить данные и запустить новый запрос в том же соединении, с помощью WS вы можете передавать потоковые запросы и потоковые ответы, а затем работать, когда захотите.

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

WebSocket-клиент

Теперь мы увидим простой клиент WebSocket, который будет подключаться к эхо-серверу WebSocket. Его поведение довольно простое: вы отправляете сообщение, а сервер отвечает тем же сообщением, поэтому вы можете проверить отправку и получение вашего клиента.

JavaScript WebSocket-клиент

Вот упрощенная версия тестового клиента WebSocket

<! —

js простой клиент WebSocket

Я использую эхо-сервер ws://echo.websocket.org/

Когда вы отправляете сообщение на этот сервер, вы получаете

ответ с тем же сообщением

‹!DOCTYPE html›

‹html›

‹метакодировка="utf-8/›

‹title›Тест WebSocket‹/название›

#выход {

граница: сплошная 1px #999999;

цвет верхней границы: #CCCCCC;

цвет левой границы: #CCCCCC;

отступ: 5px;

ширина: 300 пикселей;

высота: 172 пикселя;

переполнение-у: прокрутка;

семейство шрифтов: «Open Sans»;

размер шрифта: 13px;

}

‹язык сценария=”javascript” тип=”текст/javascript”›

var wsUri = «wss://echo.websocket.org/»;

вывод переменной;

инициализация функции () {

вывод = документ.getElementById («выход»);

тестВеб-сокет();

}

функция testWebSocket() {

веб-сокет = новый веб-сокет (wsUri);

websocket.onopen = функция (evt) {

onOpen(evt)

};

websocket.onclose = функция (evt) {

при закрытии (evt)

};

websocket.onmessage = функция (evt) {

onMessage (evt)

};

websocket.onerror = функция (evt) {

при ошибке (evt)

};

}

функция onOpen(evt) {

writeToScreen("ПОДКЛЮЧЕНО");

// doSend("Привет, я простой клиент Ayush JS!!");

}

функция onClose (evt) {

writeToScreen("ОТКЛЮЧЕНО");

}

функция onMessage(evt) {

writeToScreen(‘‹span style="color: blue;"›ОТВЕТ: ‘ + evt.data + ‘‹/span›’);

// веб-сокет.close();

}

функция при ошибке (evt) {

writeToScreen(‘‹span style=”color: red;”›ERROR:‹/span› ‘ + evt.data);

}

функция doSend(сообщение) {

writeToScreen("ОТПРАВЛЕНО: " + сообщение);

websocket.send (сообщение);

}

функция writeToScreen(сообщение) {

var pre = document.createElement("p");

pre.style.wordWrap = «разрывное слово»;

pre.innerHTML = сообщение;

вывод.appendChild (предварительно);

output.scrollTop = output.scrollHeight;

}

window.addEventListener («загрузка», инициализация, ложь);

‹/скрипт›

‹h2›Тест WebSocket‹/h2›

‹div id="вывод"›‹/div›

<br/>

‹тип ввода=”кнопка” значение=”Отправить сообщение!” onclick="doSend('Простое сообщение клиента Ayush js!!')"/›‹/div›

‹input type="button" value="Закрыть соединение!" onclick="websocket.close()"/›‹/div›

‹/html›

Важной частью кода является

функция testWebSocket() {

веб-сокет = новый веб-сокет (wsUri);

websocket.onopen = функция (evt) {

onOpen(evt)

};

websocket.onclose = функция (evt) {

при закрытии (evt)

};

websocket.onmessage = функция (evt) {

onMessage (evt)

};

websocket.onerror = функция (evt) {

при ошибке (evt)

};

}

Это простой код для установления соединения и присоединения ряда функций обратного вызова к указанным событиям:

§ onopen: вызывается при установлении соединения;

§ onclose: вызывается при отключении клиента;

§ onmessage: здесь вызывается, когда приходит сообщение;

§ onerror: вызывается при возникновении ошибки.

Как вы можете видеть, чтобы отправить сообщение, вы должны просто вызвать websocket.send после соединения.

Библиотека

Вы можете найти библиотеку непосредственно в репозитории библиотек Arduino (Tools --> Manage libraries..).

Обратите внимание, что для esp8266 и esp32 необходимо использовать версию 2.x.x.

esp8266 WebSocket-клиент

Чтобы подключиться к вашему esp8266, код довольно прост.

/*

* esp8266 простой клиент WebSocket

*

* Я использую эхо-сервер ws://echo.websocket.org/

* Когда вы отправляете сообщение на этот сервер, вы получаете

* ответ с тем же сообщением

*

*/

#include ‹Arduino.h›

#include ‹ESP8266WiFi.h›

#include ‹WebSocketsClient.h›

WebSocketsClient webSocket;

const char *ssid = «‹ВАШ-SSID›»;

const char *password = «‹ВАШ-ПАРОЛЬ›»;

длинное сообщение без знака Interval = 5000;

логическое соединение = ложь;

#define Серийный номер DEBUG_SERIAL

void webSocketEvent (тип WStype_t, uint8_t * полезная нагрузка, длина size_t) {

переключатель (тип) {

случай WStype_DISCONNECTED:

DEBUG_SERIAL.printf("[WSc] Отключено!\n");

подключен = ложь;

перерыв;

случай WStype_CONNECTED: {

DEBUG_SERIAL.printf("[WSc] Подключен к URL: %s\n", полезная нагрузка);

подключен = истина;

// отправляем сообщение на сервер при подключении

DEBUG_SERIAL.println("[WSc] SENT: Connected");

webSocket.sendTXT («Подключено»);

}

перерыв;

случай WStype_TEXT:

DEBUG_SERIAL.printf("[WSc] RESPONSE: %s\n", полезная нагрузка);

перерыв;

случай WStype_BIN:

DEBUG_SERIAL.printf("[WSc] получить двоичную длину: %u\n", длина);

hexdump(полезная нагрузка, длина);

перерыв;

случай WStype_PING:

// понг будет отправлен автоматически

DEBUG_SERIAL.printf("[WSc] получить ping\n");

перерыв;

случай WStype_PONG:

// ответ на пинг, который мы отправляем

DEBUG_SERIAL.printf("[WSc] get pong\n");

перерыв;

}

}

недействительная установка () {

DEBUG_SERIAL.begin(115200);

// DEBUG_SERIAL.setDebugOutput(true);

DEBUG_SERIAL.println();

DEBUG_SERIAL.println();

DEBUG_SERIAL.println();

for(uint8_t t = 4; t › 0; t — ) {

DEBUG_SERIAL.printf("[НАСТРОЙКА] ОЖИДАНИЕ ЗАГРУЗКИ %d…\n", t);

DEBUG_SERIAL.flush();

задержка(1000);

}

WiFi.begin(ssid, пароль);

в то время как ( WiFi.status() != WL_CONNECTED ) {

задержка ( 500 );

Serial.print (".");

}

DEBUG_SERIAL.print («Локальный IP:»); DEBUG_SERIAL.println(WiFi.localIP());

// адрес сервера, порт и URL

webSocket.begin("echo.websocket.org", 80, "/");

// обработчик события

webSocket.onEvent(webSocketEvent);

}

unsigned long lastUpdate = millis();

недействительный цикл () {

веб-сокет.цикл();

если (подключено && lastUpdate+messageInterval‹millis()){

DEBUG_SERIAL.println("[WSc] SENT: Простое сообщение клиента js!!");

webSocket.sendTXT("Простое клиентское сообщение Ayush js!!");

последнее обновление = миллис();

}

}

Основная часть:

переключатель (тип) {

случай WStype_DISCONNECTED:

DEBUG_SERIAL.printf("[WSc] Отключено!\n");

подключен = ложь;

перерыв;

случай WStype_CONNECTED: {

DEBUG_SERIAL.printf("[WSc] Подключен к URL: %s\n", полезная нагрузка);

подключен = истина;

// отправляем сообщение на сервер при подключении

DEBUG_SERIAL.println("[WSc] SENT: Connected");

webSocket.sendTXT («Подключено»);

}

перерыв;

случай WStype_TEXT:

DEBUG_SERIAL.printf("[WSc] RESPONSE: %s\n", полезная нагрузка);

перерыв;

случай WStype_BIN:

DEBUG_SERIAL.printf("[WSc] получить двоичную длину: %u\n", длина);

hexdump(полезная нагрузка, длина);

перерыв;

случай WStype_PING:

// понг будет отправлен автоматически

DEBUG_SERIAL.printf("[WSc] получить ping\n");

перерыв;

случай WStype_PONG:

// ответ на пинг, который мы отправляем

DEBUG_SERIAL.printf("[WSc] get pong\n");

перерыв;

}

§ WStype_CONNECTED: когда соединение установлено;

§ WStype_DISCONNECTED: когда клиент отключен;

§ WStype_TEXT: при получении текстового сообщения;

§ WStype_BIN: при поступлении двоичного сообщения;

§ WStype_PING и WStype_PONG: здесь используется как сообщение «подтверждения активности».

Для подключения необходимо использовать эту команду:

// адрес сервера, порт и URL

webSocket.begin("echo.websocket.org", 80, "/");

и с помощью webSocket.onEvent мы присоединяем обратный вызов к событиям.

С клиентом для отправки простого сообщения мы используем webSocket.sendTXT.

Как мы видим, управление WS достаточно простое и одинаковое на всех языках.

WebSocket-клиент esp32

Версия esp32 очень похожа на версию esp8266.

/*

* esp32 простой клиент WebSocket

*

* Я использовал эхо-сервер ws://echo.websocket.org/

* Когда вы отправляете сообщение на этот сервер, вы получаете

* ответ с тем же сообщением

*

*/

#include ‹Arduino.h›

#include ‹WiFi.h›

#include ‹WebSocketsClient.h›

WebSocketsClient webSocket;

const char *ssid = «‹ВАШ-SSID›»;

const char *password = «‹ВАШ-ПАРОЛЬ›»;

длинное сообщение без знака Interval = 5000;

логическое соединение = ложь;

#define Серийный номер DEBUG_SERIAL

void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) {

const uint8_t* src = (const uint8_t*) mem;

DEBUG_SERIAL.printf("\n[HEXDUMP] Адрес: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);

for(uint32_t i = 0; i ‹ len; i++) {

если (i% столбцов == 0) {

DEBUG_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);

}

DEBUG_SERIAL.printf("%02X", *src);

источник++;

}

DEBUG_SERIAL.printf("\n");

}

void webSocketEvent (тип WStype_t, uint8_t * полезная нагрузка, длина size_t) {

переключатель (тип) {

случай WStype_DISCONNECTED:

DEBUG_SERIAL.printf("[WSc] Отключено!\n");

подключен = ложь;

перерыв;

случай WStype_CONNECTED: {

DEBUG_SERIAL.printf("[WSc] Подключен к URL: %s\n", полезная нагрузка);

подключен = истина;

// отправляем сообщение на сервер при подключении

DEBUG_SERIAL.println("[WSc] SENT: Connected");

webSocket.sendTXT («Подключено»);

}

перерыв;

случай WStype_TEXT:

DEBUG_SERIAL.printf("[WSc] RESPONSE: %s\n", полезная нагрузка);

перерыв;

случай WStype_BIN:

DEBUG_SERIAL.printf("[WSc] получить двоичную длину: %u\n", длина);

hexdump(полезная нагрузка, длина);

перерыв;

случай WStype_PING:

// понг будет отправлен автоматически

DEBUG_SERIAL.printf("[WSc] получить ping\n");

перерыв;

случай WStype_PONG:

// ответ на пинг, который мы отправляем

DEBUG_SERIAL.printf("[WSc] get pong\n");

перерыв;

случай WStype_ERROR:

случай WStype_FRAGMENT_TEXT_START:

случай WStype_FRAGMENT_BIN_START:

случай WStype_FRAGMENT:

случай WStype_FRAGMENT_FIN:

перерыв;

}

}

недействительная установка () {

DEBUG_SERIAL.begin(115200);

// DEBUG_SERIAL.setDebugOutput(true);

DEBUG_SERIAL.println();

DEBUG_SERIAL.println();

DEBUG_SERIAL.println();

for(uint8_t t = 4; t › 0; t — ) {

DEBUG_SERIAL.printf("[НАСТРОЙКА] ОЖИДАНИЕ ЗАГРУЗКИ %d…\n", t);

DEBUG_SERIAL.flush();

задержка(1000);

}

WiFi.begin(ssid, пароль);

в то время как ( WiFi.status() != WL_CONNECTED ) {

задержка ( 500 );

DEBUG_SERIAL.print («.»);

}

DEBUG_SERIAL.print («Локальный IP:»); DEBUG_SERIAL.println(WiFi.localIP());

// адрес сервера, порт и URL

webSocket.begin("echo.websocket.org", 80, "/");

// обработчик события

webSocket.onEvent(webSocketEvent);

}

unsigned long lastUpdate = millis();

недействительный цикл () {

веб-сокет.цикл();

если (подключено && lastUpdate+messageInterval‹millis()){

DEBUG_SERIAL.println("[WSc] SENT: Простое клиентское сообщение Ayush js!");

webSocket.sendTXT("Простое клиентское сообщение Ayush js!");

последнее обновление = миллис();

}

}

Но мы можем сразу выделить некоторые отличия, в webSocketEvent надо обратить внимание на метод hexdump для декодирования бинарного сообщения и есть набор переключателей case, которые надо реализовать (их я добавил в конце).

esp8266 сервер веб-сокетов

Сервер WebSocket Это очень похоже на клиент, у нас есть серия событий для управления сообщениями и статусом соединения, вот пример

/*

* esp8266 простой сервер WebSocket

*

* Ответ сервера с

* эхо сообщения, которое вы отправляете

* и отправлять рассылку каждые 5 секунд

*

*/

#include ‹Arduino.h›

#include ‹ESP8266WiFi.h›

#include ‹WebSocketsServer.h›

const char *ssid = «‹ВАШ-SSID›»;

const char *password = «‹ВАШ-ПАРОЛЬ›»;

const uint8_t wsPort = 81;

длинное сообщение без знака Interval = 5000;

логическое соединение = ложь;

#define Серийный номер DEBUG_SERIAL

WebSocketsServer webSocket = WebSocketsServer(wsPort);

void webSocketEvent (число uint8_t, тип WStype_t, uint8_t * полезная нагрузка, длина size_t) {

переключатель (тип) {

случай WStype_DISCONNECTED:

DEBUG_SERIAL.printf("[%u] Отключено!\n", число);

перерыв;

случай WStype_CONNECTED:

{

IP-адрес ip = webSocket.remoteIP(число);

DEBUG_SERIAL.printf("[%u] Подключено с URL-адреса %d.%d.%d.%d: %s\n", num, ip[0], ip[1], ip[2], ip[3 ], полезная нагрузка);

// отправляем сообщение клиенту

webSocket.sendTXT (число, «Подключено»);

}

перерыв;

случай WStype_TEXT:

DEBUG_SERIAL.printf("[%u] ПОЛУЧИТЬ TXT: %s\n", число, полезная нагрузка);

// отправляем сообщение клиенту

webSocket.sendTXT(num, "(ECHO MESSAGE) "+String((char *)payload));

// отправляем данные всем подключенным клиентам

// webSocket.broadcastTXT("сообщение здесь");

перерыв;

случай WStype_BIN:

DEBUG_SERIAL.printf("[%u] получить двоичную длину: %u\n", число, длина);

hexdump(полезная нагрузка, длина);

// отправляем сообщение клиенту

// webSocket.sendBIN(число, полезная нагрузка, длина);

перерыв;

}

}

недействительная установка () {

DEBUG_SERIAL.begin(115200);

// DEBUG_SERIAL.setDebugOutput(true);

DEBUG_SERIAL.println();

DEBUG_SERIAL.println();

DEBUG_SERIAL.println();

for(uint8_t t = 4; t › 0; t — ) {

DEBUG_SERIAL.printf("[НАСТРОЙКА] ОЖИДАНИЕ ЗАГРУЗКИ %d…\n", t);

DEBUG_SERIAL.flush();

задержка(1000);

}

WiFi.begin(ssid, пароль);

в то время как ( WiFi.status() != WL_CONNECTED ) {

задержка ( 500 );

Serial.print (".");

}

DEBUG_SERIAL.println («Полный URI WebSocket: «);

DEBUG_SERIAL.print("ws://");

DEBUG_SERIAL.print(WiFi.localIP());

DEBUG_SERIAL.print («:»);

DEBUG_SERIAL.print(wsPort);

DEBUG_SERIAL.println («/»);

веб-сокет.начать();

webSocket.onEvent(webSocketEvent);

}

unsigned long lastUpdate = millis();

недействительный цикл () {

веб-сокет.цикл();

если (lastUpdate+messageInterval‹millis()){

DEBUG_SERIAL.println("[WSc] SENT: Простое широковещательное клиентское сообщение!!");

webSocket.broadcastTXT("Простое широковещательное клиентское сообщение!!");

последнее обновление = миллис();

}

}

Вы можете получить ws uri для установки клиента из последовательного вывода:

[НАСТРОЙКА] ОЖИДАНИЕ ЗАГРУЗКИ 4…

[НАСТРОЙКА] ОЖИДАНИЕ ЗАГРУЗКИ 3…

[НАСТРОЙКА] ОЖИДАНИЕ ЗАГРУЗКИ 2…

[НАСТРОЙКА] ОЖИДАНИЕ ЗАГРУЗКИ 1…

…..

Полный uri WebSocket:

ws://192.168.1.127:81/

Структура довольно похожа на клиент, но вы должны обратить внимание на:

Когда клиент подключен, он связан с идентификатором, и его можно использовать для отправки сообщения конкретному клиенту, например, эхо-сообщение.

// отправляем сообщение клиенту

webSocket.sendTXT(num, "(ECHO MESSAGE) "+String((char *)payload));

Чтобы отправить сообщение всем клиентам, вы можете использовать broadcastTXT:

webSocket.broadcastTXT("Простое широковещательное клиентское сообщение!!");

esp32 WebSocket-сервер

/*

* esp32 простой сервер WebSocket

*

* Ответ сервера с

* эхо сообщения, которое вы отправляете

* и отправлять рассылку каждые 5 секунд

*

*/

#include ‹Arduino.h›

#include ‹WiFi.h›

#include ‹WebSocketsServer.h›

const char *ssid = «‹ВАШ-SSID›»;

const char *password = «‹ВАШ-ПАРОЛЬ›»;

const uint8_t wsPort = 81;

длинное сообщение без знака Interval = 5000;

логическое соединение = ложь;

#define Серийный номер DEBUG_SERIAL

WebSocketsServer webSocket = WebSocketsServer(wsPort);

void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) {

const uint8_t* src = (const uint8_t*) mem;

DEBUG_SERIAL.printf("\n[HEXDUMP] Адрес: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);

for(uint32_t i = 0; i ‹ len; i++) {

если (i% столбцов == 0) {

DEBUG_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);

}

DEBUG_SERIAL.printf("%02X", *src);

источник++;

}

DEBUG_SERIAL.printf("\n");

}

void webSocketEvent (число uint8_t, тип WStype_t, uint8_t * полезная нагрузка, длина size_t) {

переключатель (тип) {

случай WStype_DISCONNECTED:

DEBUG_SERIAL.printf("[%u] Отключено!\n", число);

перерыв;

случай WStype_CONNECTED:

{

IP-адрес ip = webSocket.remoteIP(число);

DEBUG_SERIAL.printf("[%u] Подключено с URL-адреса %d.%d.%d.%d: %s\n", num, ip[0], ip[1], ip[2], ip[3 ], полезная нагрузка);

// отправляем сообщение клиенту

webSocket.sendTXT (число, «Подключено»);

}

перерыв;

случай WStype_TEXT:

DEBUG_SERIAL.printf("[%u] ПОЛУЧИТЬ TXT: %s\n", число, полезная нагрузка);

// отправляем сообщение клиенту

webSocket.sendTXT(num, "(ECHO MESSAGE) "+String((char *)payload));

// отправляем данные всем подключенным клиентам

// webSocket.broadcastTXT("сообщение здесь");

перерыв;

случай WStype_BIN:

DEBUG_SERIAL.printf("[%u] получить двоичную длину: %u\n", число, длина);

hexdump(полезная нагрузка, длина);

// отправляем сообщение клиенту

// webSocket.sendBIN(число, полезная нагрузка, длина);

перерыв;

случай WStype_ERROR:

случай WStype_FRAGMENT_TEXT_START:

случай WStype_FRAGMENT_BIN_START:

случай WStype_FRAGMENT:

случай WStype_FRAGMENT_FIN:

случай WStype_PING:

случай WStype_PONG:

перерыв;

}

}

недействительная установка () {

DEBUG_SERIAL.begin(115200);

// DEBUG_SERIAL.setDebugOutput(true);

DEBUG_SERIAL.println();

DEBUG_SERIAL.println();

DEBUG_SERIAL.println();

for(uint8_t t = 4; t › 0; t — ) {

DEBUG_SERIAL.printf("[НАСТРОЙКА] ОЖИДАНИЕ ЗАГРУЗКИ %d…\n", t);

DEBUG_SERIAL.flush();

задержка(1000);

}

WiFi.begin(ssid, пароль);

в то время как ( WiFi.status() != WL_CONNECTED ) {

задержка ( 500 );

DEBUG_SERIAL.print («.»);

}

DEBUG_SERIAL.println();DEBUG_SERIAL.println("Полный URI WebSocket: ");

DEBUG_SERIAL.print("ws://");

DEBUG_SERIAL.print(WiFi.localIP());

DEBUG_SERIAL.print («:»);

DEBUG_SERIAL.print(wsPort);

DEBUG_SERIAL.println («/»);

веб-сокет.начать();

webSocket.onEvent(webSocketEvent);

}

unsigned long lastUpdate = millis()+messageInterval;

недействительный цикл () {

веб-сокет.цикл();

если (lastUpdate+messageInterval‹millis()){

DEBUG_SERIAL.println("[WSc] SENT: Простое широковещательное клиентское сообщение!!");

webSocket.broadcastTXT("Простое широковещательное клиентское сообщение!!");

последнее обновление = миллис();

}

}

Есть несколько отличий: импорт WiFi, шестнадцатеричный дамп, не реализованный изначально, и некоторые параметры, которые являются обязательными в случае переключателя управления событиями.