websocket和SSE学习记录

websocket学习记录

websocket使用场景

  1. 即时聊天
  2. 在线文档协同编辑
  3. 实施地图位置

从开发角度来学习websocket开发

即使通信项目

  1. 通过node建立简单的后端接口,利用fs, path, express
javascript 复制代码
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的存储

javascript 复制代码
    info.setData('name', userName)
    info.setData('url', sPorSrc)
    initWebSocket()
    localStorage.setItem('YouChatName', userName)
    localStorage.setItem('YouChatPor', sPorSrc)

初始化websocket的服务主要的代码为

  1. 建立连接, window.socket.on,使用的是socket.io这个库,socket.io是基于事件驱动的实时通信库, 底层默认使用的是websocket的协议。会自动处理兼容问题,不支持websocket的情况下可以回退到轮询方案。发布订阅模式

剩下的就是逻辑的处理了

连接事件中注册登录时间,监听登录时间,写入数据,监听各种不同的事件,根据事件对数据进行处理。

如下为注册,监听的事件

javascript 复制代码
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的长连接,支持自动重连

javascript 复制代码
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}`);
});
javascript 复制代码
<!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>
相关推荐
豆豆41 分钟前
day26 学习笔记
笔记·opencv·学习·计算机视觉
Kx…………1 小时前
Day2—3:前端项目uniapp壁纸实战
前端·css·学习·uni-app·html
ghost1432 小时前
C#学习第17天:序列化和反序列化
开发语言·学习·c#
算法练习生2 小时前
数据结构学习笔记 :排序算法详解与C语言实现
数据结构·学习·排序算法
a东方青3 小时前
vue3学习笔记之属性绑定
vue.js·笔记·学习
豆芽8194 小时前
科学研究:怎么做
学习·科研·学习方法
摸鱼 特供版4 小时前
智能翻译播放器,让无字幕视频不再难懂
windows·学习·电脑·音视频·软件需求
豆豆4 小时前
day28 学习笔记
图像处理·笔记·opencv·学习·计算机视觉
开开心心就好5 小时前
实用电脑工具,轻松实现定时操作
python·学习·pdf·电脑·word·excel·生活