Table of Contents
- Class: WebSocketServer
- new WebSocketServer(options[, callback])
- Event: ‘close’
- Event: ‘connection’
- Event: ‘error’
- Event: ‘headers’
- Event: ‘listening’
- Event: ‘wsClientError’
- server.address()
- server.clients
- server.close([callback])
- server.handleUpgrade(request, socket, head, callback)
- server.shouldHandle(request)
- Class: WebSocket
- Ready state constants
- new WebSocket(address[, protocols][, options])
- IPC connections
- Event: ‘close’
- Event: ‘error’
- Event: ‘message’
- Event: ‘open’
- Event: ‘ping’
- Event: ‘pong’
- Event: ‘redirect’
- Event: ‘unexpected-response’
- Event: ‘upgrade’
- websocket.addEventListener(type, listener[, options])
- websocket.binaryType
- websocket.bufferedAmount
- websocket.close([code[, reason]])
- websocket.extensions
- websocket.isPaused
- websocket.onclose
- websocket.onerror
- websocket.onmessage
- websocket.onopen
- websocket.pause()
- websocket.ping([data[, mask]][, callback])
- websocket.pong([data[, mask]][, callback])
- websocket.protocol
- websocket.readyState
- websocket.removeEventListener(type, listener)
- websocket.resume()
- websocket.send(data[, options][, callback])
- websocket.terminate()
- websocket.url
- createWebSocketStream(websocket[, options])
- Environment variables
- WS_NO_BUFFER_UTIL
- WS_NO_UTF_8_VALIDATE
- Error codes
- WS_ERR_EXPECTED_FIN
- WS_ERR_EXPECTED_MASK
- WS_ERR_INVALID_CLOSE_CODE
- WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH
- WS_ERR_INVALID_OPCODE
- WS_ERR_INVALID_UTF8
- WS_ERR_UNEXPECTED_MASK
- WS_ERR_UNEXPECTED_RSV_1
- WS_ERR_UNEXPECTED_RSV_2_3
- WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH
- WS_ERR_UNSUPPORTED_MESSAGE_LENGTH
Class: WebSocketServer
This class represents a WebSocket server. It extends the EventEmitter
.
new WebSocketServer(options[, callback])
options
{Object}backlog
{Number} The maximum length of the queue of pending connections.clientTracking
{Boolean} Specifies whether or not to track clients.handleProtocols
{Function} A function which can be used to handle the
WebSocket subprotocols. See description below.host
{String} The hostname where to bind the server.maxPayload
{Number} The maximum allowed message size in bytes. Defaults to
100 MiB (104857600 bytes).noServer
{Boolean} Enable no server mode.path
{String} Accept only connections matching this path.perMessageDeflate
{Boolean|Object} Enable/disable permessage-deflate.port
{Number} The port where to bind the server.server
{http.Server|https.Server} A pre-created Node.js HTTP/S server.skipUTF8Validation
{Boolean} Specifies whether or not to skip UTF-8
validation for text and close messages. Defaults tofalse
. Set totrue
only if clients are trusted.verifyClient
{Function} A function which can be used to validate incoming
connections. See description below. (Usage is discouraged: see
Issue #337)WebSocket
{Function} Specifies theWebSocket
class to be used. It must
be extended from the originalWebSocket
. Defaults toWebSocket
.
callback
{Function}
Create a new server instance. One and only one of port
, server
or noServer
must be provided or an error is thrown. An HTTP server is automatically created,
started, and used if port
is set. To use an external HTTP/S server instead,
specify only server
or noServer
. In this case the HTTP/S server must be
started manually. The «noServer» mode allows the WebSocket server to be
completely detached from the HTTP/S server. This makes it possible, for example,
to share a single HTTP/S server between multiple WebSocket servers.
NOTE: Use of
verifyClient
is discouraged. Rather handle client
authentication in the'upgrade'
event of the HTTP server. See examples for
more details.
If verifyClient
is not set then the handshake is automatically accepted. If it
has a single parameter then ws
will invoke it with the following argument:
info
{Object}origin
{String} The value in the Origin header indicated by the client.req
{http.IncomingMessage} The client HTTP GET request.secure
{Boolean}true
ifreq.socket.authorized
or
req.socket.encrypted
is set.
The return value (Boolean
) of the function determines whether or not to accept
the handshake.
If verifyClient
has two parameters then ws
will invoke it with the following
arguments:
info
{Object} Same as above.cb
{Function} A callback that must be called by the user upon inspection of
theinfo
fields. Arguments in this callback are:result
{Boolean} Whether or not to accept the handshake.code
{Number} Whenresult
isfalse
this field determines the HTTP
error status code to be sent to the client.name
{String} Whenresult
isfalse
this field determines the HTTP
reason phrase.headers
{Object} Whenresult
isfalse
this field determines additional
HTTP headers to be sent to the client. For example,
{ 'Retry-After': 120 }
.
handleProtocols
takes two arguments:
protocols
{Set} The list of WebSocket subprotocols indicated by the client
in theSec-WebSocket-Protocol
header.request
{http.IncomingMessage} The client HTTP GET request.
The returned value sets the value of the Sec-WebSocket-Protocol
header in the
HTTP 101 response. If returned value is false
the header is not added in the
response.
If handleProtocols
is not set then the first of the client’s requested
subprotocols is used.
perMessageDeflate
can be used to control the behavior of permessage-deflate
extension. The extension is disabled when false
(default
value). If an object is provided then that is extension parameters:
serverNoContextTakeover
{Boolean} Whether to use context takeover or not.clientNoContextTakeover
{Boolean} Acknowledge disabling of client context
takeover.serverMaxWindowBits
{Number} The value ofwindowBits
.clientMaxWindowBits
{Number} Request a custom client window size.zlibDeflateOptions
{Object} Additional options to pass to
zlib on deflate.zlibInflateOptions
{Object} Additional options to pass to
zlib on inflate.threshold
{Number} Payloads smaller than this will not be compressed if
context takeover is disabled. Defaults to 1024 bytes.concurrencyLimit
{Number} The number of concurrent calls to zlib. Calls
above this limit will be queued. Default 10. You usually won’t need to touch
this option. See this issue for more details.
If a property is empty then either an offered configuration or a default value
is used. When sending a fragmented message the length of the first fragment is
compared to the threshold. This determines if compression is used for the entire
message.
callback
will be added as a listener for the 'listening'
event on the HTTP
server when the port
option is set.
Event: ‘close’
Emitted when the server closes. This event depends on the 'close'
event of
HTTP server only when it is created internally. In all other cases, the event is
emitted independently.
Event: ‘connection’
websocket
{WebSocket}request
{http.IncomingMessage}
Emitted when the handshake is complete. request
is the http GET request sent
by the client. Useful for parsing authority headers, cookie headers, and other
information.
Event: ‘error’
error
{Error}
Emitted when an error occurs on the underlying server.
Event: ‘headers’
headers
{Array}request
{http.IncomingMessage}
Emitted before the response headers are written to the socket as part of the
handshake. This allows you to inspect/modify the headers before they are sent.
Event: ‘listening’
Emitted when the underlying server has been bound.
Event: ‘wsClientError’
error
{Error}socket
{net.Socket|tls.Socket}request
{http.IncomingMessage}
Emitted when an error occurs before the WebSocket connection is established.
socket
and request
are respectively the socket and the HTTP request from
which the error originated. The listener of this event is responsible for
closing the socket. When the 'wsClientError'
event is emitted there is no
http.ServerResponse
object, so any HTTP response, including the response
headers and body, must be written directly to the socket
. If there is no
listener for this event, the socket is closed with a default 4xx response
containing a descriptive error message.
server.address()
Returns an object with port
, family
, and address
properties specifying the
bound address, the address family name, and port of the server as reported by
the operating system if listening on an IP socket. If the server is listening on
a pipe or UNIX domain socket, the name is returned as a string.
server.clients
- {Set}
A set that stores all connected clients. This property is only added when the
clientTracking
is truthy.
server.close([callback])
Prevent the server from accepting new connections and close the HTTP server if
created internally. If an external HTTP server is used via the server
or
noServer
constructor options, it must be closed manually. Existing connections
are not closed automatically. The server emits a 'close'
event when all
connections are closed unless an external HTTP server is used and client
tracking is disabled. In this case the 'close'
event is emitted in the next
tick. The optional callback is called when the 'close'
event occurs and
receives an Error
if the server is already closed.
server.handleUpgrade(request, socket, head, callback)
request
{http.IncomingMessage} The client HTTP GET request.socket
{net.Socket|tls.Socket} The network socket between the server and
client.head
{Buffer} The first packet of the upgraded stream.callback
{Function}.
Handle a HTTP upgrade request. When the HTTP server is created internally or
when the HTTP server is passed via the server
option, this method is called
automatically. When operating in «noServer» mode, this method must be called
manually.
If the upgrade is successful, the callback
is called with two arguments:
websocket
{WebSocket} AWebSocket
object.request
{http.IncomingMessage} The client HTTP GET request.
server.shouldHandle(request)
request
{http.IncomingMessage} The client HTTP GET request.
See if a given request should be handled by this server. By default this method
validates the pathname of the request, matching it against the path
option if
provided. The return value, true
or false
, determines whether or not to
accept the handshake.
This method can be overridden when a custom handling logic is required.
Class: WebSocket
This class represents a WebSocket. It extends the EventEmitter
.
Ready state constants
Constant | Value | Description |
---|---|---|
CONNECTING | 0 | The connection is not yet open. |
OPEN | 1 | The connection is open and ready to communicate. |
CLOSING | 2 | The connection is in the process of closing. |
CLOSED | 3 | The connection is closed. |
new WebSocket(address[, protocols][, options])
address
{String|url.URL} The URL to which to connect.protocols
{String|Array} The list of subprotocols.options
{Object}followRedirects
{Boolean} Whether or not to follow redirects. Defaults to
false
.generateMask
{Function} The function used to generate the masking key. It
takes aBuffer
that must be filled synchronously and is called before a
message is sent, for each message. By default the buffer is filled with
cryptographically strong random bytes.handshakeTimeout
{Number} Timeout in milliseconds for the handshake
request. This is reset after every redirection.maxPayload
{Number} The maximum allowed message size in bytes. Defaults to
100 MiB (104857600 bytes).maxRedirects
{Number} The maximum number of redirects allowed. Defaults
to 10.origin
{String} Value of theOrigin
orSec-WebSocket-Origin
header
depending on theprotocolVersion
.perMessageDeflate
{Boolean|Object} Enable/disable permessage-deflate.protocolVersion
{Number} Value of theSec-WebSocket-Version
header.skipUTF8Validation
{Boolean} Specifies whether or not to skip UTF-8
validation for text and close messages. Defaults tofalse
. Set totrue
only if the server is trusted.- Any other option allowed in
http.request()
orhttps.request()
.
Options given do not have any effect if parsed from the URL given with the
address
parameter.
perMessageDeflate
default value is true
. When using an object, parameters
are the same of the server. The only difference is the direction of requests.
For example, serverNoContextTakeover
can be used to ask the server to disable
context takeover.
Create a new WebSocket instance.
IPC connections
ws
supports IPC connections. To connect to an IPC endpoint, use the following
URL form:
-
On Unices
ws+unix:/absolute/path/to/uds_socket:/pathname?search_params
-
On Windows
ws+unix:\.pipepipe_name:/pathname?search_params
The character :
is the separator between the IPC path (the Unix domain socket
path or the Windows named pipe) and the URL path. The IPC path must not include
the characters :
and ?
, otherwise the URL is incorrectly parsed. If the URL
path is omitted
ws+unix:/absolute/path/to/uds_socket
it defaults to /
.
Event: ‘close’
code
{Number}reason
{Buffer}
Emitted when the connection is closed. code
is a numeric value indicating the
status code explaining why the connection has been closed. reason
is a
Buffer
containing a human-readable string explaining why the connection has
been closed.
Event: ‘error’
error
{Error}
Emitted when an error occurs. Errors may have a .code
property, matching one
of the string values defined below under Error codes.
Event: ‘message’
data
{Buffer|ArrayBuffer|Buffer[]}isBinary
{Boolean}
Emitted when a message is received. data
is the message content. isBinary
specifies whether the message is binary or not.
Event: ‘open’
Emitted when the connection is established.
Event: ‘ping’
data
{Buffer}
Emitted when a ping is received from the server.
Event: ‘pong’
data
{Buffer}
Emitted when a pong is received from the server.
Event: ‘redirect’
url
{String}request
{http.ClientRequest}
Emitted before a redirect is followed. url
is the redirect URL. request
is
the HTTP GET request with the headers queued. This event gives the ability to
inspect confidential headers and remove them on a per-redirect basis using the
request.getHeader()
and request.removeHeader()
API. The request
object should be used only for this purpose. When there is at least one listener
for this event, no header is removed by default, even if the redirect is to a
different domain.
Event: ‘unexpected-response’
request
{http.ClientRequest}response
{http.IncomingMessage}
Emitted when the server response is not the expected one, for example a 401
response. This event gives the ability to read the response in order to extract
useful information. If the server sends an invalid response and there isn’t a
listener for this event, an error is emitted.
Event: ‘upgrade’
response
{http.IncomingMessage}
Emitted when response headers are received from the server as part of the
handshake. This allows you to read headers from the server, for example
‘set-cookie’ headers.
websocket.addEventListener(type, listener[, options])
type
{String} A string representing the event type to listen for.listener
{Function|Object} The listener to add.options
{Object}once
{Boolean} ABoolean
indicating that the listener should be invoked
at most once after being added. Iftrue
, the listener would be
automatically removed when invoked.
Register an event listener emulating the EventTarget
interface. This method
does nothing if type
is not one of 'close'
, 'error'
, 'message'
, or
'open'
.
websocket.binaryType
- {String}
A string indicating the type of binary data being transmitted by the connection.
This should be one of «nodebuffer», «arraybuffer» or «fragments». Defaults to
«nodebuffer». Type «fragments» will emit the array of fragments as received from
the sender, without copyfull concatenation, which is useful for the performance
of binary protocols transferring large messages with multiple fragments.
websocket.bufferedAmount
- {Number}
The number of bytes of data that have been queued using calls to send()
but
not yet transmitted to the network. This deviates from the HTML standard in the
following ways:
- If the data is immediately sent the value is
0
. - All framing bytes are included.
websocket.close([code[, reason]])
code
{Number} A numeric value indicating the status code explaining why the
connection is being closed.reason
{String|Buffer} The reason why the connection is closing.
Initiate a closing handshake.
websocket.isPaused
- {Boolean}
Indicates whether the websocket is paused.
websocket.extensions
- {Object}
An object containing the negotiated extensions.
websocket.onclose
- {Function}
An event listener to be called when connection is closed. The listener receives
a CloseEvent
named «close».
websocket.onerror
- {Function}
An event listener to be called when an error occurs. The listener receives an
ErrorEvent
named «error».
websocket.onmessage
- {Function}
An event listener to be called when a message is received from the server. The
listener receives a MessageEvent
named «message».
websocket.onopen
- {Function}
An event listener to be called when the connection is established. The listener
receives an OpenEvent
named «open».
websocket.pause()
Pause the websocket causing it to stop emitting events. Some events can still be
emitted after this is called, until all buffered data is consumed. This method
is a noop if the ready state is CONNECTING
or CLOSED
.
websocket.ping([data[, mask]][, callback])
data
{Array|Number|Object|String|ArrayBuffer|Buffer|DataView|TypedArray} The
data to send in the ping frame.mask
{Boolean} Specifies whetherdata
should be masked or not. Defaults to
true
whenwebsocket
is not a server client.callback
{Function} An optional callback which is invoked when the ping
frame is written out. If an error occurs, the callback is called with the
error as its first argument.
Send a ping. This method throws an error if the ready state is CONNECTING
.
websocket.pong([data[, mask]][, callback])
data
{Array|Number|Object|String|ArrayBuffer|Buffer|DataView|TypedArray} The
data to send in the pong frame.mask
{Boolean} Specifies whetherdata
should be masked or not. Defaults to
true
whenwebsocket
is not a server client.callback
{Function} An optional callback which is invoked when the pong
frame is written out. If an error occurs, the callback is called with the
error as its first argument.
Send a pong. This method throws an error if the ready state is CONNECTING
.
websocket.protocol
- {String}
The subprotocol selected by the server.
websocket.resume()
Make a paused socket resume emitting events. This method is a noop if the ready
state is CONNECTING
or CLOSED
.
websocket.readyState
- {Number}
The current state of the connection. This is one of the ready state constants.
websocket.removeEventListener(type, listener)
type
{String} A string representing the event type to remove.listener
{Function|Object} The listener to remove.
Removes an event listener emulating the EventTarget
interface. This method
only removes listeners added with
websocket.addEventListener()
.
websocket.send(data[, options][, callback])
data
{Array|Number|Object|String|ArrayBuffer|Buffer|DataView|TypedArray} The
data to send.Object
values are only supported if they conform to the
requirements ofBuffer.from()
. If those constraints are not met, a
TypeError
is thrown.options
{Object}binary
{Boolean} Specifies whetherdata
should be sent as a binary or
not. Default is autodetected.compress
{Boolean} Specifies whetherdata
should be compressed or not.
Defaults totrue
when permessage-deflate is enabled.fin
{Boolean} Specifies whetherdata
is the last fragment of a message
or not. Defaults totrue
.mask
{Boolean} Specifies whetherdata
should be masked or not. Defaults
totrue
whenwebsocket
is not a server client.
callback
{Function} An optional callback which is invoked whendata
is
written out. If an error occurs, the callback is called with the error as its
first argument.
Send data
through the connection. This method throws an error if the ready
state is CONNECTING
.
websocket.terminate()
Forcibly close the connection. Internally this calls socket.destroy()
.
websocket.url
- {String}
The URL of the WebSocket server. Server clients don’t have this attribute.
createWebSocketStream(websocket[, options])
websocket
{WebSocket} AWebSocket
object.options
{Object} Options to pass to theDuplex
constructor.
Returns a Duplex
stream that allows to use the Node.js streams API on top of a
given WebSocket
.
Environment variables
WS_NO_BUFFER_UTIL
When set to a non empty value, prevents the optional bufferutil
dependency
from being required.
WS_NO_UTF_8_VALIDATE
When set to a non empty value, prevents the optional utf-8-validate
dependency
from being required.
Error codes
Errors emitted by the websocket may have a .code
property, describing the
specific type of error that has occurred:
WS_ERR_EXPECTED_FIN
A WebSocket frame was received with the FIN bit not set when it was expected.
WS_ERR_EXPECTED_MASK
An unmasked WebSocket frame was received by a WebSocket server.
WS_ERR_INVALID_CLOSE_CODE
A WebSocket close frame was received with an invalid close code.
WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH
A control frame with an invalid payload length was received.
WS_ERR_INVALID_OPCODE
A WebSocket frame was received with an invalid opcode.
WS_ERR_INVALID_UTF8
A text or close frame was received containing invalid UTF-8 data.
WS_ERR_UNEXPECTED_MASK
A masked WebSocket frame was received by a WebSocket client.
WS_ERR_UNEXPECTED_RSV_1
A WebSocket frame was received with the RSV1 bit set unexpectedly.
WS_ERR_UNEXPECTED_RSV_2_3
A WebSocket frame was received with the RSV2 or RSV3 bit set unexpectedly.
WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH
A data frame was received with a length longer than the max supported length
(2^53 — 1, due to JavaScript language limitations).
WS_ERR_UNSUPPORTED_MESSAGE_LENGTH
A message was received with a length longer than the maximum supported length,
as configured by the maxPayload
option.
Написание клиентских приложений с помощью веб-сокетов
Веб-сокеты — технология, которая позволяет открыть интерактивную сессию общения между браузером пользователя и сервером. Соединяясь через веб-сокеты, веб-приложения могут осуществлять взаимодействие в реальном времени вместо того, чтобы делать запросы к клиенту о входящих/исходящих изменениях.
Примечание: Замечание: У нас есть работающий пример чата, части кода из которого используются в статье. Пример будет доступен, когда инфраструктура сайта сможет должным образом поддерживать хостинг примеров с использованием веб-сокетов.
Доступность веб-сокетов
API веб-сокетов доступно в Javascript коде, область видимости которого включает объект DOM Window
или любой объект, реализующий WorkerUtils
; это означает, что вы можете использовать Web Workers.
Примечание: Замечание: API веб-сокетов (как и протокол лежащий в его основе) всё ещё проходят этап активной разработки; в настоящее время существует много проблем совместимости с разными браузерами (и даже с разными релизами одного и того же браузера).
Создание объекта WebSocket
Чтобы общаться через протокол веб-сокетов необходимо создать объект WebSocket
; при его создании автоматически происходит попытка открыть соединение с сервером.
Конструктор WebSocket принимает один обязательный и один необязательный параметр:
WebSocket WebSocket( in DOMString url, in optional DOMString protocols ); WebSocket WebSocket( in DOMString url, in optional DOMString[] protocols );
url
-
URL, с которым происходит соединение; это должен быть URL веб-сокет-сервера.
protocols
Необязательный-
Может быть одной строкой протокола или массивом таких строк. Эти строки используют для индикации под-протоколов; таким образом, один сервер может реализовывать несколько под-протоколов веб-сокетов (к примеру, вам может потребоваться, чтобы сервер мог обрабатывать разные типы взаимодействий в зависимости от определённого под-протокола). Если вы не укажете строку протокола, то будет передана пустая строка.
В конструкторе могут возникать следующие исключения:
SECURITY_ERR
-
Порт, к которому проводится подключение, заблокирован.
Ошибки подключения
Если ошибка случается во время попытки подключения, то в объект WebSocket
сначала посылается простое событие с именем «error» (таким образом, задействуя обработчик onerror
), потом — событие CloseEvent
(таким образом, задействуя обработчик onclose
) чтобы обозначить причину закрытия соединения.
Однако, начиная с версии Firefox 11, типичным является получение в консоль от платформы Mozilla расширенного сообщения об ошибке и кода завершения, как то определено в RFC 6455, Section 7.4 посредством CloseEvent
.
Примеры
Этот простой пример создаёт новый WebSocket, подключаемый к серверу ws://www.example.com/socketserver
. В данном примере в конструктор сокета в качестве дополнительного параметра передаётся пользовательский протокол «protocolOne», хотя эта часть может быть опущена.
var exampleSocket = new WebSocket("ws://www.example.com/socketserver", "protocolOne");
После выполнения функции, exampleSocket.readyState
(en-US) будет иметь значение CONNECTING
. readyState
изменится на OPEN
как только соединение станет готовым к передаче данных.
Если нужно открыть соединение, поддерживающее несколько протоколов, можно передать массив протоколов:
var exampleSocket = new WebSocket("ws://www.example.com/socketserver", ["protocolOne", "protocolTwo"]);
Когда соединение установлено (что соответствует, readyState
OPEN
), exampleSocket.protocol
сообщит, какой протокол выбрал сервер.
В приведенных выше примерах ws
заменяет http
, аналогично wss
заменяет https
. Установка соединения через WebSocket зависит от механизма обновления HTTP, таким образом запрос на обновление неявный, когда мы обращаемся к серверу HTTP с помощью ws://www.example.com
или wss://www.example.com
.
Отправка данных на сервер
Однажды открыв соединение, вы можете передавать данные на сервер. Для осуществления этого, вызовите метод send()
объекта WebSocket
для каждого сообщение, которое желаете отправить:
exampleSocket.send("Вот текст, который будет отправлен серверу.");
Вы можете пересылать данные в виде строки, Blob
, так и ArrayBuffer
.
Примечание: Замечание: До версии 11, Firefox поддерживал отправку данных только в виде строки.
Так как установка соедиения асинхронна и подвержена сбоям, то нет никакой гарантии, что вызов метода send()
, после создания объекта WebSocket, будет завершен успешно. По крайней мере, мы можем быть уверены, что попытка отправить данные будет иметь место только после того, как соединение будет установлено, определив обработчик onopen
для выполнения этого действия:
exampleSocket.onopen = function (event) {
exampleSocket.send("Вот текст, который будет отправлен серверу.");
};
Использование JSON для передачи объектов
Одна удобная вещь которую вы можете сделать, это использовать JSON для пересылки сложных данных на сервер. Например, приложение-чат может взаимодействовать с сервером, используя протокол, реализованный с использованием пакетов данных, инкапсулированных в JSON:
// Отправьте текст всем пользователям через сервер
function sendText() {
// Создайте объект содержащий данные, необходимые серверу для обрабоки сообщения от клиента чата.
var msg = {
type: "message",
text: document.getElementById("text").value,
id: clientID,
date: Date.now()
};
// Отправьте объект в виде JSON строки.
exampleSocket.send(JSON.stringify(msg));
// Очистите элемент ввода текста, чтобы получить следующую строку текста от пользователя.
document.getElementById("text").value = "";
}
Получение сообщений от сервера
WebSockets — это API, управляемый событиями; когда сообщения получены, событие «message» доставлено в функцию onmessage
. Чтобы начать прослушивание входящих данных, вы можете сделать что-то вроде этого:
exampleSocket.onmessage = function (event) {
console.log(event.data);
}
Получение и интерпретация JSON объектов
Давайте рассмотрим клиентское приложение чата, которое впервые упоминалось в разделе Использование JSON для передачи объектов. Есть разные типы пакетов данных, которые может получить клиент, например:
- Вход в систему
- Текст сообщения
- Обновление списка пользователей
Код обрабатывающий эти входящие сообщения, может выглядеть так:
exampleSocket.onmessage = function(event) {
var f = document.getElementById("chatbox").contentDocument;
var text = "";
var msg = JSON.parse(event.data);
var time = new Date(msg.date);
var timeStr = time.toLocaleTimeString();
switch(msg.type) {
case "id":
clientID = msg.id;
setUsername();
break;
case "username":
text = "<b>User <em>" + msg.name + "</em> signed in at " + timeStr + "</b><br>";
break;
case "message":
text = "(" + timeStr + ") <b>" + msg.name + "</b>: " + msg.text + "<br>";
break;
case "rejectusername":
text = "<b>Your username has been set to <em>" + msg.name + "</em> because the name you chose is in use.</b><br>"
break;
case "userlist":
var ul = "";
for (i=0; i < msg.users.length; i++) {
ul += msg.users[i] + "<br>";
}
document.getElementById("userlistbox").innerHTML = ul;
break;
}
if (text.length) {
f.write(text);
document.getElementById("chatbox").contentWindow.scrollByPages(1);
}
};
Здесь мы используем JSON.parse()
чтобы преобразовать JSON строку в объект, затем обработайте его.
Формат текстовых данных
Текст, полученный через WebSocket должен иметь кодировку UTF-8
До Gecko 9.0 (Firefox 9.0 / Thunderbird 9.0 / SeaMonkey 2.6), некоторые не символьные значения в допустимом тексте UTF-8 могут привести к разрыву соединения. Теперь Gecko допускает эти значения.
Закрытие соединения
Когда вы закончили использовать соединение WebSocket, закройте его используя метод close()
:
Перед попыткой закрыть соединение может быть полезно проверить атрибут bufferedAmount
чтобы определить, не переданы ли еще какие-либо данные по сети.
Безопасность
WebSocket не следует использовать в среде со смешанным содержимым: то есть вы не должны открывать незащищенное соединение WebSocket со страницы, загруженной с использованием HTTPS, или наоборот. Фактически, некоторые браузеры явно запрещают это, например Firefox 8 и выше.
I’ve been developing browser-based multi player game for a while now and I’ve been testing different ports accessibility in various environment (client’s office, public wifi etc.). All is going quite well, except one thing: I can’t figure out is how to read error no. or description when onerror event is received.
Client websocket is done in javascript.
For example:
// Init of websocket
websocket = new WebSocket(wsUri);
websocket.onerror = OnSocketError;
...etc...
// Handler for onerror:
function OnSocketError(ev)
{
output("Socket error: " + ev.data);
}
‘output’ is just some utility function that writes into a div.
What I am getting is ‘undefined’ for ev.data. Always. And I’ve been googling around but it seems there’s no specs on what params this event has and how to properly read it.
Any help is appreciated!
asked Sep 14, 2013 at 16:38
1
Alongside nmaier’s answer, as he said you’ll always receive code 1006. However, if you were to somehow theoretically receive other codes, here is code to display the results (via RFC6455).
you will almost never get these codes in practice so this code is pretty much pointless
var websocket;
if ("WebSocket" in window)
{
websocket = new WebSocket("ws://yourDomainNameHere.org/");
websocket.onopen = function (event) {
$("#thingsThatHappened").html($("#thingsThatHappened").html() + "<br />" + "The connection was opened");
};
websocket.onclose = function (event) {
var reason;
alert(event.code);
// See https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1
if (event.code == 1000)
reason = "Normal closure, meaning that the purpose for which the connection was established has been fulfilled.";
else if(event.code == 1001)
reason = "An endpoint is "going away", such as a server going down or a browser having navigated away from a page.";
else if(event.code == 1002)
reason = "An endpoint is terminating the connection due to a protocol error";
else if(event.code == 1003)
reason = "An endpoint is terminating the connection because it has received a type of data it cannot accept (e.g., an endpoint that understands only text data MAY send this if it receives a binary message).";
else if(event.code == 1004)
reason = "Reserved. The specific meaning might be defined in the future.";
else if(event.code == 1005)
reason = "No status code was actually present.";
else if(event.code == 1006)
reason = "The connection was closed abnormally, e.g., without sending or receiving a Close control frame";
else if(event.code == 1007)
reason = "An endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the message (e.g., non-UTF-8 [https://www.rfc-editor.org/rfc/rfc3629] data within a text message).";
else if(event.code == 1008)
reason = "An endpoint is terminating the connection because it has received a message that "violates its policy". This reason is given either if there is no other sutible reason, or if there is a need to hide specific details about the policy.";
else if(event.code == 1009)
reason = "An endpoint is terminating the connection because it has received a message that is too big for it to process.";
else if(event.code == 1010) // Note that this status code is not used by the server, because it can fail the WebSocket handshake instead.
reason = "An endpoint (client) is terminating the connection because it has expected the server to negotiate one or more extension, but the server didn't return them in the response message of the WebSocket handshake. <br /> Specifically, the extensions that are needed are: " + event.reason;
else if(event.code == 1011)
reason = "A server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.";
else if(event.code == 1015)
reason = "The connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can't be verified).";
else
reason = "Unknown reason";
$("#thingsThatHappened").html($("#thingsThatHappened").html() + "<br />" + "The connection was closed for reason: " + reason);
};
websocket.onmessage = function (event) {
$("#thingsThatHappened").html($("#thingsThatHappened").html() + "<br />" + "New message arrived: " + event.data);
};
websocket.onerror = function (event) {
$("#thingsThatHappened").html($("#thingsThatHappened").html() + "<br />" + "There was an error with your websocket.");
};
}
else
{
alert("Websocket is not supported by your browser");
return;
}
websocket.send("Yo wazzup");
websocket.close();
See http://jsfiddle.net/gr0bhrqr/
answered Feb 8, 2015 at 16:09
PhylliidaPhylliida
4,1273 gold badges22 silver badges34 bronze badges
3
The error Event
the onerror
handler receives is a simple event not containing such information:
If the user agent was required to fail the WebSocket connection or the WebSocket connection is closed with prejudice, fire a simple event named error at the WebSocket object.
You may have better luck listening for the close
event, which is a CloseEvent
and indeed has a CloseEvent.code
property containing a numerical code according to RFC 6455 11.7 and a CloseEvent.reason
string property.
Please note however, that CloseEvent.code
(and CloseEvent.reason
) are limited in such a way that network probing and other security issues are avoided.
answered Sep 14, 2013 at 17:14
nmaiernmaier
31.8k5 gold badges62 silver badges77 bronze badges
2
Potential stupid fix for those who throw caution to the wind: return a status code. The status code can be viewed from the onerror
event handler by accessing the message
property of the argument received by the handler. I recommend going with the 440s—seems to be free real estate.
«Unexpected server response: 440»
Little bit of regex does the trick:
const socket = new WebSocket(/* yuh */);
socket.onerror = e => {
const errorCode = e.message.match(/d{3}/)[0];
// errorCode = '440'
// make your own rudimentary standard for error codes and handle them accordingly
};
Might be useful in a pinch, but don’t come crying to me for any unforeseen repercussions.
answered Jun 16, 2021 at 7:14
Kael KirkKael Kirk
3242 silver badges9 bronze badges
6
[Советуем почитать] Другие 19 частей цикла
Перед вами — перевод пятого материала из серии, посвящённой особенностям JS-разработки. В предыдущих статьях мы рассматривали основные элементы экосистемы JavaScript, возможностями которых пользуются разработчики серверного и клиентского кода. В этих материалах, после изложения основ тех или иных аспектов JS, даются рекомендации по их использованию. Автор статьи говорит, что эти принципы применяются в ходе разработки приложения SessionStack. Современный пользователь библиотек и фреймворков может выбирать из множества возможностей, поэтому любому проекту, для того, чтобы достойно смотреться в конкурентной борьбе, приходится выжимать из технологий, на которых он построен, всё, что можно.
В этот раз мы поговорим о коммуникационных протоколах, сопоставим и обсудим их особенности и составные части. Тут мы займёмся технологиями WebSocket и HTTP/2, в частности, поговорим о безопасности и поделимся советами, касающимися выбора подходящих протоколов в различных ситуациях.
Введение
В наши дни сложные веб-приложения, обладающие насыщенными динамическими пользовательскими интерфейсами, воспринимаются как нечто само собой разумеющееся. А ведь интернету пришлось пройти долгий путь для того, чтобы достичь его сегодняшнего состояния.
В самом начале интернет не был рассчитан на поддержку подобных приложений. Он был задуман как коллекция HTML-страниц, как «паутина» из связанных друг с другом ссылками документов. Всё было, в основном, построено вокруг парадигмы HTTP «запрос/ответ». Клиентские приложения загружали страницы и после этого ничего не происходило до того момента, пока пользователь не щёлкнул мышью по ссылке для перехода на очередную страницу.
Примерно в 2005-м году появилась технология AJAX и множество программистов начало исследовать возможности двунаправленной связи между клиентом и сервером. Однако, все сеансы HTTP-связи всё ещё инициировал клиент, что требовало либо участия пользователя, либо выполнения периодических обращений к серверу для загрузки новых данных.
«Двунаправленный» обмен данными по HTTP
Технологии, которые позволяют «упреждающе» отправлять данные с сервера на клиент существуют уже довольно давно. Среди них — Push и Comet.
Один из наиболее часто используемых приёмов для создании иллюзии того, что сервер самостоятельно отправляет данные клиенту, называется «длинный опрос» (long polling). С использованием этой технологии клиент открывает HTTP-соединение с сервером, который держит его открытым до тех пор, пока не будет отправлен ответ. В результате, когда у сервера появляются данные для клиента, он их ему отправляет.
Вот пример очень простого фрагмента кода, реализующего технологию длинного опроса:
(function poll(){
setTimeout(function(){
$.ajax({
url: 'https://api.example.com/endpoint',
success: function(data) {
// Делаем что-то с `data`
// ...
// Рекурсивно выполняем следующий запрос
poll();
},
dataType: 'json'
});
}, 10000);
})();
Эта конструкция представляет собой функцию, которая сама себя вызывает после того, как, в первый раз, будет запущена автоматически. Она задаёт 10-секундный интервал для каждого асинхронного Ajax-обращению к серверу, а после обработки ответа сервера снова выполняется планирование вызова функции.
Ещё одна используемая в подобной ситуации техника — это Flash или составной запрос HXR, и так называемые htmlfiles.
У всех этих технологий одна и та же проблема: дополнительная нагрузка на систему, которую создаёт использование HTTP, что делает всё это неподходящим для организации работы приложений, где требуется высокая скорость отклика. Например, это что-то вроде многопользовательской браузерной «стрелялки» или любой другой онлайн-игры, действия в которой выполняются в режиме реального времени.
Введение в технологию WebSocket
Спецификация WebSocket определяет API для установки соединения между веб-браузером и сервером, основанного на «сокете». Проще говоря, это — постоянное соединение между клиентом и сервером, пользуясь которыми клиент и сервер могут отправлять данные друг другу в любое время.
Клиент устанавливает соединение, выполняя процесс так называемого рукопожатия WebSocket. Этот процесс начинается с того, что клиент отправляет серверу обычный HTTP-запрос. В этот запрос включается заголовок Upgrade
, который сообщает серверу о том, что клиент желает установить WebSocket-соединение.
Посмотрим, как установка такого соединения выглядит со стороны клиента:
// Создаём новое WebSocket-соединение.
var socket = new WebSocket('ws://websocket.example.com');
URL, применяемый для WebSocket-соединения, использует схему ws
. Кроме того, имеется схема wss
для организации защищённых WebSocket-соединений, что является эквивалентом HTTPS.
В данном случае показано начало процесса открытия WebSocket-соединения с сервером websocket.example.com
.
Вот упрощённый пример заголовков исходного запроса.
GET ws://websocket.example.com/ HTTP/1.1
Origin: http://example.com
Connection: Upgrade
Host: websocket.example.com
Upgrade: websocket
Если сервер поддерживает протокол WebSocket, он согласится перейти на него и сообщит об этом в заголовке ответа Upgrade
. Посмотрим на реализацию этого механизма с использованием Node.js:
// Будем использовать реализацию WebSocket из
//https://github.com/theturtle32/WebSocket-Node
var WebSocketServer = require('websocket').server;
var http = require('http');
var server = http.createServer(function(request, response) {
// обработаем HTTP-запрос.
});
server.listen(1337, function() { });
// создадим сервер
wsServer = new WebSocketServer({
httpServer: server
});
// WebSocket-сервер
wsServer.on('request', function(request) {
var connection = request.accept(null, request.origin);
// Это - самый важный для нас коллбэк, где обрабатываются
// сообщения от клиента.
connection.on('message', function(message) {
// Обработаем сообщение WebSocket
});
connection.on('close', function(connection) {
// Закрытие соединения
});
});
После установки соединения в ответе сервера будут сведения о переходе на протокол WebSocket:
HTTP/1.1 101 Switching Protocols
Date: Wed, 25 Oct 2017 10:07:34 GMT
Connection: Upgrade
Upgrade: WebSocket
После этого вызывается событие open
в экземпляре WebSocket на клиенте:
var socket = new WebSocket('ws://websocket.example.com');
// Выводим сообщение при открытии WebSocket-соединения.
socket.onopen = function(event) {
console.log('WebSocket is connected.');
};
Теперь, после завершения фазы рукопожатия, исходное HTTP-соединение заменяется на WebSocket-соединение, которое использует то же самое базовое TCP/IP-соединение. В этот момент и клиент и сервер могут приступать к отправке данных.
Благодаря использованию WebSocket можно отправлять любые объёмы данных, не подвергая систему ненужной нагрузке, вызываемой использованием традиционных HTTP-запросов. Данные передаются по WebSocket-соединению в виде сообщений, каждое из которых состоит из одного или нескольких фреймов, содержащих отправляемые данные (полезную нагрузку). Для того, чтобы обеспечить правильную сборку исходного сообщения по достижению им клиента, каждый фрейм имеет префикс, содержащий 4-12 байтов данных о полезной нагрузке. Использование системы обмена сообщениями, основанной на фреймах, помогает сократить число служебных данных, передаваемых по каналу связи, что значительно уменьшает задержки при передаче информации.
Стоит отметить, что клиенту будет сообщено о поступлении нового сообщения только после того, как будут получены все фреймы и исходная полезная нагрузка сообщения будет реконструирована.
Различные URL протокола WebSocket
Выше мы упоминали о том, что в WebSocket используется новая схема URL. На самом деле — их две: ws://
и wss://
.
При построении URL-адресов используются определённые правила. Особенностью URL WebSocket является то, что они не поддерживают якоря (#sample_anchor
).
В остальном к URL WebSocket применяются те же правила, что и к URL HTTP. При использовании ws-адресов соединение оказывается незашифрованным, по умолчанию применяется порт 80. При использовании wss требуется TLS-шифрование и применяется порт 443.
Протокол работы с фреймами
Взглянем поближе на протокол работы с фреймами WebSocket. Вот что можно узнать о структуре фрейма из соответствующего RFC:
Если говорить о версии WebSocket, стандартизированной RFC, то можно сказать, что в начале каждого пакета имеется небольшой заголовок. Однако, устроен он довольно сложно. Вот описание его составных частей:
fin
(1 бит): указывает на то, является ли этот фрейм последним фреймом, завершающим передачу сообщения. Чаще всего для передачи сообщения достаточно одного фрейма и этот бит всегда оказывается установленным. Эксперименты показали, что Firefox создаёт второй фрейм после того, как размер сообщения превысит 32 Кб.rsv1
,rsv2
,rsv3
(каждое по 1-му биту): эти поля должны быть установлены в 0, только если не было достигнуто договорённости о расширениях, которая и определит смысл их ненулевых значений. Если в одном из этих полей будет установлено ненулевое значение и при этом не было достигнуто договорённости о смысле этого значения, получатель должен признать соединение недействительным.opcode
(4 бита): здесь закодированы сведения о содержимом фрейма. В настоящее время используются следующие значения:0x00
: в этом фрейме находится следующая часть передаваемого сообщения.0x01
: в этом фрейме находятся текстовые данные.0x02
: в этом фрейме находятся бинарные данные.0x08
: этот фрейм завершает соединение.0x09
: это ping-фрейм.0x0a
: это pong-фрейм.
Как видите, здесь достаточно неиспользуемых значений. Они зарезервированы на будущее.
mask
(1 бит): указывает на то, что фрейм замаскирован. Сейчас дело обстоит так, что каждое сообщение от клиента к серверу должно быть замаскировано, в противном случае спецификации предписывают разрывать соединения.payload_len
(7 битов): длина полезной нагрузки. Фреймы WebSocket поддерживают следующие методы указания размеров полезной нагрузки. Значение 0-125 указывает на длину полезной нагрузки. 126 означает, что следующие два байта означают размер. 127 означает, что следующие 8 байтов содержат сведения о размере. В результате длина полезной нагрузки может быть записана примерно в 7 битах, или в 16, или в 64-х.masking-key
(32 бита): все фреймы, отправленные с клиента на сервер, замаскированы с помощью 32-битного значения, которое содержится во фрейме.payload
: передаваемые во фрейме данные, которые, наверняка, замаскированы. Их длина соответствует тому, что задано вpayload_len
.
Почему протокол WebSocket основан на фреймах, а не на потоках? Если вы знаете ответ на этот вопрос — можете поделиться им в комментариях. Кроме того, вот интересное обсуждение на эту тему на HackerNews.
Данные во фреймах
Как уже было сказано, данные могут быть разбиты на множество фреймов. В первом фрейме, с которого начинается передача данных, в поле opcode
, задаётся тип передаваемых данных. Это необходимо, так как в JavaScript, можно сказать, не было поддержки бинарных данных во время начала работы над спецификацией WebSockets. Код 0x01
указывает на данные в кодировке UTF-8, код 0x02
используется для бинарных данных. Часто в пакетах WebSocket передают JSON-данные, для которых обычно устанавливают поле opcode
как для текста. При передаче бинарных данных они будут представлены в виде Blob-сущностей, специфичных для веб-браузера.
API для передачи данных по протоколу WebSocket устроено очень просто:
var socket = new WebSocket('ws://websocket.example.com');
socket.onopen = function(event) {
socket.send('Some message'); // Отправка данных на сервер.
};
Когда, на клиентской стороне, WebSocket принимает данные, вызывается событие message
. Это событие имеет свойство data
, которое можно использовать для работы с содержимым сообщения.
// Обработка сообщений, отправленных сервером.
socket.onmessage = function(event) {
var message = event.data;
console.log(message);
};
Узнать, что находится внутри фреймов WebSocket-соединения, можно с помощью вкладки Network (Сеть) инструментов разработчика Chrome:
Фрагментация данных
Полезные данные могут быть разбиты на несколько отдельных фреймов. Предполагается, что получающая сторона будет буферизовать фреймы до тех пор, пока не поступит фрейм с установленным полем заголовка fin
. В результате, например, сообщение «Hello World» можно передать в 11 фреймах, каждый из которых несёт 1 байт полезной нагрузки и 6 байтов заголовочных данных. Фрагментация управляющих пакетов запрещена. Однако, спецификация даёт возможность обрабатывать чередующиеся управляющие фреймы. Это нужно в том случае, если TCP-пакеты прибывают в произвольном порядке.
Логика объединения фреймов, в общих чертах, выглядит так:
- Принять первый фрейм.
- Запомнить значение поля
opcode
. - Принимать другие фреймы и объединять полезную нагрузку фреймов до тех пор, пока не будет получен фрейм с установленным битом
fin
. - Проверить, чтобы поле
opcode
у всех фреймов, кроме первого, было установлено в ноль.
Основная цель фрагментации заключается в том, чтобы позволить отправку сообщений, размер которых неизвестен на момент начала отправки данных.
Благодаря фрагментации сервер может подобрать буфер разумного размера, а, когда буфер заполняется, отправлять данные в сеть. Второй вариант использования фрагментации заключается в мультиплексировании, когда нежелательно, чтобы сообщение занимало весь логический канал связи. В результате для целей мультиплексирования нужно иметь возможность разбивать сообщения на более мелкие фрагменты для того, чтобы лучше организовать совместное использование канала.
О heartbeat-сообщениях
В любой момент после процедуры рукопожатия, либо клиент, либо сервер, может решить отправить другой стороне ping-сообщение. Получая такое сообщение, получатель должен отправить, как можно скорее, pong-сообщение. Это и есть heartbeat-сообщения. Их можно использовать для того, чтобы проверить, подключён ли ещё клиент к серверу.
Сообщения «ping» и «pong» — это всего лишь управляющие фреймы. У ping-сообщений поле opcode
установлено в значение 0x9
, у pong-сообщений — в 0xA
. При получении ping-сообщения, в ответ надо отправить pong-сообщение, содержащее ту же полезную нагрузку, что и ping-сообщение (для таких сообщений максимальная длина полезной нагрузки составляет 125). Кроме того, вы можете получить pong-сообщение, не отправляя перед этим ping-сообщение. Такие сообщения можно просто игнорировать.
Подобная схема обмена сообщениями может быть очень полезной. Есть службы (вроде балансировщиков нагрузки), которые останавливают простаивающие соединения.
Вдобавок, одна из сторон не может, без дополнительных усилий, узнать о том, что другая сторона завершила работу. Только при следующей отправке данных вы можете выяснить, что что-то пошло не так.
Обработка ошибок
Обрабатывать ошибки в ходе работы с WebSocket-соединениями можно, подписавшись на событие error
. Выглядит это примерно так:
var socket = new WebSocket('ws://websocket.example.com');
// Обработка ошибок.
socket.onerror = function(error) {
console.log('WebSocket Error: ' + error);
};
Закрытие соединения
Для того, чтобы закрыть соединение, либо клиент, либо сервер, должен отправить управляющий фрейм с полем opcode
, установленным в 0x8
. При получении подобного фрейма другая сторона, в ответ, отправляет фрейм закрытия соединения. Первая сторона затем закрывает соединение. Таким образом, данные, полученные после закрытия соединения, отбрасываются.
Вот как инициируют операцию закрытия WebSocket-соединения на клиенте:
// Закрыть соединение, если оно открыто.
if (socket.readyState === WebSocket.OPEN) {
socket.close();
}
Кроме того, для того, чтобы произвести очистку после завершения закрытия соединения, можно подписаться на событие close
:
// Выполнить очистку.
socket.onclose = function(event) {
console.log('Disconnected from WebSocket.');
};
Серверу нужно прослушивать событие close
для того, чтобы, при необходимости, его обработать:
connection.on('close', function(reasonCode, description) {
// Соединение закрывается.
});
Сравнение технологий WebSocket и HTTP/2
Хотя HTTP/2 предлагает множество возможностей, эта технология не может полностью заменить существующие push-технологии и потоковые способы передачи данных.
Первое, что важно знать об HTTP/2, заключается в том, что это — не замена всего, что есть в HTTP. Виды запросов, коды состояний и большинство заголовков остаются такими же, как и при использовании HTTP. Новшества HTTP/2 заключаются в повышении эффективности передачи данных по сети.
Если сравнить HTTP/2 и WebSocket, мы увидим много общих черт.
Показатель | HTTP/2 | WebSocket |
Сжатие заголовков | Да (HPACK) | Нет |
Передача бинарных данных | Да | Да (бинарные или текстовые) |
Мультиплексирование | Да | Да |
Приоритизация | Да | Нет |
Сжатие | Да | Да |
Направление | Клиент/Сервер и Server Push | Двунаправленная передача данных |
Полнодуплексный режим | Да | Да |
Как уже было сказано, HTTP/2 вводит технологию Server Push, которая позволяет серверу отправлять данные в клиентский кэш по собственной инициативе. Однако, при использовании этой технологии данные нельзя отправлять прямо в приложение. Данные, отправленные сервером по своей инициативе, обрабатывает браузер, при этом нет API, которые позволяют, например, уведомить приложение о поступлении данных с сервера и отреагировать на это событие.
Именно в подобной ситуации весьма полезной оказывается технология Server-Sent Events (SSE). SSE — это механизм, который позволяет серверу асинхронно отправлять данные клиенту после установления клиент-серверного соединения.
После соединения сервер может отправлять данные по своему усмотрению, например, когда окажется готовым к передаче очередной фрагмент данных. Этот механизм можно представить себе как одностороннюю модель издатель-подписчик. Кроме того, в рамках этой технологии существует стандартное клиентское API для JavaScript, называемое EventSource
, реализованное в большинстве современных браузеров как часть стандарта HTML5 W3C. Обратите внимание на то, что для браузеров, которые не поддерживают API EventSource, существуют полифиллы.
Так как технология SSE основана на HTTP, она отлично сочетается с HTTP/2. Её можно скомбинировать с некоторыми возможностями HTTP/2, что открывает дополнительные перспективы. А именно, HTTP/2 даёт эффективный транспортный уровень, основанный на мультиплексированных каналах, а SSE даёт приложениям API для передачи данных с сервера.
Для того, чтобы в полной мере понять возможности мультиплексирования и потоковой передачи данных, взглянем на определение IETF: «поток» — это независимая, двунаправленная последовательность фреймов, передаваемых между клиентом и сервером в рамках соединения HTTP/2. Одна из его основных характеристик заключается в том, что одно HTTP/2-соединение может содержать несколько одновременно открытых потоков, причём, любая конечная точка может обрабатывать чередующиеся фреймы из нескольких потоков.
Технология SSE основана на HTTP. Это означает, что с использованием HTTP/2 не только несколько SSE-потоков могут передавать данные в одном TCP-соединении, но то же самое может быть сделано и с комбинацией из нескольких наборов SSE-потоков (отправка данных клиенту по инициативе сервера) и нескольких запросов клиента (уходящих к серверу).
Благодаря HTTP/2 и SSE теперь имеется возможность организации двунаправленных соединений, основанных исключительно на возможностях HTTP, и имеется простое API, которое позволяет обрабатывать в клиентских приложениях данные, поступающие с серверов. Недостаточные возможности в сфере двунаправленной передачи данных часто рассматривались как основной недостаток при сравнении SSE и WebSocket. Благодаря HTTP/2 подобного недостатка больше не существует. Это открывает возможности по построению систем обмена данными между серверными и клиентскими частями приложений исключительно с использованием возможностей HTTP, без привлечения технологии WebSocket.
WebSocket и HTTP/2. Что выбрать?
Несмотря на чрезвычайно широкое распространение связки HTTP/2+SSE, технология WebSocket, совершенно определённо, не исчезнет, в основном из-за того, что она отлично освоена и из-за того, что в весьма специфических случаях у неё есть преимущества перед HTTP/2, так как она была создана для обеспечения двустороннего обмена данными с меньшей дополнительной нагрузкой на систему (например, это касается заголовков).
Предположим, вы хотите создать онлайн-игру, которая нуждается в передаче огромного количества сообщений между клиентами и сервером. В подобном случае WebSocket подойдёт гораздо лучше, чем комбинация HTTP/2 и SSE.
В целом, можно порекомендовать использование WebSocket для случаев, когда нужен по-настоящему низкий уровень задержек, приближающийся, при организации связи между клиентом и сервером, к обмену данными в реальном времени. Помните, что такой подход может потребовать переосмысления того, как строится серверная часть приложения, а также то, что тут может потребоваться обратить внимание на другие технологии, вроде очередей событий.
Если вам нужно, например, показывать пользователям в реальном времени новости или рыночные данные, или вы создаёте чат-приложение, использование связки HTTP/2+SSE даст вам эффективный двунаправленный канал связи, и, в то же время — преимущества работы с технологиями из мира HTTP. А именно, технология WebSocket нередко становится источником проблем, если рассматривать её с точки зрения совместимости с существующей веб-инфраструктурой, так как её использование предусматривает перевод HTTP-соединения на совершенно другой протокол, ничего общего с HTTP не имеющий. Кроме того, тут стоит учесть соображения масштабируемости и безопасности. Компоненты веб-систем (файрволы, средства обнаружения вторжений, балансировщики нагрузки) создают, настраивают и поддерживают с оглядкой на HTTP. В результате, если говорить об отказоустойчивости, безопасности и масштабируемости, для больших или очень важных приложений лучше подойдёт именно HTTP-среда.
Кроме того, во внимание стоит принять и поддержку технологий браузерами. Посмотрим, как с этим дела обстоят у WebSocket:
Тут всё выглядит очень даже прилично. Однако, в случае с HTTP/2 всё уже не так:
Тут можно отметить следующие особенности поддержки HTTP/2 в разных браузерах:
- Поддержка HTTP/2 только с использованием TLS (что, на самом деле, не так уж и плохо).
- Частичная поддержка в IE 11, но только в Windows 10.
- Поддержка только в OSX 10.11+ для Safari.
- Поддержка HTTP/2 только в том случае, если есть возможность пользоваться ALPN (а сервер это должен поддерживать явно).
Поддержка SSE, однако, выглядит лучше:
Не поддерживают эту технологию лишь IE/Edge. (Да, Opera Mini не поддерживает ни SSE, ни WebSocket, поэтому поддержку в этом браузере мы можем, сравнивая эти технологии, и не учитывать.) Однако, для IE/Edge существуют достойные полифиллы.
Итоги
Как видите, у технологий WebSockets и HTTP/2+SSE есть, в сравнении друг с другом, и преимущества, и недостатки. Что же всё-таки выбрать? Пожалуй, на этот вопрос поможет ответить лишь анализ конкретного проекта и всесторонний учёт его требований и особенностей. Возможно, помощь при принятии решения окажет знание того, как эти технологии используют в уже существующих проектах. Так, автор этого материала говорит, что они, в SessionStack, используют, в зависимости от ситуации, и WebSockets, и HTTP.
Когда библиотеку SessionStack интегрируют в веб-приложение, она начинает собирать и записывать все изменения DOM, события, возникающие при взаимодействии с пользователем, JS-исключения, результаты трассировки стека, неудачные сетевые запросы, отладочные сообщения, позволяя воспроизводить проблемные ситуации и наблюдать за всем, что происходит при работе пользователя с приложением. Всё это происходит в режиме реального времени и не должно влиять на производительность веб-приложения. Администратор может наблюдать за сеансом работы пользователя прямо в процессе работы этого пользователя. В этом сценарии в SessionStack решили использовать HTTP, так как двунаправленный обмен данными тут не нужен (сервер просто передаёт данные в браузер). Использование в подобной ситуации WebSocket было бы неоправданно, привело бы к усложнению поддержки и масштабирования решения. Однако, библиотека SessionStack, интегрируемая в веб-приложение, использует WebSocket, и, только если организовать обмен данными по WebSocket невозможно, переходит на HTTP.
Библиотека собирает данные в пакеты и отправляет на сервера SessionStack. В настоящее время реализуется лишь передача данных с клиента на сервер, но не наоборот, однако, некоторые возможности библиотеки, которые появятся в будущем, требуют двунаправленного обмена данными, именно поэтому здесь и используется технология WebSocket.
Уважаемые читатели! Пользовались ли вы технологиями WebSocket и HTTP/2+SSE? Если да — просим рассказать о том, какие задачи вы с их помощью решали, и о том, как вам понравилось то, что получилось.
If you’re looking to take your programming skills to another level, Treehouse offers unlimited access to a variety of courses starting at $25/month. Try our program out with a free seven-day trial today.
In this blog post we’re going to cover how to use WebSockets to create real-time web applications. Before we dive into learning about the WebSocket protocol and API I first want to spend a little time going through some of problems that face real-time web applications and how the WebSocket spec aims to solve them.
A Brief History of Real-Time Web Applications
The web was built around the idea that a client’s job is to request data from a server, and a server’s job is to fulfill those requests. This paradigm went unchallenged for a number of years but with the introduction of AJAX around 2005 many people started to explore the possibilities of making connections between a client and server bidirectional.
Web applications had grown up a lot and were now consuming more data than ever before. The biggest thing holding them back was the traditional HTTP model of client initiated transactions. To overcome this a number of different strategies were devised to allow servers to push data to the client. One of the most popular of these strategies was long-polling. This involves keeping an HTTP connection open until the server has some data to push down to the client.
The problem with all of these solutions is that they carry the overhead of HTTP. Every time you make an HTTP request a bunch of headers and cookie data are transferred to the server. This can add up to a reasonably large amount of data that needs to be transferred, which in turn increases latency. If you’re building something like a browser-based game, reducing latency is crucial to keeping things running smoothly. The worst part of this is that a lot of these headers and cookies aren’t actually needed to fulfil the client’s request.
What we really need is a way of creating a persistent, low latency connection that can support transactions initiated by either the client or server. This is exactly what WebSockets provide and in this post you are going to learn all about how to use them in your own applications.
How to Use WebSockets
WebSockets are used to provide a connection between a client and a server so that both parties can send data at any time.
The client uses a process known as the WebSocket handshake, which helps establish a connection between the server and the client. This handshake process starts when the client sends a regular HTTP request to the server. An Upgrade
header is included in this request that informs the server that the client wishes to establish a WebSocket connection.
Here is a simplified example of the initial request headers.
GET ws://websocket.example.com/ HTTP/1.1
Origin: http://example.com
Connection: Upgrade
Host: websocket.example.com
Upgrade: websocket
Note: WebSocket URLs use the ws
scheme. There is also wss
for secure WebSocket connections which is the equivalent of HTTPS
.
If the server supports the WebSocket protocol, it agrees to the upgrade and communicates this through an Upgrade
header in the response.
HTTP/1.1 101 WebSocket Protocol Handshake
Date: Wed, 16 Oct 2013 10:07:34 GMT
Connection: Upgrade
Upgrade: WebSocket
Once the handshake is complete, the initial HTTP connection is replaced by a WebSocket connection that uses the same underlying TCP/IP connection. From here, either party can start sending data.
WebSockets allows you to transfer as much data as you’d like without incurring the overhead associated with traditional HTTP requests. Data that is transferred through a WebSocket is referred to as messages. These messages consist of one or more frames that contain the data you’re sending (the payload).
Each frame is pre-fixed with 4-12 bytes of data. This is to ensure the message can be properly reconstructed when it reaches the client side. This frame-based messaging system helps to reduce the amount of non-payload data that is transferred, leading to significant reductions in latency.
Note: It’s worth noting that the client will only be notified about a new message once all of the frames have been received and the original message payload has been reconstructed.
Tutorial: Demo Using WebSockets
Building a WebSockets Demo Application
In true Treehouse style you’ll create a simple demo application that communicates with a WebSocket server. Before we dive into the details of the API you first need to set up a few files for your demo.
See the Demo Download The Code View on CodePen
Create an index.html
file and populate it with the following markup.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>WebSockets Demo</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="page-wrapper">
<h1>WebSockets Demo</h1>
<div id="status">Connecting...</div>
<ul id="messages"></ul>
<form id="message-form" action="#" method="post">
<textarea id="message" placeholder="Write your message here..." required></textarea>
<button type="submit">Send Message</button>
<button type="button" id="close">Close Connection</button>
</form>
</div>
<script src="app.js"></script>
</body>
</html>
Here you have created a few key elements that will be used in your application: a <div>
for displaying messages about the connection status; a list that will be used to display messages sent to, and received from the server; and a form for inputting messages.
The style.css
file referenced here can be found in the code resources download.
Next up create a file called app.js
and add the following JavaScript code in it.
window.onload = function() {
// Get references to elements on the page.
var form = document.getElementById('message-form');
var messageField = document.getElementById('message');
var messagesList = document.getElementById('messages');
var socketStatus = document.getElementById('status');
var closeBtn = document.getElementById('close');
// The rest of the code in this tutorial will go here...
};
Here you have created a number of variables and initialized them by fetching key elements on the page.
Creating WebSocket Connections
Okay, so now that you’ve got your demo application setup we can start learning about the WebSocket API. To begin with you are going to learn how to create a new WebSocket connection.
Creating WebSocket connections is really simple. All you have to do is call the WebSocket
constructor and pass in the URL of your server.
Copy the following code into your app.js
file to create a new WebSocket connection.
// Create a new WebSocket.
var socket = new WebSocket('ws://echo.websocket.org');
Once the connection has been established the open
event will be fired on your WebSocket instance.
For your demo application you are going to add an event listener that will update the status <div>
with a message to show the user that a connection has been established.
Add the following code to you app.js
file.
// Show a connected message when the WebSocket is opened.
socket.onopen = function(event) {
socketStatus.innerHTML = 'Connected to: ' + event.currentTarget.url;
socketStatus.className = 'open';
};
Here you also add the class open
to the status <div>
, this is purely for styling purposes.
How to Fix WebSocket Errors
You can handle any errors that occur by listening out for the error
event.
For your demo application you’re just going to add some code that will log any errors to the console.
// Handle any errors that occur.
socket.onerror = function(error) {
console.log('WebSocket Error: ' + error);
};
Sending Messages With WebSocket
To send a message through the WebSocket connection you call the send()
method on your WebSocket
instance; passing in the data you want to transfer.
socket.send(data);
You can send both text and binary data through a WebSocket.
For your demo application you need to send the contents of the textarea to the server when the form is submitted. To do this you first need to set up an event listener on the form.
Add the following code to your app.js
file.
// Send a message when the form is submitted.
form.onsubmit = function(e) {
e.preventDefault();
// Retrieve the message from the textarea.
var message = messageField.value;
// Send the message through the WebSocket.
socket.send(message);
// Add the message to the messages list.
messagesList.innerHTML += '<li class="sent"><span>Sent:</span>' + message +
'</li>';
// Clear out the message field.
messageField.value = '';
return false;
};
When the form is submitted this code will retrieve the message from the messageField
and send it through the WebSocket. The message is then added to the messagesList
and displayed on the screen. To finish up, the value of messageField
is reset ready for the user to type in a new message.
Receiving Messages With WebSocket
When a message is received the message
event is fired. This event includes a property called data
that can be used to access the contents of the message.
For the demo application you need to create an event listener that will be fired when a new message is received. Your code should then retrieve the message from the event and display it in the messagesList
.
To achieve this, copy the following code into your app.js
file.
// Handle messages sent by the server.
socket.onmessage = function(event) {
var message = event.data;
messagesList.innerHTML += '<li class="received"><span>Received:</span>' +
message + '</li>';
};
Closing Connections
Once you’re done with your WebSocket you can terminate the connection using the close()
method.
socket.close();
After the connection has been closed the browser will fire a close
event. Attaching an event listener to the close
event allows you to perform any clean up that you might need to do.
For your demo you will want to update the connection status when the connection is closed. Add the following code to your app.js
file to do this.
// Show a disconnected message when the WebSocket is closed.
socket.onclose = function(event) {
socketStatus.innerHTML = 'Disconnected from WebSocket.';
socketStatus.className = 'closed';
};
To complete the demo application you need to add an event listener that will be fired when the ‘Close Connection’ button is clicked. This should call close()
on the WebSocket.
// Close the WebSocket connection when the close button is clicked.
closeBtn.onclick = function(e) {
e.preventDefault();
// Close the WebSocket.
socket.close();
return false;
};
Your demo application is now complete!
Open index.html
in your browser and try sending some messages. You should see that the server echoes your messages back to you.
WebSocket Monitoring in Chrome Dev Tools
The developer tools in Google Chrome include a feature for monitoring traffic through a WebSocket. You can access this tool by following these steps:
- Open up the Developer Tools.
- Switch to the
Network
tab. - Click on the entry for your WebSocket connection.
- Switch to the
Frames
tab.
This tools will show you a summary of all the data sent through the connection. In my testing it didn’t seem to update automatically so you might need to switch in and out of the Frames
tab if you’re sending messages with the dev tools open.
WebSockets on the Server
In this article we have mainly focused on how to use WebSockets from a client-side perspective. If you’re looking to build your own WebSocket server there are plenty of libraries out there that can help you out. One of the most popular is socket.io, a Node.JS library that provides cross-browser fallbacks so you can confidently use WebSockets in your applications today.
Some other libraries include:
- C++: libwebsockets
- Erlang: Shirasu.ws
- Java: Jetty
- Node.JS: ws
- Ruby: em-websocket
- Python: Tornado, pywebsocket
- PHP: Ratchet, phpws
Browser Support for WebSockets
WebSockets are supported in almost all modern web browsers. The only exceptions being the Android browser and Opera Mini.
For up-to-date information on browser support check out: Can I use Web Sockets.
Final Thoughts
In this post you’ve learned about the WebSocket protocol and how to use the new API to build real-time web applications.
WebSockets represent a big step in the evolution of the internet. Just as AJAX changed the game in the mid 2000s; having the ability to open bidirectional, low latency connections enables a whole new generation of real-time web applications. Including what I hope will be some pretty awesome games!
If you’re interested in learning more about WebSockets – and browser networking in general – I recommend checking out some of the links below.
Further Reading
- High Performance Browser Networking: WebSocket
- IETF WebSocket Protocol
- WebSocket.org
- WebSockets (MDN)
- WebSocket Specification (WHATWG)
- Can I Use: Web Sockets
Как только между клиентом и сервером установлено соединение, из экземпляра Web Socket вызывается событие open . Ошибки генерируются за ошибки, которые имеют место во время общения. Это отмечается с помощью события onerror . Ошибка всегда сопровождается разрывом соединения.
Событие onerror вызывается, когда между сообщениями происходит что-то не так. За ошибкой события следует завершение соединения, которое является событием закрытия .
Хорошей практикой является постоянное информирование пользователя о непредвиденных ошибках и попытка их повторного подключения.
socket.onclose = function(event) { console.log("Error occurred."); // Inform the user about the error. var label = document.getElementById("status-label"); label.innerHTML = "Error: " + event; }
Когда дело доходит до обработки ошибок, вы должны учитывать как внутренние, так и внешние параметры.
-
Внутренние параметры включают ошибки, которые могут быть сгенерированы из-за ошибок в вашем коде или неожиданного поведения пользователя.
-
Внешние ошибки не имеют ничего общего с приложением; скорее они связаны с параметрами, которыми невозможно управлять. Наиболее важным из них является подключение к сети.
-
Любое интерактивное двунаправленное веб-приложение требует активного подключения к Интернету.
Внутренние параметры включают ошибки, которые могут быть сгенерированы из-за ошибок в вашем коде или неожиданного поведения пользователя.
Внешние ошибки не имеют ничего общего с приложением; скорее они связаны с параметрами, которыми невозможно управлять. Наиболее важным из них является подключение к сети.
Любое интерактивное двунаправленное веб-приложение требует активного подключения к Интернету.
Проверка доступности сети
Представьте, что ваши пользователи наслаждаются вашим веб-приложением, когда внезапно сетевое соединение перестает отвечать на запросы в середине их задачи. В современных собственных настольных и мобильных приложениях обычной задачей является проверка доступности сети.
Наиболее распространенный способ сделать это – просто сделать HTTP-запрос к веб-сайту, который должен быть активирован (например, http://www.google.com). Если запрос выполняется успешно, настольное или мобильное устройство знает, что существует активное подключение. Аналогично, в HTML есть XMLHttpRequest для определения доступности сети.
HTML5, тем не менее, сделал это еще проще и представил способ проверить, может ли браузер принимать веб-ответы. Это достигается с помощью навигатора объекта –
if (navigator.onLine) { alert("You are Online"); }else { alert("You are Offline"); }
Автономный режим означает, что либо устройство не подключено, либо пользователь выбрал автономный режим на панели инструментов браузера.
Вот как сообщить пользователю, что сеть недоступна, и попытаться переподключиться, когда происходит событие закрытия WebSocket.
socket.onclose = function (event) { // Connection closed. // Firstly, check the reason. if (event.code != 1000) { // Error code 1000 means that the connection was closed normally. // Try to reconnect. if (!navigator.onLine) { alert("You are offline. Please connect to the Internet and try again."); } } }
Демо для получения сообщений об ошибках
Следующая программа объясняет, как отображать сообщения об ошибках с помощью веб-сокетов –
<!DOCTYPE html> <html> <meta charset = "utf-8" /> <title>WebSocket Test</title> <script language = "javascript" type = "text/javascript"> var wsUri = "ws://echo.websocket.org/"; var output; function init() { output = document.getElementById("output"); testWebSocket(); } function testWebSocket() { websocket = new WebSocket(wsUri); websocket.onopen = function(evt) { onOpen(evt) }; websocket.onclose = function(evt) { onClose(evt) }; websocket.onerror = function(evt) { onError(evt) }; } function onOpen(evt) { writeToScreen("CONNECTED"); doSend("WebSocket rocks"); } function onClose(evt) { writeToScreen("DISCONNECTED"); } function onError(evt) { writeToScreen('<span style = "color: red;">ERROR:</span> ' + evt.data); } function doSend(message) { writeToScreen("SENT: " + message); websocket.send(message); } function writeToScreen(message) { var pre = document.createElement("p"); pre.style.wordWrap = "break-word"; pre.innerHTML = message; output.appendChild(pre); } window.addEventListener("load", init, false); </script> <h2>WebSocket Test</h2> <div id = "output"></div> </html>
Выход выглядит следующим образом –
Once a connection has been established between the client and the server, an open event is fired from the Web Socket instance. Error are generated for mistakes, which take place during the communication. It is marked with the help of onerror event. Onerror is always followed by termination of connection.
The onerror event is fired when something wrong occurs between the communications. The event onerror is followed by a connection termination, which is a close event.
A good practice is to always inform the user about the unexpected error and try to reconnect them.
socket.onclose = function(event) { console.log("Error occurred."); // Inform the user about the error. var label = document.getElementById("status-label"); label.innerHTML = "Error: " + event; }
When it comes to error handling, you have to consider both internal and external parameters.
-
Internal parameters include errors that can be generated because of the bugs in your code, or unexpected user behavior.
-
External errors have nothing to do with the application; rather, they are related to parameters, which cannot be controlled. The most important one is the network connectivity.
-
Any interactive bidirectional web application requires, well, an active Internet connection.
Checking Network Availability
Imagine that your users are enjoying your web app, when suddenly the network connection becomes unresponsive in the middle of their task. In modern native desktop and mobile applications, it is a common task to check for network availability.
The most common way of doing so is simply making an HTTP request to a website that is supposed to be up (for example, http://www.google.com). If the request succeeds, the desktop or mobile device knows there is active connectivity. Similarly, HTML has XMLHttpRequest for determining network availability.
HTML5, though, made it even easier and introduced a way to check whether the browser can accept web responses. This is achieved via the navigator object −
if (navigator.onLine) { alert("You are Online"); }else { alert("You are Offline"); }
Offline mode means that either the device is not connected or the user has selected the offline mode from browser toolbar.
Here is how to inform the user that the network is not available and try to reconnect when a WebSocket close event occurs −
socket.onclose = function (event) { // Connection closed. // Firstly, check the reason. if (event.code != 1000) { // Error code 1000 means that the connection was closed normally. // Try to reconnect. if (!navigator.onLine) { alert("You are offline. Please connect to the Internet and try again."); } } }
Demo for receiving error messages
The following program explains how to show error messages using Web Sockets −
<!DOCTYPE html> <html> <meta charset = "utf-8" /> <title>WebSocket Test</title> <script language = "javascript" type = "text/javascript"> var wsUri = "ws://echo.websocket.org/"; var output; function init() { output = document.getElementById("output"); testWebSocket(); } function testWebSocket() { websocket = new WebSocket(wsUri); websocket.onopen = function(evt) { onOpen(evt) }; websocket.onclose = function(evt) { onClose(evt) }; websocket.onerror = function(evt) { onError(evt) }; } function onOpen(evt) { writeToScreen("CONNECTED"); doSend("WebSocket rocks"); } function onClose(evt) { writeToScreen("DISCONNECTED"); } function onError(evt) { writeToScreen('<span style = "color: red;">ERROR:</span> ' + evt.data); } function doSend(message) { writeToScreen("SENT: " + message); websocket.send(message); } function writeToScreen(message) { var pre = document.createElement("p"); pre.style.wordWrap = "break-word"; pre.innerHTML = message; output.appendChild(pre); } window.addEventListener("load", init, false); </script> <h2>WebSocket Test</h2> <div id = "output"></div> </html>
The output is as follows −
От автора: на заре Интернета веб-приложения были построены на основе HTTP-запросов, запускаемых взаимодействием пользователей. С развитием технологий возникла потребность в передаче данных в реальном времени и двусторонней связи.
Это было требованием для приложений с малой задержкой, таких как:
Многопользовательские онлайн-игры;
Чат-приложения;
Обновление социальных лент в реальном времени;
JavaScript. Быстрый старт
Изучите основы JavaScript на практическом примере по созданию веб-приложения
Узнать подробнее
Табло спортивных результатов, спортивные тикеры и т.д.
Решением стали WebSockets. С из широким распространением появилось множество библиотек, упрощающих приложения. Следовательно, многие из нас начали использовать эту технологию, не зная внутреннего устройства, что приводило к неэффективности.
Поэтому в данной статье я попытаюсь охватить основные атрибуты WebSockets, чтобы уменьшить эти пробелы.
Архитектура WebSockets
По своей сути WebSockets определяет API, который устанавливает соединение сокета между клиентом и сервером. Это позволяет веб-браузеру и серверу отправлять данные в любом направлении. Кроме того, он также имеет несколько оптимизаций по сравнению с HTTP, что делает его лучше для связи в реальном времени.
Связь в реальном времени
С помощью HTTP-запросов браузер отправляет файлы cookie и другие заголовки, используя несколько сотен байтов, увеличивая накладные расходы на связь в реальном времени.
Однако с WebSockets последующие сообщения имеют небольшой размер и всего 6 байт служебных данных (2 для заголовка и 4 для значения маски).
Таким образом, WebSockets лучше подходят для передачи данных в реальном времени и особенно для приложений с малой задержкой, поскольку затраты меньше.
Подключение через WebSocket
Открыть соединение WebSocket несложно. Если вам нужно указать подпротоколы, это также можно сделать с помощью второго параметра.
// Create a new WebSocket let socketConnection = new WebSocket(‘ws://websocket.mysite.com’); // Create a new WebSocket with subprotocols let socketConnection = new WebSocket(‘ws://websocket.mysite.com’, [‘soap’, ‘xmpp’]); |
После создания сокетного соединения вы можете прикрепить к нему обработчики событий, что позволит вам узнать, когда соединение открыто, когда оно получает сообщения и когда возникает ошибка.
// When the connection is open, some data is sent to the server socketConnection.onopen = function () { connection.send(‘Hello, the socket connection is open!’); // Send a message to the server }; // Log errors socketConnection.onerror = function (error) { console.log(‘WebSocket Error ‘ + error); }; // Log messages from the server socketConnection.onmessage = function (e) { console.log(‘Server: ‘ + e.data); }; |
После установления соединения, событие onopen будет запущено в экземпляре WebSocket. И на этом действие завершено. С этого момента любая из сторон может отправлять данные в любое время. Когда WebSocket получает данные на стороне клиента, запускается событие onmessage. Событие onerror может быть использовано для обработки ошибок.
Вы можете спросить, что в этом нового? Разве мы не всегда так поступаем, создавая соединение и прослушивая сообщения?
В случае с WebSockets важно, как мы обрабатываем соединение. То, как мы обрабатываем соединение и повторные попытки подключения при ошибках, также определяет общую отказоустойчивость связи.
Повторное подключение для обеспечения отказоустойчивости
Распространенная проблема при работе с WebSockets — это разорванные соединения. Это происходит, когда клиент или сервер не отвечает. Чтобы избежать каких-либо проблем, связанных с этим, вы должны реализовать механизм для корректного закрытия соединения. Особенно если срок службы соединения WebSocket длительный, для обеспечения бесперебойной системы связи необходимо внедрять метод обновления соединений (закрывать и снова открывать соединения).
Масштабирование соединений
Поскольку для WebSockets требуется высокая доступность из-за постоянных подключений, сервер должен быть масштабируемым, чтобы при необходимости удовлетворять высокий спрос. Однако после открытия ws соединения, большую часть времени оно будет бездействовать.
Следовательно, вы можете задаться вопросом, как нам масштабировать серверную часть Websocket?
Масштабирование серверной части WebSocket — сложная задача, для которой потребуется постоянное хранилище (также известное как объединительная плата) для отслеживания подключений и доставленных сообщений, если какой-либо узел сервера выходит из строя.
Кроме того, было бы лучше реализовать стратегию горизонтального масштабирования с учетом количества открытых подключений.
JavaScript. Быстрый старт
Изучите основы JavaScript на практическом примере по созданию веб-приложения
Узнать подробнее
Поскольку большинство пользователей могут не часто повторно подключаться, улучшение масштабируемости на основе открытых подключений имеет гораздо больший смысл.
Шаблоны передачи данных
При передаче данных через веб-сокеты можно учитывать разные закономерности. Вы можете либо передать сообщение напрямую через WebSockets, либо отправить клиенту уведомление о доступности сообщения.
WebSockets для отправки уведомлений для веб-приложений
Отправка уведомлений в приложении — распространенный вариант использования WebSockets. Соединение WebSocket используется только для предупреждения браузера о наличии нового сообщения.
Как только пользователь получает уведомление и посещает страницу уведомлений, приложение может отправить HTTP-запрос для получения содержимого сообщения.
Таким образом, в этом подходе WebSocket не отправляет фактическое содержимое сообщения и используется в качестве механизма сигнализации для информирования внешнего интерфейса о доступности уведомлений.
Использование WebSockets для передачи данных в реальном времени
Для многопользовательских игр в реальном времени или приложений чата данные необходимо отправлять без задержки, так как активный пользователь всегда смотрит на экран в ожидании данных.
В этом сценарии мы можем отправлять данные сообщения напрямую через соединение WebSocket для более быстрой доставки сообщений.
Сжатие данных
Сжатие с помощью WebSockets — это не тема, которая часто обсуждается. Но если необходимо отправить большие объемы данных в режиме реального времени, полезно использовать метод сжатия.
Однако, чтобы добиться сжатия данных с помощью WebSockets, и клиент, и сервер должны согласиться с этим.
Знаете ли вы, что WebSockets предоставляет расширение для сжатия данных?
Когда клиент инициирует согласование, объявляя расширение permessage-deflate в заголовке Sec-Websocket-Extensions, сервер должен подтвердить объявленное расширение, повторив его в своем ответе.
Инициирование клиента:
GET /socket HTTP/1.1 Host: thirdparty.com Origin: http://example.com Connection: Upgrade Upgrade: websocket Sec—WebSocket—Version: 13 Sec—WebSocket—Key: dGhlIHNhbXBsZSBub25jZQ== Sec—WebSocket—Extensions: permessage—deflate |
Ответ сервера:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Access—Control—Allow—Origin: http://example.com Sec—WebSocket—Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec—WebSocket—Extensions: permessage—deflate |
Безопасность веб-сокетов
WebSockets позволяет неограниченному количеству сообщений попадать на сервер. Это может легко дать злоумышленнику доступ для выполнения DoS-атаки.
Следовательно, важно использовать механизм аутентификации для обеспечения безопасности. Одним из распространенных способов использования является использование токенов JWT, которые ускоряют проверку подписи запроса.
Кроме того, очень важно использовать wss вместо ws, который защитит туннель связи, аналогично HTTPS.
Совместимость с браузером
WebSockets имеют хорошую совместимость практически со всеми браузерами. Также, в WebSockets есть встроенная связь между источниками. Он позволяет общаться с любой стороной в любом домене. Это можно контролировать, определяя домены, с которыми сервер может взаимодействовать, что повышает безопасность.
Кроме того, популярные реализации WebSockets, такие как socket.IO (NodeJS) или SignalR (.NET), поддерживают возврат к HTTP в старых браузерах.
Заключение
Когда вам нужно более качественное соединение с малой задержкой между клиентом и сервером, WebSockets — ваш лучший вариант.
Однако интеграция WebSockets в существующую веб-инфраструктуру может вызывать затруднения, поскольку для этого требуется изменение архитектуры. Кроме того, вы также можете взглянуть на шаблон Event Sourcing, который эффективно использует WebSockets для связи.
Вы можете посмотреть демонстрацию подключения WebSocket здесь. Спасибо за внимание.
Автор: Viduni Wickramarachchi
Источник: blog.bitsrc.io
Редакция: Команда webformyself.
JavaScript. Быстрый старт
Изучите основы JavaScript на практическом примере по созданию веб-приложения
Узнать подробнее
Хотите узнать, что необходимо для создания сайта?
Посмотрите видео и узнайте пошаговый план по созданию сайта с нуля!
Смотреть