WebSocket基础与实战:NestJS中的实时通信指南

引言

在众多 Web 通信技术 中,HTTP 是最基础且广泛应用的技术之一。它基于简明的 请求-响应模式,客户端发起请求,服务端返回响应,适用于绝大多数 Web 应用。HTTP 的无状态性简化了交互模式,但在需要持续状态或实时交互的场景中,其效率受到限制。

Server-Sent Events (SSE) 在 HTTP 上实现了一种单向持续数据流机制,服务端可以通过text/event-stream内容类型向客户端发送更新。这种模式允许客户端发起单次请求后,服务端多次推送数据,适合实时数据场景。

进一步地,WebSocket 支持全双工通信,在客户端与服务端建立连接后,任意时刻均可发起数据交换,极适合高实时性要求的场景,如在线聊天和实时监控。WebSocket 的连接建立涉及一次特定的握手过程:

  1. 客户端发送握手请求 :通过 HTTP 请求,包括Upgrade: websocketSec-WebSocket-Key
  2. 服务器响应 :使用客户端的Sec-WebSocket-Key计算Sec-WebSocket-Accept,回应状态码101 Switching Protocols,完成协议切换。
  3. 数据传输:升级连接后,客户端与服务器通过二进制帧进行直接通信。

本文将由浅入深讲解 WebSocket,介绍如何使用原生 WebSocket API 和 socket.io 库,以提高实时通信应用开发的效率。特别是在 NestJS 框架中的实现方式,为实际应用提供参考。

原生 WebSocket API 的使用

浏览器的原生 WebSocket API 遵循 WebSocket 协议,定义于 Web 标准中,允许开发者在客户端应用中直接使用,无需依赖第三方库。基本使用步骤如下:

  1. 创建连接 :通过WebSocket实例与服务器建立连接。

    javascript 复制代码
    const socket = new WebSocket('ws://example.com/socketserver');

    注意:原生 WebSocket 使用ws://wss://协议进行实时通信。

  2. 事件监听 :监听连接生命周期的不同事件,如onopenonmessageonerroronclose。我们有两种主要方式:直接赋值和使用事件监听器。直接赋值方式允许我们为每个事件指定一个 处理函数,而事件监听器方式则提供了更大的灵活性,允许为同一事件绑定多个监听函数。后者的优势在于其扩展性和动态性,可以轻松添加或移除监听器,适用于复杂的应用场景。下面是两种方式的示例代码:

    直接赋值方式:

    javascript 复制代码
    socket.onopen = function(event) {
        console.log('连接已打开');
    };
    
    socket.onmessage = function(event) {
        console.log('收到消息:', event.data);
    };
    
    socket.onerror = function(event) {
        console.error('发生错误');
    };
    
    socket.onclose = function(event) {
        console.log('连接已关闭');
    };

    事件监听器方式:

    javascript 复制代码
    socket.addEventListener('open', function (event) {
        console.log('连接已打开');
    });
    
    socket.addEventListener('message', function (event) {
        console.log('收到消息:', event.data);
    });
    
    socket.addEventListener('error', function (event) {
        console.error('发生错误');
    });
    
    socket.addEventListener('close', function (event) {
        console.log('连接已关闭');
    });
  3. 发送消息 :使用send方法向服务器发送数据。

    javascript 复制代码
    socket.send('Hello, server!');
  4. 关闭连接 :使用close方法结束通信。

    javascript 复制代码
    socket.close();

虽然原生 WebSocket API 提供了实现基础实时通信的全部功能,但在开发复杂的实时应用时,我们可能会遇到一些挑战,比如心跳检测、处理网络断连重连、兼容不同浏览器或网络环境、以及实现复杂的消息分发逻辑等。

引入 socket.io

面对复杂实时应用的挑战,socket.io 库凭借其丰富的功能集合和卓越的开发体验,显著优化了构建复杂实时应用的过程。在架构上,socket.io-client 专为前端设计,而其后端对应部分则由 socket.io 库负责

简化连接和事件处理

socket.io提供了简洁的API,简化了服务器连接和事件处理。使用socket.on方法监听服务器事件并作出响应:

javascript 复制代码
const io = require('socket.io-client');
const socket = io('http://example.com');

socket.on('connect', () => {
    console.log('连接已建立');
});

socket.on('event', data => {
    console.log('收到事件:', data);
});

socket.on('disconnect', () => {
    console.log('连接已断开');
});

高级功能

socket.io时, 支持房间和命名空间,允许服务器向所有客户端或特定组的客户端广播消息,极大地简化了复杂通信逻辑的实现。

兼容性和后备机制

可以看到与上文原生 API 使用 ws:// 或 wss:// 不同,socket.io初始使用 http:// 或 https:// 。

javascript 复制代码
const io = require('socket.io-client');
const socket = io('http://example.com');

这是因为它在建立连接时进行一个兼容性更强 的 HTTP 握手过程。这样做使得 socket.io 能够在不同环境下自动选择最佳的传输方式(包括 WebSocket),并且在 WebSocket 不可用时退回到其他后备传输机制,如轮询,从而确保通信的可靠性和灵活性。

NestJS 中的 WebSocket 实现

接着我们就用 NestJS 实现后端代码。在 NestJS 中,WebSocket 通信可通过socket.iows库实现。NestJS 默认支持socket.io,使用ws则需切换适配器:

javascript 复制代码
const app = await NestFactory.create(AppModule);
app.useWebSocketAdapter(new WsAdapter(app));

因此本文将以socket.io为例进行介绍。在NestJS 中实现 WebSocket 其实很简单。

1. 安装所需包

通过运行以下命令安装必要的包来开始:

bash 复制代码
npm i --save @nestjs/websockets @nestjs/platform-socket.io

2. 生成资源

使用 NestJS CLI 生成与 WebSocket 相关的资源:

bash 复制代码
nest g resource aaa

当提示选择传输层时,选择 WebSocket,并在询问是否生成 CRUD 入口点时选择 Yes: 这将自动生成 CRUD 操作相关的消息处理逻辑,生成的代码如下: 如上图所示,@WebSocketGateway 装饰器用于声明一个 WebSocket 网关,它可以接受端口号和其他配置选项。

typescript 复制代码
@WebSocketGateway(80, { /* options */ })

消息的订阅 和处理可以通过@SubscribeMessage@MessageBody装饰器实现。同时,使用@ConnectedSocket可以获取到客户端实例:

typescript 复制代码
@SubscribeMessage('events')
handleEvent(@MessageBody() data: string, @ConnectedSocket() client: Socket): string {
   client.emit('onMessage', data);
   return 'Event handled';
}

具体的CRUD操作将在AaaService中实现。例如,这是在 AaaService 中实现的 create 方法。

typescript 复制代码
export class AaaService {
   create(createAaaDto: CreateAaaDto) {
       return 'This action adds a new aaa';
   }
}

3. 前端集成

为了在前端与 WebSocket 服务器进行交互,我们可以利用以下 HTML 和 JavaScript 代码。 首先,通过 CDN 引入 socket.io-client 库。

接着,连接上 app.listen 的那个端口(默认 http://localhost:3000 )。 通过调用 emit 方法发布 消息,服务器上对应的 handleEvent() 方法将被触发。同时,为了接收服务器响应的消息,设置一个监听器来处理从服务器发回的数据:

html 复制代码
<html>
<head>
  <script src="https://cdn.socket.io/4.3.2/socket.io.min.js"></script>
  <script>
    const socket = io('http://localhost:3000');
    socket.emit('createAaa', { name: 'Cici' }, response => {
      console.log('createAaa', response);
    });
  </script>
</head>
<body></body>
</html>

异步响应处理

在 NestJS 中,异步响应可以通过 async 方法或利用 RxJS 中的 Observable 来实现。

RxJS 是一个专注于异步事件处理的库,它通过一系列的操作符(operators)来简化异步逻辑的处理。数据通过被称为 observable 的数据源开始流动,并经过一系列操作符(如 mapfilter)处理后,最终到达接收者。

示例:使用 RxJS Observable

typescript 复制代码
import { SubscribeMessage, MessageBody } from '@nestjs/websockets';
import { Observable, from } from 'rxjs';
import { map } from 'rxjs/operators';

@SubscribeMessage('events')
onEvent(@MessageBody() data: unknown): Observable<WsResponse<number>> {
  const event = 'events';
  const response = [1, 2, 3];

  return from(response).pipe(
    map(data => ({ event, data })),
  );
}

此示例中,onEvent 方法创建了一个从数组 [1, 2, 3] 发出数据的 Observable,每个元素经过 map 操作后,作为异步响应返回。

示例:自定义 Observable 来输出异步数据流

以下是使用 setTimeout 来模拟异步数据流的示例,展示了如何在不同时间点向客户端发送不同的数据:

typescript 复制代码
import { SubscribeMessage } from '@nestjs/websockets';
import { Observable } from 'rxjs';

@SubscribeMessage('findAllAaa')
findAll() {
  return new Observable(observer => {
    observer.next({ event: 'guang', data: { msg: 'aaa'} });

    setTimeout(() => {
      observer.next({ event: 'guang', data: { msg: 'bbb'} });
    }, 2000);

    setTimeout(() => {
      observer.complete();
    }, 5000);
  });
}

生命周期管理

NestJS WebSocket 网关提供了生命周期钩子,例如 OnGatewayInit, OnGatewayConnection, 和 OnGatewayDisconnect,允许开发者在网关初始化、客户端连接及断开时执行自定义逻辑。

typescript 复制代码
import { WebSocketGateway, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server } from 'socket.io';

@WebSocketGateway()
export class AaaGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {

  afterInit(server: Server) {
    // 初始化逻辑
  }

  handleConnection(client: Server, ...args: any[]) {
    // 连接逻辑
  }

  handleDisconnect(client: Server) {
    // 断开连接逻辑
  }
}

这些生命周期钩子提供了对 WebSocket 网关行为的细粒度控制,使得管理连接的生命周期变得直接而清晰。

参考资料

相关推荐
哟哟耶耶2 分钟前
css-设置元素的溢出行为为可见overflow: visible;
前端·css
sunly_5 分钟前
CSS:跑马灯
前端·css
2301_8187320612 分钟前
用layui表单,前端页面的样式正常显示,但是表格内无数据显示(数据库连接和获取数据无问题)——已经解决
java·前端·javascript·前端框架·layui·intellij idea
yqcoder13 分钟前
npm link 作用
前端·npm·node.js
林涧泣19 分钟前
【Uniapp-Vue3】页面和路由API-navigateTo及页面栈getCurrentPages
前端·vue.js·uni-app
Komorebi゛21 分钟前
【uniapp】获取上传视频的md5,适用于APP和H5
前端·javascript·uni-app
林涧泣26 分钟前
【Uniapp-Vue3】动态设置页面导航条的样式
前端·javascript·uni-app
杰九43 分钟前
【全栈】SprintBoot+vue3迷你商城(10)
开发语言·前端·javascript·vue.js·spring boot
Hopebearer_1 小时前
入门 Canvas:Web 绘图的强大工具
前端·javascript·es6·canva可画
ILUUSION_S1 小时前
Vue平台开发三——项目管理页面
javascript·vue.js