Nodejs原生实现WebSocket服务
这段Demo展示了如何使用Nodejs原生实现WebSocket服务。
加密和data处理的算法。
import { createHash } from 'node:crypto';
/* 对Sec-Websocket-Key进行加密,作为Sec-Websocket-Accept的值 */
export function encryptSocketKey(key) {
const WEBSOCKET_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
const sha1Hash = createHash('sha1');
sha1Hash.update(`${ key }${ WEBSOCKET_GUID }`);
return sha1Hash.digest('base64');
}
/* 处理接收到的数据 */
export function decodeWsFrame(data) {
let start = 0;
const frame = {
isFinal: (data[start] & 0x80) === 0x80,
opcode: data[start++] & 0xF,
masked: (data[start] & 0x80) === 0x80,
payloadLen: data[start++] & 0x7F,
maskingKey: '',
payloadData: null
};
if (frame.payloadLen === 126) {
frame.payloadLen = (data[start++] << 8) + data[start++];
} else if (frame.payloadLen === 127) {
frame.payloadLen = 0;
for (let i = 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;
frame.payloadData = data
.subarray(start, start + frame.payloadLen)
.map((byte, index) => byte ^ maskingKey[index % 4]);
} else {
frame.payloadData = data.slice(start, start + frame.payloadLen);
}
}
return frame;
}
/* 处理发送的数据 */
export function encodeWsFrame(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;
const frame = [];
if (isFinal) {
frame.push((1 << 7) + opcode);
} else {
frame.push(opcode);
}
if (payloadLen < 126) {
frame.push(payloadLen);
} else if (payloadLen < 65536) {
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));
}
}
return payloadData
? Buffer.concat([Buffer.from(frame), payloadData])
: Buffer.from(frame);
}
export function rStr(len: number): string {
const str: string = 'QWERTYUIOPASDFGHJKLZXCVBNM1234567890';
let result: string = '';
for (let i: number = 0; i < len; i++) {
const rIndex: number = Math.floor(Math.random() * str.length);
result += str[rIndex];
}
return result;
}
服务端的实现。
import net from 'node:net';
import { encodeWsFrame, decodeWsFrame, encryptSocketKey } from './utils.js';
/* 建立请求 */
function createConnect(socket) {
return new Promise((resolve, reject) => {
socket.once('data', function(buffer) {
const data = buffer.toString();
const headers = data.split('\r\n')
.splice(1)
.filter((o) => o !== '')
.reduce((result, header, index) => {
const [key, value] = header.split(': ');
result[key.toLowerCase()] = value;
return result;
}, {});
// 判断是否为websocket并检查版本
if (!(
headers.upgrade === 'websocket'
&& headers['sec-websocket-version'] === '13'
)) {
socket.end();
resolve(false);
return;
}
const responseHeader = `HTTP/1.1 101 Switching Protocols\r
Upgrade: websocket\r
Connection: Upgrade\r
Sec-Websocket-Accept: ${ encryptSocketKey(headers['sec-websocket-key']) }\r\n\r\n`
socket.write(responseHeader);
resolve(true);
});
});
}
const server = net.createServer(async function(socket) {
const createResult = await createConnect(socket);
if (!createResult) return;
socket.on('data', function(buffer) {
const data = decodeWsFrame(buffer);
const { opcode, payloadData } = data;
if (opcode === 8) {
socket.end();
} else {
socket.write(encodeWsFrame({
opcode: 1,
payloadData: `接收:${ payloadData.toString() }`
}));
}
});
});
server.listen(5059);
客户端的实现。
import net from 'node:net';
import { setInterval } from 'node:timers';
import { encryptSocketKey, encodeWsFrame, decodeWsFrame, rStr } from './utils.js';
const client = new net.Socket();
/* 建立请求 */
function createConnect(client, uuid) {
return new Promise((resolve, reject) => {
client.once('data', function(buffer) {
const data = buffer.toString();
const headers = data.split('\r\n')
.splice(1)
.filter((o) => o !== '')
.reduce((result, header, index) => {
const [key, value] = header.split(': ');
result[key.toLowerCase()] = value;
return result;
}, {});
// 判断是否为websocket并检查版本
if (!(
headers.upgrade === 'websocket'
&& headers['sec-websocket-accept'] === encryptSocketKey(uuid)
)) {
client.end();
resolve(false);
return;
}
resolve(true);
});
});
}
client.connect(5059, '127.0.0.1', async function() {
const uuid = btoa(rStr(16));
client.write(`GET /index.html HTTP/1.1\r
Connection: Upgrade\r
Upgrade: websocket\r
Sec-WebSocket-Key: ${ uuid }\r
Sec-WebSocket-Version: 13\r\n\r\n`);
const createResult = await createConnect(client, uuid);
if (!createResult) return;
let i = 0;
client.on('data', function(buffer) {
const data = decodeWsFrame(buffer);
console.log(data.payloadData.toString());
});
setInterval(() => {
console.log(`发送:${ i }`);
client.write(encodeWsFrame({
opcode: 1,
payloadData: `${ i++ }`
}));
}, 3_000);
});