websocket服务端对客户的数据的FIN,mask,大端序payload数据复制以及解码,WS写浏览器端和cmd客户端模拟掩码发送文本、二进制、ping的方法

//服务器端处理数据如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 循环内做其它的事情,但需要根据具体的需求来决定是否合适。以下是一些可能的场景:

  1. 处理解析后的数据
    在 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; // 发生错误时退出循环
    }
}
  1. 性能监控
    你可以在 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() 的正常工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值