# websocket
* [WebSocket:5分钟从入门到精通](https://juejin.cn/post/6844903544978407431)
> 支持双向通信
使用场景:
* 大屏展示
* 日志监控
* 即时聊天
* 通知系统
服务端向客户端单向通讯: sse
## 语法
### 创建
```js
const ws = new WebSocket(url[, protocols])
```
```js
const uid = 123
const url = 'localhost:3000'
const ws = new WebSocket(`ws://${url}/ws/${uid}`)
```
### 属性
* readyState: 链接状态
* 0: 正在尝试建立连接
* 1: 已链接, 可以通讯
* 2: 正在关闭
* 3: 已关闭或无法打开
* url: 绝对路径
### 事件
> 通过 addEventListener() 监听
* open
* message
* error
* close
```js
ws.addEventListener('open', handleOpen)
ws.addEventListener('message', handleMessage)
ws.addEventListener('error', handleError)
ws.addEventListener('close', handleClose)
```
### 方法
* send
* close
```js
ws.send('hello')
ws.close()
```
## 示例
### 前端
```html
Document
连接状态:
未连接
```
### 后端
```js
'use strict'
const fastify = require('fastify')()
// 注册 WebSocket 插件
fastify.register(require('@fastify/websocket'), {
options: { maxPayload: 1048576 },
})
// 添加连接映射表
const connections = new Map()
// 注册 WebSocket 路由
fastify.register(async function (fastify) {
fastify.get('/ws/:uid', { websocket: true }, (socket, req) => {
const userId = req.params.uid
console.log(`用户 ${userId} 已连接`)
// 存储连接
connections.set(userId, socket)
socket.send(`欢迎用户 ${userId}`)
socket.on('message', message => {
console.log(`收到来自用户 ${userId} 的消息:`, message.toString())
socket.send(`服务器已收到消息: ${message}`)
})
socket.on('close', () => {
console.log(`用户 ${userId} 断开连接`)
connections.delete(userId)
socket.close()
})
})
fastify.get('/send', (req, reply) => {
const { msg, uid } = req.query
if (!msg || !uid) {
return reply.code(400).send({
success: false,
message: '缺少必要参数,请确保提供 msg 和 uid',
example: '/send?msg=你的消息&uid=用户ID',
})
}
console.log(`尝试发送消息给用户 ${uid}: ${msg}`)
const userSocket = connections.get(uid)
if (userSocket && userSocket.readyState === 1) {
userSocket.send(msg)
reply.send({ success: true, message: `消息已发送给用户 ${uid}: ${msg}` })
} else {
reply.code(404).send({
success: false,
message: `用户 ${uid} 不在线或连接已断开`,
})
}
})
})
// 直接在外部注册 `send` 路由
fastify.listen({ port: 3003, host: '0.0.0.0' }, err => {
if (err) {
fastify.log.error(err)
process.exit(1)
}
console.log('WebSocket 服务器运行在端口 3003')
})
fastify.ready(err => {
// 打印所有路由
if (err) throw err
console.log(fastify.printRoutes())
})
```
## SSE (Server-Sent Events)
1. **客户端请求**:客户端通过发送一个HTTP请求到服务器,请求建立SSE连接。这个请求通常是一个`GET`请求,并且包含一个特殊的`Accept`头,表明客户端希望接收`text/event-stream`类型的数据。
2. **服务器响应**:服务器接收到请求后,会保持连接打开,并开始发送事件流。服务器发送的数据格式是 `text/event-stream`,每个事件由一行或多行文本组成,以 `\n\n`(两个换行符)结束。
3. **事件格式**:每个事件可以包含以下字段:
* `data:`:事件的数据内容,可以是一行或多行。
* `id:`:事件的唯一标识符,用于断线重连时指定从哪个事件开始接收。
* `event:`:事件的类型,客户端可以根据这个字段来处理不同类型的事件。
* `retry:`:指定客户端在连接断开后重新连接的时间间隔(毫秒)。
4. **客户端处理**:客户端通过`EventSource`对象来接收和处理服务器发送的事件。`EventSource`会自动处理连接的重连和事件的解析。
### 服务端
```js
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/events') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
let eventId = 0;
setInterval(() => {
res.write(`id: ${eventId}\n`);
res.write(`data: ${JSON.stringify({ message: 'Hello, world!', time: new Date() })}\n\n`);
eventId++;
}, 1000);
} else {
res.writeHead(404);
res.end();
}
}).listen(3000, () => {
console.log('Server running at http://localhost:3000');
});
```
### 客户端
```html
SSE Example
Server-Sent Events Example
```