Websocket

WebSocket — протокол для обмена сообщениями между браузером и веб-сервером в режиме реального времени. Т.е. он позволяет увеличить скорость обмена данными между сервером и клиентом за счет более легкого, нежели HTTP протокола и организации постоянного соединения. Читать википедию или доки мозиллы (En или Ru). Проверить поддержку WebSocket в браузере можно введением названия одноименного конструктора в консоли веб-клиента, большинство браузеров найдут существующий объект. Основные задачи использования сокетов – задачи реального времени. Чаты, уведомления, игровые клиенты, онлайн слежение за показателями.

Создание WebSocket клиента

Давайте создадим клиента (WebSocket-клиента) для работы на сокетах.

Создадим index.html со стандартным набором тегов и HTML-форму внутри. При помощи Emmet можно развернуть строку (Посмотреть как работает Emmet):

  !>form[name=messages]>.row*3>input^^#status

Получится HTML-код

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<form action="" name="messages">
<div class="row"><input type="text"></div>
<div class="row"><input type="text"></div>
<div class="row"><input type="text"></div>
</form>
<div id="status"></div>
</body>
</html>

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>Пример работы с WebSocket</h1>
<form action="" name="messages">
<div class="row">Имя: <input type="text"></div>
<div class="row">Текст: <input type="text"></div>
<div class="row"><input type="submit" value="Поехали"></div>
</form>
<div id="status"></div>
<script>

</script>
</body>
</html>

 

В разделе script создадим переменную socket на основе конструктора WebSocket. В качестве аргумента передадим URL с протоколом «ws» (а для использования защищенного соединения используется wss, при этом нужно не забывать о SSL сертификате).

var socket = new WebSocket("ws://echo.websocket.org");

Адрес ws://echo.websocket.org указывает на расположение Websoket-сервера. Сейчас это эхо-сервер, он отвечает сообщением, которые мы будем ему отправлять.

Получим ссылку на HTML-элемент с идентификатором «status» для отображения дальнейшего статуса работы (Посмотреть как работает document.querySelectorAll()  ):

var status = document.querySelector("#status");

У объекта socket есть четыре основных события:

  • onopen — при установке/открытии соединения
  • onclose – при закрытии соединения
  • onmessage – при получении данных
  • onerror – при возникновении ошибки

Закрытие соединения может происходить по плану, когда мы явно отключаемся от сокета, или не запланированно, при проблемах связи или прекращении работы сокет-сервера. Выяснить было ли соединение закрыто по плану или нет, можно при помощи свойства event.wasClean объекта события.

Примечания: работу по созданию соединения нужно проводить после полной загрузки окна, иначе можно при отправке сообщения на тестовый сокет-сервер можно получить Uncaught DOMException: Failed to execute ‘send’ on ‘WebSocket’: Still in CONNECTING state. CONNECTING – это одна из констант, которые назначаются свойству readyState объекта WebSocket.

CONNECTING (ПОДКЛЮЧЕНИЕ) 0 Подключение еще не открыто.
OPEN (ОТКРЫТО) 1 Соединение открыто и готово к работе.
CLOSING (ЗАКРЫТИЕ) 2 Соединение находится в процессе закрытия.
CLOSED (ЗАКРЫТО) 3 Соединение закрыто или не может быть открыто.

 

Полное содержимое раздела script, при котором должна появиться строка статуса «соединение установлено».

window.onload = function(){
var socket = new WebSocket("ws://echo.websocket.org");
var status = document.querySelector("#status");

socket.onopen = function() {
  status.innerHTML = "cоединение установлено";
};

socket.onclose = function(event) {
  if (event.wasClean) {
    status.innerHTML = 'cоединение закрыто';
  } else {
    status.innerHTML = 'соединения как-то закрыто';
  }
  status.innerHTML += '<br>код: ' + event.code + ' причина: ' + event.reason;
};

socket.onmessage = function(event) {
  status.innerHTML = "пришли данные " + event.data;
};

socket.onerror = function(event) {
  status.innerHTML = "ошибка " + event.message;
};
}

Для отправки сообщения в вебсокет, используется метод send объекта WebSocket. Метод принимает строковый аргумент. Добавим имена нашим текстовым полям:

<form action="" name="messages">
  <div class="row">Имя: <input type="text" name="fname"></div>
  <div class="row">Текст: <input type="text" name="msg"></div>
  <div class="row"><input type="submit" value="Поехали"></div>
</form>

и пропишем реакцию на отправку формы, помещая в send() имя и текст отправителя:

//в рамках onload
document.forms["messages"].onsubmit = function(){
  let fname = this.fname.value;
  let msg   = this.msg.value;
  socket.send(`${fname} ${msg}`);
  return false;
}

let, обратные косые кавычки и знак доллара с фигурными скобками – это все стиль JavaScript2015. Если вы ещё не сталкивались с ним, то код var вместо let также будет  работать (let – оператор объявления переменной с блочным уровнем видимости, обратные косые кавычки позволяют создавать мультистроки, т.е. строки с переносами строки при редактировании текста, ${} – возможность подстановки переменных в строку без указания бесчисленного количества операторов склеивания +):

document.forms["messages"].onsubmit = function(){
  var fname = this.fname.value;
  var msg   = this.msg.value;
  socket.send(fname + ' ' + msg);
  return false;
}

Но в нашем примере останется первый способ.

Теперь при отправке данных из формы, ниже будет появляться текст «пришли данные» и далее будет идти фрагмент самими данными с эхо-сервера.

Отправка данных в формате JSON

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

Перепишем код нашего клиента так, чтобы он отправлял данные в этом формате, а при получении строки от эхо-сервера, конвертировал её снова в JSON.

Будем использовать методы встроенного в JavaScript объекта JSON:

  • JSON.stringify(obj) конвертация JavaScript-объекта (на самом деле и массив можно) в строку
  • JSON.parse(string) получение из строки объекта в JSON-нотации
document.forms["messages"].onsubmit = function(){
  let message = {
  name:this.fname.value,
  msg: this.msg.value
}
socket.send(JSON.stringify(message));
  return false;
}

socket.onmessage = function(event) {
  let message = JSON.parse(event.data);
  status.innerHTML = `пришли данные: <b>${message.name}</b>: ${message.msg}`;
};

Теперь на эхо-сервер отправляется JSON сериализованный в строку, а назад получается строка, из которой мы опять получаем JS-объект. Посмотреть реализацию вебсовета на Codepen.