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