一文了解 WebSocket

为什么需要webSocket

Http的单向性缺陷

HTTP 基于请求-响应模式,服务器无法主动推送数据,导致实时应用(如股票行情,即时聊天)需要客户端频繁轮询(Polling),浪费带宽和服务器资源

传统轮询的(Polling)的效率问题

  • 短轮询:客户端定期发送请求,即使数据未更新也会接受冗余响应,导致不必要的网络传输和CPU消耗
  • 长轮询(Long Polling):虽减少空响应(新数据到来时才发送响应),但仍有延迟(最快2×RTT),且高频请求仍占用资源

头部开销与低效传输

HTTP头部庞大(可达400+字节),而实际数据可能很小(如10字节),周期性传输大量大部信息浪费带宽。

WebSocket的优势

  • 双向实时通讯:建立连接后,服务端和客户端可随时主动推送数据,实现低延迟交互
  • 轻量级协议:连接建立后,数据传输头部仅2-10字节,显著减少开销
  • 持久连接:避免重复建立连接的开销,适合高频数据的交换场景

总结:

WebSocket弥补了HTTP在实时性,双向通信和传输效率的不足,成为现代web应用(如在线游戏,实时监控,协作工具)的理想选择

OSI模型与TCP/IP

OSI模型的说明

  • 定义:OSI(open system interconnection)模型是由国际标准化组织(ISO)提出的概念性框架,用于标准化不同计算机系统之间的通信。它将网络通信划分为7层,每一层负责特定的功能,并通过接口与相邻层交互
  • 7层结构及功能
层数 名称 主要功能
7 应用层 提供用户接口,支持应用程序访问网络服务(HTTP,WS)
6 表示层 数据格式转换(加密,压缩,编码等)确保不同系统能正确解析数据
5 会话层 建立,管理或终止应用程序之间的对话
4 传输层 提供端到端的可靠数据传输(tcp)或不可靠但高效的传输UDP
3 网络层 负责IP和路由选择,确保数据包跨网络传输
2 数据链路层 在物理网络上传输数据帧,进行错误检测和流量控制
1 物理层 定义电压和光信号等机械特性,负责比特流的传输

而TCP/IP协议可以看作OSI模型的一种简化

  • 定义:TCP/IP是互联网的实际通信标准,采用4层结构,比OSI更简化,但功能覆盖OSI的多个层
  • 4层结构及对应的OSI
