# websocket 及 socket.io 简介
websocket 是一种在单个 TCP 连接上进行全双工通信的协议。
websocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
以前很多网站为了实现推送技术,所用的技术都是轮询,即浏览器需要不断向服务器发送请求,这样导致过多占用带宽资源和后端资源,而且如果想占用少,那么必然要提高轮询的时间间隔,同时也降低了请求的响应及时性,从任何角度它都不利于我们网站的性能要求。
websocket 在 html5 的标准下应运而生,到现在已不再是一个新的东西了,各个语言都有针对 websocket 协议的适配,在这之中,属 nodejs 阵营的 socket.io 最为出色.
socket.io
在这里不做过多介绍,官方文档里有关于它的详细 api 和例子可供参考,接下来就将 socket.io 集成到 Nest 框架中.
# Nest 集成 socket.io
在 Nest 里,监听 websocket 协议的模块叫做网关 (Gateway)
# 安装必要模块
$ npm i --save @nestjs/websockets @nestjs/platform-socket.io |
# 创建 socketio网关
$ nest g ga socketio/socketio |
import { SubscribeMessage, WebSocketGateway } from '@nestjs/websockets'; | |
@WebSocketGateway() | |
export class SocketIoGateway { | |
@SubscribeMessage('message') | |
handleMessage(client: any, payload: any): string { | |
return 'Hello world!'; | |
} | |
} |
# 修改配置项
简单的一条命令就创建好了,它默认监听我们 HTTP 服务监听的端口,我们可以修改端口号也可以修改 path
, namespace
等参数,相应地能做什么修改可以参考 socket.io 的 options 选项
接下来让我们补齐它的配置吧
@WebSocketGateway({ | |
path: "/ws", | |
transports: ["websocket"], | |
cors: { | |
origin: "*" | |
} | |
}) |
@WebSocketGateway
装饰器可以设置 socket.io 的配置,这里我们暂时不修改它的端口号,只设置 path
, transports
及跨域 cos
这里设置 transports
为 websocket
, 意为使用 websocket 协议
为了访问原生的 Server
实例,可以使用 @WebSocketServer
装饰器,这里的 Server
可以从 socket.io
导入
.... | |
import { Server, Socket } from 'socket.io'; | |
@WebSocketGateway({ | |
path: "/ws", | |
transports: ["websocket"], | |
cors: { | |
origin: "*" | |
} | |
}) | |
export class SocketIoGateway implements OnGatewayDisconnect,OnGatewayConnection,OnGatewayInit{ | |
@WebSocketServer() | |
server: Server; | |
..... | |
} |
OnGatewayInit
, OnGatewayDisconnect
, OnGatewayConnection
是 3 个有用的生命周期钩子,分别是实例初始化,客户端断开,客户端连接的接口
# 搭建简单的聊天室应用
使用 Nest 框架集成 socket.io 完成之后让我们搭建一个简单的聊天室应用,这里采用 socket.io
的 Get started
项目 (前端代码几乎一样)
# 部署前端代码
HTML 部分 index.html
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Socket.IO chat</title> | |
<style> | |
body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } | |
#form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); } | |
#input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; } | |
#input:focus { outline: none; } | |
#form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; } | |
#messages { list-style-type: none; margin: 0; padding: 0; } | |
#messages > li { padding: 0.5rem 1rem; } | |
#messages > li:nth-child(odd) { background: #efefef; } | |
</style> | |
</head> | |
<body> | |
<ul id="messages"></ul> | |
<form id="form" action=""> | |
<input id="input" autocomplete="off" /><button>Send</button> | |
</form> | |
</body> | |
</html> |
JS 部分
<script type="text/javascript" src="https://cdn.socket.io/4.5.0/socket.io.min.js"></script> | |
<script type="text/javascript"> | |
const socket = io("ws://localhost:9000",{path:"/ws",transports:["websocket"]}); | |
socket.on("connect",()=>{ | |
console.log("连接成功:",socket.id); | |
socket.emit("message","你好啊",(res)=>{ | |
console.log(res); | |
}); | |
}); | |
socket.on("disconnect",(reason)=>{ | |
console.log("已断开连接:",reason); | |
}); | |
socket.on('response_message', function(msg) { | |
let item = document.createElement('li'); | |
item.textContent = msg; | |
messages.appendChild(item); | |
window.scrollTo(0, document.body.scrollHeight); | |
}); | |
const form = document.getElementById('form'); | |
const input = document.getElementById('input'); | |
form.addEventListener('submit', function(e) { | |
e.preventDefault(); | |
if (input.value) { | |
socket.emit('chat_message', input.value,(res)=>{ | |
console.log(res); | |
}); | |
input.value = ''; | |
} | |
}); | |
</script> |
SocketIoGateway
import { | |
SubscribeMessage, | |
WebSocketGateway, | |
OnGatewayDisconnect, | |
OnGatewayConnection, | |
OnGatewayInit, | |
MessageBody, | |
ConnectedSocket, | |
WsResponse, | |
WebSocketServer, | |
} from '@nestjs/websockets'; | |
import { Server, Socket } from 'socket.io'; | |
@WebSocketGateway({ | |
path: "/ws", | |
transports: ["websocket"], | |
cors: { | |
origin: "*" | |
} | |
}) | |
export class SocketIoGateway implements OnGatewayDisconnect,OnGatewayConnection,OnGatewayInit{ | |
// 这里的 server 即 socket.io 的 server 实例 | |
@WebSocketServer() | |
server: Server; | |
@SubscribeMessage('message') | |
handleMessage(@ConnectedSocket() client: Socket, @MessageBody() data: any): string { | |
return `Hello ${data}!`; | |
} | |
@SubscribeMessage('chat_message') | |
handleChatMessage(@ConnectedSocket() client: Socket,@MessageBody() data:any): string{ | |
// 向所有人广播 | |
this.server.emit("response_message",data) | |
return "发送成功!"; | |
} | |
// 断开链接 | |
handleDisconnect(client: Socket){ | |
console.log("断开链接:",client.id); | |
} | |
// 建立链接 | |
handleConnection(client: Socket){ | |
console.log("建立链接:",client.id); | |
} | |
// 初始化完成 | |
afterInit(server:Server){ | |
console.log("server 初始化完成"); | |
} | |
} |
以上便是所有代码了,我们可以使用浏览器打开 2 个 index.html
进行测试
# 测试简单聊天室
首先启动我们的 Nest 框架
$ npm run dev | |
..... | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [InstanceLoader] TypeOrmCoreModule dependencies initialized +739ms | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [InstanceLoader] TypeOrmModule dependencies initialized +0ms | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [InstanceLoader] OptionsModule dependencies initialized +1ms | |
server 初始化完成 | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [WebSocketsController] SocketIoGateway subscribed to the "message" message +20ms | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [WebSocketsController] SocketIoGateway subscribed to the "chat_message" message +1ms | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [RoutesResolver] AppController {/api}: +1ms | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [RouterExplorer] Mapped {/api/hello, GET} (version: 1) route +3ms | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [RouterExplorer] Mapped {/api/hello, GET} (version: 2) route +0ms | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [RouterExplorer] Mapped {/api/mysql_host, GET} route +1ms | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [RoutesResolver] OptionsController {/api/options}: +0ms | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [RouterExplorer] Mapped {/api/options, GET} route +0ms | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [RouterExplorer] Mapped {/api/options/set, POST} route +1ms | |
[Nest] 14440 - 2022/09/01 14:20:58 LOG [NestApplication] Nest application successfully started +3ms | |
server listen on 9000! | |
建立链接: ZlExP3YVjbZRFHEJAAAB | |
建立链接: de8IBLbBg_sQacOZAAAD |
接下来可以欢快地聊天了