Разница между веб-сокетами и Socket.IO

Original author: Michele Riva
  • Translation


Доброго времени суток, друзья!

Веб-сокеты и Socket.IO, вероятно, являются двумя наиболее распространенными средствами коммуникации в режиме реального времени (далее — живое общение). Но чем они отличаются?

При построении приложения для живого общения наступает момент, когда необходимо выбрать средство для обмена данными между клиентом и сервером. Веб-сокеты и Socket.IO являются самыми популярными средствами живого общения в современном вебе. Какое из них выбрать? В чем разница между этими технологиями? Давайте выясним.

Веб-сокеты


Говоря о веб-сокетах, мы имеем ввиду протокол веб-коммуникации, представляющий полнодуплексный канал коммуникации поверх простого TCP-соединения. Проще говоря, эта технология позволяет установить связь между клиентом и сервером с минимальными затратами, позволяя создавать приложения, использующие все преимущества живого общения.

Например, представьте, что вы создаете чат: вам необходимо получать и отправлять данные как можно быстрее, верно? С этим прекрасно справляются веб-сокеты! Вы можете открыть TCP-соединение и держать его открытым сколько потребуется.

Веб-сокеты появились в 2010 году в Google Chrome 4, первый RFC (6455) опубликован в 2011.

Веб-сокеты используются в следующих случаях:

  • Чаты
  • Многопользовательские игры
  • Совместное редактирование
  • Социальные (новостные) ленты
  • Приложения, работающие на основе местоположения

и т.д.

Socket.IO


Socket.IO — библиотека JavaScript, основанная (написанная поверх) на веб-сокетах… и других технологиях. Она использует веб-сокеты, когда они доступны, или такие технологии, как Flash Socket, AJAX Long Polling, AJAX Multipart Stream, когда веб-сокеты недоступны. Легкой аналогией может служить сравнение Fetch API и Axios.

Разница между веб-сокетами и Socket.IO


Главными преимуществами Socket.IO является следующее:

  • В отличие от веб-сокетов, Socket.IO позволяет отправлять сообщения всем подключенным клиентам. Например, вы пишете чат и хотите уведомлять всех пользователей о подключении нового пользователя. Вы легко можете это реализовать с помощью одной операции. При использовании веб-сокетов, для реализации подобной задачи вам потребуется список подключенных клиентов и отправка сообщений по одному.
  • В веб-сокетах сложно использовать проксирование и балансировщики нагрузки. Socket.IO поддерживает эти технологии из коробки.
  • Как отмечалось ранее, Socket.IO поддерживает постепенную (изящную) деградацию.
  • Socket.IO поддерживает автоматическое переподключение при разрыве соединения.
  • С Socket.IO легче работать.

Может показаться, что Socket.IO — лучшее средство для живого общения. Однако существует несколько ситуаций, когда лучше использовать веб-сокеты.

Во-первых, веб-сокеты поддерживаются всеми современными браузерами. Поэтому вы редко нуждаетесь в поддержке других технологий, предоставляемой Socket.IO.

Если говорить о сетевом трафике, то веб-сокеты отправляют всего два запроса:

  • GET для получения HTML страницы
  • UPGRADE для соединения с веб-сокетами

Это позволяет установить соединение с сервером. А что насчет Socket.IO?

  • GET для получения HTML страницы
  • Клиентская библиотека Socket.IO (207кб)
  • Три long polling (длинные опросы) Ajax-запроса
  • UPGRADE для соединения с веб-сокетами

В мире JS 207кб — это много. Какое нерациональное использование сетевого трафика!

В npm существует пакет «websocket-vs-socket.io», который позволяет сравнить сетевой трафик этих технологий:

Сетевой трафик веб-сокетов:




Сетевой трафик Socket.IO:




Разница очевидна!

Пишем код


Простой сервер на веб-сокетах


В нашей программе на Node.js мы создадим сервер, работающий на порту 3001. При каждом подключении клиента мы будем присваивать ему уникальный ID. При отправке сообщения клиентом мы будем уведомлять его об успехе: [<client-id>]: <message>

const WebSocket = require('ws')
const UUID = require('uuid')
const wss = new WebSocket.Server({ port: 3001 })

wss.on('connection', ws => {
  ws.id = UUID()

  ws.on('message', message => {
    ws.send(`[${ws.id}]: ${message}`)
  })
})

Отлично! Но что если мы хотим рассылать сообщение каждому подключенному клиенту? Веб-сокеты не поддерживают рассылку по умолчанию. Это можно реализовать следующим образом:

