Шкарбатов Дмитрий
Руководитель департамента
разработки кассовых продуктов
WebSocket (RFC 6455) — протокол полнодуплексной связи (может передавать и принимать одновременно) поверх TCP-соединения, предназначенный для обмена сообщениями между браузером и веб-сервером в режиме реального времени.
Он позволяет пересылать любые данные, на любой домен, безопасно и почти без лишнего сетевого трафика.
WebSocket
Протокол WebSocket работает над HTTP.
Это означает, что при соединении браузер отправляет специальные заголовки, спрашивая: «поддерживает ли сервер WebSocket?».
Если сервер в ответных заголовках отвечает «да, поддерживаю», то дальше HTTP прекращается и общение идёт на специальном протоколе WebSocket, который уже не имеет с HTTP ничего общего.
Установление WebSocket соединений
Соединение WebSocket можно открывать как WS:// или как WSS://. Протокол WSS представляет собой WebSocket над HTTPS.
Кроме большей безопасности, у WSS есть важное преимущество перед обычным WS – большая вероятность соединения.
Дело в том, что HTTPS шифрует трафик от клиента к серверу, а HTTP – нет.
Если между клиентом и сервером есть прокси, то в случае с HTTP все WebSocket-заголовки и данные передаются через него. Прокси имеет к ним доступ, ведь они никак не шифруются, и может расценить происходящее как нарушение протокола HTTP, обрезать заголовки или оборвать передачу. А в случае с WSS весь трафик сразу кодируется и через прокси проходит уже в закодированном виде. Поэтому заголовки гарантированно пройдут.
WSS
В протокол встроена проверка связи при помощи управляющих фреймов типа PING и PONG.
Тот, кто хочет проверить соединение, отправляет фрейм PING с произвольным телом. Его получатель должен в разумное время ответить фреймом PONG с тем же телом.
Этот функционал встроен в браузерную реализацию, так что браузер ответит на PING сервера, но управлять им из JavaScript нельзя.
Иначе говоря, сервер всегда знает, жив ли посетитель или у него проблема с сетью.
PING/PONG
Can I use
Как с ними работать?
// Создаем объект сокета
var socket = new WebSocket("ws://ws.privatbank.ua");
// У объекта 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("Привет");
Нагрузка
Вы будете создавать коннект через сокет при каждой загрузке страницы! У вас банально закончатся все свободные коннекты.
Информация о сокетах
dmitriy@shkarbatov-PC:/srv/curex$ ss -s && ss -otp state time-wait | wc -l
Total: 2031 (kernel 2206)
TCP: 33958 (estab 868, closed 32848, orphaned 6, synrecv 0, timewait 32847/0), ports 17458
Transport Total IP IPv6
* 2206 - -
RAW 0 0 0
UDP 142 135 7
TCP 1110 1105 5
INET 1252 1240 12
FRAG 0 0 0
33357
Оптимизация сервера
net.ipv4.tcp_tw_recycle = 1
Разрешает быструю утилизацию сокетов, находящихся в состоянии TIME-WAIT.
net.ipv4.tcp_tw_reuse = 1
Определяет возможность повторного использования сокетов TIME-WAIT для новых соединений.
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 10
Время, через которое можно повторно использовать сокет, перед этим бывшем в состоянии TIME_WAIT.
Зачение по умолчанию = 120 сек.
Тестирование
-
Нагрузка сервера с помощью Apache Jmeter (нужен дополнительный плагин) автоматизированная работа
-
Фронт, с привлечением разработчика ручная работа
Каково решение?
Серебрянной пули - нет!
Отдельный контекст для выполнения фоновых задач, который не блокирует UI. Обычно worker создаётся в виде отдельного скрипта, ресурсы worker-а живут в процессе создавшей его страницы.
В worker-е есть:
- navigator
- location
- applicationCache
- XHR, websocket
- importScripts для синхронной загрузки скриптов
Но он нам не подойдет :)
Worker
То же самое, что и Worker, но может быть использован с нескольких страниц.
Shared Worker
DOM
В worker-е нельзя использовать DOM, вместо window глобальный объект называется self. Нельзя получить доступ к localStorage и рисовать на canvas.
Доступ к объектам
Из worker-ов нельзя вернуть объект. В javascript нет lock-ов и других возможностей потокобезопасности, поэтому из worker-ов нельзя передавать объекты по ссылке, всё отправленное в worker или из него будет скопировано.
Ограничения
CORS
Пока что worker-ы не поддерживают совместное использование ресурсов между разными источниками, создать worker можно только загрузив его со своего домена.
Размер стека
Для worker-ов выделяется меньший размер стека, иногда это имеет значение.
Ограничения
- когда он закроется сам, вызвав self.close()
- когда закроются все странички, его использующие (при этом у worker-а не будет возможности закончить вычисления)
- когда пользователь принудительно завершит его (например, в хроме из chrome://inspect)
- когда упадёт или он, или процесс странички, где он живёт
Завершение работы
Can I use
Debug
chrome://inspect/#workers
chrome://inspect/#service-workers
Использование
web_worker = new SharedWorker('shared_worker.js');
web_worker.port.addEventListener('message', function(e) {
// On message receive
if (e.data.operation === 'on_message') {
alert('SocketOnMessage');
// On error connection
} else if (e.data.operation === 'on_error') {
alert('SocketOnError');
// On close connection, trying to reconnect in 3 sec
} else if (e.data.operation === 'on_close') {
alert('SocketOnClose');
}
}, false);
// Shared Worker On Error
web_worker.onerror = function(err){
alert('WebWorkerError ' + err.message);
web_worker.port.close();
};
// Init SharedWorker
web_worker.port.start();
// Start command Shared Worker
web_worker.port.postMessage({'cmd': 'start', 'url': 'ws://ws.privatbank.ua'});
shared_worker.js
var port;
var ws = null;
var count = 0;
var peers = [];
self.addEventListener('connect', function(e) {
port = e.ports[0];
peers.push(port);
count += 1;
port.addEventListener('message', function(e) {
// Start
if (e.data.cmd === 'start') {
if (ws === null) {
// Socket init
ws = new WebSocket(e.data.url);
}
// On message receive
ws.onmessage = function (msg) {
send({
'operation': 'on_message',
'data': JSON.parse(msg.data)
});
};
// On error connection
ws.onerror = function () {
ws = null;
send({'operation': 'on_error'});
};
// On close connection
ws.onclose = function (event) {
ws = null;
send({'operation': 'on_close',
'data': event.code});
};
// Send Message
} else if (e.data.cmd === 'send') {
if (ws !== null && ws.readyState === 1)
ws.send(e.data.data);
}
// Отправляем данные клиенту
function send (data) {
peers.forEach(function (port) {
port.postMessage(data);
});
}
}, false);
port.start();
}, false);
SharedWorker
Service workers
Service workers фактически действуют как прокси серверы, находящиеся между web-приложением и браузером. Они призваны для того, чтобы позволять описывать корректное поведение в режиме офлайн, перехватывать запросы сети и принимать соответствующие меры, основываясь на том, доступна сеть или нет, и обновлять данные, находящиеся на сервере. Так же они будут позволять отправлять уведомления и выполять фоновую синхронизацию API.
Service workers
Service workers запускаются только поверх HTTPS из соображений безопасности.
Многие функции Service Worker теперь включены по умолчанию в новых браузерах, поддерживающих эту технологию. Однако для некоторых вам может понадобиться их включить.
Can I use
Выводы
- Нет серебрянной пули
- Нет 100% поддержки браузерами
- Очень мало понятной информации
+ В нашем случае снижение нагрузки на 50%
+ Технология проста в освоении
+ Гибкая в использовании
Shared Worker -> Worker -> WebSocket -> AJAX
Вопросы?
Удачи!
WebSockets
By James Jason
WebSockets
- 948