WebSocket (5 minutes to get you started)

Posted by LordTyphon on Wed, 12 Jan 2022 11:31:45 +0100

WebSocket

Background:

The HTTP protocol has a flaw: communication can only be initiated by the client. This one-way request is doomed 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, send a query to see if the server has any new information. The most typical scene is the chat room.

Polling is inefficient and a waste of resources (because the connection must be kept, or the HTTP connection is always open). Therefore, WebSocket was born in 2008 and became an international standard in 2011. All browsers already support it.

characteristic:

  1. WebSocket can be used in the browser
  2. Support two-way communication
  3. It's easy to use
  4. Once the WebSocket protocol is established, the request header consumed by mutual communication is very small

Application scenario:

  • Instant messaging

  • Multiplayer game

  • Online collaborative editing / editing

  • Instant Web application: the instant Web application uses a Web socket to display data on the client, which is continuously sent by the back-end server. In WebSocket, data is continuously pushed / transmitted to the same open connection, which is why WebSocket is faster and improves application performance. For example, in trading websites or bitcoin transactions, this is the most unstable thing. It is used to display price fluctuations. The data is continuously pushed to the client by the back-end server using the Web socket channel.

    Game applications: in game applications, you may notice that the server continues to receive data without refreshing the user interface. The user interface on the screen will refresh automatically, and there is no need to establish a new connection, so it is very helpful in WebSocket game application.

    Chat application: chat application can exchange, publish and broadcast messages between subscribers only by establishing a connection with WebSocket. It reuses the same WebSocket connection for sending and receiving messages and one-to-one message transmission.

The protocol identifier is ws (wss if encrypted) and the server URL is the URL.

1, Simple client example:

The following is an example of a web script (Click here Look at the operation results), you can basically understand at a glance.

let 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.");
};      

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

// The client connects to the server.
let ws = new WebSocket('ws://localhost:8080');

For a list of all attributes and methods of the instance object, see here.

API:

webSocket.readyState

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

  • CONNECTING: a value of 0 indicates that a connection is in progress.
  • OPEN: a value of 1 indicates that the connection is successful and communication is available.
  • CLOSING: a value of 2 indicates that the connection is CLOSING.
  • CLOSED: a value of 3 indicates that the connection has been CLOSED or the open connection failed.
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 -- specifies the callback function after 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 -- specifies the callback function after the connection is closed.
ws.onclose = function(event) {
  let code = event.code;
  let reason = event.reason;
  let wasClean = event.wasClean;
  // handle close event
};

ws.addEventListener("close", function(event) {
  let code = event.code;
  let reason = event.reason;
  let wasClean = event.wasClean;
  // handle close event
});
webSocket.onmessage -- specifies the callback function after receiving server data.
ws.onmessage = function(event) {
  let data = event.data;
  // Processing data
};

ws.addEventListener("message", function(event) {
  let data = event.data;
  // Processing data
});

Note: the server data may be text or binary data (blob object or Arraybuffer object)

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

  if(event.data instanceof ArrayBuffer){
    let buffer = event.data;
    console.log("Received arraybuffer");
  }
}
In addition to dynamically judging 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() -- send data to the server.
// Send text
ws.send('your message');

// Send Blob object
let file = document
  .querySelector('input[type="file"]')
  .files[0];
ws.send(file);

// Send ArrayBuffer object
let img = canvas_context.getImageData(0, 0, 400, 320);
let 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 -- judge whether the sending is over.
let data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
  // Send complete
} else {
  // Sending is not over yet
}
webSocket.onerror -- specifies the callback function when an error is reported.
socket.onerror = function(event) {
  // handle error event
};

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

2, Server

WebSocket server implementation, you can view Wikipedia list.

There are three common Node implementations.

Please check their documentation for specific usage

3, Problem analysis

1. How to judge online and offline?

When the client sends the request to the server for the first time, it will carry the unique ID and time stamp. The server will query the unique ID of the change request in db or cache. If it does not exist, it will be stored in db or cache,

The second time the client sends the request again regularly, it still carries the unique ID and time stamp. The server goes to db or cache to query the unique ID of the change request. If it exists, it takes out the last time stamp and subtracts the last time with the current time stamp,

Judge whether the obtained milliseconds are greater than the specified time. If less than, it is online, otherwise it is offline;

2. How to solve the disconnection problem

One is to modify nginx configuration information, and the other is to send heartbeat packets through websocket

Reason for websocket disconnection:

① websocket timeout no message automatically disconnects

