WebSocket
WebSocket 是一种双向实时通信协议,允许客户端和服务器建立持久连接,以实时传递消息和数据
搭建 WS 服务
NodeJs 中,可以使用 websockets/ws 搭建 WebSocket 服务端
$ npm i ws
const client = new WebSocket('ws://localhost:3000')
client.addEventListener('open', ()=> {
console.log('Client open')
client.send('Hello server')
})
client.addEventListener('message', event => {
console.log('Client receive: ', event)
})
client.addEventListener('close', () => {
console.log('Client close')
})import { WebSocketServer } from 'ws'
const server = new WebSocketServer({ port: 3000 })
server.on('connection', (socket) => {
console.log('connection')
socket.on('message', msg => {
console.log('Server recive: ', msg.toString())
socket.send('server over')
})
socket.on('close', () => {
console.log('close');
})
})<body>
<script src="./client.ts" type="module"></script>
</body>$ tsx server.ts$ vite
Greeting: Hello World通过上面的代码,通过搭建 WebSocket 服务,实现了服务端和客户端的通信,这非常不错
但是代入实际场景中,我们会发现另一个问题,在我们已有的 HTTP 服务上添加 webSocket 服务似乎似乎有一些弊端:比如需要开启额外端口,对于开放防火墙端口和代理都不友好
WS over HTTP
WS 是基于 HTTP 的,所以实际上我们有更好的方式,就是 WebSocket over HTTP。这种行为允许在相同的端口上同时提供 HTTP 和 WebSocket 服务
import { WebSocketServer} from 'ws'
import { createServer } from 'http'
const server = createServer()
const wss = new WebSocketServer({ server })
wss.on('connection', (ws) => {
console.log('connection')
ws.on('message', msg => {
console.log('Server recive: ', msg.toString())
ws.send('anything')
})
ws.on('close', () => {
console.log('close');
})
})
server.listen(3000)这样就实现了一个简易的 WS over HTTP,但是还不够,上面的效果上和我们直接创建 WS 服务没啥区别,所以让我们稍微改一下,基于不同请求路径去响应不同的 WS 服务
比如我们留两个 HTTP 接口 __api_1__、__api_2__,用于连接不同的 WS 服务:
import { WebSocketServer} from 'ws'
import { createServer } from 'http'
const server = createServer()
const wss1 = new WebSocketServer({ noServer: true })
const wss2 = new WebSocketServer({ noServer: true })
wss1.on('connection', (ws) => {
//...
})
wss2.on('connection', (ws) => {
//...
})
// 有 WS 请求来的时候,upgrade 被触发
server.on('upgrade', (req, socket, head) => {
const { pathname } = new URL(req.url || '/', 'http://localhost')
// 当请求路径是 __api_1__ 的时候,升级为 ws 服务,让 wss1 去处理
if(pathname === '/__api_1__') {
wss1.handleUpgrade(req, socket, head, ws => {
wss.emit('connection', ws, req)
})
// 当请求路径是 __api_2__ 的时候,升级为 ws 服务,让 wss2 去处理
} else if(pathname === '/__api_2__') {
wss2.handleUpgrade(req, socket, head, ws => {
wss.emit('connection', ws, req)
})
}
})
server.listen(3000)对应的客户端代码只需要加一个路径即可
- const client = new WebSocket('ws://localhost:3000')
+ const client1 = new WebSocket('ws://localhost:3000/__api_1__')
+ const client2 = new WebSocket('ws://localhost:3000/__api_2__')这种方式的好处就是,可以启动任意多个 WS 服务在一个端口上,只需要设置好对应的请求路径即可
参考 Vite 当中,对于 HMR 服务的实现,也是采用类似上面的方式:
vitejs/vite/packages/vite/src/node/server/ws.ts
Lines 112 to 141 #587ad7b
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141