WebSocket

WebSocket is a network communication protocol, and many advanced functions require it.

People who are new to WebSocket will ask the same question: We already have the HTTP protocol, why do we need another protocol? What benefits can it bring?

The answer is simple, because the HTTP protocol has a flaw: the communication can only be initiated by the client. For example, if we want to know today's weather, we can only ask the client to send a request to the server, and the server returns the query result. The HTTP protocol cannot enable the server to actively push information to the client. This one-way request feature of the HTTP protocol is destined to be very troublesome for the client to know if the server has continuous state changes. We can only use "polling": every time a query is sent to find out if the server has new information. The most typical scenario is a chat room.

Polling is inefficient and wastes resources (because the connection must be kept connected or the HTTP connection is always open). Therefore, engineers have been thinking about whether there is a better way. This is how WebSocket was invented.

Introduction

The WebSocket protocol was born in 2008 and became an international standard in 2011. All browsers are already supported.

Its biggest feature is that the server can actively push information to the client, and the client can also actively send information to the server. It is a true two-way equal dialogue and belongs to a type of server push technology. WebSocket allows full-duplex communication between the server and the client. For example, the HTTP protocol is a bit like sending an email, and you must wait for the other party to reply after sending it; WebSocket is like making a call. The server and the client can send data to each other at the same time, and there is a continuously open data channel between them.

Other features include:

(1) Based on the TCP protocol, the server-side implementation is relatively easy.

(2) It has good compatibility with HTTP protocol. The default ports are also 80 and 443, and the HTTP protocol is used in the handshake phase, so it is not easy to block during the handshake and can pass through various HTTP proxy servers.

(3) The data format is relatively lightweight, the performance overhead is small, and the communication is efficient.

(4) You can send text or binary data.

(5) There is no homology restriction, the client can communicate with any server, which can completely replace Ajax.

(6) The protocol identifier is ws (if encrypted, it is wss, corresponding to the HTTPS protocol), and the server website is the URL.

ws://example.com:80/some/path

WebSocket handshake

The WebSocket handshake request sent by the browser is similar to the following:

GET / HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Host: example.com
Origin: null
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

Among the above header information, one of the HTTP headers is Upgrade. The HTTP1.1 protocol stipulates that the Upgrade field indicates that the communication protocol is changed from HTTP/1.1 to the protocol specified by this field. The Connection field indicates that the browser informs the server, if possible, to upgrade to the WebSocket protocol. The Origin field is used to provide the domain name of the request for the server to verify whether it is within the permitted range (the server can also not verify). Sec-WebSocket-Key is the key used for the handshake protocol, which is a 16-byte random string of Base64 encoding.

The server's WebSocket response is as follows.

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Origin: null
Sec-WebSocket-Location: ws://example.com/

In the above code, the server also uses the Connection field to notify the browser that the protocol needs to be changed. The Sec-WebSocket-Accept field is that the server appends the string "Sec-WebSocket-Key" provided by the browser to RFC6456 standard "258EAFA5 -E914-47DA-95CA-C5AB0DC85B11" string, and then take the SHA-1 hash value. The browser will verify this value to prove that the target server actually responded to the WebSocket request. The Sec-WebSocket-Location field indicates the WebSocket URL for communication.

After the handshake is completed, the WebSocket protocol is on top of the TCP protocol and begins to transmit data.

Simple example of the client

The usage of WebSocket is quite simple.

The following is an example of a web script, which can basically be understood at a glance.

var ws = new WebSocket("wss: //echo.websocket.org");

ws.onopen = function (evt) {
  console.log("Connection open ...");
  ws.send("Hello WebSockets!");
};

ws.onmessage = function (evt) {
  console.log("Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function (evt) {
  console.log("Connection closed.");
};

Client API

The browser's handling of the WebSocket protocol is nothing more than three things.

  • Establish and disconnect
  • Send data and receive data
  • Handling errors

Constructor WebSocket

The WebSocket object is used as a constructor to create a new instance of WebSocket.

var ws = new WebSocket("ws://localhost:8080");

After executing the above statement, the client will connect with the server.

webSocket.readyState

The readyState property returns the current state of the instance object. There are four types.

  • CONNECTING: The value is 0, which means it is connecting.
  • OPEN: The value is 1, which means the connection is successful and you can communicate.
  • CLOSING: The value is 2, which means the connection is being closed.
  • CLOSED: The value is 3, which means the connection has been closed, or the connection has failed to open.

The following is an example.

switch (ws.readyState) {
  case WebSocket.CONNECTING:
    // do something
    break;
  case WebSocket.OPEN:
    // do something
    break;
  case WebSocket.CLOSING:
    // do something
    break;
  case WebSocket.CLOSED:
    // do something
    break;
  default:
    // this never happens
    break;
}

webSocket.onopen

The onopen property of the instance object is used to specify the callback function after a successful connection.

ws.onopen = function () {
  ws.send("Hello Server!");
};

If you want to specify multiple callback functions, you can use the addEventListener method.

ws.addEventListener("open", function (event) {
  ws.send("Hello Server!");
});

webSocket.onclose

The onclose attribute of the instance object is used to specify the callback function after the connection is closed.

ws.onclose = function (event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
};

ws.addEventListener("close", function (event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
});

webSocket.onmessage

The onmessage attribute of the instance object is used to specify the callback function after receiving the server data.

ws.onmessage = function (event) {
  var data = event.data;
  // Data processing
};

ws.addEventListener("message", function (event) {
  var data = event.data;
  // Data processing
});

Note that the server data may be text or binary data (a blob object or an Arraybuffer object).

ws.onmessage = function(event){
  if(typeOf event.data === String) {
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}

In addition to dynamically determining the received data type, you can also use the binaryType attribute to explicitly specify the received binary data type.

// received blob data
ws.binaryType = "blob";
ws.onmessage = function (e) {
  console.log(e.data.size);
};

// The received is ArrayBuffer data
ws.binaryType = "arraybuffer";
ws.onmessage = function (e) {
  console.log(e.data.byteLength);
};

webSocket.send()

The send() method of the instance object is used to send data to the server.

Example of sending text.

ws.send("your message");

Example of sending a Blob object.

var file = document.querySelector('input[type="file"]').files[0];
ws.send(file);

Example of sending ArrayBuffer object.

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
ws.send(binary.buffer);

webSocket.bufferedAmount

The bufferedAmount property of the instance object indicates how many bytes of binary data have not been sent. It can be used to determine whether the transmission is over.

var data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
  // Sent
} else {
  // Sending is not over yet
}

webSocket.onerror

The onerror attribute of the instance object is used to specify the callback function when an error is reported.

socket.onerror = function (event) {
  // handle error event
};

socket.addEventListener("error", function (event) {
  // handle error event
});

WebSocket server

The WebSocket protocol requires server support. For the implementation of various servers, you can check Wikipedia's list.

There are three commonly used Node implementations as follows.

For specific usage, please check their documents, this tutorial will not introduce them in detail.