WebSockets - NurOrNuLL/ESDP-AP-5-6-TEAM-2 GitHub Wiki

WebSockets

Раньше, чтобы получить новую информацию от сервера, клиент (браузер) должен был направить ему запрос, а сервер отправлял ответ. Без запроса не было ответа, то есть обновления страницы — сервер не мог ничего отправить сам. Например, пользователь получил сообщение или push-уведомление на сайте. Чтобы клиент об этом узнал, он должен опрашивать сервер с некоторой периодичностью, нет ли новых данных.

В таком подходе были недостатки:

  • Лишняя сетевая нагрузка. Клиент должен постоянно отправлять запросы на сервер, даже если новой информации нет.

  • Накладные расходы на установление нового соединения при каждом запросе/ответе. При этом расходуется и какое-то время. Забегая вперед, отметим, что в WebSocket соединение устанавливается один раз и держится открытым, поэтому дополнительных накладных расходов нет.

  • Низкая частота обновления данных. Клиент не может получить данные сразу, как только они появились на сервере. Новые данные поступают к клиенту только после того, как сервер получил запрос. Если важно получить данные как можно скорее, то можно было обращаться к серверу чаще, например один раз в 0,5 секунды. Однако это приводило к дополнительной нагрузке на сеть и сервер, которым приходилось проводить и обрабатывать множество запросов.

Веб-сокеты же позволяют устанавливать постоянное соединение, и теперь сервер может сам отправить клиенту новые данные, не дожидаясь запроса. Эта интерактивность устраняет вышеперечисленные недостатки. Чтобы увидеть преимущества протокола WebSocket, посмотрим, как он работает, сравнив его с протоколом HTTP.

Как работает WebSocket и в чем его отличия от HTTP

Протокол HTTP однонаправленный. После цикла «запрос — ответ» соединение закрывается, а любой следующий запрос каждый раз устанавливает новое соединение с сервером: сколько запросов, столько и соединений. Процесс передачи данных происходит с некоторыми задержками за счет того, что есть накладные расходы на установку нового соединения при каждом запросе/ответе, а также сетевая и серверная нагрузка из-за обилия периодических запросов. В чистом виде протокол HTTP сейчас используется все реже, ему на смену приходит HTTPS. Это не отдельный протокол, а надстройка над HTTP, позволяющая шифровать данные.

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

У веб-сокетов также есть возможность шифровать передаваемые данные, для этого используется надстройка над протоколом — WSS. Если передаваемые данные не зашифрованы, они становятся объектом для привлечения таких угроз, как несанкционированный доступ к клиенту третьих сторон, использование вредоносного ПО. Специальные надстройки протоколов передачи данных кодируют информацию на стороне отправителя и раскодируют на стороне получателя, оставляя ее зашифрованной для любых посредников. Так достигается безопасный транспортный уровень.

Механизм работы WebSocket

В интернете существуют строгие правила для передачи данных между клиентом и сервером (стек протоколов TCP/IP), но нет жестких правил о том, как устанавливать соединение и структурировать передаваемое сообщение. А это влияет на скорость.

Для установления соединения веб-сокет применяет метод открывающего рукопожатия. Он заключается в том, что клиент предваряет отправку/получение сообщений предварительным запросом, в котором клиент и сервер «договариваются» использовать веб-сокеты. Это и есть «рукопожатие». Структура такого запроса похожа на HTTP, но немного отличается от него. Затем клиент и сервер обмениваются данными уже в рамках этого соединения.

Кроме того, у веб-сокетов есть дополнительные расширения, которые дополняют и расширяют протокол. Например, есть расширение для сжатия данных. Или возможность передавать данные в формате протоколов SOAP, WAMP или XMPP. Чтобы эти расширения работали, они должны поддерживаться и сервером, и клиентом.

Когда использовать и не использовать WebSocket

WebSocket подходит, когда нужны обновления данных в реальном времени и возможность доставлять сообщения клиенту. Несколько примеров для WebSocket:

  • торговые приложения с изменчивостью котировок, цен в реальном времени: платформы продаж, биржи;
  • игровые приложения;
  • чаты, в том числе на сайтах поддержки;
  • push-уведомления;
  • социальные сети;
  • управление устройствами в IoT (используется подпротокол WAMP). Когда нужно получить неизменные данные, которые извлекаются только один раз, чтобы обработать их приложением, лучше использовать протокол HTTP, а не WebSocket. Это может быть, например, страница со статьей. После публикации статья практически не меняется, поэтому нет смысла использовать постоянное соединение для ее отображения.

