标题更新:[Nodejs用Node实现WebSocket协议] - 原:[WebSocket在高并发和大数据情况下的传输问题]
标题更新:[Nodejs用Node实现WebSocket协议] - 原:[WebSocket在高并发和大数据情况下的传输问题]
下面是之前遇到的问题:
上周在班里做了关于WebSocket协议的技术分享,因为当时演示的是小数据、间歇性数据发送。所以很成功,并没有遇到啥问题。
不过回去之后发现用一个10000次的循环不断向服务器发送数据时,不论数据的大小,后几千次的数据都变成了乱码:
for (i = 0; i < 10000; i++) ws.send('a');
另外一个问题是,当我发送了一次长度为65535 byte的数据(也就是Payload_len = 126, Payload length = Math.pow(2, 16) - 1),服务器却得到了两次的数据请求:
第一次是FIN = 1, Opcode = 0, Payload length为65535的Frame;
第二次是FIN = 1, Opcode为随机, MASK也是随机的, Payload length还是随机。。。
可是经过查看发现第一次数据的实际长度往往是Payload length值的一半左右,第二次的数据全是乱码。
下面是WebSocket握手结束后数据解码部分的代码:
function decodeFrame(frame) {
var counter = 0;
var fin_offset = 7,
opcode_offset = parseInt(1111, 2),
mask_offset = 7,
payload_len_offset = parseInt(1111111, 2);
var FIN = frame[counter] >> fin_offset,
Opcode = frame[counter++] & opcode_offset,
MASK = frame[counter] >> mask_offset,
Payload_len = frame[counter++] & payload_len_offset;
Payload_len === 126 &&
(Payload_len = frame.readUInt16BE(counter)) &&
counter += 2;
Payload_len === 127 &&
(Payload_len = frame.readUInt32BE(counter + 4)) &&
counter += 8;
var Payload_data = [];
if (MASK) {
var Masking_key = frame.slice(counter, counter + 4);
counter += 4;
for (var i = 0; i < Payload_len; i++) {
var j = i % 4;
frame[counter + i] ^= Masking_key[j];
}
}
// 这里得到上面说的测试结果
return {
FIN: FIN,
Opcode: Opcode,
MASK: MASK,
Payload_len: Payload_len,
Payload_data: frame.slice(counter, Payload_len)
}
};
WebSocket协议还有什么细节需要处理还是Buffer无法瞬间接受过大的数据吗?
一直没想明白这两种情况下乱码究竟是怎么产生的。。
求社区神牛指导啊~~
问题终于解决~
实现WebSocket真够让人纠结的,正确的代码已上传至github : https://github.com/abbshr/websocket_talk
在这方面遇到麻烦的同学可以参考参考啦~
标题更新:Nodejs用Node实现WebSocket协议
下面是之前遇到的问题:
上周在班里做了关于WebSocket协议的技术分享,因为当时演示的是小数据、间歇性数据发送。所以很成功,并没有遇到啥问题。
不过回去之后发现用一个10000次的循环不断向服务器发送数据时,不论数据的大小,后几千次的数据都变成了乱码:
for (i = 0; i < 10000; i++) ws.send('a');
另外一个问题是,当我发送了一次长度为65535 byte的数据(也就是Payload_len = 126, Payload length = Math.pow(2, 16) - 1),服务器却得到了两次的数据请求:
- 第一次是FIN = 1, Opcode = 0, Payload length为65535的Frame;
- 第二次是FIN = 1, Opcode为随机, MASK也是随机的, Payload length还是随机。。。
可是经过查看发现第一次数据的实际长度往往是Payload length值的一半左右,第二次的数据全是乱码。
下面是WebSocket握手结束后数据解码部分的代码:
function decodeFrame(frame) {
var counter = 0;
var fin_offset = 7,
opcode_offset = parseInt('1111', 2),
mask_offset = 7,
payload_len_offset = parseInt('1111111', 2);
var FIN = frame[counter] >> fin_offset,
Opcode = frame[counter++] & opcode_offset,
MASK = frame[counter] >> mask_offset,
Payload_len = frame[counter++] & payload_len_offset;
if (Payload_len === 126) {
Payload_len = frame.readUInt16BE(counter);
counter += 2;
}
if (Payload_len === 127) {
Payload_len = frame.readUInt32BE(counter + 4);
counter += 8;
}
var Payload_data = [];
if (MASK) {
var Masking_key = frame.slice(counter, counter + 4);
counter += 4;
for (var i = 0; i < Payload_len; i++) {
var j = i % 4;
frame[counter + i] ^= Masking_key[j];
}
}
// 返回解码后的数据
return {
FIN: FIN,
Opcode: Opcode,
MASK: MASK,
Payload_len: Payload_len,
Payload_data: frame.slice(counter, counter + Payload_len)
};
}
解决方案
在上述代码中,问题主要出在Payload_data
的切片上。由于counter
的计算错误,导致了数据的不正确切片。以下是修正后的代码:
function decodeFrame(frame) {
var counter = 0;
var fin_offset = 7,
opcode_offset = parseInt('1111', 2),
mask_offset = 7,
payload_len_offset = parseInt('1111111', 2);
var FIN = frame[counter] >> fin_offset,
Opcode = frame[counter++] & opcode_offset,
MASK = frame[counter] >> mask_offset,
Payload_len = frame[counter++] & payload_len_offset;
if (Payload_len === 126) {
Payload_len = frame.readUInt16BE(counter);
counter += 2;
}
if (Payload_len === 127) {
Payload_len = frame.readUInt32BE(counter + 4);
counter += 8;
}
var Payload_data = [];
if (MASK) {
var Masking_key = frame.slice(counter, counter + 4);
counter += 4;
for (var i = 0; i < Payload_len; i++) {
var j = i % 4;
frame[counter + i] ^= Masking_key[j];
}
}
// 修正后的Payload_data切片
return {
FIN: FIN,
Opcode: Opcode,
MASK: MASK,
Payload_len: Payload_len,
Payload_data: frame.slice(counter, counter + Payload_len)
};
}
总结
通过上述代码的调整,我们解决了WebSocket数据传输中的乱码问题。WebSocket协议有许多细节需要注意,尤其是在处理大数据传输时。如果还有其他问题,可以参考我的GitHub仓库:WebSocket 实现。
WebSocket协议本身定义是没有问题的,应该是你的发送端处理或接收端的处理有问题。
恩,已解决,不过这个问题产生的原因确实很蛋疼。。 的确是读取的问题。要在读取时循环调用解码函数。
解释与解决方案
首先,我们来看一下WebSocket协议的一些核心细节:
- FIN(Final Frame):指示这是消息的最后一帧。
- Opcode:表示帧的操作类型,例如文本帧(1)、二进制帧(2)等。
- Mask:客户端发送的数据是否被掩码处理,服务器端不能设置掩码位。
- Payload Length:数据负载的长度。
对于你提到的问题,主要是由于编码和解码逻辑不正确导致的。在发送大数据时,需要分帧处理。以下是正确实现WebSocket协议的一些关键点:
发送数据
当你发送大数据时,应该将其拆分为多个帧,并且每帧都需要正确设置FIN标志位。以下是一个简单的发送函数示例:
function sendLargeData(ws, data) {
const maxPayloadLength = 125; // 单个帧的最大负载长度
let remainingData = data;
while (remainingData.length > 0) {
const chunk = remainingData.slice(0, maxPayloadLength);
remainingData = remainingData.slice(maxPayloadLength);
const isLastChunk = remainingData.length === 0;
const fin = isLastChunk ? 1 : 0;
const frame = new ArrayBuffer(10 + chunk.byteLength);
const view = new DataView(frame);
view.setUint8(0, fin << 7 | 1); // FIN + OPCODE (1 for text)
view.setUint8(1, 0); // MASK (not set by server)
view.setUint8(2, chunk.byteLength); // Payload length
// Copy the data into the frame
new Uint8Array(frame, 10).set(new Uint8Array(chunk));
ws.send(view.buffer);
}
}
解码数据
解码函数需要正确处理多帧的情况,并且确保所有帧都被正确合并。以下是改进后的解码函数示例:
function decodeFrame(buffer) {
const view = new DataView(buffer);
const fin = view.getUint8(0) >> 7;
const opcode = view.getUint8(0) & 0x0F;
const mask = view.getUint8(1) >> 7;
const payloadLength = view.getUint8(1) & 0x7F;
let offset = 2;
if (payloadLength === 126) {
payloadLength = view.getUint16(offset);
offset += 2;
} else if (payloadLength === 127) {
payloadLength = view.getUint32(offset);
offset += 8;
}
const maskingKey = mask ? buffer.slice(offset, offset + 4) : null;
offset += 4;
const payload = buffer.slice(offset, offset + payloadLength);
if (maskingKey) {
for (let i = 0; i < payloadLength; i++) {
payload[i] ^= maskingKey[i % 4];
}
}
return {
fin,
opcode,
payloadLength,
payload
};
}
通过以上示例,你可以更好地理解和实现WebSocket协议。确保在发送和接收大数据时进行适当的分帧处理,并正确处理FIN标志位以标识消息的结束。