changed 4 months ago
Published Linked with GitHub

2025: Комп'ютерні системи і мережі. 4. Протокол управління передачею TCP. Протокол WebSocket. Бібліотека Socket.IO

Протокол управління передачею (TCP, Transmission Control Protocol)

Це один з основних протоколів передачі даних інтернету. Призначений для керування передачею даних Інтернету. Пакети TCP називаються сегментами.

У стеку протоколів TCP/IP виконує функції транспортного рівня моделі OSI.
(См.таблицу)

Механізм TCP надає потік даних із попередньою установкою з'єднання,

здійснює повторний запит даних у разі втрати даних і усуває дублювання при отриманні двох копій одного пакета, гарантуючи тим самим (на відміну від UDP) цілісність даних, що передаються, і повідомлення відправника про результати передачі.

Реалізації TCP зазвичай вбудовані в ядра ОС. Існують реалізації TCP, що працюють у просторі користувача.

Коли передача здійснюється від комп'ютера до комп'ютера через інтернет, TCP працює на верхньому рівні між двома кінцевими системами, наприклад, браузером і веб-сервером. TCP здійснює надійну передачу потоку байтів від одного процесу до іншого. У цьому випадку TCP здійснює управління передачею даних.

Протокол WebSocket

WebSocket - це високорівневий інтернет-протокол передачі повідомлень (даних) по двонаправленим (повнодуплексним) комунікаційним каналам, що використовують TCP з'єднання. Протокол WebSocket був стандартизований комітетом IETF як RFC 6455 у 2011 році. WebSocket додає мінімальний функціонал поверх TCP:

  • модель безпеки, заснована на інформації про кінцеві вузли з'єднання
  • використання URL адресації замість чистого IP протоколу в TCP
  • протокол обміну повідомленнями поверх потоку байтів TCP
  • протокол закриття з'єднання

    Ініціація з'єднання WebSocket (Protocol handshake)

    Ініціація з'єднання стартує зі спеціального клієнтського HTTP-запиту та серверної відповіді. Такий підхід дозволяє серверам працювати як з HTTP-, так і WebSocket-з'єднаннями на тому самому порту.

Приклад HTTP-фази ініціалізації протоколу WebSocket

Приклад клієнтського HTTP запиту на встановлення WebSocket з'єднання

(так само, як і для чистого HTTP, кожен рядок повинен завершуватися парою байт \r\n і в кінці запиту має бути порожній рядок):

GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com

Відповідь сервера:

HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat

Як тільки з'єднання встановлено, комунікація та передача даних переключаться на двонаправлений бінарно-текстовий протокол, який вже ніяк не пов'язаний з протоколом HTTP.


Мал. Порівняння діаграм обміну повідомленнями для протоколів HTTP, SSE и WebSocket

Приклад клієнтського коду WebSocket

// Creates new WebSocket object with a ws URI as the parameter const socket = new WebSocket('ws://game.example.com:12010/updates'); let repeatingReq; //// ---Fired when a connection with a WebSocket is opened,---- socket.onopen = () => { repeatingReq= setInterval(() => { if (socket.bufferedAmount == 0) socket.send(getUpdateData()); }, 50); }; ////--- Fired when data is received through a WebSocket,--- socket.onmessage = event => { handleUpdateData(event.data) }; //// --- Fired when a connection with a WebSocket is closed,--- socket.onclose = event => { clearInterval(repeatingReq); onSocketClose(event)}; //// ---Fired when a connection has been closed because of an error,--- socket.onerror = event =>{ clearInterval(repeatingReq); onSocketError(event)};

Приклад тестової реалізації роботи WebSocket

Код серверної частини з цього прикладу:

/* From the Express module, only one function is used here - automatic delivery of files from the client folder to the client Specifically, on the first GET / request from the browser, the index.html file will be sent to the client */ var express = require('express'); var app = express(); app.use(express.static('client')); /* In the index.html file running on the client side, there is a request wss://websocket-server.arthmax.repl.co/ over the WebSocket protocol. The first phase of this request involves an HTTP handshake. That is why the http module is connected in the code and initialized http server decorated with an express application. And then the ws module and its server are connected, which is initialized by the newly created http server. */ let server = require('http').createServer(app), WebSocketServer = require('ws').Server, wss = new WebSocketServer({ server: server }); // After the handshake, ws and http servers work independently. // It's convenient to store clients in a Map data structure. let webSockets = new Map(); // Since there is no built-in connection numbering mechanism in WebSocket, // we will number the connections ourselves. let new_id = 0; //// --- New connection(socket) handler --------------------- //// must contain event handlers for events from the client socket. wss.on('connection', function connection(socket) { // initial processing when connected socket.id = new_id++; webSockets.set(socket, socket.id); socket.send(` Your id=${socket.id} `); let repl_mess=`A new socket connected with id=${socket.id} ` console.dir(webSockets.values()); for (let w of webSockets.keys()){w.send(repl_mess);} //// --- standard event handlers --- socket.on('message', function(mess) { socket.send(` from Client round trip message echoing from Server at ${new Date().toLocaleString()} "${mess}"`); }); //// --- socket.on('close', (code) =>{ let close_mess= `closing socket id=${socket.id} code=${code} ` console.log(close_mess); for (let w of webSockets.keys()){w.send(close_mess);}; webSockets.delete(socket) console.dir(webSockets.values()); }); }); // Start the http-server on the available port of the process server.listen(process.env.PORT, function () { console.log(`Started at ${new Date().toLocaleString()}, Your app is listening on port ${server.address().port}`); });

Фрагмент коду - обробник отриманих повідомлень, який визначає тип отриманих даних по протоколу WebSocket:

ws.onmessage = function(msg){ if(msg.data instanceof Blob)processBlob(msg.data); else processText(msg.data); }

