Что такое веб-сокеты?
За последние несколько лет в сети и в мобильных приложениях начал появляться новый тип коммуникации, который называется веб-сокеты . Этот протокол долгождался, и в 2011 году он был окончательно стандартизирован IETF, открыв путь для широкого использования.
Этот новый протокол открывает гораздо более быструю и эффективную линию связи с клиентом. Как и HTTP, веб-сокеты работают поверх TCP-соединения, но они намного быстрее, потому что нам не нужно открывать новое соединение каждый раз, когда мы хотим отправить сообщение, так как соединение сохраняется до тех пор, пока сервер или клиент хочет.
Более того, поскольку соединение никогда не прерывается, у нас наконец-то появилась возможность полнодуплексной связи, то есть мы можем отправлять данные клиенту, а не ждать, пока он запросит данные с сервера . Это позволяет передавать данные туда и обратно, что идеально подходит для таких вещей, как приложения чата в реальном времени или даже игры.
Как работают веб-сокеты?
По своей сути веб-сокет - это просто TCP-соединение, которое обеспечивает полнодуплексную связь, то есть любая сторона соединения может отправлять данные другой, даже в одно и то же время.
Чтобы установить это соединение, протокол фактически инициирует рукопожатие как обычный HTTP-запрос, но затем «обновляется» с помощью HTTP-заголовка запроса на обновление, например:
GET /ws/chat HTTP/1.1
Host: chat.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: q1PZLMeDL4EwLkw4GGhADm==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 15
Origin: http://example.com
Затем сервер отправляет ответ HTTP 101 «Switching Protocols», подтверждая, что соединение будет обновлено. Как только это соединение установлено, оно переключается на двунаправленный двоичный протокол, после чего данные приложения могут быть отправлены.
Все, что протокол должен сделать, чтобы соединение оставалось открытым,
- это отправить несколько пакетов ping / pong, которые сообщают другой стороне, что они все еще там. Чтобы закрыть соединение, отправляется простой пакет «закрыть соединение».
Некоторые примеры веб-сокетов
Из множества доступных нам различных веб-библиотек для Node.js я решил использовать socket.io в этой статье, потому что он кажется наиболее популярным и, на мой взгляд, самым простым в использовании. Хотя каждая библиотека имеет свой собственный уникальный API, они также имеют много общего, поскольку все они построены на основе одного и того же протокола, поэтому, надеюсь, вы сможете перевести приведенный ниже код в любую библиотеку, которую хотите использовать.
В качестве HTTP-сервера я буду использовать Express , который является самым популярным сервером Node. Имейте в виду, что вы также можете просто использовать простой модуль http, если вам не нужны все функции Express. Хотя, поскольку большинство приложений будут использовать Express, мы будем использовать именно его.
Примечание . В этих примерах я удалил большую часть стандартного кода, поэтому часть этого кода не будет работать сразу после установки. В большинстве случаев вы можете обратиться к первому примеру, чтобы получить шаблонный код.
Установление соединения
Чтобы установить соединение между клиентом и сервером, сервер должен делать две вещи:
- Подключитесь к HTTP-серверу для обработки подключений через веб-сокеты.
socket.io.js
клиентскую библиотеку socket.io.js как статический ресурс
В приведенном ниже коде вы можете увидеть выполнение пункта (1) в
третьей строке. Элемент (2) выполняется за вас (по умолчанию)
socket.io
и обслуживается по пути /socket.io/socket.io.js
. По
умолчанию все соединения и ресурсы /socket.io
пути /socket.io.
Сервер
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
app.get('/', function(req, res) {
res.sendFile(__dirname + '/index.html');
});
server.listen(8080);
Клиенту также необходимо сделать две вещи:
- Загрузите библиотеку с сервера
- Вызовите
.connect()
на адрес сервера и путь к веб-сокету.
Клиент
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('/');
</script>
Если вы перейдете в браузере по http://localhost:8080
и проверите
HTTP-запросы за кулисами с помощью инструментов разработчика вашего
браузера, вы сможете увидеть выполняемое рукопожатие, включая запросы
GET и результирующий ответ HTTP 101 Switching Protocols.
Отправка данных с сервера на клиент
Хорошо, теперь перейдем к более интересным частям. В этом примере мы покажем вам наиболее распространенный способ отправки данных с сервера клиенту. В этом случае мы отправим сообщение на канал, на который можно подписаться и получить его. Так, например, клиентское приложение может прослушивать канал «объявлений», который будет содержать уведомления о общесистемных событиях, например, когда пользователь присоединяется к комнате чата.
На сервере это выполняется путем ожидания установления нового
соединения, а затем путем вызова socket.emit()
для отправки сообщения
всем подключенным клиентам.
Сервер
io.on('connection', function(socket) {
socket.emit('announcements', { message: 'A new user has joined!' });
});
Клиент
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('/');
socket.on('announcements', function(data) {
console.log('Got announcement:', data.message);
});
</script>
Отправка данных от клиента к серверу
Но что мы будем делать, если хотим отправить данные другим способом, от
клиента к серверу? Он очень похож на последний пример, в нем
используются socket.emit()
и socket.on()
.
Сервер
io.on('connection', function(socket) {
socket.on('event', function(data) {
console.log('A client sent us this dumb message:', data.message);
});
});
Клиент
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('/');
socket.emit('event', { message: 'Hey, I have an important message!' });
</script>
Подсчет подключенных пользователей
Это хороший пример для изучения, поскольку он показывает еще несколько
функций socket.io
(например, disconnect
), его легко реализовать и
он применим ко многим веб-приложениям. Мы будем использовать
connection
и disconnect
чтобы подсчитать количество активных
пользователей на нашем сайте, и мы обновим всех пользователей с текущим
счетчиком.
Сервер
var numClients = 0;
io.on('connection', function(socket) {
numClients++;
io.emit('stats', { numClients: numClients });
console.log('Connected clients:', numClients);
socket.on('disconnect', function() {
numClients--;
io.emit('stats', { numClients: numClients });
console.log('Connected clients:', numClients);
});
});
Клиент
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('/');
socket.on('stats', function(data) {
console.log('Connected clients:', data.numClients);
});
</script>
Гораздо более простой способ отслеживать количество пользователей на сервере - просто использовать это:
var numClients = io.sockets.clients().length;
Но, по-видимому, здесь есть некоторые проблемы , поэтому вам, возможно, придется самостоятельно отслеживать количество клиентов.
Комнаты и пространства имен
Скорее всего, по мере роста сложности вашего приложения вам потребуется дополнительная настройка ваших веб-сокетов, например отправка сообщений определенному пользователю или группе пользователей. Или, может быть, вам нужно строгое разделение логики между разными частями вашего приложения. Здесь на помощь приходят комнаты и пространства имен.
Примечание . Эти функции не являются частью протокола websocket, но
добавляются в него с помощью socket.io
.
По умолчанию socket.io
использует корневое пространство имен ( /
)
для отправки и получения данных. Программно вы можете получить доступ к
этому пространству имен через io.sockets
, хотя многие из его методов
имеют ярлыки в io
. Итак, эти два вызова эквивалентны:
io.sockets.emit('stats', { data: 'some data' });
io.emit('stats', { data: 'some data' });
Чтобы создать собственное пространство имен, все, что вам нужно сделать, это следующее:
var iosa = io.of('/stackabuse');
iosa.on('connection', function(socket){
console.log('Connected to Stack Abuse namespace'):
});
iosa.emit('stats', { data: 'some data' });
Кроме того, клиент должен явно подключиться к вашему пространству имен:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io('/stackabuse');
</script>
Теперь любые данные, отправленные в этом пространстве имен, будут
отделены от /
по умолчанию, независимо от того, какой канал
используется.
Идя еще дальше, в каждом пространстве имен вы можете присоединяться к «комнатам» и выходить из них. Эти комнаты обеспечивают еще один уровень разделения поверх пространств имен, и, поскольку клиента можно добавить только в комнату на стороне сервера , они также обеспечивают некоторую дополнительную безопасность. Поэтому, если вы хотите, чтобы пользователи не отслеживали определенные данные, вы можете использовать комнату, чтобы скрыть их.
Чтобы быть добавленным в комнату, вы должны .join()
it:
io.on('connection', function(socket){
socket.join('private-message-room');
});
Затем оттуда вы можете отправлять сообщения всем, кто находится в данной комнате:
io.to('private-message-room').emit('some event');
И, наконец, вызовите .leave()
чтобы перестать получать сообщения о
событиях из комнаты:
socket.leave('private-message-room');
Заключение
Это всего лишь одна библиотека, реализующая протокол веб-сокетов, и существует еще много других, каждая со своими уникальными особенностями и сильными сторонами. Я бы посоветовал попробовать некоторые другие (например, node-websockets ), чтобы вы почувствовали, что там есть.
Всего за несколько строк вы можете создать несколько довольно мощных приложений, поэтому мне любопытно посмотреть, что вы можете придумать!
У вас есть интересные идеи или вы уже создали несколько приложений с использованием веб-сокетов? Дайте нам знать об этом в комментариях!