TCP/IP层 对应的OSI层 主要功能
应用层 应用层,表示层,会话层 提供应用程序的通信接口
传输层 传输层 提供端到端的可靠数据传输(tcp)或不可靠但高效的传输UDP
网络层 网络层 负责IP和路由选择,确保数据包跨网络传输
网络接口层 数据链路层,物理层 管理物理网络连接,如以太网,Wi-Fi等,处理比特流和帧的传输
  • 特点:

    • 互联网(如HTTP,IP,TCP)均基于 TCP/IP 协议栈
    • 更简化:合并了 OSI 的高层(应用,表示,会话)和底层(数据链路层和物理层
    • 灵活高效:OSI过于复杂,而TCP/IP更简洁,适合实际部署

总结

  1. OSI是理论模型,帮助理解网络通信的分层协议
  2. TCP/IP是实际协议栈,支撑现代互联网的运行
  3. 两者关系:TCP/IP可视为OSI的简化版,但层间界限更模糊,更注重实用性

HTTP与WebSocket 的关系

HTTP,websocket等应用层协议,都是基于TCP协议来传输数据的,我们可以把这些高级协议理解成对 TCP的封装

对于 WebSocket 来说,它必须依赖HTTP协议进行一次握手,握手成功后,数据就直接从 TCP 通道传输,与HTTP无关了

简单来说,WS由两部分组成:握手 数据传输

握手

  1. 客户端请求握手
arduino 复制代码
GET /chat HTTP/1.1            //1
Host: server.example.com      //2
Upgrade: websocket            //3
Connection: Upgrade           //4
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== //5
Origin: http://example.com    //6
Sec-WebSocket-Protocol: chat, superchat //7
Sec-WebSocket-Version: 13     //8

逐行解析:

  1. 请求行:使用HTTP get方法,请求路径为/chat,HTTP版本为1.1
  2. Host头:指定服务器域名
  3. Upgrade头:表明客户端希望升级到web socket 协议
  4. Connection头:值为 upgrade,表明这是一个协议升级请求
  5. Sec-Web-Socket-key:Base64编码的随机16字节值,用于安全认证
  6. Origin头:用于防止跨站攻击,标明请求来源
  7. Sec-WebSocket-Protocol:客户端支持的子协议列表
  8. Sec-WebSocket-Version:指定webSocket协议版本
  1. 服务器响应
arduino 复制代码
HTTP/1.1 101 Switching Protocols //1
Upgrade: websocket              //2
Connection: Upgrade              //3
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  //4
Sec-WebSocket-Protocol: chat     //5

逐行解析:

  1. 状态行:101状态码表示协议切换成功
  2. Upgrade头:确认升级到 webSocket协议
  3. Connection头:确认连接已升级
  4. Sec-WebSocket-Accept:服务器对客户端key的验证响应
  5. Sec-WebSocket-Protocol:服务器选择的子协议
  1. 关键机制说明:
  • 协议升级:通过HTTP upgrade机制实现从HTTP到WebSocket的转换

  • 安全验证:

    • 客户端发送随机key
    • 服务器返回计算后的 Accept 值
    • 防止恶意或非WebSocket连接
  • 子协议协商:

    • 客户端列出支持的协议(如chat,superchat)
    • 服务器选择其中一个或拒绝
  • 版本控制:

    • 通过version字段确保双方使用兼容的协议版本
  • 代理兼容性

    • 使用HTTP兼容的握手方式
    • 通过Connection头确保代理正确处理

握手成功后,TCP连接保持打开字段,双方可以开始WebSocket数据帧的交换,实现全双工的通信

WebSocket协议Url:

ws协议默认使用80端口,wss协议默认使用443端口:

ini 复制代码
ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
 
host = <host, defined in [RFC3986], Section 3.2.2>
port = <port, defined in [RFC3986], Section 3.2.3>
path = <path-abempty, defined in [RFC3986], Section 3.3>
query = <query, defined in [RFC3986], Section 3.4>

注:wss协议是WebSocket使用SSL/TLS加密后的协议,类似于HTTP/HTTPS

WebSocket连接准备前的的准备工作:

  1. 连接建立的基本流程:

客户端握手的小要求

  1. 基本要求:

    1. 请求格式:必须是符合RFC 2616(HTTP/1.1)定义的HTTP请求消息
    2. 请求方法:必须是get
    3. HTTP版本:必须大于1.1
  2. 必包含的头字段

    1. host字段:指定服务器的域名
    2. Upgrade:值必须包含websocket关键字
    3. Connection头字段:值必须包含upgrade指令
    4. Sec-WebSocket-Key:值是16字节随机数的Base64编码
    5. Sec-WebSocket-Key:头字段值必须是13,表示WebSocket协议版本是13
  3. 条件必须的头字段

    1. origin头字段:如果客户端时浏览器则必须包含

WebSocket数据帧详解

  1. 发送数据的基本原则

    1. 数据帧的处理要求:

      1. 客户端发送的帧:必须进行掩码处理
      2. 服务端发送的帧:不能进行掩码处理
      3. 违规处理:如果违反上述规则,对方应当发送关闭帧终止连接
    2. 帧的基本组成:

      1. 帧类型标识码(Opcode)
      2. 负载长度(payload length)
      3. 负载内容(payload):包括扩展数据和应用数据
  2. 帧类型

    1. 帧类型由4位Opcode表示,收到Opcode应当立即断开连接

    2. 数据帧类型

      1. 0x0-继续帧:表示当前帧是消息片段,需要与上一个非0x0帧拼接,常用于大消息分片传输
      2. 0x1-文本帧:携带UTF-8编码的文本数据
      3. 0x2-二进制帧:图片,音频等任意二进制数据
      4. 0x3-0x7-保留值:未未来非控制帧预留
    3. 控制帧类型

      1. 0x8-关闭连接帧:
      2. 0x9-ping帧:内容可以是任意数据,用于心跳检测/保活机制
      3. 0xA-pong帧:对ping的响应,内容应与ping帧相同
      4. 0xB-0xF保留值:位未来控制帧预留
  3. 帧格式概述

    1. 基础头部(2字节):

      1. FIN(一位):是否为最终帧
      2. RSV1-3(扩展用)
      3. Opcode(4位):帧类型
      4. Mask(1位):是否掩码
      5. Payload(7位):基础长度
    2. 扩展长度(可选)

    3. 掩码密钥:4字节,仅客户端发送时存在

    4. 有效载荷数据:

      1. 扩展数据:如协商的压缩数据
      2. 应用数据:实际传输内容
  4. 示例

    1. 大文件分片传输:
    ini 复制代码
     Frame1: FIN=0, Opcode=2, Payload=[二进制数据第一部分]
     Frame2: FIN=0, Opcode=0, Payload=[第二部分]
     Frame3: FIN=1, Opcode=0, Payload=[最后部分]
     

项目应用方面的思考

思考一:WebSocket 的连接建立过程,以及在前端代码中如何检测连接状态和处理断线重连

  1. 首先,前端通过 WebSocket API 创建连接

    ini 复制代码
     const ws = new WebSocket('ws://localhost:8080');
  2. 连接确立后,WebSocket 会触发onopen事件,表示连接成功。

  3. 在项目中,我们可以自定义一个Hook用来统一管理这些连接状态:

    1. onopen:连接成功
    2. onclose:连接关闭
    3. onerror:连接异常
    4. onmessage:连接异常
    ini 复制代码
     ws.onopen = () => setStatus('connected');
     ws.onclose = () => setStatus('disconnected');
     ws.onerror = () => setStatus('error');
  4. 短线重连处理:当检测到oncloseonerror 时,可以重新设置定时器尝试重新连接;或者我们可以用 useEffect 监听状态变化,实现自动重连

    scss 复制代码
     ws.onclose = () => {
       setStatus('disconnected');
       setTimeout(() => {
         // 重新创建 WebSocket 实例
         reconnect();
       }, 3000);
     }; 

思考二:在 React 项目中,如何优雅地管理 WebSocket 的生命周期

思路:用 useEffect 钩子来处理连接建立与关闭问题,用 useState 钩子来管理 WebSocket 的连接状态,用 useRef 来保存 WebSocket 的实力,避免不必要的组件重新渲染,并利用 useCallback 优化回调函数。

代码示例如下:

ini 复制代码
import { useEffect, useRef, useState, useCallback } from 'react';

export const useWebSocket = (url) => {
  const socketRef = useRef(null);
  const [state, setState] = useState({
    data: null,
    isConnected: false,
    error: null,
  });

  // 处理消息的回调函数
  const handleMessage = useCallback((event) => {
    try {
      // 尝试解析JSON数据,如果失败则按原始数据处理
      const parsedData = JSON.parse(event.data);
      setState(prevState => ({ ...prevState, data: parsedData }));
    } catch (e) {
      console.error('Failed to parse message data:', e);
      setState(prevState => ({ ...prevState, data: event.data, error: null }));
    }
  }, []);

  useEffect(() => {
    // 1. 在组件挂载时建立连接
    const socket = new WebSocket(url);
    socketRef.current = socket;

    // 监听连接打开事件
    socket.onopen = () => {
      console.log('WebSocket 连接成功');
      setState(prevState => ({ ...prevState, isConnected: true, error: null }));
    };

    // 监听消息事件
    socket.onmessage = handleMessage;

    // 监听连接关闭事件
    socket.onclose = () => {
      console.log('WebSocket 连接已关闭');
      setState(prevState => ({ ...prevState, isConnected: false, error: null }));
    };

    // 监听错误事件
    socket.onerror = (event) => {
      console.error('WebSocket 连接错误:', event);
      setState(prevState => ({ ...prevState, isConnected: false, error: event }));
    };

    // 2. useEffect 的清理函数:在组件卸载时执行
    return () => {
      if (socketRef.current) {
        // 优雅地关闭连接
        socketRef.current.close();
        console.log('WebSocket 连接已清理');
      }
    };
  }, [url, handleMessage]);

  // 发送消息的方法
  const sendMessage = useCallback((message) => {
    if (socketRef.current?.readyState === WebSocket.OPEN) {
      // 如果消息是对象,则将其转换为 JSON 字符串
      const dataToSend = typeof message === 'string' ? message : JSON.stringify(message);
      socketRef.current.send(dataToSend);
    } else {
      console.error('WebSocket is not connected. Message not sent.');
    }
  }, []);

  return {
    ...state,
    sendMessage,
  };
};

思考三:如何保证 WebSocket 通信的安全性

  1. 使用 wss 协议:使用了 TLS/SSL 加密,与 HTTPS 类似。
  2. 身份验证与授权:在 WebSocket 握手时,在协议头中携带一个认证令牌 token ,服务器在接受连接前会验证这个 token 的有效性,可以防止未经授权的用户连接到 WebSocket 服务
  3. 限制连接来源(CORS):服务端可以检查 WebSocket 请求的 Origin 头,只允许来自特定域名,防止恶意网站的攻击。
相关推荐
骑士雄师18 分钟前
java面试记录: sychonized 锁,熔断组件,分布式锁
java·开发语言·面试
HUMHSX22 分钟前
Vue 项目启动全流程解析:从入口文件到全局指令注册与页面渲染
前端·javascript·vue.js
有颜有货34 分钟前
PMC生产排产的4种算法,一次讲清
java·服务器·前端
小虎牙00736 分钟前
Android kotlin图片库Coil源码详解
android·前端
随风一样自由1 小时前
【前端领域】前端开发核心应用场景与落地实践
前端·前端框架
an317421 小时前
弹窗数据流设计的两种高阶架构实践
前端·vue.js·架构
贺国亚1 小时前
AI制品Registry与发布门禁
面试
谢尔登1 小时前
【React】 状态管理方案
前端·react.js·前端框架
用户2136610035722 小时前
Vue商品详情与放大镜组件
前端·javascript
半个落月2 小时前
从Tapas小Demo理清localStorage、事件与this
前端·javascript