Skip to content

我们最常用的网络协议是 HTTP,它是一问一答的模式,客户端发送请求,服务端返回响应。

有时候也会用 Server Sent Event,它是基于 HTTP 的,客户端发送请求,服务端返回 text/event-stream 类型的响应,可以多次返回数据。

但是 HTTP 不能服务端向客户端推送数据,SSE 适合一次请求之后服务端多次推送数据的场景。

类似聊天室这种,需要实时的双向通信的场景,还是得用 WebSocket。

在 Nest 里实现 WebSocket 的服务还是很简单的。

我们创建个项目:

nest new nest-websocket

进入项目,安装用到的包:

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

然后创建个 websocket 模块:

nest g resource aaa

生成的代码很容易看懂:

@WebSocketGateWay 声明这是一个处理 weboscket 的类。

默认的端口和 http 服务 app.listen 的那个端口一样。

然后 @SubscribeMessage 是指定处理的消息。

通过 @MessageBody 取出传过来的消息内容。

分别声明了 find、create、update、remove 这些 CRUD 的消息类型。

具体的实现在 AaaService 里:

然后我们加一下客户端代码,跑起来试试。

添加 pages/index.html

html
<html>
  <head>
    <script src="https://cdn.socket.io/4.3.2/socket.io.min.js" integrity="sha384-KAZ4DtjNhLChOB/hxXuKqhMLYvx3b5MlT55xPEiNmREKRzeEm+RVPlTnAn0ajQNs" crossorigin="anonymous"></script>
    <script>
      const socket = io('http://localhost:3000');
      socket.on('connect', function() {
        console.log('Connected');

        socket.emit('findAllAaa', response =>
          console.log('findAllAaa', response),
        );

        socket.emit('findOneAaa', 1, response =>
          console.log('findOneAaa', response),
        );

        socket.emit('createAaa', {name: 'guang'},response =>
          console.log('createAaa', response),
        );

        socket.emit('updateAaa',{id: 2, name: 'dong'},response =>
          console.log('updateAaa', response),
        );

        socket.emit('removeAaa', 2,response =>
          console.log('removeAaa', response),
        );
      });
      socket.on('disconnect', function() {
        console.log('Disconnected');
      });
    </script>
  </head>

  <body></body>
</html>

这段代码也比较容易看懂,就是用 socket.io 来连接 ws 服务端。

connect 之后,分别发送 find、remove、update 等消息。

然后在 main.ts 里支持下这个 pages 静态目录的访问:

javascript
import { NestApplication, NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);

  app.useStaticAssets('pages');
  await app.listen(3000);
}
bootstrap();

把服务跑起来:

npm run start:dev

浏览器访问下 http://localhost:3000

可以看到,CRUD 方法都有了正确的响应。

在 Nest 里写 WebSocket 服务就这么简单。

那如果响应接受和返回消息不想用同样的名字呢?

这样:

分别指定 event 和 data。

这时候原来的代码就收不到 findAll 返回的消息了:

因为返回的消息是 guang,可以加一下这个事件的监听:

javascript
socket.on('guang', function(data) {
    console.log('guang', data);
});

这样就收到消息了:

那如果我不是马上发送消息,而是过几秒再发呢?

这就要返回 rxjs 的 Observer 了:

javascript
@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.next({ event: 'guang', data: { msg: 'ccc'} });
      }, 5000);
    });
}

测试下:

可以看到,2s、5s 的时候,收到了服务端传过来的消息。

有这些就足够用了,websocket 是用来双向实时通信的。

当然,如果你想用具体平台的 api,也可以注入实例。

安装 socket.io(Nest 默认使用 socket.io 包实现 WebSocket 功能)

npm install socket.io

javascript
@SubscribeMessage('findOneAaa')
findOne(@MessageBody() id: number, @ConnectedSocket() server: Server) {

    server.emit('guang', 666);
    return this.aaaService.findOne(id);
}

这样也可以,但是和具体的平台耦合了,不建议这样写。

除了 @ConnectedSocket 装饰器注入实例,也可以用 @WebSocketServer 注入实例:

javascript
@WebSocketServer()
server: Server;

@SubscribeMessage('createAaa')
create(@MessageBody() createAaaDto: CreateAaaDto) {
    this.server.emit('guang', 777);
    return this.aaaService.create(createAaaDto);
}

同样,也是不建议用的。

此外,服务端也有 connected、disconnected 等生命周期函数:

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

  handleDisconnect(client: Server) {
  }

  handleConnection(client: Server, ...args: any[]) {
  }
    
  afterInit(server: Server) {
  }
}

分别实现 OnGatewayInit、OnGatewayConnection、OnGatewayDisconnect 接口。

在生命周期函数里可以拿到实例对象。

案例代码上传了小册仓库

总结

这节我们学习了 Nest 实现 WebSocket 服务。

需要用到 @nestjs/websockets 和 @nestjs/platform-socket.io 包。

涉及到这些装饰器:

  • @WebSocketGateWay:声明这是一个处理 weboscket 的类。

  • @SubscribeMessage:声明处理的消息。

  • @MessageBody:取出传过来的消息内容。

  • @WebSocketServer:取出 Socket 实例对象

  • @ConnectedSocket:取出 Socket 实例对象注入方法

客户端也是使用 socket.io 来连接。

如果想异步返回消息,就通过 rxjs 的 Observer 来异步多次返回。

整体来说,Nest 里用 WebSocket 来做实时通信还是比较简单的。