const WebSocket = require("ws")
const UUID      = require("uuid")
const wss       = new WebSocket.Server({ port: 3001 })

function broadcast(clientId, message) {
  wss.clients.forEach(client => {
    if(client.readyState === WebSocket.OPEN) {
      client.send(`[${clientId}]: ${message}`)
    }
  })
}

wss.on('conection', ws => {
  ws.id = UUID()
  ws.on('message', message => broadcast(ws.id, message))
})

Легко и просто! Как видите, WebSocket.Server хранит записи о каждом подключенном клиенте, поэтому мы можем сделать итерацию и отправить сообщение каждому. Вы можете протестировать код на компьютере (MacOS) или в браузере (Chrome).

Простой сервер на Socket.IO


Это было не сложно. Может ли Socket.IO сделать это еще проще? Как нам написать такой же сервер на Socket.IO?

const io = require('socket.io')
const server = io.listen(3002)

server.on('connection', socket => {
  socket.on('message', message => {
    socket.emit(`[${socket.id}]: ${message}`)
    socket.broadcast.emit(`[${socket.id}]: ${message}`)
  })
})

Код получился почти наполовину короче! Как видите, метод «broadcast» не отправляет уведомление отправителю, поэтому мы вынуждены делать это вручную.

Существует проблема: код нельзя протестировать на обычном клиенте веб-сокетов. Это связано с тем, что, как отмечалось ранее, Socket.IO использует не чистые веб-сокеты, а множество технологий для поддержки всех возможных клиентов. Так как же нам проверить его работоспособность?

// head
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@2.3.0/dist/socket.io.slim.js"></script>

// body
<script>
  ioClient = io.connect('http://localhost:3002')
  ioClient.on('connect', socket => {
    ioClient.send('hello world')
    ioClient.on('message', msg => console.log(msg))
  })
</script>

Необходимо использовать специальный клиент. В приведенном примере мы загружаем его из CDN. Этот клиент позволяет нам провести быстрые (грязные) тесты в браузере.

Как видите, наши примеры не сильно отличаются. Однако, если говорить о совместимости, следует помнить о том, что Socket.IO работает с собственной библиотекой и его нельзя использовать в целях, не связанных с веб-разработкой. В тоже время веб-сокеты могут использоваться для решения широкого спектра задач, таких как P2P коммуникация, обмен данными между серверами в режиме реального времени и т.д.

На заметку


Горизонтальное масштабирование. Допустим, ваш чат обрел популярность и вам необходимо добавить еще один сервер и балансировщик нагрузки для обработки запросов. Ну, если вы открываете соединение на «server 1», затем балансировщик переключает вас на «server 2», вы получите ошибку: «Error during WebSocket handshake: Unexpected response code: 400». Socket.IO решает эту проблему с помощью cookie (или с помощью маршрутизации соединений на основе исходных адресов), а у веб-сокетов не существует подобного механизма.
Производительность. Как отмечалось ранее, Socket.IO предоставляет несколько абстрактных уровней над транспортным уровнем веб-сокетов. Также здесь используется упаковывание данных в формат JSON, так что возможность отправлять на сервер (и наоборот) бинарные данные отсутствует. Если вам необходим такой функционал, придется «поколдовать» над кодом библиотеки с целью обеспечения нужного поведения. С веб-сокетами таких проблем не возникает.

Так что же выбрать?


Решать вам.

Socket.IO облегчает жизнь, вам не нужно заботиться о проблемах, связанных с балансировкой нагрузки, разрывом соединений или рассылкой сообщений… но необходим ли вам такой функционал? Клиентская библиотека Socket.IO весит больше, чем пакеты React, Redux и React-Redux вместе взятые. Уверены ли вы, что не можете ограничиться веб-сокетами?

Еще одной важной вещью, о которой следует помнить, является то, что при использовании Socket.IO на стороне сервера, большая часть кода будет написана на абстракциях, предоставляемых этой библиотекой. Если у вас возникнет необходимость переписать Node.js-микросервисы на Go, Elixir, Java или другой язык программирования, вам придется переписывать почти всю логику. Например, для рассылки сообщений в Socket.IO используется метод «broadcast» (который в веб-сокетах реализуется вручную), поэтому при рефакторинге придется понять, как этот метод работает. В этом случае следует предпочесть веб-сокеты, поскольку их легче адаптировать.

