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.
- A status-line with A 101 response code as per RFC2616 [RFC2616]. Such A response could look like “HTTP/1.1 101 Switching Protocols”.
- An |Upgrade| header field with value “websocket” as per RFC 2616 [RFC2616].
- A |Connection| header field with value “Upgrade”.
- 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/…