下面我将详细讲解“node.js 使用 net 模块模拟 WebSocket 握手进行数据传递操作示例”的完整攻略。
简介
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。在 WebSocket 连接被建立后,数据可以双向流动。WebSocket 协议使用的默认端口是 80 和 443,其中 80 是非安全连接,443 是安全连接。
Node.js 中可以使用 net
模块模拟 WebSocket 连接。net
模块是 Node.js 的核心模块之一,用于创建基于流的 TCP 服务器和客户端。
下面将演示如何使用 net
模块模拟 WebSocket 握手并进行数据传递。
WebSocket 握手
WebSocket 握手是客户端与服务器端建立 WebSocket 连接的第一步。在进行 WebSocket 握手时,客户端发送一条 HTTP 请求给服务器,请求头中包含 Upgrade
和 Connection
字段,告诉服务器要升级协议并使用 WebSocket 协议进行通信。服务器收到客户端的请求之后,会发送一条 HTTP 响应,响应头中同样包含 Upgrade
和 Connection
字段,并且也会包含一个 Sec-WebSocket-Accept
字段,该字段的值是经过计算的客户端的 Sec-WebSocket-Key
的哈希值,以此确认客户端使用 WebSocket 协议进行通信。
下面是一个示例的 WebSocket 握手请求和响应:
请求:
GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
WebSocket 数据帧
WebSocket 协议通过数据帧传递数据。WebSocket 数据帧由 2 ~ 14 个字节的帧头和帧体组成。帧头中包含了数据的类型、长度和掩码等信息。
帧类型包括文本帧和二进制帧。对于文本帧,帧体中是 UTF-8 编码的字符串;对于二进制帧,帧体中可以是任何二进制数据。
对于 WebSocket 帧,如果数据长度小于 126,则帧头的第二个字节值为数据的长度;如果数据长度在 126 个字节和 65535 个字节之间,则帧头的第二个字节值为 126,后续两个字节为数据的长度;如果数据长度超过 65535 个字节,则帧头的第二个字节值为 127,后续八个字节为数据的长度。
帧头第九个字节到第十二个字节为掩码,用于对数据进行编解码。在发送数据时,数据经过异或运算和掩码进行编码;在接收数据时,数据经过异或运算和掩码进行解码。
下面是一个示例的 WebSocket 数据帧的格式:
0 1 2 3 4 5 6 7
+-+-+-+-+-------+---------+
|F|R|R|R| opcode|M| Payload |
|I|S|S|S| |A| len |
|N|V|V|V| |S| |
| |1|2|3| |K| |
+-+-+-+-+-------+---------+
示例 1
下面是一个使用 net
模块模拟 WebSocket 握手的示例。在该示例中,客户端发起一条 GET 请求,服务器返回一条 101 切换协议的响应。
const net = require('net');
const server = net.createServer((socket) => {
console.log('Client connected');
socket.on('data', (data) => {
console.log(`Received data: ${data}`);
// 解析 HTTP 请求数据
const request = data.toString();
const [method, path, version] = request.trim().split('\r\n')[0].split(' ');
// 发送 HTTP 响应数据
socket.write(`HTTP/${version} 101 Switching Protocols\r\n`);
socket.write('Upgrade: websocket\r\n');
socket.write('Connection: Upgrade\r\n');
socket.write('Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n');
socket.write('\r\n');
});
socket.on('end', () => {
console.log('Client disconnected');
});
});
server.on('error', (error) => {
console.error(`Server error: ${error}`);
});
server.listen(3000, () => {
console.log('Server started');
});
上面的代码创建了一个服务器,并监听 3000 端口。当客户端连接到该服务器时,服务器会在控制台输出 Client connected
消息,在客户端向服务器发送数据时,服务器会在控制台输出 Received data
消息并解析 HTTP 请求数据,然后向客户端发送 101 切换协议的 HTTP 响应。
运行该示例后,可以使用 WebSocket 客户端工具向服务器发送一条 GET 请求:
GET / HTTP/1.1
Host: localhost:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
正在监听 3000 端口的服务器将输出以下内容:
Client connected
Received data: GET / HTTP/1.1
Host: localhost:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
示例 2
下面是一个使用 Buffer
对象编解码 WebSocket 数据帧的示例。该示例中,客户端向服务器发送一条文本帧,服务器将该文本帧返回给客户端。
const net = require('net');
const FIN = 0x80;
const TEXT_FRAME = 0x01;
function createWebSocketFrame(payload) {
const length = Buffer.byteLength(payload);
if (length < 126) {
// 数据长度小于 126 字节
const buffer = Buffer.alloc(2 + length);
buffer.writeUInt8(FIN | TEXT_FRAME, 0);
buffer.writeUInt8(length, 1);
buffer.write(payload, 2);
return buffer;
} else if (length < 0xFFFF) {
// 数据长度在 126 字节和 65535 字节之间
const buffer = Buffer.alloc(2 + 2 + length);
buffer.writeUInt8(FIN | TEXT_FRAME, 0);
buffer.writeUInt8(126, 1);
buffer.writeUInt16BE(length, 2);
buffer.write(payload, 4);
return buffer;
} else {
// 数据长度超过 65535 字节
const buffer = Buffer.alloc(2 + 8 + length);
buffer.writeUInt8(FIN | TEXT_FRAME, 0);
buffer.writeUInt8(127, 1);
buffer.writeBigUInt64BE(BigInt(length), 2);
buffer.write(payload, 10);
return buffer;
}
}
const server = net.createServer((socket) => {
console.log('Client connected');
socket.on('data', (data) => {
console.log(`Received data: ${data}`);
const length = data.readUInt8(1) & 0x7F;
const payload = data.toString('utf8', 2, 2 + length);
console.log(`Received payload: ${payload}`);
const frame = createWebSocketFrame(payload);
socket.write(frame);
});
socket.on('end', () => {
console.log('Client disconnected');
});
});
server.on('error', (error) => {
console.error(`Server error: ${error}`);
});
server.listen(3000, () => {
console.log('Server started');
});
上面的代码创建了一个服务器,并监听 3000 端口。当客户端连接到该服务器时,服务器会在控制台输出 Client connected
消息,在客户端向服务器发送数据时,服务器会在控制台输出 Received payload
消息并解码 WebSocket 数据帧的数据,然后将该数据作为 WebSocket 帧的负载写回客户端。
运行该示例后,可以使用 WebSocket 客户端工具向服务器发送一条文本帧,例如:
81 85 37 ea b2 12 df a2 6f aa 58
其中 81
是帧头,85
是帧长度,以及负载数据 37 ea b2 12 df a2 6f aa 58
。正在监听 3000 端口的服务器将输出以下内容:
Client connected
Received data: <Buffer 81 85 37 ea b2 12 df a2 6f aa 58>
Received payload: Hello, world!
结论
通过使用 net
模块模拟 WebSocket 握手并进行数据传递的示例,我们了解了 WebSocket 握手和数据帧的格式,并学会了如何使用 Node.js 编写 WebSocket 服务器和客户端代码。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:node.js 使用 net 模块模拟 websocket 握手进行数据传递操作示例 - Python技术站