[ENG]:This is russian translation of official documentation socket.io (https://socket.io/docs/).
[RU]: Перевод документации socket.io на русский язык (https://socket.io/docs/).
От переводчика:
не смог найти достаточно подробный обзор, сайт с описанием данной библиотеки, кроме англоязычной документации socket.io, а на хабраютубах не находил ничего сложнее чата на вебсокетах. Поэтому принял решение перевести официальную документацию на великий и могучий :)
Также перевод будет дополнен ответами на часто задаваемые вопросы и примерами.
Пожелания, замечания пишите в twitter или в issue на гитхабе. Лучшей помощью, не считая доната, является помощь с переводом и распространение данной информации. Ну и, конечно, звезда на репозитории.
P. S. здесь и далее будет удалена точка из названий библиотек, поскольку точка генерирует ссылку и вводит читателей в заблуждение (например, вместо Engine.IO будет написано EngineIO, вместо Socket.IO - SocketIO и т. д. В тех местах, где ссылка присутствует, она является верной.
- Обзор
- Что такое SocketIO
- Чем SocketIO не является
- Установка
- Использование с http-сервером Node
- Использование с Express
- Отправка и получение событий
- Ограничение пространством имен
- Отправка нестабильных сообщений
- Отправка и получение данных (подтверждений)
- Широковещательные сообщения
- Использование в качестве кроссбраузерного WebSocket
- Комнаты и пространства имен
- Миграция с 0,9
- Использование нескольких узлов
- Логирование и отладка
- Шпаргалка по Emit
- Обзор фреймворка изнутри
- Часто задаваемые вопросы
SocketIO - это библиотека, которая обеспечивает двустороннюю и основанную на событиях связь в режиме реального времени между браузером и сервером. Она состоит из:
- сервера Node.js: Исходный код | API
- клиентской библиотеки Javascript для браузера (которую также можно запустить из Node.js): Исходный код | API
Его основными особенностями являются:
Соединения устанавливаются даже при наличии:
- прокси-серверов и балансировщиков нагрузки;
- персональных брандмауэров и антивирусного ПО. Для этой цели используется EngineIO, который сначала устанавливает long-polling соединение, а затем пытается улучшить его до WebSocket.
Если не указано иное, отключенный клиент будет пытаться восстановить соединение до тех пор, пока сервер снова не станет доступен.
Механизм heartbeat (вики: «сердцебиение» — это периодический сигнал, генерируемый аппаратным или программным обеспечением для индикации нормальной работы или для синхронизации других частей компьютерной системы) реализован на уровне EngineIO, что позволяет как серверу, так и клиенту знать, когда собеседник больше не отвечает.
Эта функциональность достигается с помощью таймеров, установленных как на сервере, так и на клиенте, со значениями тайм-аута (параметры pingInterval и pingTimeout), которые используются совместно при установлении соединения. Эти таймеры требуют, чтобы любые последующие клиентские вызовы направлялись на один и тот же сервер, поэтому при использовании нескольких узлов (нод) требуется механизм sticky session.
Sticky session — метод балансировки нагрузки, при котором запросы клиента передаются на один и тот же сервер группы.
Любые сериализуемые структуры данных могут быть переданы, в том числе:
- ArrayBuffer и Blob в браузере
- ArrayBuffer и Buffer в Node.js
Сериализация — процесс перевода какой-либо структуры данных в последовательность битов.
Чтобы создать разделение групп в вашем приложении (например, для каждого модуля или на основе разрешений), SocketIO позволяет вам создать несколько пространств имен, которые будут действовать как отдельные каналы связи, но будут использовать одно и то же базовое соединение.
В каждом пространстве имен вы можете определить произвольные каналы, называемые комнатами, к которым сокеты могут присоединяться и отсоединяться. Затем вы можете транслировать в любую комнату, достигнув каждого сокета, который присоединился к ней.
Это полезная функция для отправки уведомлений группе пользователей или данному пользователю, например, подключенному к нескольким устройствам. Эти функции поставляются с простым и удобным API, который выглядит следующим образом:
io.on('connection', function(socket){
socket.emit('request', /* */); // отправить событие в сокет
io.emit('broadcast', /* */); // отправить событие на все подключенные сокеты
socket.on('reply', function(){ /* */ }); // слушать событие
});
SocketIO НЕ является реализацией WebSocket. Хотя SocketIO действительно использует WebSocket в качестве протокола передачи данных, когда это возможно, он добавляет некоторые метаданные к каждому пакету: тип пакета, пространство имен и идентификатор ACK, когда требуется подтверждение сообщения. Вот почему клиент WebSocket не сможет успешно подключиться к серверу SocketIO, а клиент SocketIO также не сможет подключиться к серверу WebSocket.
// ВНИМАНИЕ: клиент НЕ сможет подключиться!
const client = io ('ws: //echo.websocket.org');
npm install --save socket.io
Автономная сборка клиента предоставляется по умолчанию сервером по адресу /socket.io/socket.io.js
.
Также это может быть сделано из CDN, через cdnjs.
Для использования c Node.js, или с помощью сборщика, например, webpack или browserify, вы также можете установить пакетную сборку из npm:
npm install --save socket.io-client
Существует несколько реализаций клиента на других языках, которые поддерживаются сообществом:
- Java: https://github.com/socketio/socket.io-client-java
- C++: https://github.com/socketio/socket.io-client-cpp
- Swift: https://github.com/socketio/socket.io-client-swift
- Dart: https://github.com/rikulo/socket.io-client-dart
- Python: https://github.com/miguelgrinberg/python-socketio
- .Net: https://github.com/Quobject/SocketIoClientDotNet
var app = require('http').createServer(handler)
var io = require('socket.io')(app);
var fs = require('fs');
app.listen(80);
function handler (req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io('http://localhost');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my: 'data' });
});
</script>
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(80);
// ВНИМАНИЕ: app.listen(80) здесь не будет работать!
app.get('/', function (req, res) {
res.sendFile(__dirname + '/index.html');
});
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my: 'data' });
});
</script>
Socket.IO позволяет отправлять и получать пользовательские события. Кроме connect
, message
и disconnect
, вы можете создавать свои события:
// note, io(<port>) создаст http сервер
var io = require('socket.io')(80);
io.on('connection', function (socket) {
io.emit('this', { will: 'будет принято всеми'});
socket.on('private message', function (from, msg) {
console.log('Я получил личное сообщение от ', from, ' который сказал ', msg);
});
socket.on('disconnect', function () {
io.emit('Пользователь отсоединился');
});
});
Если у вас есть контроль над всеми сообщениями и событиями, генерируемыми и отправляемыми для конкретного приложения, работает пространство имен по умолчанию. Если вы хотите использовать сторонний код или создавать код для совместного использования с другими, socket.io предоставляет способ создания пространства имен для сокета.
Это дает возможность мультиплексировать
одно соединение. Вместо использования двух соединений типа WebSocket
, SocketIO использует одно.
var io = require('socket.io')(80);
var chat = io
.of('/chat')
.on('connection', function (socket) {
socket.emit('a message', {
that: 'only'
, '/chat': 'will get'
});
chat.emit('a message', {
everyone: 'in'
, '/chat': 'will get'
});
});
var news = io
.of('/news')
.on('connection', function (socket) {
socket.emit('item', { news: 'item' });
});
<script>
var chat = io.connect('http://localhost/chat')
, news = io.connect('http://localhost/news');
chat.on('connect', function () {
chat.emit('hi!');
});
news.on('news', function () {
news.emit('woot');
});
</script>
Иногда определенные сообщения могут быть сброшены. Допустим, у вас есть приложение, которое показывает твиты в реальном времени по ключевому слову bieber
.
Если определенный клиент не готов к приему сообщений (из-за медленной работы сети, других проблем, или из-за того, что он подключен через long polling и находится в середине цикла запрос-ответ), если он не получает ВСЕ твиты,связанные с bieber
,то ваше приложение не пострадает.
В этом случае вы можете отправить эти сообщения как нестабильные(изменчивые) сообщения.
var io = require('socket.io')(80);
io.on('connection', function (socket) {
var tweets = setInterval(function () {
getBieberTweet(function (tweet) {
socket.volatile.emit('bieber tweet', tweet);
});
}, 100);
socket.on('disconnect', function () {
clearInterval(tweets);
});
});
Иногда может потребоваться callback, когда клиент подтвердил получение сообщения
Для этого просто передайте функцию в качестве последнего параметра .send
или .emit
. Более того, когда вы используете .emit
, подтверждение делается вами, что означает, что вы также можете передавать данные:
var io = require('socket.io')(80);
io.on('connection', function (socket) {
socket.on('ferret', function (name, word, fn) {
fn(name + ' says ' + word);
});
});
<script>
var socket = io(); // СОВЕТ: io() без аргументов производит автообнаружение
socket.on('connect', function () { // СОВЕТ: вы можете избежать прослушивания через `connect` и прослушивать события напрямую!
socket.emit('ferret', 'tobi', 'woot', function (data) { // аргументы отправляются для подтверждения функции
console.log(data); // 'tobi says woot'
});
});
</script>
Чтобы отправить широковещательное сообщение (broadcasting), просто добавьте флаг broadcast
в вызовы методов emit
и send
.
Broadcasting - отправка сообщения всем, кроме сокета, который его запускает.
var io = require('socket.io')(80);
io.on('connection', function (socket) {
socket.broadcast.emit('user connected');
});
Если вам нужно понимание WebSocket, то используйте send
и прослушайте событие message
:
var io = require('socket.io')(80);
io.on('connection', function (socket) {
socket.on('message', function () { });
socket.on('disconnect', function () { });
});
<script>
var socket = io('http://localhost/');
socket.on('connect', function () {
socket.send('hi');
socket.on('message', function (msg) {
// моё сообщение
});
});
</script>
Если вам не важна логика переподключения и тому подобное, посмотрите Engine.IO, который использует Socket.IO и является транспортным уровнем WebSocket.
SocketIO позволяет вам «именовать» свои сокеты, то есть назначать различные конечные точки (endpoints) или пути (paths).
Это полезная функция, позволяющая минимизировать количество ресурсов (TCP-соединений) и в то же время разделять проблемы в вашем приложении за счет разделения каналов связи.
Мы называем пространством имен по умолчанию /
, и это то, к чему клиенты SocketIO подключаются по умолчанию, и то, которое сервер слушает по умолчанию.
Это пространство имен задается с помощью io.sockets
или io
:
// оба примера будут генерировать все сокеты, подключенные к`/`
io.sockets.emit('hi', 'everyone');
io.emit('hi', 'everyone'); // краткая форма
Каждое пространство имен генерирует событие connection
, которое получает каждый экземпляр Socket
в качестве параметра
io.on('connection', function(socket){
socket.on('disconnect', function(){ });
});
Чтобы настроить собственное пространство имен, вы должны вызвать функцию of
на стороне сервера:
const nsp = io.of('/my-namespace');
nsp.on('connection', function(socket){
console.log('someone connected');
});
nsp.emit('hi', 'everyone!');
На стороне клиента вы подключаете SocketIO к этому пространству имен:
const socket = io('/my-namespace');
ВАЖНО: Пространство имен является частью реализации протокола SocketIO и не связано с фактическим URL-адресом базового транспортного протокола, который по умолчанию равен /socket.io/…
.
В каждом пространстве имен вы также можете определить произвольные каналы, которые сокеты могут присоединять
(join
) и покидать
(leave
).
Вы можете вызвать join
, чтобы подписать сокет на данный канал:
io.on('connection', function(socket){
socket.join('some room');
});
А затем используйте to
или in
(они одинаковы) при трансляции(broadcasting) или генерации(emitting):
io.to('some room').emit('some event');
Чтобы выйти из канала, вы вызываете exit
так же, как join
.
Каждый Socket
в SocketIO идентифицируется случайным, неопределяемым, уникальным идентификатором Socket#id
. Для удобства каждый сокет автоматически присоединяется к комнате, идентифицируемой этим идентификатором.
Это позволяет легко транслировать сообщения на другие сокеты:
io.on('connection', function(socket){
socket.on('say to someone', function(id, msg){
socket.broadcast.to(id).emit('my message', msg);
});
});
При отключении сокеты автоматически «покидают» (leave
) все каналы, частью которых они были, и с вашей стороны не требуется никаких специальных действий.
В некоторых случаях вам может потребоваться отправлять события в сокеты в пространствах имен/комнатах вне контекста ваших процессов Socket.IO.
Существует несколько способов решения этой проблемы, например, создание собственного канала для отправки сообщений в процесс.
Чтобы облегчить этот вариант использования, существует два модуля:
Реализация в Redis паттерна Adapter
:
const io = require('socket.io')(3000);
const redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
затем вы можете генерировать
(emit
) сообщения из любого другого процесса на любой канал
const io = require('socket.io-emitter')({ host: '127.0.0.1', port: 6379 });
setInterval(function(){
io.emit('time', new Date);
}, 5000);
SocketIO оснащен минималистичной, но чрезвычайно мощной утилитой под названием debug от TJ Holowaychuk.
До версии 1.0 сервер SocketIO по умолчанию записывал все в консоль. Это раздражало многих пользователей (хотя было полезно для других), поэтому теперь по умолчанию запись в консоль отключена.
Основная идея заключается в том, что каждый модуль, используемый SocketIO, предоставляет различные области отладки, которые дают вам представление о внутреннем устройстве. По умолчанию весь вывод скрыт, и вы можете выбрать просмотр сообщений, указав переменную окружения DEBUG
(в Node.JS) или свойство localStorage.debug
(в браузерах).
Вы можете увидеть демонстрацию работы этого инструмента, кликнув по ссылке.
Лучший способ узнать, какая информация доступна, это использовать *
:
DEBUG=* node yourfile.js
или в браузере:
localStorage.debug = '*';
Затем выполните фильтрацию по интересующим вас областям. Вы можете поставить перед префиксом области *
, разделив запятой, если их несколько. Например, чтобы увидеть только операторы отладки из клиента socket.io на Node.js, выполните:
DEBUG=socket.io:client* node yourfile.js
Чтобы увидеть все отладочные сообщения от engine и socket.io:
DEBUG=engine,socket.io* node yourfile.js
io.on('connect', onConnect);
function onConnect(socket){
// отправка клиенту
socket.emit('hello', 'can you hear me?', 1, 2, 'abc');
// отправка всем клиентам, кроме отправителя
socket.broadcast.emit('broadcast', 'hello friends!');
// отправка всем клиентам комнаты 'game', кроме отправителя
socket.to('game').emit('nice game', "let's play a game");
// отправка всем клиентам комнат 'game1' и/или 'game2' , кроме отправителя
socket.to('game1').to('game2').emit('nice game', "let's play a game (too)");
// отправка всем клиентам комнаты 'game' , включая отправителя
io.in('game').emit('big-announcement', 'the game will start soon');
// отправка всем клиентам пространства имен 'myNamespace', включая отправителя
io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon');
// отправка в определенную комнату в определенном пространстве имен, включая отправителя
io.of('myNamespace').to('room').emit('event', 'message');
// отправка на индивидуальный socketid (личное сообщение)
io.to(`${socketId}`).emit('hey', 'I just met you');
// ВНИМАНИЕ: `socket.to(socket.id).emit()` НЕ будет работать, как бы мы отправляли сообщение всем в комнату
// `socket.id`, а не отправителю. Вместо этого, используйте `socket.emit()`.
// отправка с подтверждением
socket.emit('question', 'do you think so?', function (answer) {});
// отправка без сжатия
socket.compress(false).emit('uncompressed', "that's rough");
// отправка сообщения, которое может быть отброшено, если клиент не готов к приему сообщений
socket.volatile.emit('maybe', 'do you really need it?');
// указание, имеют ли данные для отправки двоичные данные
socket.binary(false).emit('what', 'I have no binaries!');
// отправка всем клиентам на этом узле (при использовании нескольких узлов)
io.local.emit('hi', 'my lovely babies');
// отправка всем подключенным клиентам
io.emit('an event sent to all connected clients');
};
Важно: Следующие события зарезервированы и не должны использоваться в качестве имен событий вашим приложением:
error
connect
disconnect
disconnecting
newListener
removeListener
ping
pong