An overview of the

Historically, creating bi-directional communication between Web application clients and servers (for example, instant messaging and gaming applications) has required the abuse of HTTP to poll the server to send updates.

Problems caused by:

  • The server creates a number of different base TCP for each client:
    • A connection that sends information to a client
    • Add a new client for each incoming message
  • The overhead of wired protocols is high, with each client to server having HTTP headers
  • From sending the request to responding to the reply, the client needs to track the reply

Solutions:

The use of a single TCP connection for two-way communication is known as the WebSocket protocol (in order to be able to handle both HTTP and WebSocket requests on the same port, WebSocket uses the Upgrade field)

Features & Advantages:

Both client and server can actively push messages, which can be text or binary data. And there is no same-origin policy restriction, there is no cross-domain problem.

Relationship to HTTP

  • The WebSocket protocol is an independent TCP-based protocol. Its only relationship to HTTP is that its handshake is made by the HTTP server as an upgrade request
  • After a successful handshake establishes a webSocket connection, communication uses websocket-independent data frames instead of HTTP data frames.

This section describes the common apis of websocket

Developer.mozilla.org/en-US/docs/…

Protocol implementation

References: https://tools.ietf.org/html/rfc6455

The handshake from the client looks as follows:

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Origin: http://example.com
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
Copy the code

The handshake from the server looks as follows:

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    Sec-WebSocket-Protocol: chat
Copy the code

Field Meaning Resolution

  • Sec-websocket-key: specifies the Key required for handshake.
  • Sec-websocket-protocol: indicates the subprotocol used
  • Upgrade: websocket (used to check whether HTTP and other protocols are available and tell the server to use wobsocket for communication)
  • (With Upgrade, you need to specify Connection:Upgrade)
  • For requests with the Upgrade header field, the server returns the 101 Switching Protocols status code as a response
  • Sec-websocket-accept: the value is generated by sec-websocket-key and verifies the handshake value

These fields are checked by the WebSocket client on the script page. If the SEC-Websocket-Accept value does not match expectations, or the response header field is missing, or the HTTP status code is not 101, the connection will not be established, and the WebSocket frame will not be sent.

Handshake. Correct return of response

If the server chooses to accept the incoming connection, it MUST reply with a valid HTTP response indicating the following.

  1. A status-line with A 101 response code as per RFC2616 [RFC2616]. Such A response could look like “HTTP/1.1 101 Switching Protocols”.
  2. An |Upgrade| header field with value “websocket” as per RFC 2616 [RFC2616].
  3. A |Connection| header field with value “Upgrade”.
  4. A |Sec-WebSocket-Accept| header field. The value of this header field is constructed by concatenating /key/, defined above in step 4 in Section 4.2.2, with the string “258EAFA5- E914-47DA-95CA-C5AB0DC85B11”, taking the SHA-1 hash of this concatenated value to obtain a 20-byte value and base64- encoding (see Section 4 of [RFC4648]) this 20-byte hash.

The response must contain the following

  • HTTP / 1.1 101 Switching separate Protocols
  • Upgrade: websocket
  • Connection: Upgrade
  • Sec-WebSocket-Accept:XXXXXX

Sec-websocket-accept calculation rule

Select sec-websocket-key, connect global unique identifier 258eAFa5-E914-47DA-95CA-C5AB0DC85b11, hash it sha-1, and encode it base64. Write the resulting value to the response field sec-websocket-accept

const crypto = require('crypto');
const key=crypto.createHash('sha1').update('NlkoAtcpQlENt8+gHHx1Aw==258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64'); console.log(key); //JBGobEpXbLx2POeZo0zcho4iZ1c=Copy the code

The server must prove to the client that it has received the client’s WebSocket handshake so that the server does not accept connections that are not WebSocket connections.

server-1.js

