为什么需要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更简洁,适合实际部署
总结
- OSI是理论模型,帮助理解网络通信的分层协议
- TCP/IP是实际协议栈,支撑现代互联网的运行
- 两者关系:TCP/IP可视为
OSI
的简化版,但层间界限更模糊,更注重实用性
HTTP与WebSocket 的关系
HTTP,websocket等应用层协议,都是基于TCP协议来传输数据的,我们可以把这些高级协议理解成对 TCP的封装
对于 WebSocket 来说,它必须依赖HTTP协议进行一次握手,握手成功后,数据就直接从 TCP 通道传输,与HTTP无关了
简单来说,WS由两部分组成:握手
和 数据传输
握手
- 客户端请求握手
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
逐行解析:
- 请求行:使用HTTP get方法,请求路径为/chat,HTTP版本为1.1
- Host头:指定服务器域名
- Upgrade头:表明客户端希望升级到web socket 协议
- Connection头:值为 upgrade,表明这是一个协议升级请求
- Sec-Web-Socket-key:Base64编码的随机16字节值,用于安全认证
- Origin头:用于防止跨站攻击,标明请求来源
- Sec-WebSocket-Protocol:客户端支持的子协议列表
- Sec-WebSocket-Version:指定webSocket协议版本
- 服务器响应
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
逐行解析:
- 状态行:101状态码表示协议切换成功
- Upgrade头:确认升级到 webSocket协议
- Connection头:确认连接已升级
- Sec-WebSocket-Accept:服务器对客户端key的验证响应
- Sec-WebSocket-Protocol:服务器选择的子协议
- 关键机制说明:
-
协议升级:通过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连接准备前的的准备工作:
- 连接建立的基本流程:

客户端握手的小要求
-
基本要求:
- 请求格式:必须是符合RFC 2616(HTTP/1.1)定义的HTTP请求消息
- 请求方法:必须是get
- HTTP版本:必须大于1.1
-
必包含的头字段
- host字段:指定服务器的域名
- Upgrade:值必须包含websocket关键字
- Connection头字段:值必须包含upgrade指令
- Sec-WebSocket-Key:值是16字节随机数的Base64编码
- Sec-WebSocket-Key:头字段值必须是13,表示
WebSocket
协议版本是13
-
条件必须的头字段
- origin头字段:如果客户端时浏览器则必须包含
WebSocket
数据帧详解
-
发送数据的基本原则
-
数据帧的处理要求:
- 客户端发送的帧:必须进行掩码处理
- 服务端发送的帧:不能进行掩码处理
- 违规处理:如果违反上述规则,对方应当发送关闭帧终止连接
-
帧的基本组成:
- 帧类型标识码(Opcode)
- 负载长度(payload length)
- 负载内容(payload):包括扩展数据和应用数据
-
-
帧类型
-
帧类型由4位Opcode表示,收到Opcode应当立即断开连接
-
数据帧类型
0x0
-继续帧:表示当前帧是消息片段,需要与上一个非0x0帧拼接,常用于大消息分片传输0x1
-文本帧:携带UTF-8编码的文本数据0x2
-二进制帧:图片,音频等任意二进制数据0x3-0x7
-保留值:未未来非控制帧预留
-
控制帧类型
0x8
-关闭连接帧:0x9-ping
帧:内容可以是任意数据,用于心跳检测/保活机制0xA-pong
帧:对ping的响应,内容应与ping帧相同0xB-0xF
保留值:位未来控制帧预留
-
-
帧格式概述
-
基础头部(2字节):
- FIN(一位):是否为最终帧
RSV1-3
(扩展用)- Opcode(4位):帧类型
- Mask(1位):是否掩码
- Payload(7位):基础长度
-
扩展长度(可选)
-
掩码密钥:4字节,仅客户端发送时存在
-
有效载荷数据:
- 扩展数据:如协商的压缩数据
- 应用数据:实际传输内容
-
-
示例
- 大文件分片传输:
iniFrame1: FIN=0, Opcode=2, Payload=[二进制数据第一部分] Frame2: FIN=0, Opcode=0, Payload=[第二部分] Frame3: FIN=1, Opcode=0, Payload=[最后部分]
项目应用方面的思考
思考一:WebSocket 的连接建立过程,以及在前端代码中如何检测连接状态和处理断线重连
-
首先,前端通过 WebSocket API 创建连接
iniconst ws = new WebSocket('ws://localhost:8080');
-
连接确立后,WebSocket 会触发
onopen
事件,表示连接成功。 -
在项目中,我们可以自定义一个Hook用来统一管理这些连接状态:
onopen
:连接成功onclose
:连接关闭onerror
:连接异常onmessage
:连接异常
iniws.onopen = () => setStatus('connected'); ws.onclose = () => setStatus('disconnected'); ws.onerror = () => setStatus('error');
-
短线重连处理:当检测到
onclose
或onerror
时,可以重新设置定时器尝试重新连接;或者我们可以用 useEffect 监听状态变化,实现自动重连scssws.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 通信的安全性
- 使用 wss 协议:使用了 TLS/SSL 加密,与 HTTPS 类似。
- 身份验证与授权:在 WebSocket 握手时,在协议头中携带一个认证令牌 token ,服务器在接受连接前会验证这个 token 的有效性,可以防止未经授权的用户连接到 WebSocket 服务。
- 限制连接来源(CORS):服务端可以检查 WebSocket 请求的 Origin 头,只允许来自特定域名,防止恶意网站的攻击。