node.js 使用 net 模块模拟 websocket 握手进行数据传递操作示例

yizhihongxing

下面我将详细讲解“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 请求给服务器,请求头中包含 UpgradeConnection 字段,告诉服务器要升级协议并使用 WebSocket 协议进行通信。服务器收到客户端的请求之后,会发送一条 HTTP 响应,响应头中同样包含 UpgradeConnection 字段,并且也会包含一个 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技术站

(0)
上一篇 2023年6月8日
下一篇 2023年6月8日

相关文章

  • nodejs入门教程一:概念与用法简介

    下面为你详细讲解“nodejs入门教程一:概念与用法简介”的完整攻略。 Node.js入门教程一:概念与用法简介 什么是Node.js Node.js是一个基于Chrome V8 JavaScript引擎的平台,用来构建快速的、可扩展的网络应用程序。Node.js使用事件驱动、非阻塞I/O模型,使其轻量又高效。Node.js自带了一个包管理器npm,可以方便…

    node js 2023年6月7日
    00
  • Node.js里面的内置模块和自定义模块的实现

    Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它提供了一些内置的模块以及支持自定义模块,这些模块可以帮助我们更加容易地开发和管理工程项目。下面,我们将详细讲解“Node.js里面的内置模块和自定义模块的实现”的完整攻略,包含以下几个方面的内容: Node.js内置模块的使用和常用方法 自定义模块的实现和调用 模块引用路径的设置…

    node js 2023年6月8日
    00
  • nodejs async异步常用函数总结(推荐)

    Node.js Async 异步常用函数总结 异步函数的重要性 在 Node.js 应用程序中,涉及到很多涉及异步操作的场景,例如:操作数据库、调用 API 函数获取数据、处理大量的文件等等。而 Node.js 主要采用异步操作模式,这也导致了在编写 Node.js 应用程序时,我们需要学习和使用它的异步模块,特别是 Node.js 异步流程控制模块 Asy…

    node js 2023年6月8日
    00
  • 浅谈NodeJs之数据库异常处理

    浅谈NodeJs之数据库异常处理 在NodeJs开发过程中,经常需要对数据库进行增、删、改、查操作。在操作过程中,难免会遇到各种异常情况,如重复插入、删除不存在的数据、修改不存在的数据等,这时我们需要对这些异常做出相应的处理,以保证数据的完整性和程序的稳定性。 异常处理的基本思路 数据库操作是异步的,不能简单地使用try-catch来捕获异常。在NodeJs…

    node js 2023年6月8日
    00
  • node.js学习总结之调式代码的方法

    当我们在使用Node.js编写代码时,难免会遇到一些问题,需要调试代码才能找出问题所在。在本文中,我们将分享一些调试代码的方法以及如何使用它们来解决问题。 1. 使用console.log() console.log()是调试代码的基本工具之一。它允许我们在不修改代码的情况下输出变量值和调试信息。例如,在以下代码中,我们要输出变量x的值: let x = 1…

    node js 2023年6月8日
    00
  • node.js实现快速截图

    Node.js实现快速截图的攻略可以分为以下几个步骤: 1. 安装依赖 使用Node.js实现截图需要用到puppeteer这个库,它是一个Chrome Headless浏览器的Node.js API。因此,我们需要先安装Node.js和puppeteer库。 # 安装Node.js,建议使用版本号为10及以上的LTS版本 # Mac用户可使用Homebre…

    node js 2023年6月8日
    00
  • node.js插件nodeclipse安装图文教程

    下面我将详细讲解“node.js插件nodeclipse安装图文教程”的完整攻略,包括安装步骤、操作步骤和示例说明。 安装步骤 下载并安装Eclipse IDE for JavaScript Web Developers。可以在官网下载安装包,也可以使用Eclipse Marketplace进行安装。 在Eclipse中安装Node.js插件。打开Eclip…

    node js 2023年6月8日
    00
  • 论Java Web应用中调优线程池的重要性

    论Java Web应用中调优线程池的重要性 在Java Web应用中,线程池是非常重要的组成部分。线程池是一个预先分配的线程队列,可以被任务动态地添加至其中。对于一些需要处理高并发请求的应用(如电商网站),线程池能够帮助我们管理线程,保护我们的应用能够在高负载下正常运行。因此,对于线程池的调优至关重要。 1. 确定适当的线程池参数 调优一个线程池需要我们考虑…

    node js 2023年6月8日
    00
合作推广
合作推广
分享本页
返回顶部