Также HTTP-протокол предпочтительнее, если мы не хотим сохранять соединение в течение определенного времени или повторно использовать одно соединение для передачи данных. Это, например, ситуации, когда сервер должен отдать все данные для формы одним ответом.

Пример браузерного кода

Для открытия соединения достаточно создать объект WebSocket, указав в нём специальный протокол ws.:

var socket = new WebSocket("ws://javascript.ru/ws");

У объекта socket есть четыре колбэка: один при получении данных и три – при изменениях в состоянии соединения:

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

socket.onclose = function(event) {
  if (event.wasClean) {
    alert('Соединение закрыто чисто');
  } else {
    alert('Обрыв соединения'); // например, "убит" процесс сервера
  }
  alert('Код: ' + event.code + ' причина: ' + event.reason);
};

socket.onmessage = function(event) {
  alert("Получены данные " + event.data);
};

socket.onerror = function(error) {
  alert("Ошибка " + error.message);
};

Для посылки данных используется метод socket.send(data). Пересылать можно любые данные.

Например, строку:

socket.send("Привет");

Или файл, выбранный в форме:

socket.send(form.elements[0].file);

Для того, чтобы коммуникация была успешной, сервер должен поддерживать протокол WebSocket.

Django Channels

Эта библиотека должна принести в Django асинхронное сетевое программирование.

ASGI

Абстракция — это протокол под названием ASGI. Это стандартный интерфейс, который лежит между любым сетевым интерфейсом, сервером, будь то синхронный или асинхронный протокол и вашим приложением. Основным его понятием является канал.

Channel

Канал — это, упорядоченная по принципу first-in-first-out, очередь сообщений, которые обладают временем жизни. Эти сообщения могут быть доставлены ноль или один раз, и могут быть получены только одним Consumer’ом.

Consumers

В Consumer вы как раз пишете ваш код.

def ws_message (message) :
    message.reply_channel.send ( {
        'text': message.content ['text'],
} )

Функция, которая принимает message, может отослать несколько ответов, а может не отсылать ответ вообще. Очень похоже на view, единственная разница в том, что здесь нет функции return, тем самым мы можем говорить о том, сколько ответов мы возвращаем из функции.

Добавляем эту функцию в routing, например, вешаем ее на получение сообщения по веб-сокету.

from channels.routing import route
from myapp.consumers import ws_message
channel_routing = [
    route ('websocket.receive' ws_message),
}

Прописываем это в Django settings, также, как прописывали бы база данных.

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'asgiref.inmemory',
        'ROUTING': 'myproject.routing',
    },
}

В проекте может быть несколько Channel Layers, точно также как может быть несколько баз данных. Эта штука очень похожа на db router, если кто-то этим пользовался.

Далее мы определяем наше ASGI приложение. В нем синхронизируется то, как запускается Twisted и то, как запускаются синхронные воркеры — им всем нужно это приложение.

import os
from channels.asgi import get_channel_layer
os.environ.setdefault(
    'DJANGO_SETTINGS_MODULE',
    'myproject.settings',
)
channel_layer = get_channel_layer()

После этого, деплоим код: запускаем gunicorn, стандартно отправляем HTTP-запрос, синхронно, с view, как привыкли. Запускаем асинхронный сервер, который будет стоять фронтом перед нашей синхронной Django, и воркеры, которые будут обрабатывать сообщения.

$ gunicorn myproject.wsgi
$ daphne myproject.asgi:channel_layer
$ django-admin runworker

Reply channel

Как мы видели, у message есть такое понятие как Reply channel. Зачем это нужно?

Сhannel однонаправленный, соответственно WebSocket receive, WebSocket connect, WebSocket disconnect — это общий channel на систему для входных сообщений. А Reply channel — это channel, который строго привязан к коннекту пользователя. Соответственно, message имеет входной и выходной канал. Эта пара позволяет вам идентифицировать от кого вам пришло это сообщение.