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?

代码示例

客户端

// 创建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('连接失败');
}

服务器

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 的请求头。

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 可以在每个平台、每个浏览器和每个设备上工作。

原文链接:https://juejin.cn/post/7337518755918200867 作者:静水流深_沧海一粟

(0)
上一篇 2024年2月21日 上午10:32
下一篇 2024年2月21日 上午10:42

相关推荐

发表评论

登录后才能评论