Фрагмент коду, що задає та контролює тип бінарних даних:

var ws = new WebSocket('wss://example.com/socket'); ws.binaryType = "arraybuffer"; ws.onmessage = function(msg) { if(msg.data instanceof ArrayBuffer) processArrayBuffer(msg.data); else processText(msg.data); }

Фрагмент коду, що виконує жорсткий контроль буфера даних, що надсилаються, перед відправкою нових даних:

if(ws.bufferedAmount == 0 ) ws.send(data);

Посилання на реалізацію інтернет онлайн гри, що використовує протокол WebSocket:
https://glitch.com/edit/#!/volkov-game?path=server.js%3A1%3A31

Введення в бібліотеку Socket.IO

Бібліотека Socket.IO - це клієнтське та серверне API, що інкапсулює функціонал та можливості протоколу WebSocket і безшовне переключення на протокол XHR у разі неможливості роботи з WebSocket.

Подібно до node.js, Socket.IO подієво-орієнтована бібліотека.

Надсилання та отримання повідомлень на клієнті та на сервері

Приклад серверного коду, який використовує socket.IO

const http = require("http"); // use http module const socketio = require("socket.io"); // use socket.io module! const express = require("express"); // use express module const app = express(); // create instance of express app.use(express.static("public")); // use "public" directory for static files const server = http.Server(app); // create http server enriched by express functions const io = socketio(server); // create instance of socketio over http server //// ------------------------------------------------------- io.on("connection", socket => { //// --- when server recieves the "joined" message socket.on("joined", // send message to client () => {io.emit("joined")}); //// --- when someone closes the tab socket.on("disconnect", () => {io.emit("leave")}); }); server.listen(3000); // run server on port 3000

Приклад клієнтського коду (браузерного) socket.IO

<!-- use socket.io library --> <script src="/socket.io/socket.io.js"></script> <script> const socket = io(); // create new instance //// --- when server tells to this client that someone has opened the page socket.on("joined", () => { console.log("someone joined");}); //// --- when server tells to this client that someone has closed the page socket.on("leave", () => {console.log("someone closed page");}); socket.emit("joined"); // tell server that someone opened the page </script>

Фрагменти, що ілюструють найпростіші можливості socket.IO API

Повідомлення з одним параметром

Передача із клієнта на сервер повідомлення з одним параметром:

 socket.emit("number", 10);

Передача із сервера клієнтам повідомлення з одним параметром:

 io.emit("number", 10);

Прийом повідомлення з одним параметром (на клієнті чи сервері):

socket.on("number", number => {
                               // do something here
                              });

Повідомлення з множиною параметрів

Передача повідомлення з множиною параметрів:

// send message
socket.emit("numbers", 1, 2, 3);

Прийом повідомлення з множиною параметрів:

// recieve message
socket.on("numbers",
          (x, y, z) => {
                        // do something with the x, y, z variables
                       });

Порівняння WebSocket та Socket.IO

Переваги Socket.IO

  • На відміну від веб-сокетів, Socket.IO дозволяє надсилати повідомлення всім підключеним клієнтам. Наприклад, ви пишете чат і хочете повідомляти всіх користувачів про підключення нового користувача. Ви легко можете реалізувати це за допомогою однієї операції. При використанні веб-сокетів, для реалізації подібного завдання вам знадобиться список підключених клієнтів та надсилання повідомлень по одному.

  • У веб-сокетах складно використовувати проксування та балансувальники навантаження. Socket.IO підтримує проксі та балансування з коробки.

  • Socket.IO підтримує поступову (витончену) деградацію роботи каналів при неконтрольованих змінах стану мережі.

  • Socket.IO підтримує автоматичне перепідключення при розриві з'єднання.

  • Бібліотека Socket.IO дозволяє писати коротший код.

    Переваги WebSocket

  • WebSocket є стандартним сучасним протоколом інтернет та його API підтримується всіма сучасними браузерами.

  • Коротший мережевий трафік у фазі встановлення з'єднання.
    Веб-сокети надсилають лише два запити:

HTTP GET (Для отримання HTML сторінки),
HTTP UPGRADE (для з'єднання з веб-сокетами)

Це дозволяє встановити з'єднання із сервером.

Socket.IO у фазі встановлення з'єднання відправляє:

HTTP GET  (Для отримання HTML сторінки)
Socket.IO client lib  (>200кб)
XHR 3 long polling requests 
HTTP UPGRADE (для з'єднання з веб-сокетами)

Завдання на лабораторну роботу №4

Виконати завдання лабораторної роботи 3, використовуючи методи протоколу WebSocket

Ресурси

  1. https://hpbn.co/ High Performance Browser Networking, Ilya Grigorik, online version
  2. https://en.wikipedia.org/wiki/Transmission_Control_Protocol Transmission Control Protocol
  3. https://replit.com/talk/learn/SocketIO-Tutorial-What-its-for-and-how-to-use/143781 Socket.IO Tutorial
  4. https://en.wikipedia.org/wiki/WebSocket Протокол WebSocket
  5. https://replit.com/join/bscsfctxby-arthmax Приклад реалізації сервера echo на бібліотеці Socket.IO
  6. https://glitch.com/edit/#!/volkov-game?path=server.js%3A1%3A31 Приклад інтернет-програми, що використовує протокол WebSocket
  7. https://replit.com/join/byvoqjthld-arthmax Приклад тестової реалізації роботи WebSocket
  8. Посилання на реалізацію асинхронної оболонки для socket.emit()
  9. Emitting event on socketio using async await
  10. SocketIO acknowledgements
  11. SocketIO Client Initialization
  12. SocketCluster.Highly scalable pub/sub and RPC framework optimized for async/await
  13. uwebsockets
Select a repo