preface

In the communication between browser and server, the traditional HTTP request is not ideal in some scenarios, such as real-time chat, real-time mini-games, etc., it faces two main disadvantages:

  • Unable to achieve the “real-time” message;
  • The server cannot actively push information;

Its main HTTP-based solutions are:

  • Ajax-based polling: the client constantly requests the interface to the server at regular or dynamic intervals, asking whether the server has new information; Its disadvantages are also obvious: redundant empty requests (waste of resources), data acquisition delay;
  • Long Poll: it adopts the obstructive scheme. The client initiates an Ajax request to the server, and the server suspends the request without returning data until there is new data. After receiving the data, the client performs Long Poll again. In this scenario, every request suspends the server resource, which is unacceptable in the case of a large number of connections;

It can be seen that all schemes based on HTTP protocol contain an essential defect — “passive”, the server cannot push down the message, only the client can initiate a request to continuously ask whether there is a new message, and there is a performance consumption for both the client and the server.

WebSocket is a network technology of full duplex communication between browser and server provided by HTML5. The WebSocket communication protocol was standardized by THE IETF in 2011 as RFC 6455, and the WebSocketAPI was standardized by the W3C. In the WebSocket API, the browser and the server only need to do a handshake, and then a fast channel is formed between the browser and the server. Data can be transmitted directly between the two.

WebSocket is a new network protocol standard proposed in HTML5, which contains several features:

  • Application layer based on TCP protocol;
  • Once a connection is established (until disconnection or error), the server and the client shake hands and keep the connection state, is a persistent connection;
  • The server can proactively deliver messages through the real-time channel.
  • “Real-time (relative)” and “timing” of data reception;

practice

Using Websocket in a browser is very simple. Browsers that support Websocket provide native WebSocekt objects, where message reception and data frame processing are encapsulated in the browser. Here is a simple example to explain how to use WebSocekt;

Server-side implementation

Of course, using Websocket requires both the server and client to provide corresponding capabilities. Here, based on Node.js and WS, we simply establish a server Websocket interface:

const express = require('express');
const WebSocket = require('ws');
const http = require('http');

const app = express();

app.get('/', function (req, res) {
  res.sendfile('./index.html');
});

const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

wss.on('connection', function connection(ws, req) {
  ws.on('message', function incoming(message) {
    ws.send('received: ' + message + '(From Server)');
  });

  ws.send('Hello Client');
});

server.listen(8080, function listening() {
  console.log('Listening on %d', server.address().port);
});
Copy the code