At this time, we need to know the timeout length set by the server and send heartbeat packets within the timeout time. There are two schemes: one is that the client actively sends uplink heartbeat packets, and the other is that the server actively sends downlink heartbeat packets.

The reason why the hop packet is called heartbeat packet is that it sends it every fixed time like heartbeat to tell the server that the client is still alive. In fact, this is to maintain a long connection. There are no special provisions on the contents of this package, but it is generally a small package or an empty package containing only the header.

In the TCP mechanism, there is a heartbeat packet mechanism, that is, the TCP option: SO_KEEPALIVE. The system defaults to the set heartbeat rate of 2 hours. But it can't check the machine power failure, network cable unplugging and firewall disconnection. Moreover, the logical layer may not be easy to handle disconnection. In general, it is OK if it is only used to keep alive.

Heartbeat packets are generally implemented by sending empty echo packets at the logic layer. The next timer sends an empty packet to the client at a certain time interval, and then the client feeds back the same empty packet. If the server cannot receive the feedback packet sent by the client within a certain time, it can only be determined that the line is disconnected.

In a long connection, there may be no data exchange for a long time. Theoretically, the connection is always connected, but in practice, it is difficult to know if the intermediate node fails. What's more, some nodes (firewalls) will automatically disconnect the connection without data interaction within a certain period of time. At this time, we need our heartbeat package to maintain a long connection and keep alive.

Heartbeat detection steps:

  1. The client sends a probe packet to the server every other time interval
  2. Start a timeout timer when the client contracts
  3. The server receives a detection packet and should respond to a packet
  4. If the client receives the response packet from the server, the server is normal and the timeout timer is deleted
  5. If the timeout timer of the client times out and still does not receive the response packet, the server hangs
// Front end solution: heartbeat detection
let heartCheck = {
    timeout: 30000, //A heartbeat in 30 seconds
    timeoutObj: null,
    serverTimeoutObj: null,
    reset: function(){
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
        return this;
    },
    start: function(){
        var self = this;
        this.timeoutObj = setTimeout(function(){
            //A heartbeat is sent here. After receiving it, the backend returns a heartbeat message,
            //onmessage gets the returned heartbeat, indicating that the connection is normal
            ws.send("ping");
            console.log("ping!")

            self.serverTimeoutObj = setTimeout(function(){//If it has not been reset after a certain period of time, it indicates that the back-end is actively disconnected
                ws.close(); //If onclose will execute reconnect, we will execute WS Just close () If you directly execute reconnect, onclose will be triggered, resulting in reconnection twice
            }, self.timeout);
        }, this.timeout);
    }
}
② websocket exceptions include service interruption, interactive screen cutting and other client-side exceptions

The interrupt solution to this exception is to handle reconnection

Use js library to process: introduce reconnecting websocket Min.js, ws link establishment method uses JS library api method:

let ws = new ReconnectingWebSocket(url);
// Disconnection and reconnection:
reconnectSocket(){
    if ('ws' in window) {
        ws = new ReconnectingWebSocket(url);
    } else if ('MozWebSocket' in window) {
       ws = new MozWebSocket(url);
    } else {
      ws = new SockJS(url);
    }
}

Disconnection monitoring supports the use of JS Library: offline min.js

onLineCheck(){
    Offline.check();
    console.log(Offline.state,'---Offline.state');
    console.log(this.socketStatus,'---this.socketStatus');

    if(!this.socketStatus){
        console.log('Network connection disconnected!');
        if(Offline.state === 'up' && websocket.reconnectAttempts > websocket.maxReconnectInterval){
            window.location.reload();
        }
        reconnectSocket();
    }else{
        console.log('Network connection succeeded!');
        websocket.send("heartBeat");
    }
}

// Use: call network interrupt monitoring when websocket disconnects
websocket.onclose => () {
    onLineCheck();
};
Summary:
  • WebSocket is a protocol for dual channel communication on web applications. Compared with polling HTTP requests, WebSocket has the advantages of saving server resources and high efficiency.

  • The mask in WebSocket is set to prevent intermediate cache pollution attacks in earlier versions. The client needs a mask to send data to the server, and the server does not need a mask to send data to the client.

  • The generation algorithm of SEC WebSocket key in WebSocket is to splice the strings generated by the server and the client, perform SHA1 hash algorithm, and then encode it with base64.

  • The WebSocket protocol handshake relies on the HTTP protocol and the HTTP response 101 for protocol upgrade and conversion.

Topics: Front-end server Network Protocol websocket