前言准备
在nestjs官方文档中提到,在nestjs中有两个现成支持的 WS 平台:socket.io 和 ws,这俩都是基于websocket封装的框架,其中socket.io在websocket的基础上集成了强大的功能
- 实时双向通信:Socket.IO 提供了实时的双向通信,允许服务器主动向客户端发送消息,并且客户端也可以向服务器发送消息。这种双向通信方式非常适合实时应用程序的需求,例如聊天或协作工具。
- 自动重新连接:当客户端与服务器之间的连接中断时,Socket.IO 具有自动重新连接的能力,它会尝试重新建立连接,并维护连接的稳定性。
- 分房间/分组:Socket.IO 允许将客户端连接划分到不同的房间或分组中,可以根据不同的需求将客户端进行分组管理,并实现群发或与特定组进行通信。
- 自定义事件:Socket.IO 允许开发者定义和触发自定义事件,这些事件可以是应用程序内部的逻辑事件或用户自定义的事件。通过自定义事件,可以实现灵活的消息传递和通信模式。
- 跨平台支持:Socket.IO 不仅支持浏览器端,还支持服务器端的多种编程语言,如 Node.js、Python、Java 和 .NET 等。这使得开发者能够在不同的环境中使用相同的通信协议和模式。
并且当 WebSocket 连接不可用时,Socket.IO 会使用 HTTP 协议作为备选方案来保持实时通信。
这俩框架选谁都可以,我这里用socket.io来为大家做演示了。
构建准备
先安装对应的包
pnpm i --save @nestjs/websockets @nestjs/platform-socket.io
接着通过创建一个gateway模块,与此同时创建一个event.gateway.ts,用于创建我们的websocket服务
接着在我们的event.gateway.ts中创建我们的类
import {WebSocketGateway} from '@nestjs/websockets' @WebSocketGateway() export class EventGateway { }
@WebSocketGateway是一个装饰器,用于创建WebSocket网关类。WebSocket网关类是用于处理 WebSocket连接和消息的核心组件之一。它充当WebSocket服务端的中间人,负责处理客户端发起的连接请求,并定义处理不同类型消息的逻辑
对于它可以接受一些参数,这里我们就快速过一遍挑重要的说 @WebSocketGateway(80, options)
第一个参数可以传递一个端口号,如果不传默认和http监听的端口一样,也就是main.ts中的端口,nestjs默认是3000,第二个是一些配置项,有很多例如跨域配置cors,以及namespace命名空间,这里只说配置cors跨域,其他的配置项,可以自行去[服务器选项 |Socket.IO]看看
在现在的新版本中客户端连接nestjs端的websocket服务时会出现跨域问题的,所以需要配置一下
@WebSocketGateway({ cors: { origin: '*', }, })
配置完这些,我们还需要在module中提供一下,因为网关可以被视为provider,可以注入依赖项,也可以被其他类注入
import { Module } from '@nestjs/common'; import { EventGateway } from './enent.gateway'; @Module({ providers: [EventGateway] }) export class GatewayModule {}
实现订阅消息
到这里我们就配置好了,但是我们想想一个最简易的聊天是不是得有发送消息和接收消息吧,就是发布订阅模式,nestjs中为我们内置好了对应的装饰器 @SubscribeMessage,看到这个英文单词就知道啥意思了,就是订阅消息的意思,在我们订阅到消息时可以写一下逻辑处理
import { MessageBody, SubscribeMessage, WebSocketGateway } from '@nestjs/websockets' @WebSocketGateway({ cors: { origin: '*' } }) export class EventGateway { @SubscribeMessage('newMessage') handleMessage(@MessageBody() body: any) { console.log(body); } }
@MessageBody这个装饰器可以获取消息,到这里我们的订阅服务已经起来了,打开控制台启动一下项目看看
看到里面有Websocketscontroller就代表启动成功了
如果不想用装饰器可以这样做,效果一样
@SubscribeMessage('events') handleEvent(client: Socket, data: any): any { return data; }
第一个参数是socket实例,第二个data是从客户端接受的消息,但是官方不推荐这种写法哈,建议还是用装饰器
实现发布消息
那么处理好了订阅消息,如何实现发布消息呢,官方文档中介绍了一个装饰器 @ConnectedSocket,可以实例化一个socket,我们可以通过上面的emit方法来监听一个自定义事件来发布消息
import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway } from '@nestjs/websockets' import { Socket } from 'socket.io' @WebSocketGateway({ cors: { origin: '*' } }) export class EventGateway { @SubscribeMessage('newMessage') handleMessage(@MessageBody() body: any, @ConnectedSocket() client: Socket,) { client.emit('onMessage') console.log(body); } }
接着打开postman来测试,我推荐这款工具的原因是,我在apifox中发现对websocket的支持不好,而且在postman中有对socket.io专门的测试。下面带着大家上手试试
经过这些操作我们就拿到了发出的消息 注意要订阅的是newMessage也就是subscribe装饰器的参数
下面介绍另外一种,借助WebsocketServer
import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets' import { Server, Socket } from 'socket.io' @WebSocketGateway({ cors: { origin: '*' } }) export class EventGateway { @WebSocketServer() server: Server @SubscribeMessage('newMessage') handleMessage(@MessageBody() body: any, @ConnectedSocket() client: Socket,) { this.server.emit('onMessage', { msg: 'new Message', content: body }) } }
实现的效果一样的。
利用SocketClient创建客户端
接下来利用postman模拟客户端
新开一个nestjs服务,注意mian.ts中的端口号改一下,别冲突了,创建一个socket模块,跟上面步骤一样
import { Module } from '@nestjs/common'; import { SocketClient } from './socket-client'; @Module({ providers: [SocketClient] }) export class SocketModule { }
下面在socket-client.ts中创建如下代码
import { Injectable, OnModuleInit } from "@nestjs/common"; import { io, Socket } from 'socket.io-client' @Injectable() export class SocketClient implements OnModuleInit { public socketClient: Socket constructor() { this.socketClient = io('http://localhost:3000') } onModuleInit() { this.registerConsumerEvent() } private registerConsumerEvent() { this.socketClient.on('connect', () => { console.log('connect to gateway'); }) this.socketClient.on('onMessage', (payload: any) => { console.log('socketClientClass'); console.log(payload); }) } }
我们这里构造了一个socketClient客户端, OnMouleInit是nestjs的生命周期函数,就是模块初始化时,我们调用了registerConsumerEvent()函数,在这个函数内,我们做了两件事,第一件事,在连接时,做 了逻辑处理,第二件事,监听了onMessage事件,并且能够别人发布的值,接着利用postman来测试一下
也是输入url连接之后,需要选监听的events,打开listen 做到这样就可以了,我们从第一个服务中发送一条消息试试
可以看到监听到了数据,到此我们已经做完了利用socketClient构造客户端
vue3搭配nestjs实现websocket小demo
首先在前端中导入包
pnpm i socket.io-client
之后我们直接在socket.io的官网拿到vue3模板
import { reactive } from "vue"; import { io } from "socket.io-client"; export const state = reactive <{ connected: boolean fooEvents: Array<any> barEvents: Array<any> }> ({ connected: false, fooEvents: [], barEvents: [] }); const URL = "http://localhost:3000" export const socket = io(URL); socket.on("connect", () => { state.connected = true; }); socket.on("disconnect", () => { state.connected = false; }); socket.on("foo", (...args) => { state.fooEvents.push(args); }); socket.on("bar", (...args) => { state.barEvents.push(args); });
接着在app.vue中引入
<script setup lang="ts"> import { onBeforeMount, onMounted, onUnmounted, reactive } from 'vue'; import { socket } from './socket' const chatList = reactive<{ value: string list: Array<any> }>({ value: '', list: [] }) // 组件挂载前让socket连接起来 onBeforeMount(() => { socket.connect(); }); // 组件挂载完毕完成后,监听onMessage事件 onMounted(() => { socket.on("onMessage", (e) => { console.log(e); chatList.list.push(e.content); }); }); // 组件销毁时断开连接 onUnmounted(() => { socket.disconnect(); }); // 点击btn发送socket消息 const handleClick = () => { socket.emit("newMessage", chatList.value, (e: any) => { console.log(e); }); }; </script> <template> <div> <input v-model="chatList.value" /> <button @click="handleClick()" style="margin-left: 12px;">发送</button> <div > <ul> <li v-for="(item, index) in chatList.list" :key="index">{{ item }}</li> </ul> </div> </div> </template> <style scoped> header { line-height: 1.5; } .logo { display: block; margin: 0 auto 2rem; } @media (min-width: 1024px) { header { display: flex; place-items: center; padding-right: calc(var(--section-gap) / 2); } .logo { margin: 0 2rem 0 0; } header .wrapper { display: flex; place-items: flex-start; flex-wrap: wrap; } } </style>
到此我们就配置完成了,接下来看看效果吧
到此我们就实现了websocket在nestjs的使用,并且能够和客户端进行连接