Express and Websocket requests are listened for on port 8080 because their protocols (HTTP (S):// and WS (S)://) are different and therefore do not conflict. You can also see in the code that it first listens for a Connection event (triggered by establishing a connection), listens for a Message event (received a message) in its callback and immediately sends a piece of data.

Browser implementation

Websocket API

The native WebSocket class is provided in the browser, instantiated with the new keyword:

WebSocket WebSocket(String url,optional String | [] protocols);
Copy the code

Receives two parameters:

  • Url indicates the address to connect to, for example: ws://localhost:8080;
  • Protocols Optional parameter, which can be a string or an array, to represent subprotocols. This allows a server to implement multiple WebSocket subprotocols.

Instantiating an object provides two methods:

  • The send receive a String | ArrayBuffer | Blob data, as the data sent to the server.
  • Close Receives an (optional) code (shutdown status number, default 1000) and an (optional) string (indicating the reason for the disconnection), and the client disconnects voluntarily;

Connection status:

  • The WebSocket class provides constants to indicate connection status:
    • Websocket. CONNECTING 0 The connection is not started.
    • Websocket.open 1 The connection is OPEN and ready to communicate;
    • WebSocket.CLOSING 3 Connection in process of CLOSING;
    • Websocket. CLOSED 4 The connection is CLOSED or cannot be established.
  • The readyState property is provided in the WebSocket instance object to determine the current state;

The following events can be heard in the instantiated object:

  • Open The callback event to open the connection, when readyState becomes open;
  • Message receives a callback event for the message, and the callback function receives a MessageEvent data.
  • Close The connection CLOSED callback event, when readyState becomes CLOSED;
  • Error An error callback event occurred during the establishment and connection process.

Code implementation

const ws = new WebSocket('ws://localhost:8080'); let sendTimmer = null; let sendCount = 0; ws.onopen = function () { console.log('@open'); sendCount++; ws.send('Hello Server! ' + sendCount); sendTimmer = setInterval(function () { sendCount++; ws.send('Hi Server! ' + sendCount); if (sendCount === 10) { ws.close(); }}, 2000); }; ws.onmessage = function (e) { console.log('@message'); console.log(e.data); }; ws.onclose = function () { console.log('@close'); sendTimmer && clearInterval(sendTimmer); }; ws.onerror = function () { console.log('@error'); };Copy the code

You can see from the console:

@open @message Hello Client @message received: Hello Server! 1(From Server) @message received: Hi Server! 2(From Server) @message received: Hi Server! 3(From Server) @message received: Hi Server! 4(From Server) @message received: Hi Server! 5(From Server) @message received: Hi Server! 6(From Server) @message received: Hi Server! 7(From Server) @message received: Hi Server! 8(From Server) @message received: Hi Server! 9(From Server) @closeCopy the code

The open event will be triggered first, and the server will reply every time after sending data, so the Message event will be triggered. After sending data for 10 times, the browser will disconnect, so the close event will be triggered. There is no reply from the server after the last send because the client immediately disconnects; Of course, more specific data interaction can be seen from network;

Events and Data

There are two ways to listen for events on WebSocket instances, using message events as an example:

  • Assign the onMessage property directly, as above:ws.onmessage = function () {};;
  • Use addEventListener to listen for events such as:ws.addEventListener('message', function () {});

In the message callback function, we get the MessageEvent type parameter E. The data we need can be obtained by e.data. To note is that regardless of the client and the server, the received data is serialized string (or ArrayBuffer | Blob data), many times we need to parse processing data, such as JSON, parse (e.d ata);

Connection stability

Due to the complex network environment, disconnection or connection error may occur in some cases, so we need to listen for abnormal disconnection and reconnection in close or error events.

For some reason the browser does not respond to callback events during error, so it is safe to start a timed task after open to determine the current connection state readyState and try to reconnect in case of an exception.

The heartbeat

The WebSocket specification defines the heartbeat mechanism by which one party can send a ping (opCode 0x9) message to the other party, and the other party should return Pong (0xA) as soon as possible.

The heartbeat mechanism is used to detect the online status of the connected party. Therefore, if there is no heartbeat, there is no way to determine the connection status of the party. Some network layers such as Nginx or browser layer will actively disconnect the connection.

In JavaScript, WebSocket does not open the PING /pong API, although the browser has its own heartbeat processing, but different vendors implement different, so we need to agree with the server in the development of a self-implemented heartbeat mechanism; For example, when the browser detects an open event, it starts a scheduled task that sends 0x9 to the server each time, and the server returns 0xA as a response. In practice, heartbeat timing tasks are usually sent at intervals of 15-20 seconds.


Network protocol

Websocket is built on top of TCP, so how does it relate to HTTP?

Websocket connection is divided into connection establishment stage and connection stage. In the connection establishment stage, HTTP is used, while in the connection stage, HTTP has nothing to do with.

Built the stage

From the browser’s Network, find the WS connection and see:

General Request URL:ws://localhost:8080/ Request Method:GET Status Code:101 Switching Protocols Response Headers HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade SEC-websocket-accept: Py9bt3HbjicUUmFWJfI0nhGombo = Request Headers GET ws: / / localhost: 8080 / HTTP / 1.1 Host: localhost: 8080 Connection: Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket Origin: http://localhost:8080 sec-websocket-version: 13 User-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36 DNT: 1 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh; Q = 0.9, en. Q = 0.8, useful - TW; Q = 0.7, la; Q = 0.6, ja. Q =0.5 sec-websocket-key: 2idFk3+96Hs5hh+c9GOQCg== sec-websocket-extensions: permessage-deflate; client_max_window_bitsCopy the code

This is a standard HTTP request. There are several fields in the request header compared to our common HTTP request protocol:

Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: 2idFk3+96Hs5hh+c9GOQCg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Copy the code

“Connection” is “Upgrade”, “Upgrade” is “websocket”, indicating that Nginx and Apache servers are informed that this Connection is not an HTTP Connection, but actually a websocket. Therefore, the server will forward the corresponding WebSocket task processing;

Sec-websocket-key is a Base64 encode value randomly generated by the browser to verify that the server connection is correct; Sec-websocket-versio indicates the version of the WebSocket service used.

In the response header:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: py9bt3HbjicUUmFWJfI0nhGombo=
Copy the code

It can be seen that the return status code is 101, indicating protocol switchover. If Upgrade and Connection are used to reply to the client, the protocol has been successfully switched. The SEC-websocket-Accept field corresponds to the sec-websocket-key field and is used to verify the correctness of the service.

Connection phase

After the handshake is established over HTTP, the real Websocket connection is followed, which sends and receives data over TCP, and Websocket encapsulates and opens the interface.

WSS

In HTTP protocol, HTTPS request (HTTP + TCL) is often used for encryption and security. Accordingly, in the Websocket protocol, encrypted transport can also be used — WSS, such as WSS ://localhost:8080.

It uses the same certificate as HTTPS, which is typically handled by a service layer such as Nginx.