websocket 实现原理和技术方案

websocket

WebSocket 是 HTML5 开始提供的一种浏览器与服务器进行全双工通讯的网络技术,用以取代轮询与长连接,使客户端浏览器具备像 C/S 框架下桌面系统的即使通讯能力

websocket协议是建立在tcp协议之上的,建立连接需要三次握手。

websocket协议的连接过程:

  1. 客户端连接服务器(TCP / IP),三次握手,建立了连接通道
  2. 客户端发送一个http格式的消息(特殊格式),服务器也响应一个http格式的消息(特殊格式),称之为http握手
  3. 客户端与服务器完成握手,开始双向通信
  4. 客户端或服务器断开,通道销毁

特点

  1. 建立在tcp协议之上的,建立连接需要三次握手
  2. 通信格式是二进制,可以传输任意数据
  3. 通信格式是http协议,可以传输任意数据

因为 websocket 是基于(TCP / IP)协议,所以我们的代码示例使用nodejs的net模块。我们的示例代码只演示了websocket的握手过程,并没有实现完整的通信。如果想要实现完整的通信,需要深入理解WebSocket协议的工作原理,包括消息帧的解析、心跳机制、分片消息的传输等等。可以参考以下内容

RFC6455:websocket规范

规范:数据帧掩码细节

规范:数据帧格式

server-example

编写websocket服务器

对网络基础设施的攻击(数据掩码操作所要预防的事情)

What is Sec-WebSocket-Key for?

10.3. Attacks On Infrastructure (Masking)

Why are WebSockets masked?

How does websocket frame masking protect against cache poisoning?

What is the mask in a WebSocket frame?

代码示例

客户端

javascript 复制代码
// 创建websocket连接
const ws = new WebSocket('ws://127.0.0.1:5008')
// 监听连接成功事件
ws.onopen = function () {
    console.log('连接成功');
}
// 监听接受消息事件
ws.onmessage = function (e) {
    console.log('接受消息');
}
// 监听连接关闭事件
ws.onclose = function () {
    console.log('连接关闭');
}
// 监听连接失败事件
ws.onerror = function () {
    console.log('连接失败');
}

服务器

ini 复制代码
const net = require('net');
const server = net.createServer((socket) => {
    console.log("收到客户端的连接");
    socket.once('data', (chunk) => {
        const hearder = parseRequestHeader(chunk.toString('utf-8'));
        const crypto = require("crypto");
        const hash = crypto.createHash("sha1");
        hash.update(
            hearder['Sec-WebSocket-Key'] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
        );
        const key = hash.digest("base64");
        socket.write(`HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: ${key}
`);
            socket.on('data', (chunk) => {
                console.log(chunk);
            })
    });
});
server.listen(5008);
/**
 * 解析请求头
 * @param {string} hearderStr 
 */
function parseRequestHeader(hearderStr = '') {
    
    const hearders = hearderStr.split('\r\n');
   
    hearders.shift();
    return Object.fromEntries(hearders
        .filter(hearder => hearder)
        .map(hearder => {
            const i = hearder.indexOf(":");
            return [hearder.substring(0, i), hearder.substring(i + 1).trim()];
        }));
};

websocket 扩展知识点

websocket 连接服务端,三次握手建立通道,客户端还需要发送一个特殊格式的http请求。

该格式包含 http1.1 协议升级 和 websocket 的请求头。

yaml 复制代码
GET ws://127.0.0.1:5500/public/index.html/ws HTTP/1.1
Host: 127.0.0.1:5500
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Upgrade: websocket
Origin: http://127.0.0.1:5500
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Sec-WebSocket-Key: Re+vR4i5EJmWPU3H7horvg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

Sec-WebSocket-Key 和 Sec-WebSocket-Extensions 为 websocket 请求头

Upgrade: websocket 告诉服务端 将 http1.1 协议升级为 websocket 协议

Connection: Upgrade 告诉服务端 连接升级

http1.1 协议升级

HTTP/1.1 协议提供了一种使用 Upgrade (en-US) 标头字段的特殊机制,这一机制允许将一个已建立的连接升级成新的、不相容的协议

这个机制是可选的;它并不能强制协议的更改(通常来说这一机制总是由客户端发起的)。如果它们支持新协议,实现甚至可以不利用 upgrade,在实践中,这种机制主要用于引导 WebSocket 连接。

因为 Upgrade 是一个逐跳(Hop-by-hop)标头,它还需要在 Connection 标头字段中列出

如果服务器决定升级这次连接,就会返回一个 101 Switching Protocols 响应状态码,和一个要切换到的协议的标头字段 Upgrade

HTTP/2 明确禁止使用此机制;这个机制只属于 HTTP/1.1

websocket 请求头

请求标头

  • Sec-WebSocket-Extensions 用于指定一个或多个请求服务器使用的协议级 WebSocket 扩展。允许在一个请求中使用多个 Sec-WebSocket-Extension 标头;结果跟在一个标头文件中包含了所有列出的扩展一样。
  • Sec-WebSocket-Key 该标头向服务器提供确认客户端有权请求升级到 WebSocket 的所需信息。当不安全(HTTP)客户端希望升级时,可以使用该标头,以提供一定程度防止滥用的保护。密钥的值是使用 WebSocket 规范中定义的算法计算的,不提供安全性。
  • Sec-WebSocket-Version 指定客户端支持的 WebSocket 版本。
  • Sec-WebSocket-Protocol 指定你希望用的一个或者多个 WebSocket 协议。

响应标头

  • Sec-WebSocket-Version 如果服务器无法使用指定版本的 Websocket 协议进行通信,它将响应一个错误(例如 426 Upgrade Required),该错误在它的标头中包含一个 Sec-WebSocket-Version 标头,其中包含支持的逗号分隔列表的协议版本。如果服务器确实支持请求的协议版本,则响应中不包含 Sec-WebSocket-Version 标头
  • Sec-WebSocket-Accept 服务器端响应的标头,用于确认客户端请求。如果提供了 Sec-WebSocket-Key 标头,那么将通过以下流程计算此标头的值:首先取密钥的值,然后将该值与"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"进行拼接,再取拼接后的字符串的 SHA-1 哈希。最后对得出的 20 字节的值进行 base64 编码以获得该属性的值。

第三方库

  • ws

    • ws 是一个快速、易于使用、文档齐全且经过全面测试的 WebSocket 客户端和服务器实现,几乎支持所有浏览器
  • socket.io

    • socket.io 可以在每个平台、每个浏览器和每个设备上工作。
相关推荐
deli00739 分钟前
手机拍灯为什么有一条条黑纹?PWM 240Hz 实测
前端
wangruofeng5 小时前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
wangruofeng6 小时前
给前端开发者的 100 个设计参考网站
前端·设计
星栈9 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技9 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户47949283569159 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
风骏时光牛马11 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng12 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
清晨Coding13 小时前
为什么用 React Developer Tools 查看组件树这么乱?
前端·react.js
鶴哥只手遮天13 小时前
《Electron 45.2.3 内核解剖》第 4 章 补丁体系:380 个补丁改了什么
前端·electron