//服务器端处理数据如FIN、mask、payload数据及解码的代码
//如果有不明白的地方看我这篇文章https://blog.youkuaiyun.com/cdcdhj/article/details/145671414
//websocketConnection.js文件
const events=require('events');
const http=require('http');
const crypto=require('crypto');
const util=require('util');
let opcodes={
TEXT:1,
BINARY:2,
CLOSE:8,
PING:9,
PONG:10
};
let WebSocketConnection=function(req,socket,upgradeHead){
let self=this;
let key=hashWebSocketKey(req.headers["sec-websocket-key"]);
this.socket=socket;
this.closed=false;
this.buffer=Buffer.alloc(0);
socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' +
'Upgrade: WebSocket\r\n' +
'Connection: Upgrade\r\n' +
'sec-websocket-accept: ' + key + '\r\n\r\n'
);
socket.on('data',function(buf){
self.buffer=Buffer.concat([self.buffer,buf]);
while(self._processBuffer())
{
//这里可以处理解析后的数据
//如:限流或批处理,如果你需要限制数据处理的速度,或者将多个帧合并处理,可以在 while 循环内添加限流或批处理的逻辑。
//错误处理,如果 self._processBuffer() 中可能抛出错误,你可以在 while 循环内捕获并处理这些错误。
//性能监控你可以在 while 循环内添加性能监控的逻辑,例如记录处理每个帧所花费的时间。
console.log('已经循环出数据');
}
});
socket.on('close',function(had_error){
if(!self.closed)
{
//emit 属性用于触发一个事件,在eventEmitter中
self.emit("close",1006);
self.closed=true;
}
})
}
util.inherits(WebSocketConnection,events.EventEmitter);
WebSocketConnection.prototype._doSend=function(opcode,payload)
{
this.socket.write(encodeMessage(opcode,payload))
}
WebSocketConnection.prototype.send=function(obj)
{
let opcode;
let payload;
if(Buffer.isBuffer(obj))
{
opcode=opcodes.BINARY;
payload=obj;
}else if(typeof obj=='string')
{
opcode=opcodes.TEXT;
payload=new Buffer.from(obj,'utf8');
}
else
{
throw new Error("不能发送对象,必须是字符或buffer");
}
this._doSend(opcode,payload);
}
WebSocketConnection.prototype._handleFrame=function(opcode,buffer)
{
let payload;
switch(opcode)
{
case opcodes.TEXT:
payload=buffer.toString('utf8');
this.emit('data',opcode,payload);
break;
case opcodes.BINARY:
payload=buffer;
this.emit("data",opcode,payload);
break;
case opcodes.PING:
this._doSend(opcodes.PONG,buffer);
break;
case opcodes.PONG:
break;
case opcodes.CLOSE:
let code,reason;
if(buffer.length>=2)
{
code=buffer.readUInt16BE(0);
reason=buffer.toString("utf8",2);
}
this.close(code,reason);
this.emit("data",code,reason);
break;
default:
this.close(1002,"unknown opcode");
}
}
WebSocketConnection.prototype.close=function(code,payload)
{
let opcode=opcodes.CLOSE;
let buffer;
if(code)
{
buffer=Buffer.alloc(Buffer.byteLength(payload)+2);
buffer.writeUInt16BE(code,0);
buffer.write(payload,2);
}else
{
buffer=Buffer.alloc(0);
}
this._doSend(opcode,buffer);
this.closed=true;
}
WebSocketConnection.prototype._processBuffer=function(){
let buffer=this.buffer;
if(buffer.length<2)
{
console.error('Buffer too short');
return;
}
let idx=2;
let b1=buffer.readUInt8(0);
let fin=b1 & 0x80;
let opcode=b1 & 0x0f;
let b2=buffer.readUInt8(1);
let mask=b2 & 0x80;
let length=b2 & 0x7f; //表示的有效数据长度
//处理扩展长度
if(length > 125)
{
//这里是(1<<16)小于65535的数据
if(length ===126)
{
//fin标志1bit,mask标志1bit,数据长度在writeUInt16BE写入的大端序中,占2Bit,共4的长度
if(buffer.length < 4)
{
console.error('Buffer too short for extended length (16-bit)');
return;
}
length=buffer.readUInt16BE(2);
idx+=2;
}else if(length===127)
{
if(buffer.length<10)
{
console.error('Buffer too short for extended length (64-bit)');
return;
}
//读取低32位长度(忽略高32位)
length=buffer.readUInt32BE(6);
idx+=8;
}
}
//检查掩码标志
if(mask)
{
if(buffer.length < idx + 4 + length)
{
console.error("Buffer too short for masked payload");
return;
}
//提取掩码字节
let maskBytes=buffer.subarray(idx,idx+4);
idx+=4;
//提取有效载荷并解掩码,就是提取有效的数据并进行解码
let payload=buffer.subarray(idx,idx+length);
payload=unmask(maskBytes,payload);
this._handleFrame(opcode,payload);
//输出解码后的数据
// console.log('Decoded payload: ',payload.toString());
this.buffer=buffer.subarray(idx+length);
return true;
}else
{
//如果没有掩码,直接提取有效载荷数据
if(buffer.length<idx+length)
{
console.error('Buffer too short for payload');
return;
}
let payload=buffer.subarray(idx,idx+length);
this._handleFrame(opcode,payload);
this.buffer=buffer.subarray(idx+length);
return true;
//console.log('解码后数据:',payload.toString());
}
//处理剩余数据(如果有的话)
let remainingData=buffer.subarray(idx+length);
if(remainingData.length>0)
{
console.log('剩余数据:',remainingData);
}
}
let encodeMessage=function(opcode,payload)
{
let buf;
let b1=0x80 | opcode;
let b2=0;
let length=payload.length;
if(length<126)
{
buf=Buffer.alloc(payload.length+2+0);
b2 |=length;
buf.writeUInt8(b1,0);
buf.writeUInt8(b2,1);
payload.copy(buf,2);
}else if(length<(1<<16))
{
buf=Buffer.alloc(payload.length+2+2);
b2|=126;
buf.writeUInt8(b1,0);
buf.writeUInt8(b2,1);
buf.writeUInt16BE(length,2);
payload.copy(buf,4);
}else
{
buf=Buffer.alloc(payload.length+2+8);
b2|=127;
buf.writeUInt8(b1,0);
buf.writeUInt8(b2,1);
buf.writeUInt32BE(0,2);
buf.writeUInt32BE(length,6);
payload.copy(buf,10);
}
return buf;
}
let KEY_SUFFIX="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
let hashWebSocketKey=function(key){
let sha1=crypto.createHash('sha1');
sha1.update(key+KEY_SUFFIX,'ascii');
return sha1.digest('base64');
}
let unmask=function(mask_bytes,buffer)
{
let payload=Buffer.alloc(buffer.length);
for(let i=0;i<buffer.length;i++)
{
payload[i]=mask_bytes[i%4]^buffer[i];
}
return payload;
}
let maskBytes=[0x55, 0xAA, 0x33, 0xCC];
let masked=function(maskBytes,data)
{
let d=Buffer.from(data);
let dataload=Buffer.alloc(d.length);
for(let i=0;i<d.length;i++)
{
dataload[i]=d[i] ^ maskBytes[i%4];
}
//const maskData=Buffer.from(d.map((b,i)=>b^maskBytes[i%4]));
return dataload;
}
exports.listen=function(port,host,connectionHandler){
let srv=http.createServer(function(req,res){
console.log('HTTP服务器已经创建');
});
srv.on('upgrade',function(req,socket,upgradeHead){
let ws=new WebSocketConnection(req,socket,upgradeHead);
console.log('upgrade已经创建');
connectionHandler(ws);
});
srv.listen(port,host);
}
//服务端代码,websocket_server.js
//导入上面的websocketConnection.js代码
let websocket=require('./websocketConnection.js');
websocket.listen(8000,'localhost',function(conn){
console.log("connection opened");
conn.on('data',function(opcode,data){
console.log('接收到opcode: ',opcode);
//接收到文本信息
if(opcode===1)
{
console.log('接收到文本信息:',data);
conn.send(`回复:${data}`);
}else if(opcode===2)//接收到二进制信息
{
console.log('接收到二进制信息:',data.toString());
let buf=Buffer.from(data);
conn.send(buf);
}else
{
console.error('不支持的opcode:',opcode);
conn.close(opcode,data);
}
});
//关闭连接
conn.on('close',function(code,reason){
console.log('connection closed: ',code,reason);
})
});
//在cmd中的websocket客户端连接服务端
//websocket_client.sj
const WebSocket=require('ws');
const ws=new WebSocket('ws://localhost:8000');
ws.on('open',function open(){
console.log('连接到服务端');
//发送文本信息,这里的{mask:true}可写,可不写,包括下面的二进制和Ping一样可写,可不写
ws.send('Hello,server!',{mask:true},(err)=>{
if(err)
{
console.log('Send failed:',err);
}else
{
console.log('Message sent');
}
});
//发送有掩码的二进制数据
const binaryData=Buffer.from('二进制数据');
ws.send(binaryData,(err)=>{
if(err)
{
console.error('send failed: ',err);
}else
{
console.log('Binary data sent');
}
})
//发送ping帧
ws.ping('Ping from client',{mask:true},(err)=>{
if(err)
{
console.log('Ping failed: ',err);
}else
{
console.log("Ping send");
}
})
});
ws.on('message',function msg(data){
console.log('接收到来自服务端:',data.toString());
});
//监听Ping帧
ws.on('pong',function pong(data){
console.log('接收到服务端的Pong:',data.toString());
})
ws.on('close',function close(){
console.log('disconnectioned from server');
});
ws.on('error',function error(err){
console.error('WebSocket error: ',err);
})
//在浏览器中的代码
//websocket_client_index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>websocket client</title>
</head>
<body>
<h3>websocket client</h3>
<div>
<button id="connectBtn">Connect</button>
<button id="sendTextBtn">Send Text</button>
<button id="sendBinaryBtn">Send Binary</button>
<button id="pingBtn">Send Ping</button>
<button id="closeBtn">Close</button>
</div>
<div>
<h3>信息</h3>
<pre id="message"></pre>
</div>
<script type="text/javascript">
let ws;
//连接服务器
document.getElementById("connectBtn").addEventListener('click',()=>{
ws=new WebSocket("ws://localhost:8000");
ws.onopen=()=>{
logMessage('Connected to server');
};
ws.onmessage=(event)=>{
if(typeof event.data==="string")
{
logMessage(`接收到文本信息;${event.data}`);
}
if(ArrayBuffer.isView(event.data))
{
logMessage(`接收到二进制信息: ${Array.from(new Uint8Array(event.data))}`);
}
};
ws.onclose=()=>{
logMessage('服务器连接已经断开');
};
ws.onerror=(error)=>{
logMessage(`WebSocket error: ${error.message}`);
}
});
function logMessage(message){
const messagesElement=document.getElementById('message');
messagesElement.textContent+=`${message}\n`;
}
//发送文本信息
document.getElementById('sendTextBtn').addEventListener('click',()=>{
if(ws && ws.readyState===WebSocket.OPEN)
{
ws.send('Hello,server!');
logMessage('Sent text: Hello, server!');
}else
{
logMessage('WebSocket is not connected');
}
});
//发送二进制数据
document.getElementById("sendBinaryBtn").addEventListener('click',()=>{
if(ws && ws.readyState===WebSocket.OPEN)
{
const binaryData=new Uint8Array([0x66,0x6f,0x6f]);
ws.send(binaryData);
logMessage('Sent binary:foo');
}else
{
logMessage('WebSocket is not connected');
}
});
//发送Ping帧
document.getElementById("pingBtn").addEventListener('click',()=>{
if(ws && ws.readyState===WebSocket.OPEN)
{
ws.send('ping');
logMessage('Sent ping');
}else
{
logMessage('WebSocket is not connected');
}
});
//关闭连接
document.getElementById('closeBtn').addEventListener('click',()=>{
if(ws && ws.readyState===WebSocket.OPEN)
{
ws.close();
logMessage('Connection closed');
}else
{
logMessage('WebSocket is not connected');
}
})
</script>
</body>
</html>
在cmd中运行websocket_server.js服务端
在cmd运行websocket_client.js客户端,结果如图:
在浏览器端的结果:
while (self._processBuffer()) { … }这个while循环中有什么用?
socket.on(‘data’, function(buf) { … }) 是监听客户端发送的原始数据,而 while (self._processBuffer()) { … } 是用于处理这些数据的循环。
代码的作用
socket.on(‘data’, function(buf) { … }):
当客户端发送数据时,data 事件会被触发,buf 是接收到的原始数据。
每次接收到数据后,将其追加到 self.buffer 中(self.buffer 是一个累积的缓冲区)。
while (self._processBuffer()) { … }:
self._processBuffer() 是一个方法,用于从 self.buffer 中解析出完整的 WebSocket 帧。
如果 self._processBuffer() 返回 true,表示成功解析了一个完整的帧,并可以继续解析下一个帧。
如果返回 false,表示当前缓冲区中没有足够的数据来解析一个完整的帧,循环结束。
是否可以在 while 循环内做其它的事?
是的,你可以在 while 循环内做其它的事情,但需要根据具体的需求来决定是否合适。以下是一些可能的场景:
- 处理解析后的数据
在 self._processBuffer() 中,解析出完整的 WebSocket 帧后,通常会触发事件(例如 data 事件)来处理这些数据。你可以在 while 循环内添加额外的逻辑来处理这些数据。
示例
javascript
复制
while (self._processBuffer()) {
console.log(‘已经循环出数据’);
// 在这里添加额外的逻辑
if (self.lastProcessedFrame) {
console.log('Last processed frame:', self.lastProcessedFrame);
}
}
2. 限流或批处理
如果你需要限制数据处理的速度,或者将多个帧合并处理,可以在 while 循环内添加限流或批处理的逻辑。
let batch = [];
while (self._processBuffer()) {
console.log('已经循环出数据');
// 将解析后的帧加入批处理数组
if (self.lastProcessedFrame) {
batch.push(self.lastProcessedFrame);
}
// 每处理 10 个帧后执行一次批处理
if (batch.length >= 10) {
console.log('Processing batch:', batch);
batch = []; // 清空批处理数组
}
}
// 处理剩余的帧
if (batch.length > 0) {
console.log(‘Processing remaining batch:’, batch);
}
3. 错误处理
如果 self._processBuffer() 中可能抛出错误,你可以在 while 循环内捕获并处理这些错误。
while (true) {
try {
if (!self._processBuffer()) {
break; // 如果没有更多数据可处理,退出循环
}
console.log('已经循环出数据');
} catch (err) {
console.error('Error processing buffer:', err);
break; // 发生错误时退出循环
}
}
- 性能监控
你可以在 while 循环内添加性能监控的逻辑,例如记录处理每个帧所花费的时间。
while (self._processBuffer()) {
console.log('已经循环出数据');
// 记录处理时间
const startTime = Date.now();
// 处理数据
const endTime = Date.now();
console.log(`Frame processed in ${endTime - startTime}ms`);
}
while 循环的目的
while (self._processBuffer()) { … } 的主要目的是确保从 self.buffer 中解析出所有完整的 WebSocket 帧。它的设计是为了处理以下情况:
粘包问题:
客户端可能会一次性发送多个 WebSocket 帧,或者一个帧被拆分成多个 TCP 包。
self.buffer 用于累积这些数据,直到能够解析出一个完整的帧。
提高效率:
通过循环处理,可以一次性处理多个帧,而不是每次接收到数据时只处理一个帧。
确保数据完整性:
如果 self._processBuffer() 返回 false,表示当前缓冲区中的数据不足以解析一个完整的帧,循环会结束,等待更多数据。
是否可以处理数据达到其它目的?
是的,你可以在 while 循环内处理数据以达到其它目的,但需要注意以下几点:
不要阻塞事件循环:
如果 while 循环内的逻辑过于复杂或耗时,可能会阻塞 Node.js 的事件循环,导致服务器无法处理其它请求。
如果需要处理大量数据,建议使用异步操作或将任务分发到工作线程。
保持代码清晰:
如果添加了额外的逻辑,确保代码结构清晰,避免将 self._processBuffer() 的逻辑与其它逻辑混在一起。
确保数据完整性:
如果在 while 循环内修改了 self.buffer 或其它状态,确保不会影响 self._processBuffer() 的正常工作。