//先看一个函数,看他们是怎么写入fin,mask,及payload有效数据的
//opcode表示一个数据是文本、二进制、close,ping,pong等
//payload表示数据内容
let encodeMessage = function(opcode, payload) {
let buf;
let b1 = 0x80 | opcode; // FIN 位(0x80) + 操作码(如 TEXT: 1 → 0x81)
let b2 = 0; // Mask 位为 0(服务器→客户端无需掩码)
let length = payload.length; //数据的长度
if (length < 126) { // 短消息:长度直接写入 b2
buf = Buffer.alloc(payload.length + 2); // 头部 2 字节
b2 |= length; // 设置长度字段, b2一直是0,所以用|=相当于=
buf.writeUInt8(b1, 0); // 写入 FIN + 操作码
buf.writeUInt8(b2, 1); // 写入 Mask 位 + 长度
payload.copy(buf, 2); // 复制 Payload
} else if (length < (1 << 16)) { // 中等消息:长度占 2 字节,(1<<16)就是65535
buf = Buffer.alloc(payload.length + 4); // 头部 4 字节(2 + 2)
b2 |= 126; // 长度标识符 126
buf.writeUInt8(b1, 0); // 写入 FIN + 操作码
buf.writeUInt8(b2, 1); // 写入 Mask 位 + 长度
buf.writeUInt16BE(length, 2); // 写入 16 位大端长度,大端表示从后写入buffer,小端表示从前写入
payload.copy(buf, 4); // 复制 Payload
} else { // 长消息:长度占 8 字节
buf = Buffer.alloc(payload.length + 10); // 头部 10 字节(2 + 8)
b2 |= 127; // 长度标识符 127
buf.writeUInt8(b1, 0);
buf.writeUInt8(b2, 1);
buf.writeUInt32BE(0, 2); // 高 32 位(假设长度 ≤ 2^32-1)
buf.writeUInt32BE(length, 6); // 低 32 位,为什么是6?因为UInt32表示4个bit位+b1和b2两个Bit位,所以为6
payload.copy(buf, 10); // 复制 Payload
}
return buf;
};
//理解了上面的创建FIN,MASK和数据的关系,我们就可以着手模拟创建一个 WebSocket 数据帧,包含掩码和掩码后的数据。
//模拟websocket数据帧的函数createWebsocketFrame()
//data表示数据内容
//maskBytes表示掩码,一般是固定的4个16进制,如:let maskBytes=[0x55, 0xAA, 0x33, 0xCC];里面内容可任意
function createWebSocketFrame(data,maskBytes)
{
//将数据内容转化为buffer
const payload=Buffer.from(data);
//获取buffer的长度
const payloadLength=payload.length;
//获取Buffer内容的大小,第一个2表示头部的FIN和mask;
//(payloadLength>125?(payloadLength <= 65535 ? 2 :8) : 0)判断内容是短消息,还是小于65535个字符还是大于65535
//里面的4是表示直接添加到数据中的掩码,长度是4个
let frameLength=2+(payloadLength>125?(payloadLength <= 65535 ? 2 :8) : 0)+4+payloadLength;
//创建frameLength长度的缓冲区
let buffer=Buffer.alloc(frameLength);
//设置位置标识
let offset=0;
//设置FIN和opcode,这里FIN=1,opcode=1(文本帧),也就是0x80|1=0x81
buffer.writeUInt8(0x81,offset);
offset+=1;
//设置MASK和payload length数据长度
if(payloadLength<=125)//数据为短文本时
{
buffer.writeUInt8(0x80|payloadLength,offset);
offset+=1;
}else if(payloadLength<=(1<<16))//数据小于65535时
{
buffer.writeUInt8(0x80|126,offset);
offset+=1;
buffer.writeUInt16BE(payloadLength,offset);
offset+=2;
}else
{
//数据大于65535时
buffer.writeUInt8(0x80|127,offset);
offset+=1;
buffer.writeUInt32BE(0,offset);
offset+=4;
buffer.writeUInt32BE(payloadLength,offset);
offset+=4;
}
//set直接从offset位置开始复制maskBytes到buffer中
buffer.set(maskBytes,offset);
offset+=4;
//写入掩码后的数据,将数据进行掩码
const maskedPayload=masked(maskBytes,payload);
buffer.set(maskedPayload,offset);
return buffer;
}
//接下来是对模拟出来的websocket数据帧进行解码
//解码processBuffer()函数
function processBuffer(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);
//输出解码后的数据
console.log('Decoded payload: ',payload.toString());
}else
{
//如果没有掩码,直接提取有效载荷数据
if(buffer.length<idx+length)
{
console.error('Buffer too short for payload');
return;
}
let payload=buffer.subarray(idx,idx+length);
console.log('解码后数据:',payload.toString());
}
//处理剩余数据(如果有的话)
let remainingData=buffer.subarray(idx+length);
if(remainingData.length>0)
{
console.log('剩余数据:',remainingData);
}
}
//最后是掩码函数和解码函数,就不多做介绍,可以搜我的文章有掩码和解码的具体过程
//解码函数
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;
}
//综合测试
const data='hello world!';
//当然你要测试小65535的数据时你可以这样
//const data=Buffer.alloc(126)表示创建了小于65535个缓冲区
//const data=Buffer.alloc(70000)大于65535的数据
const wsFrame=createWebSocketFrame(data,maskBytes);
processBuffer(wsFrame);
masked() 函数:用于对数据进行掩码处理。
unmask() 函数:用于对掩码后的数据进行解掩码处理。
processBuffer() 函数:用于解析 WebSocket 数据帧并解掩码数据。
createWebSocketFrame() 函数:用于模拟创建一个 WebSocket 数据帧,包含掩码和掩码后的数据。
测试步骤:
使用 createWebSocketFrame() 函数创建一个 WebSocket 数据帧。
将生成的数据帧传递给 processBuffer() 函数。
processBuffer() 函数会解析数据帧并输出解码后的数据。
输出:
运行代码后,你应该会看到 Hello world! 被正确解码并输出到控制台。