Благодарю за внимание.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 21

    +12

    [sarcasm]
    Ждем статью: разница между Vanila JS и Jquery
    [/sarcasm]

      –1
      Любопытно… То есть вы считаете что JS vs jQuery — это тривиальная тема и не стоит статьи? Или вы имеете в виду что их и так много?

      По поводу SocketIO мне в свое время не было достаточно информации. Ни документация ни SO не могли мне помочь в моих вопросах. Мне кажется эта библиотека достаточно удобна чтобы еще одна статья хотя бы напомнила о ней.
        +4
        Предположу, что ThisMan имеет в виду, что сравнивается теплое и мягкое. Ну или кирпич vs кирпичное здание.
      0
      Спасибо за перевод.

      Вообще говоря, аргументы против SocketIO не очень убедительны в плане размера. Минифицированный js файлик у меня весит 51 Кбайт, что как бы в 4 раза меньше заявленных 207 Кбайт. Тем более загружается он один раз же, а потом из кэша берется. Черт, да сейчас favicon для сайтов могут больше весить!

      Честно говоря, настроить SocketIO через nginx для Flask для вебсокетов у меня получилось не с первого раза (и даже не с третьего), но мне понравился его fallback на AJAX при проблемах с вебсокетами, а уж автоматическое восстановления соединения — это еще одна киллер-фича.

      На бэкенде он удобно используется. Мне нужны были именно broadcast-рассылки и отлично оно работает. При рефакторинге проекта попробовал переписать на Django-channels (вебсокеты) и сильно обжегся, когда осознал что там даже список клиентов не получить, не говоря уж о броадкасте.
        0

        Fallback на ajax практически не нужен ни при каких обстоятельствах. Т.к. те браузеры которые не поддерживают сокеты — они же не поддерживают и ajax в современном понимание (в основном это opera mini и старіе версии IE).


        Сама по себе библиотека socket.io имеет два кардинальных недостатка для широкого использования в высоконагруженных приложения.
        1) Запуск socket.io на сервере например на node.js или на любом другом очень быстро приводит к исчерпанию ресурсов системы т.к. каждое соединение к серверу, даже если нет интенсивного обмена данными создае большую нагрузку на систему.
        2) socket.io не гарантирует доставку всех сообщений. Поэтому для реально работающих приложений — чатов и т.п. приходится допиливать некоторую логику по доставке пропущенных сообщений (сообщения могут теряться в процессе разрыва и восстановления соединения) и дублирующих сообщений (это оборотная сторона доставки пропущенных сообщений)


        Выход — применение специализированных серверов и протоколов для связи по сокетам (например mqtt, centrifugo)

          0
          Если веб приложение может работать только через HTTPS (корпоративные пользователи), то как выйти из этой ситуации при работе через веб-сокеты?
          У меня нет опыта работы через socket.io, но от сокетов пришлось отказаться, клиенты отказывались открывать нестандартный порт.
            0

            Вебсокеты могут работать на порту 443. И любом другом. При этом различитььвеб сокеты и чистый https невозможно. Если вебсокеты работают на другом сервере чем сайт например на mqtt сервере, то его можно или через nginx проксировать на определенный урл или же поднять на субдомене. Второй вариант лучше так как nginx конечно может держать много коннектов но значительно меньше чем mqtt сервер на erlang

              0
              Эта проблема легко решается переносом веб-сокет сервера за ngnix reverse proxy. Например, веб-сокет сервер на host:8888 мапится на host:443/websocket.
              0
              Поэтому для реально работающих приложений — чатов и т.п. приходится допиливать некоторую логику по доставке пропущенных сообщений (сообщения могут теряться в процессе разрыва и восстановления соединения)

              Да, и очень часто бизнес логика напрямую завязана на состояние коннекшна. Например показать юзеру его пинг, или показать в чате, что игрок реконнетится. На сервере для реконнектов нужно контролировать тайманты на сессию, а также уметь жестко дисконнектить клиента. А еще нужно уметь настраивать коннекшн на более низком уровне (tcp_nodelay, keep_alive, send/receive buffer, etc...), защищаться от разного рода атак на ресурсы, напр. контролировать количество коннектов на клиента, или организовывать delay-пулы против overload-а. Все это становится недоступно при использовании абстрактных надстроек более высокого уровня.

                0

                Есть определенный класс приложений где гарантированная доставка не нужна. То есть если клиент отвалился то он уже вне игры. Для таких приложений натив больше подходит. Но остается другое ограничение — масштабируемость. Пожалуй любые из известных серверов кроме erlang падают при 10...100 тысячах одновременных коннектов. Специализированные серверы могут выдерживать нагрузки на пару порядков выше.

            0

            Есть другая либа SockJS, которая эмулирует вебсокет с разными транспортами. В отличие от socket.io, заточена под разные бэкенды. Для джавистов есть Atmosphere Framework, который я пробовал юзать.


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

              0

              Такие протоколы были разработаны уже дано — mqtt — один из них.

                0

                Всегда проще написать свой велосипед, понятный и заточенный под конкретную задачу, чем разбираться в абстрактном мультифункциональном протоколе, ломать голову как с помощью него реализовать необходимое, бороться с ограничениями в дизайне, а затем на проде ловить лютый гемор по поводу неучтенных нюансов в поведении провайдера и низлежащих протоколов.

                  0

                  Протокол mqtt очень продуман и разработан хорошими специалистами из ibm. Он прост и строг. Нацелен на гарантированную доставку сообщений, в этом его основная функция. Все знакомые мне попытки реализовать аналогичное поверх socket.io или наличных сокетов очень быстро превращались в трэш из за изначальной непродуманности как раз этого момента. Когда внезапно оказывается что соединение может рваться а сообщения теряться или дублироваться.

              +1
              А есть еще специально форк, но теперь уже самостоятельный проект — engine.io и также для разных языков и бекендов.

              Насколько я помню, штука с несколькими запросами фикситься переставлянием вебсокета как первого протокола. Изначально, ws не был везде по дефолту, и поэтому лучше было быстрее показать юзеру первые данные, а потом переключиться по возможности
                –2
                207 КБ это много? Видимо, именно поэтому многие сайты сейчас грузят целые мегабайты *пожатого* жс…
                  +1
                  Веб-сокеты и Socket.IO, вероятно, являются двумя наиболее распространенными средствами коммуникации в режиме реального времени (далее — живое общение). Но чем они отличаются?

                  … Веб-сокеты и Socket.IO являются самыми популярными средствами живого общения в современном вебе. Какое из них выбрать? В чем разница между этими технологиями?


                  зачем повторяться?

                    0
                    Не понял зачем столько телодвижений, если можно указать что бы socket.io как клиент так и сервер юзали только websocket без всяких там поллингов, просто добавлением конфига? Да и размер второй версии для клиента ~20kb, а на серваке пофиг… ниче не понимаю.

                    Сравнение сокета с ее же оберткой? :/

                    For instance, think about broadcasting a message to every connected client: with Socket.IO is just one method (.broadcast), but in vanilla WebSockets you’ll have to implement it on your own, so you’ll have to rethink about the way it works.


                    О боже как трудно написать проход по массиву клиентов =))

                    Автор вроде синьер… статья для начинающих в вебе?
                      0

                      Прошу прощения, но:


                      1)


                      Если говорить о сетевом трафике, то веб-сокеты отправляют всего два запроса:
                      GET для получения HTML страницы
                      UPGRADE для соединения с веб-сокетами

                      Вебсокеты отправляют ровно один запрос — GET с заголовками Connection: Upgrade и Upgrade: websocket (который Вы, видимо, назвали UPGRADE). А первый GET который Вы упомянули не имеет никакого отношения к установлению вебсокет соединения (как например и куча другий GET запросов где загружаются картинки, CSS и прочие ресурсы).


                      2)


                      Ну, если вы открываете соединение на «server 1», затем балансировщик переключает вас на «server 2», вы получите ошибку: «Error during WebSocket handshake: Unexpected response code: 400». Socket.IO решает эту проблему с помощью cookie (или с помощью маршрутизации соединений на основе исходных адресов), а у веб-сокетов не существует подобного механизма.

                      Вебсокет соединение открывается за 1 HTTP запрос, как балансировщик может сделать переключение на другой сервер посреди запроса? "Это какой-то неправильный балансировщик", как говорил Винни Пух, "и он неправильно балансирует".


                      С другой стороны если Вы делаете горизонтальное масштабирование через балансировщик HTTP запросов, то в настройке балансировщика должны быть правила "приклеивания" запросов клиента к определенному серверу на основе определенных правил (каких именно — зависит от специфики проекта и возможностей балансировщика). Если Вы это не настраиваете на балансировщике — то плохо открывающиеся вебсокеты — это будет далеко не главная проблема в Вашем проекте.

                        0

                        Ну не знаю. Использовать липкие сессии не кажется надежным решением. Уж лучше сразу настраивать кластер.

                        0
                        Я несколько раз наблюдал ситуацию: «нам достаточно websockets, а socket.io слишком громоздкий».
                        А как итог — миграция на socket.io или свой велосипед как socket.io только с блек джеком и пармезаном.

                        Only users with full accounts can post comments. Log in, please.