WebSocket Guide

WebSocket Guide

WebSocket is a network communication protocol that is required for many advanced functions.

Anyone who is new to WebSockets asks 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: communication can only be initiated by the client. For example, if we want to know today’s weather, the client can only send a request to the server, and the server returns the query result. The HTTP protocol cannot allow 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 once in a while, a query is sent to see if the server has new information. The most typical scenario is the chat room.

Polling is inefficient and wastes resources (because you have to keep connecting, or the HTTP connection is always open). Therefore, engineers have been thinking, is there a better way. That’s how WebSocket was invented.

Introduction

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

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 real two-way equal dialogue and belongs to a kind 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 have to wait for the other party to reply; WebSocket is like making a phone 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 the 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 shield during the handshake, and it 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) Text can be sent, and binary data can also be sent.

(5) There is no same-origin restriction, the client can communicate with any server, and it can completely replace Ajax.

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

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

WebSocket handshake

The WebSocket handshake request from the browser looks like 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 headers above, one of the HTTP headers is Upgrade. The HTTP1.1 protocol specifies 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 means 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 may also not verify). Sec-WebSocket-Key is the key used for the handshake protocol, which is a Base64-encoded 16-byte random string.

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 after the Sec-WebSocket-Key string provided by the browser, and the server adds the “258EAFA5” specified by the RFC6456 standard. -E914-47DA-95CA-C5AB0DC85B11” string, and then take the SHA-1 hash value. The browser will validate this value to prove that it is indeed the target server that responded to the WebSocket request. The Sec-WebSocket-Location field represents the WebSocket URL to communicate with.

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

Simple example of client

The usage of WebSocket is fairly simple.

The following is an example of a web script, which is basically understandable 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 processing of the WebSocket protocol is nothing more than three things.

  • Make connections and disconnects
  • Send data and receive data
  • handle errors

Constructor WebSocket

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

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

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

webSocket.readyState

The readyState property returns the current state of the instance object, there are four kinds.

  • CONNECTING: A value of 0 means connecting.
  • OPEN: The value is 1, indicating that the connection is successful and communication is possible.
  • CLOSING: A value of 2 means the connection is closing.
  • CLOSED: The value is 3, indicating that the connection has been closed or failed to open the connection.

Below 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 the connection is successful.

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 property 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 property 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 server data may be text or binary data (blob objects or Arraybuffer objects).

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);
};

// Received 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 an 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 property 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 various server implementations, see Wikipedia’s list.

There are three commonly used Node implementations.

Please check their documentation for specific usage, which is not covered in detail in this tutorial.

Reference link