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

引言

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

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

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

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

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

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

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

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

原生 WebSocket API 的使用

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

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

    const socket = new WebSocket('ws://example.com/socketserver');
    

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

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

    直接赋值方式:

    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('连接已关闭');
    };
    

    事件监听器方式:

    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方法向服务器发送数据。

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

    socket.close();
    

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

引入 socket.io

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

简化连接和事件处理

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

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:// 。

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则需切换适配器:

const app = await NestFactory.create(AppModule);
app.useWebSocketAdapter(new WsAdapter(app));

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

1. 安装所需包

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

npm i --save @nestjs/websockets @nestjs/platform-socket.io

2. 生成资源

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

nest g resource aaa

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

@WebSocketGateway(80, { /* options */ })

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

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

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

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>
<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

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 来模拟异步数据流的示例,展示了如何在不同时间点向客户端发送不同的数据:

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,允许开发者在网关初始化、客户端连接及断开时执行自定义逻辑。

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 网关行为的细粒度控制,使得管理连接的生命周期变得直接而清晰。

参考资料

原文链接:https://juejin.cn/post/7340479361219870757 作者:ayyo_cici

(0)
上一篇 2024年2月29日 上午10:16
下一篇 2024年2月29日 上午10:26

相关推荐

发表回复

登录后才能评论