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

参考资料

相关推荐
passerby60615 分钟前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角1 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!1 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰2 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi3 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen4 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马4 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu4 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss
IT_陈寒5 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端
乘风gg5 小时前
DeepSeek V4.1 Flash 来了,明天中午 Flash 降价 60%
前端·ai编程·claude