websocket和SSE学习记录
websocket学习记录
websocket使用场景
- 即时聊天
- 在线文档协同编辑
- 实施地图位置
从开发角度来学习websocket开发
即使通信项目
- 通过node建立简单的后端接口,利用fs, path, express
app.get('*', (req, res) => {const assetsType = req.url.split('/')[1]if (assetsType == 'YouChat'){ // 首页const filepath = path.join(path.resolve('./dist'), 'index.html')res.sendFile(filepath)}if (assetsType == 'assets'){ // 客户端资源const filepath = path.join(path.resolve('./dist'), req.url)res.sendFile(filepath.split('?')[0]) // 去hash}if (assetsType == 'file'){ // 服务端资源const filepath = path.join(path.resolve('./'), req.url)res.sendFile(filepath)}if (assetsType == 'loadImg'){ // 接口res.send({ret: 1, data: {portrait, emoticon}})}
})
涉及到一个技术点Notification
核心的websocket建立连接的代码
用户注册,将用户的数据进行一个保存,然后初始化websocket的服务,将数据进行一个本地的localstorage的存储
info.setData('name', userName)info.setData('url', sPorSrc)initWebSocket()localStorage.setItem('YouChatName', userName)localStorage.setItem('YouChatPor', sPorSrc)
初始化websocket的服务主要的代码为
2. 建立连接, window.socket.on,使用的是socket.io这个库,socket.io是基于事件驱动的实时通信库, 底层默认使用的是websocket的协议。会自动处理兼容问题,不支持websocket的情况下可以回退到轮询方案。发布订阅模式
剩下的就是逻辑的处理了
连接事件中注册登录时间,监听登录时间,写入数据,监听各种不同的事件,根据事件对数据进行处理。
如下为注册,监听的事件
import info from './info.js'
import view from './view.js'export default function(){window.socket = io()window.socket.on('connect', () => { // 连接成功const userInfo = {name: info.name,url: info.url,id: window.socket.id}info.setData('id', window.socket.id)window.socket.emit('login', userInfo)})// 登陆window.socket.on('login', userInfo => {view.drawUserList(userInfo)})// 获取当前在线列表window.socket.on('userList', userList => {view.drawUserList(userList)})// 退出window.socket.on('quit', id => {view.drawUserList(id)})// 接收群聊消息window.socket.on('sendMessageGroup', message => {info.groupMessageList.push(message)if (info.member == 'group'){view.drawMessageList(info.groupMessageList)}else{// 提示群聊新消息$('.top .group').setAttribute('data-new', 'true')let nNewNum = $('.top .group').getAttribute('data-message')$('.top .group').setAttribute('data-message', Number(nNewNum) + 1)new Notification('收到来自简言的新消息', {body: `${message.name}: ${message.text}`,icon: message.url})}})// 接收私聊消息window.socket.on('sendMessageMember', message => {if (message.id == info.id){ // 自己的消息回传if (info[`member__${message.memberId}`] == undefined) {info[`member__${message.memberId}`] = []}info[`member__${message.memberId}`].push(message)view.drawMessageList(info[`member__${message.memberId}`])}else{ // 好友私聊消息if (info[`member__${message.id}`] == undefined){info[`member__${message.id}`] = []}info[`member__${message.id}`].push(message)}if (info.member == message.id){view.drawMessageList(info[`member__${message.id}`])}else{// 提示私聊新消息if ($(`.item[data-id="${message.id}"]`)){$(`.item[data-id="${message.id}"]`).setAttribute('data-new', 'true')let nNewNum = $(`.item[data-id="${message.id}"] .item-name`).getAttribute('data-message')$(`.item[data-id="${message.id}"] .item-name`).setAttribute('data-message', Number(nNewNum)+1)new Notification('收到来自简言的新消息', {body: `${message.name}: ${message.text}`,icon: message.url})}}// userList 消息摘要if ($(`.item[data-id="${message.id}"]`)){$(`.item[data-id="${message.id}"] .item-text`).innerHTML = message.text || `[收到新灵魂]`}})
}
其中的各种事件,就是前端逻辑的处理了。这是一个简单的websocket的demo。
学习的是https://github.com/cp0725/YouChat这个库,建议加一个脚本,build: “webpack --config webpack.config.js”, 方便开发调试
心跳检测
WebSocket 心跳检测的本质
本质是通过周期性双向验证维持长连接的活性,解决以下核心问题:
网络中间层的「假性存活」
现象:虽然 TCP 层连接未断开,但防火墙/Nginx 等中间件会主动关闭长时间(如 5 分钟)无数据传输的连接
解法:通过定时发送轻量级探测包(心跳包)重置中间件的空闲计时器
「半开连接」黑洞问题
现象:客户端异常断网后,服务端无法感知连接已失效,持续等待消息
解法:通过双向心跳响应机制,实现连接状态实时探活
与 WebSocket 协议特性的深度结合
协议头优化
心跳包利用 WebSocket 的极简帧头(最低 2 字节),相比 HTTP 头节省 90%+ 流量
扩展协议支持
通过 Sec-WebSocket-Extensions 协商心跳参数(如间隔时间)
可自定义心跳包格式(如携带设备电量、网络类型等元数据)
无跨域特性
心跳机制可跨域运行,无需像 HTTP 轮询那样处理 CORS 问题
http 通过判断 header 中是否包含 Connection: Upgrade 与 Upgrade: websocket 来判断当前是否需要升级到 websocket 协议,除此之外,还有其它 header:
Sec-WebSocket-Key :浏览器随机生成的安全密钥
Sec-WebSocket-Version :WebSocket 协议版本
Sec-WebSocket-Extensions :用于协商本次连接要使用的 WebSocket 扩展
Sec-WebSocket-Protocol :协议
当服务器同意进行 WebSocket 连接时,返回响应码 101
WebSocket 特点:
支持双向通信,实时性更强;
可以发送文本,也可以二进制文件;
协议标识符是 ws,加密后是 wss ;
较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有 2~10 字节(取决于数据包长度),客户端到服务端的的话需要加上额外的 4 字节的掩码。而 HTTP 协议每次通信都需要携带完整的头部;
支持扩展。ws 协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
无跨域问题。
SSE
sse是服务器向客户端推送, 用的是https的长连接,支持自动重连
const express = require('express');
const app = express();
const port = 3000;// 允许跨域(开发环境用)
app.use((req, res, next) => {res.header('Access-Control-Allow-Origin', '*');next();
});// SSE 路由
app.get('/sse-stream', (req, res) => {// 设置 SSE 必需的头信息res.writeHead(200, {'Content-Type': 'text/event-stream','Cache-Control': 'no-cache','Connection': 'keep-alive'});// 发送初始数据res.write('event: connected\ndata: Welcome!\n\n');// 定时发送数据(模拟实时更新)let counter = 0;const timer = setInterval(() => {counter++;const data = {time: new Date().toISOString(),value: counter};// SSE 标准格式(注意换行符)res.write(`event: update\n`);res.write(`data: ${JSON.stringify(data)}\n\n`);}, 1000);// 客户端断开时清理req.on('close', () => {clearInterval(timer);res.end();});
});app.listen(port, () => {console.log(`Server running at http://localhost:${port}`);
});
<!DOCTYPE html>
<html>
<body><div id="sse-data"></div><script>const eventSource = new EventSource('http://localhost:3000/sse-stream');// 通用消息处理(默认事件)eventSource.onmessage = (e) => {console.log('Default event:', e.data);};// 自定义事件处理(对应服务端的 event:update)eventSource.addEventListener('update', (e) => {const data = JSON.parse(e.data);document.getElementById('sse-data').innerHTML = `Time: ${data.time}<br>Counter: ${data.value}`;});// 连接建立事件eventSource.addEventListener('connected', (e) => {console.log('Connection established:', e.data);});// 错误处理eventSource.onerror = (e) => {if (e.eventPhase === EventSource.CLOSED) {console.log('Connection closed');} else {console.error('SSE Error:', e);}// 自动重连(浏览器默认行为)};// 页面关闭时断开连接window.addEventListener('beforeunload', () => {eventSource.close();});</script>
</body>
</html>