const net = require('net');
const crypto = require('crypto'); Net.createserver ((socket)=>{// Start transmitting data after TCP handshake const header = {}; socket.once('data',(data)=>{
    let tmpHeader = data.toString().split('\r\n');
    tmpHeader.shift();
    tmpHeader.forEach(item=>{
      if(item){
        let index = item.indexOf(':'); const key = item.substr(0,index); const value = item.substr(index+1); header[key.trim().toLocaleLowerCase()] = value.trim(); }})if(header.upgrade.toLocaleLowerCase()==='websocket'&&header['sec-websocket-version'] = ='13'){
      const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
      const socketkey = header['sec-websocket-key']
      const hash = crypto.createHash('sha1') // Create a hash object with the signature algorithm sha1 hash.update('${socketkey}${GUID}'// Connect key to GUID, update tohash
      const result = hash.digest('base64'// Base64 const responseHeader = 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-Websocket-Accept:${result}\r\n\r\n`;
      socket.write(responseHeader)
      //server-2.js
    }
  })
}).listen(8083);
Copy the code

[Code] server-1.html

let ws = new WebSocket("ws://localhost:8083");

The data processing

After a successful connection, data is sent from the browser and the received data is printed on the server.

  • server-1.js
     socket.write(responseHeader)
     //server-2.js 
     socket.on('data',(d)=>{
      console.log(d);
     })
Copy the code
  • server-1.html
let ws = new WebSocket("ws://localhost:8083"); 
  ws.onopen = function() {setTimeout(() => { 
      ws.send('Hello, client.');
   }, 2000);
 }
Copy the code

The server received the following data

and we need to parse the data

www.bslxx.com/a/mianshiti…

Analytical data

function decodeWsFrame(data) {
  let start = 0;
  letFrame = {isFinal: (data[start] &0x80) === 0x80, opcode: data[start++] &0xf, and marshals-masked: (data[start] & 0x80) === 0x80, payloadLen: data[start++] & 0x7F,// maskingKey:' ',
    payloadData: null
  };
// if 0-125, that is the  payload length
// If 126, the following 2 bytes interpreted as a 16-bit unsigned integer are the payload length
// If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the most significant bit MUST be 0) are the payload length.
  if(frame.payloadLen === 126) {// If equal to 126, the next two bytes are the length //data[3] + data[4]; frame.payloadLen = (data[start++] << 8) + data[start++]; }else if(frame.payloadLen === 127) {// If equal to 127 the next 8 bytes are the length frame.payloadLen = 0;for (leti = 7; i >= 0; --i) { frame.payloadLen += (data[start++] << (i * 8)); }}if (frame.payloadLen) {
    if (frame.masked) {
      const maskingKey = [
        data[start++],
        data[start++],
        data[start++],
        data[start++]
      ];

      frame.maskingKey = maskingKey;
      // Octet i of the transformed data ("transformed-octet-i") is the XOR of octet i of the 
      //original data ("original-octet-i") with octet at indexi modulo 4 of the masking key ("masking-key-octet-j") : // j = i MOD 4 //transformed-octet-i = original-octet-i XOR masking-key-octet-j frame.payloadData = data .slice(start, Start + frame.payloadLen)// The length of intercepted data is in bytes. Map ((byte, IDx) => byte ^ maskingKey[IDx % 4]); / / decryption}else{ frame.payloadData = data.slice(start, start + frame.payloadLen); // No decryption required}}return frame;
}

Copy the code

Data encoding

functionEncodeWsFrame (data) {const isFinal = data.isfinal! == undefined ? data.isFinal :true, opcode = data.opcode ! == undefined ? data.opcode : 1, payloadData = data.payloadData ? Buffer.from(data.payloadData) : null, payloadLen = payloadData ? payloadData.length : 0;let frame = [];

  if (isFinal) frame.push((1 << 7) + opcode);
  else frame.push(opcode);

  if (payloadLen < 126) {
    frame.push(payloadLen);
  } else if (payloadLen < 65536) {// 127*2^8 + 127
    frame.push(126, payloadLen >> 8, payloadLen & 0xFF);
  } else {
    frame.push(127);
    for (let i = 7; i >= 0; --i) {
      frame.push((payloadLen & (0xFF << (i * 8))) >> (i * 8));
    }
  }

  frame = payloadData ? Buffer.concat([Buffer.from(frame), payloadData]) : Buffer.from(frame);
  return frame;
}

Copy the code

Comprehensive practice, simple chat room

The resources

  • Tools.ietf.org/html/rfc645…
  • Developer.mozilla.org/zh-CN/docs/…
  • Blog.csdn.net/chencl1986/…