为什么需要 WebSocket?#
socket#
- 客户端连接服务器(TCP / IP),三次握手,建立了连接通道
- 客户端和服务器通过socket接口发送消息和接收消息,任何一端在任何时候,都可以向另一端发送任何消息
- 有一端断开了,通道销毁
http#
- 客户端连接服务器(TCP / IP),三次握手,建立了连接通道
- 客户端发送一个http格式的消息(消息头 消息体),服务器响应http格式的消息(消息头 消息体)
- 客户端或服务器断开,通道销毁
只能是客户端向服务器发送请求,服务器返回查询结果,HTTP协议做不到服务器主动向客户端推送信息
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获取就非常麻烦
实时性的问题:
- 轮询
- 长连接
websocket#
专门用于解决实时传输的问题
- 客户端连接服务器(TCP / IP),三次握手,建立了连接通道
- 客户端发送一个http格式的消息(特殊格式),服务器也响应一个http格式的消息(特殊格式),称之为http握手
- 双发自由通信,通信格式按照websocket的要求进行
- 客户端或服务器断开,通道销毁
Nest使用websocket#
创建项目:
nest new nest-websocket -g -p pnpm默认Nest使用的是Socket.io,我们安装相关包
pnpm add @nestjs/websockets @nestjs/platform-socket.io -S创建模块:
nest g resource chat --no-spec注意,我们需要选择websocket


默认会帮我生成xxx.gateway.ts文件,默认代码如下:
import { WebSocketGateway, SubscribeMessage, MessageBody,} from '@nestjs/websockets'import { ChatService } from './chat.service'import { CreateChatDto } from './dto/create-chat.dto'import { UpdateChatDto } from './dto/update-chat.dto'
@WebSocketGateway()export class ChatGateway { constructor(private readonly chatService: ChatService) {}
@SubscribeMessage('createChat') create(@MessageBody() createChatDto: CreateChatDto) { return this.chatService.create(createChatDto) }
@SubscribeMessage('findAllChat') findAll() { return this.chatService.findAll() }
@SubscribeMessage('findOneChat') findOne(@MessageBody() id: number) { return this.chatService.findOne(id) }
@SubscribeMessage('updateChat') update(@MessageBody() updateChatDto: UpdateChatDto) { return this.chatService.update(updateChatDto.id, updateChatDto) }
@SubscribeMessage('removeChat') remove(@MessageBody() id: number) { return this.chatService.remove(id) }}Nestjs已经把Socket.io相关的处理做了封装,我们可以简单的使用几个装饰器,就能实现WebSocket的效果
@WebSocketGateWay 声明这是一个处理 weboscket 的类。
@SubscribeMessage 指定处理的消息
@MessageBody 取出传过来的消息内容
我们先不用管自动生成的代码,自己简单测试一下就行,比如我们创建一个下面的方法:
@SubscribeMessage('chatMessage')handleMessage(@MessageBody() msg: string): string { console.log('后端接收---', msg); return msg;}相当于指定处理消息是chatMessage,接收客户端传递过来的消息变量是msg。
这样后端我们就非常简单的处理完成了。
接下来是前端界面,我们在界面上简单处理,当打开html页面窗口,就创建连接,然后发送消息。当然,这需要客户端通过Socket.io进行连接,我们在工程根目录创建public文件夹,里面创建index.html文件。由于这是一个静态文件,并且放在服务器后端,因此,为了让客户端能够访问到后端静态文件,我们需要在main.ts中处理,这点和express是一样的
import { 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('public'); await app.listen(process.env.PORT ?? 3000);}bootstrap();<html> <head> <script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script> <script> const socket = io('http://127.0.0.1:3000'); socket.on('connect', function () { console.log('Connected');
socket.emit('chatMessage', 'hello world', (response) => console.log('chatMessage', response), ); }); socket.on('disconnect', function () { console.log('Disconnected'); }); </script> </head>
<body></body></html>从浏览器的连接信息中,就可以看出websocket比http协议请求多出了一些东西

其中:
Upgrade: websocketConnection: Upgrade这两个是 WebSocket 的核心,相当于告诉服务器:注意,我发起的请求要用 WebSocket 协议,请帮我找到对应的助理处理,而不是原来的HTTP
sec-websocket-extensions: permessage-deflate; client_max_window_bitsSec-WebSocket-Key: KQlRrXlEM3r9qfET4dnP8g==Sec-WebSocket-Version: 13Sec-WebSocket-Extensions是用于协商本次连接要使用的 WebSocket 扩展。
Sec-WebSocket-Key 这个是浏览器随机生成的,并且与服务端响应头部的 Sec-WebSocket-Accept 是配套的,提供基本的防护,这里的“配套”指的是:Sec-WebSocket-Accept 是根据请求头部的 Sec-WebSocket-Key 计算而来
Sec-WebSocket-Version是告诉服务器所使用的 WebSocket Draft (协议版本)
然后服务器会返回下列东西,表示已经接受到请求, 成功建立了WebSocket

其中:
Upgrade: websocketConnection: Upgrade就是告诉客户端即将升级的是 WebSocket 协议
Sec-WebSocket-Accept这个则是经过服务器确认,并且加密过后的Sec-WebSocket-Key
至此,HTTP 已经完成它所有工作了,接下来就是完全按照WebSocket协议进行了
总结,WebSocket连接的过程是:
首先,客户端发起http请求,经过3次握手后,建立起TCP连接;
http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等;
然后,服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据;
最后,客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。
我们当然也能把其他的方法,直接在客户端进行调用:
<html> <head> <script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script> <script> const socket = io('http://127.0.0.1:3000'); socket.on('connect', function () { console.log('Connected');
socket.emit('chatMessage', 'hello world', (response) => console.log('chartMessage', response), );
socket.emit('findAllChat', (response) => console.log('findAllChat', response), );
socket.emit('findOneChat', 1, (response) => console.log('findOneChat', response), );
socket.emit('createChat', { name: 'jack' }, (response) => console.log('createChat', response), );
socket.emit('updateChat', { id: 2, name: 'rose' }, (response) => console.log('updateChat', response), );
socket.emit('removeChat', 2, (response) => console.log('removeChat', response), ); }); socket.on('disconnect', function () { console.log('Disconnected'); }); </script> </head>
<body></body></html>