Эта статья участвует в "Сетевой протокол должен знать должен знать"Требуют документов
Из направления сетевых протоколов, технический фон, безопасность и производственные приложения, подробно описывая практику в Node.js Websocket, приземлились.
Предварительный просмотр схемы
Содержание, представленное в этой статье, включает следующие аспекты:
- эволюция сетевого протокола
- сокет.IO?
- реализация модуля ws
- Экспресс-интеграция
- Экземпляр веб-сокета
- широковещательное сообщение
- Безопасность и аутентификация
- Приложение для лучших друзей
эволюция сетевого протокола
Протокол HTTP является наиболее известным протоколом сетевого взаимодействия на внешнем интерфейсе. Обычно мы открываем веб-страницы и запрашиваем интерфейсы, которые являются HTTP-запросами.
Характеристики HTTP-запроса:请求-> 响应
. Клиент инициирует запрос, сервер отвечает после получения запроса, и запрос выполняется. Другими словами, HTTP-запрос должен быть инициирован клиентом, прежде чем сервер сможет пассивно ответить.
Кроме того, перед инициированием HTTP-запроса необходимо установить TCP-соединение посредством трехэтапного рукопожатия. Особенность HTTP/1.0 заключается в том, что каждая коммуникация должна проходить процесс «трех шагов»——TCP 连接 -> HTTP 通信 -> 断开 TCP 连接
.
Каждый такой запрос является независимым, и соединение будет разорвано после выполнения запроса.
HTTP1.1 оптимизирует процесс запроса. После того, как TCP-соединение установлено, мы можем выполнять несколько HTTP-связей и ждать, пока не будет HTTP-запроса для инициации TCP в течение определенного периода времени, прежде чем разъединить соединение, что и обеспечивает HTTP/1.1.Длинное соединениеТехнология.
Но даже в этом случае метод связи по-прежнему инициируется клиентом, а отвечает сервером, и эта фундаментальная логика не изменится.
Из-за сложности взаимодействия приложений мы обнаружили, что в некоторых сценариях необходимо получать сообщения на стороне сервера в режиме реального времени.
Например, мгновенный чат, такой как отправка сообщения, пользователь не инициирует активно запрос, но когда на сервере появляется новое сообщение, клиент должен немедленно узнать об этом и дать обратную связь пользователю.
HTTP не поддерживает активный push сервером, но эти сценарии срочно нуждаются в решениях, поэтому они появились раньшеголосование(опрос). Опрос заключается в том, что клиент периодически инициирует запрос к серверу, чтобы определить, есть ли обновление на сервере, и если есть, возвращает новые данные.
Хотя этот метод опроса прост и груб, он, очевидно, имеет два недостатка:
- Запрос потребляет слишком много. Клиенты продолжают делать запросы, тратя впустую трафик и ресурсы сервера и оказывая давление на сервер.
- Своевременность не гарантируется. Клиенту необходимо сбалансировать своевременность и производительность, а интервал запроса не должен быть слишком маленьким, чтобы не было задержек.
С появлением HTML5WebSocket
, сцена обмена мгновенными сообщениями, наконец, открыла фундаментальное решение.WebSocket
Это полнодуплексный протокол связи. После того, как клиент устанавливает соединение с сервером, обе стороны могут отправлять данные друг другу. В этом случае клиенту не нужно получать данные неэффективным способом опроса, а сервер напрямую отправляет новые сообщения клиенту.
Традиционный метод HTTP-соединения выглядит следующим образом:
## 普通连接
http://localhost:80/test
## 安全连接
https://localhost:80/test
WebSocket — это еще один протокол, подключение выглядит следующим образом:
## 普通连接
ws://localhost:80/test
## 安全连接
wss://localhost:80/test
Однако WebSocket не полностью отделен от HTTP. Чтобы установить соединение WebSocket, клиент должен активно инициировать HTTP-запрос для установления соединения. После успешного соединения клиент и сервер могут осуществлять двустороннюю связь.
сокет.IO?
Когда дело доходит до реализации WebSocket с Node.js, каждый обязательно подумает о библиотеке:Socket.IO
Да, Socket.IO в настоящее время является лучшим выбором для Node.js для разработки приложений WebSocket в производственной среде. Он мощный, высокопроизводительный, с малой задержкой и может быть интегрирован вexpress
в рамке.
Но, возможно, вы не знаете, Socket.IO — это не чистый фреймворк WebSocket. Он инкапсулирует Websocket, механизм опроса и другие методы связи в реальном времени в общий интерфейс для достижения более эффективной двусторонней связи.
Строго говоря, Websocket — это только часть Socket.IO.
Возможно, вы спросите: поскольку Socket.IO сделал так много оптимизаций на основе WebSocket и является очень зрелым, зачем вам создавать собственный сервис WebSocket?
Во-первых, Socket.IO не может пройти нативныйws
подключение по протоколу. Например, если вы попытаетесь пройтиws://localhost:8080/test-socket
Подключиться к сервису Socket.IO таким образом невозможно. Поскольку сервер Socket.IO должен подключаться через клиент Socket.IO, подключение WebSocket по умолчанию не поддерживается.
Во-вторых, Socket.IO имеет очень высокую степень инкапсуляции, и ее использование может не помочь вам понять принцип установления WebSocket-соединения.
Поэтому в этой статье мы используем базовые в Node.jsws
модуль, реализовать собственный сервис WebSocket с нуля и использовать протокол ws для прямого подключения на внешнем интерфейсе, чтобы испытать ощущение двусторонней связи!
реализация модуля ws
wsЭто следующее простое, быстрое и настраиваемое решение для реализации WebSocket для Node.js, включающее как сервер, так и клиент.
использоватьws
Встроенный сервер, браузер может передать роднойWebSocket
Конструктор подключается напрямую, что очень удобно. Клиент ws — это конструктор WebSocket, который имитирует браузер и используется для подключения к другим серверам WebSocket для связи.
Обратите внимание, что:ws
Может использоваться только в среде Node.js, недоступно в браузере.Браузеры, пожалуйста, используйте собственный конструктор WebSocket напрямую.
Начнем доступ, первый шаг - установить ws:
$ npm install ws
После установки мы сначала собираем сервер ws.
Сервер
Создание сервера WebSocket требует конструктора WebSocketServer.
const { WebSocketServer } = require('ws')
const wss = new WebSocketServer({
port: 8080
})
wss.on('connection', (ws, req) => {
console.log('客户端已连接:', req.socket.remoteAddress)
ws.on('message', data => {
console.log('收到客户端发送的消息:', data)
})
ws.send('我是服务端') // 向当前客户端发送消息
})
Код написанws-server.js
Затем запустите:
$ node ws-server.js
такой монитор8080
Сервер WebSocket порта уже запущен.
клиент
Сервер WebSocket был создан на предыдущем шаге, теперь мы подключаемся и слушаем сообщения на внешнем интерфейсе:
var ws = new WebSocket('ws://localhost:8080')
ws.onopen = function(mevt) {
console.log('客户端已连接')
}
ws.onmessage = function(mevt) {
console.log('客户端收到消息: ' + evt.data)
ws.close()
}
ws.onclose = function(mevt) {
console.log('连接关闭')
}
написать кодwsc.html
Затем откройте в браузере, см. печать следующим образом:
Видно, что после успешного подключения браузер получает сообщение, активно отправленное сервером, а затем браузер может активно закрыть соединение.
В среде Node.js посмотрим, как модуль ws инициирует соединение:
const WebSocket = require('ws')
var ws = new WebSocket('ws://localhost:8080')
ws.on('open', () => {
console.log('客户端已连接')
})
ws.on('message', data => {
console.log('客户端收到消息: ' + data)
ws.close()
})
ws.on('close', () => {
console.log('连接关闭')
})
Код точно такой же, как и логика браузера, но способ написания немного отличается, обратите внимание на разницу.
Момент, который требует особого пояснения, мониторинг на стороне браузераmessage
Функция обратного вызова события, параметрMessageEventобъект экземпляра, фактические данные, отправленные сервером, должны пройти черезmevt.data
Получать.
На клиенте ws этот параметр является фактическими данными сервера, которые можно получить напрямую.
Экспресс-интеграция
Модуль ws обычно не используется сам по себе, и лучшим решением будет его интеграция в существующую структуру. В этом разделе мы интегрируем модуль ws вExpressРамка.
Преимущество интеграции с фреймворком Express в том, что нам не нужно следить за одним портом, достаточно использовать порт, запущенный фреймворком, а также мы можем указать доступ к определенному маршруту перед инициацией соединения WebSocket.
К счастью, все это не нужно реализовывать вручную,express-wsМодули уже выполняют за нас большую часть работы по интеграции.
Сначала установите, затем импортируйте в файл ввода:
var expressWs = require('express-ws')(app)
Как и Router Express, express-ws также поддерживает регистрацию глобальных и локальных маршрутов.
Сначала посмотрите на глобальную маршрутизацию, через[host]/test-ws
соединять:
app.ws('/test-ws', (ws, req) => {
ws.on('message', msg => {
ws.send(msg)
})
})
Местные маршруты регистрируются вгруппа маршрутизацииПодмаршрут ниже. настроитьwebsocket
Группа маршрутизации и указаниеwebsocket.js
файл, код такой:
// websocket.js
var router = express.Router()
router.ws('/test-ws', (ws, req) => {
ws.on('message', msg => {
ws.send(msg)
})
})
module.exports = router
соединять[host]/websocket/test-ws
Вы можете получить доступ к этому подмаршруту.
Роль группы маршрутизации заключается в определении группы подключения к веб-сокету, и разные подмаршруты в этой группе связаны с разными требованиями. Например, вы можете单聊
а также群聊
Установите как два подмаршрута для обработки соответствующей логики связи соединения.
Полный код выглядит следующим образом:
var express = require('express')
var app = express()
var wsServer = require('express-ws')(app)
var webSocket = require('./websocket.js')
app.ws('/test-ws', (ws, req) => {
ws.on('message', msg => {
ws.send(msg)
})
})
app.use('/websocket', webSocket)
app.listen(3000)
Небольшие методы для получения общей информации в фактической разработке:
// 客户端的IP地址
req.socket.remoteAddress
// 连接参数
req.query
Экземпляр веб-сокета
Экземпляр WebSocket относится к объекту подключения клиента и первому параметру подключения к серверу.
var ws = new WebSocket('ws://localhost:8080')
app.ws('/test-ws', (ws, req) => {}
в кодеws
— это экземпляр WebSocket, представляющий установленное соединение.
браузер
Информация, содержащаяся в объекте браузера WS, заключается в следующем:
{
binaryType: 'blob'
bufferedAmount: 0
extensions: ''
onclose: null
onerror: null
onmessage: null
onopen: null
protocol: ''
readyState: 3
url: 'ws://localhost:8080/'
}
Прежде всего, это четыре свойства прослушивания, которые используются для определения функций:
-
onopen
: Функция после установления соединения -
onmessage
: Функция, которая получает push-сообщение с сервера. -
onclose
: Функция, которая закрывает соединение -
onerror
: Функция, которая связывает исключение
Наиболее часто используемыми из них являютсяonmessage
Атрибут, назначенный в качестве функции для прослушивания серверных сообщений:
ws.onmessage = mevt => {
console.log('消息:', mevt.data)
}
Еще одним ключевым атрибутом являетсяreadyState
, указывающий на состояние соединения, значение — число. и каждое значение может использоваться с常量
Представление, соответствие и значение следующие:
-
0
: постоянныйWebSocket.CONNECTING
, указывающий на подключение -
1
: постоянныйWebSocket.OPEN
указывает на подключение -
2
: постоянныйWebSocket.CLOSING
, указывая на закрытие -
3
: постоянныйWebSocket.CLOSED
, что указывает на закрытость
Конечно самое главноеsend
Метод используется для отправки информации и отправки данных на сервер:
ws.send('要发送的信息')
Сервер
Объект WS сервера указывает клиента, который в данный момент инициирован, а базовый атрибут практически такой же, как у браузера.
Например, четыре свойства прослушивания клиента выше,readyState
свойства иsend
Методы те же. Однако, поскольку сервер реализован в Node.js, поддержка будет более богатой.
Например, следующие два типа событий прослушивания имеют одинаковый эффект:
// Node.js 环境
ws.onmessage = str => {
console.log('消息:', str)
}
ws.on('message', str => {
console.log('消息:', mevt.data)
})
Подробные свойства и введение можно найти вофициальная документация
широковещательное сообщение
Сервер WebSocket не имеет только одного клиентского соединения.Рассылка сообщений означает отправку информации всем подключенным клиентам, как в громкоговоритель, который все могут услышать.Классический сценарий – это точка доступа.
Так что перед трансляцией надо решить проблему,Как получить подключенных (онлайн) клиентов?
фактическиws
Модуль предоставляет методы быстрого доступа:
var wss = new WebSocketServer({ port: 8080 })
// 获取所有已连接客户端
wss.clients
Чтобы облегчить его. посмотри сноваexpress-ws
Как получить:
var wsServer = expressWebSocket(app)
var wss = wsServer.getWss()
// 获取所有已连接客户端
wss.clients
получатьwss.clients
Тогда давайте посмотрим, как это выглядит. После печати обнаруживается, что его структура данных проще, чем ожидалось, то есть экземпляр WebSocket состоит из всех онлайн-клиентов.Set
собирать.
Затем, чтобы получить текущее количество онлайн-клиентов:
wss.clients.size
Простая и грубая реализация трансляции:
wss.clients.forEach(client => {
if (client.readyState === 1) {
client.send('广播数据')
}
})
Это очень простая базовая реализация. Представьте, если в данный момент есть 10 000 онлайн-клиентов, то этот цикл, вероятно, застрянет. Поэтому существуют такие библиотеки, как socket.io, в которых проведена большая оптимизация и инкапсуляция основных функций для повышения параллельной производительности.
Вышеуказанная трансляция принадлежит全局广播
, который должен отправить сообщение всем. Однако есть и другой сценарий, например, групповой чат из 5 человек, трансляция в это время предназначена только для отправки сообщений небольшой группе из 5 человек, поэтому это также называется局部广播
.
Реализация локального вещания более сложная и, как правило, сочетает в себе конкретные бизнес-сценарии. Это требует от нас сохранения данных клиента, когда клиент подключается. например сRedis
Храните статус и данные онлайн-клиентов, чтобы поиск и классификация выполнялись быстрее и эффективнее.
Реализована локальная трансляция, и приватный чат один на один стал проще. Просто найдите экземпляры WebSocket, соответствующие двум клиентам, и отправьте сообщения друг другу.
Безопасность и аутентификация
Сервер Websocket, построенный ранее, может быть подключен к любому клиенту по умолчанию, который определенно не принимается в производственной среде. Нам нужно обеспечить безопасность сервера WebSocket, в основном из двух аспектов:
- Аутентификация токена
- служба поддержки
Ниже приводится моя идея реализации.
Аутентификация токена
Интерфейс HTTP-запроса, который мы обычно делаемJWTДля аутентификации укажите указанный заголовок в заголовке запроса и передайте строку токена в прошлом. Серверная часть будет использовать этот токен для проверки. Если проверка не пройдена, он вернет ошибку 401, чтобы заблокировать запрос.
Мы сказали выше,WebSocket
Первым шагом в установлении соединения является инициация клиентом HTTP-запроса на соединение, затем мы проверяем этот HTTP-запрос, если проверка не удалась, будет создано соединение среднего веб-сокета, верно?
Следуя этой мысли, давайте трансформируем серверный код.
Поскольку проверка должна выполняться на уровне HTTP, используйтеhttp
Модуль создает сервер и закрывает порт для службы WebSocket.
var server = http.createServer()
var wss = new WebSocketServer({ noServer: true })
server.listen(8080)
когда клиент проходитws://
При подключении к серверу сервер обновит протокол, то есть обновит протокол http до протокола websocket, что приведет к срабатываниюupgrade
мероприятие:
server.on('upgrade', (request, socket) => {
// 用 request 获取参数做验证
// 1. 验证不通过判断
if ('验证失败') {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n')
socket.destroy()
return
}
// 2. 验证通过,继续建立连接
wss.handleUpgrade(request, socket, _, ws => {
wss.emit('connection', ws, request)
})
})
// 3. 监听连接
wss.on('connection', (ws, request) => {
console.log('客户端已连接')
ws.send('服务端信息')
})
Таким образом, добавляется аутентификация сервера, и конкретный метод аутентификации определяется в соответствии с методом передачи параметров клиента.
Клиентские соединения WebSocket не поддерживают настраиваемые заголовки, поэтому решение JWT использовать нельзя.Есть два доступных решения:
- Basic Auth
- Параметры прохода карьера
Basic Auth
Аутентификация — это просто аутентификация учетной записи + пароль, а пароль учетной записи передается в URL-адресе.
Предположим, у меня есть учетная запись, котораяruims, пароль123456, то подключение клиента выглядит так:
var ws = new WebSocket('ws://ruims:123456@localhost:8080')
Тогда сервер получит такой заголовок запроса:
wss.on('connection', (ws, req) => {
if(req.headers['authorization']) {
let auth = req.headers['authorization']
console.log(auth)
// 打印的值:Basic cnVpbXM6MTIzNDU2
}
}
вcnVpbXM6MTIzNDU2то естьruims:123456
Кодировку сервера base64 можно получить для аутентификации.
Quary
Передача параметров относительно проста, то есть обычные параметры передачи URL, вы можете взять более короткую зашифрованную строку в прошлом, и сервер получит строку, а затем аутентифицируется:
var ws = new WebSocket('ws://localhost:8080?token=cnVpbXM6MTIzNDU2')
Сервер получает параметры:
wss.on('connection', (ws, req) => {
console.log(req.query.token)
}
служба поддержки
Клиенты WebSocket используютws://
Подключение по протоколу, что значит wss?
На самом деле это очень просто, точно так же, как и принцип работы https.
https
Представляет защищенный протокол http, состоящий изHTTP + SSL
wss
представляет собой защищенный протокол ws, состоящий изWS + SSL
Так почему же нужно использовать wss? Помимо безопасности, есть еще одна ключевая причина: если ваше веб-приложение использует протокол https, вы должны использовать протокол wss в своем текущем приложении, использующем WebSocket, иначе браузер откажет в соединении.
Чтобы настроить wss, добавьте его непосредственно в конфигурацию https.location
Все, переходим непосредственно к настройке nginx:
location /websocket {
proxy_pass http://127.0.0.1:8080;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
}
Тогда подключение клиента становится таким:
var ws = new WebSocket('wss://[host]/websocket')
Приложение для лучших друзей
Возможно, вы слышали о BFF, полное имяBackend For Frontend
, что означает, что серверная часть обслуживает интерфейсную часть, которая является одной из интерфейсной и серверной части в фактической архитектуре приложения.中间层
.
Этот средний уровень обычно реализуется Node.js, так что же он делает?
Как мы все знаем, в настоящее время основной архитектурой серверной части являются микросервисы. В случае микросервисов API будут очень тонко разделены. Товарные сервисы — это стандартные сервисы, а сервисы уведомлений — это сервисы уведомлений. Если вы хотите отправить пользователю уведомление о появлении товара на полке, вам может потребоваться настроить как минимум два интерфейса.
Это на самом деле недружелюбно к интерфейсу, поэтому промежуточный уровень BFF появился позже, что эквивалентно промежуточной прокси-станции для внутренних запросов. конечный интерфейс для передачи необходимых данных.В сочетании, один возврат к внешнему интерфейсу.
Так как же нам применить много знаний о WebSocket выше на уровне BFF?
Есть как минимум 4 сценария применения, которые я могу придумать:
- Просмотр текущего онлайн-номера, онлайн-информации о пользователе
- Войдите на новое устройство, выйдите из других устройств
- Обнаружение сетевого подключения/отключения
- Новости на территории, небольшое точечное напоминание
Эти функции ранее были реализованы на серверной части и будут связаны с другими бизнес-функциями. Теперь с BFF WebSocket может быть полностью реализован на этом уровне, позволяя серверной части сосредоточиться на основной логике данных.
Видно, что после освоения практического применения WebSocket в Node.js в качестве фронтенда мы можем разорвать инволюцию и продолжить играть ценность в другом поле, разве это не прекрасно?
Исходный код + вопросы и ответы
Весь код в этой статье был проверен мной.Чтобы облегчить проверку и экспериментирование с небольшими партнерами, я создал репозиторий GitHub для хранения полного исходного кода этой статьи и полного исходного кода последующих статей.
Адрес склада здесь:Блог Yang успех
Добро пожаловать на проверку и тестирование. Если у вас возникнут сомнения, добавьте меня в WeChat.ruidoc
Консультация, а также все ваши мысли и идеи по практике WebSocket приветствуются для общения со мной~
моя колонка
Эта статья впервые появилась в колонке Nuggets.Интерфейс DevOpsВ этой рубрике будет долгосрочный вывод статей по фронтенд-инжинирингу и архитектурному направлению, как всегда качество защиты.
Вот еще одна статья, которую я рекомендуюВолшебные навыки фронтенд-архитектора, три приема для унификации стиля кода, В этой статье подробно рассказывается, как формулировать спецификации кода, проверять код и форматировать код шаг за шагом, что очень полезно для фронтенд-персонала, который унифицирует стиль кода команды.
Я не знаю, почему алгоритм рекомендаций Наггетса дал очень мало информации об этой статье, я чувствую, что жаль, поэтому давайте порекомендуем его вручную, спасибо