当前位置: 首页 > news >正文

http 基于 websocket 协议通信

1. 基础概念

1.1. WebSocket 和 TCP/IP 模型

WebSocket 是一种在 OSI 模型中位于应用层的协议,而 TCP/IP 模型中分为四层:网络接口层、网络层、传输层、应用层。TCP/IP 模型中 WebSocket 和 HTTP 都工作在应用层,但两者的交互模型不同:

  • HTTP:请求-响应模式,每次通信都需要重新建立连接。

  • WebSocket:在 HTTP 握手后,使用单个 TCP 连接进行全双工的通信,服务器和客户端可以随时互相发送消息。

1.2. WebSocket 的工作原理

  • HTTP 握手:WebSocket 建立时首先会通过 HTTP 完成一次握手请求,客户端向服务器发送包含 Upgrade 头的 HTTP 请求,表明希望升级连接到 WebSocket 协议。

  • 协议升级:服务器检查请求头,确认可以升级,并返回 101 状态码,表示协议切换成功。此时,HTTP 请求转变为 WebSocket 双向通信。

  • 数据帧传输:建立 WebSocket 连接后,通信不再是 HTTP 报文,而是使用一种特殊的二进制数据帧格式进行通信。WebSocket 使用较轻的帧协议,客户端和服务器可以自由地在该连接上发送和接收消息。

1.3. WebSocket 握手详解

1.3.1. 请求(客户端发起的握手请求)

客户端发送一个 HTTP 请求,带有特殊的 WebSocket 头部。一个典型的握手请求如下:

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
  • Upgrade: websocket:告诉服务器希望升级协议到 WebSocket。

  • Connection: Upgrade:标记为升级连接。

  • Sec-WebSocket-Key:Base64 编码的随机值,由服务器用于计算响应的 Sec-WebSocket-Accept。

  • Sec-WebSocket-Version:指定 WebSocket 协议版本。

1.3.2. 响应(服务器端的握手响应)

服务器验证了请求并准备升级协议时,会返回类似以下响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

101 Switching Protocols:HTTP 状态码,表明协议正在切换。

Sec-WebSocket-Accept:根据客户端的 Sec-WebSocket-Key 计算得出的 Base64 编码字符串,表明握手成功。

2. 实现 WebSocket 握手和通信

接下来,我们用 http 模块实现基本的 WebSocket 协议。我们将通过 HTTP 完成握手,之后转入 WebSocket 的二进制通信。

2.1. 创建 HTTP 服务器并实现 WebSocket 握手

const http = require('http');
const crypto = require('crypto');// 创建HTTP服务器
const server = http.createServer((req, res) => {// 不处理常规HTTP请求res.writeHead(400);res.end('This is a WebSocket server!');
});// 监听 upgrade 事件,处理 WebSocket 协议升级
server.on('upgrade', (req, socket, head) => {const key = req.headers['sec-websocket-key'];const acceptKey = generateAcceptValue(key);// 构造 WebSocket 握手响应const responseHeaders = ['HTTP/1.1 101 Switching Protocols','Upgrade: websocket','Connection: Upgrade',`Sec-WebSocket-Accept: ${acceptKey}`];// 将响应头写入,并升级连接socket.write(responseHeaders.join('\r\n') + '\r\n\r\n');// 接下来可以监听和发送 WebSocket 帧socket.on('data', buffer => {// 处理 WebSocket 数据帧(后续将详细说明)console.log('Received data:', buffer);});// 发送 WebSocket 帧const message = 'Hello WebSocket';const frame = createWebSocketFrame(message);socket.write(frame);
});// 生成 Sec-WebSocket-Accept 值
function generateAcceptValue(secWebSocketKey) {return crypto.createHash('sha1').update(secWebSocketKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64');
}// WebSocket 帧创建(文本帧)
function createWebSocketFrame(data) {const message = Buffer.from(data);const frame = Buffer.alloc(message.length + 2);// 0x81 表示文本帧(FIN + 1 表示文本帧)frame[0] = 0x81;frame[1] = message.length;message.copy(frame, 2);return frame;
}server.listen(8080, () => {console.log('WebSocket server running on port 8080');
});

2.2. 握手解析

  • 当客户端发起连接时,服务器通过监听 upgrade 事件响应协议升级请求。

  • 在协议升级时,服务器使用 Sec-WebSocket-Key 和 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 这串固定字符串生成一个 Base64 编码的 SHA1 哈希,作为 Sec-WebSocket-Accept 头的值。

  • 握手成功后,服务器将升级连接,并准备处理 WebSocket 数据帧。

2.3. WebSocket 数据帧格式

WebSocket 的数据通信是通过帧来进行的,每一帧都包含了如下字段:

  • FIN:标志帧是否是消息的最后一帧。

  • Opcode:定义了数据的类型,如文本帧、二进制帧等。

  • Mask:掩码标志,客户端发送给服务器的数据必须进行掩码处理。

  • Payload:实际传输的数据。

2.4. 处理 WebSocket 帧

在服务器接收到数据时,需要解码 WebSocket 帧。根据协议规范,我们解析接收到的二进制帧。

// 解析 WebSocket 帧
function parseWebSocketFrame(buffer) {const fin = buffer[0] & 0x80; // FIN bitconst opcode = buffer[0] & 0x0f; // Opcodeconst masked = buffer[1] & 0x80; // Mask bitlet payloadLength = buffer[1] & 0x7f; // Payload Lengthlet offset = 2;if (payloadLength === 126) {payloadLength = buffer.readUInt16BE(2);offset += 2;} else if (payloadLength === 127) {payloadLength = buffer.readUInt32BE(2); // For Larger payloadoffset += 8;}const mask = masked ? buffer.slice(offset, offset + 4) : null;offset += masked ? 4 : 0;let payload = buffer.slice(offset, offset + payloadLength);// 如果有掩码,需要解码 payloadif (masked) {payload = payload.map((byte, i) => byte ^ mask[i % 4]);}return payload.toString();
}

在服务器接收到客户端的帧后,我们解析出消息内容,并根据需求进行处理。

通过 http 模块直接实现 WebSocket 服务器,涉及到了:

  • WebSocket 握手机制:使用 HTTP 协议完成初始的握手,并通过升级协议从 HTTP 转为 WebSocket。

  • TCP 长连接:WebSocket 在 HTTP 握手后,建立在单个 TCP 连接上,实现全双工通信。

  • WebSocket 数据帧协议:通过帧的结构定义消息传输的格式,服务器需解析和处理这些帧。

3. socket.io

socket.io 是一个广泛使用的 JavaScript 库,专门用于实现实时、双向的通信。与 WebSocket 类似,socket.io 建立在 TCP 之上,通过使用 WebSocket 和其他协议(如长轮询)来确保实时通信的顺利进行。socket.io 不仅简化了 WebSocket 的实现,而且提供了额外的功能,如自动重连、命名空间、事件广播等,因此它成为许多实时应用的首选。

  • WebSocket:一种在客户端和服务器之间建立全双工通信的协议,适合实时数据传输。socket.io 使用 WebSocket 来进行通信,但在无法使用 WebSocket 的环境中,它会降级到其他协议,如 HTTP 长轮询。

  • 命名空间:socket.io 可以通过命名空间来分离不同的通信通道,这样不同的业务逻辑可以运行在不同的命名空间中,避免混乱。

  • 房间:socket.io 可以将不同的连接分组到房间中,一个房间中的所有客户端可以相互通信,这在实现聊天应用时非常有用。

3.1. socket.io 的优势

  • 跨协议支持:如果 WebSocket 不可用,socket.io 可以回退到轮询等其他机制,确保连接的稳定性。

  • 事件驱动:socket.io 使用事件驱动的方式通信,可以方便地绑定各种事件,像 message、connect、disconnect 等。

  • 自动重连:当连接丢失时,socket.io 提供自动重连机制,确保客户端和服务器保持连接。

3.2. 服务端与客户端基本实现

3.2.1. 安装 socket.io

首先,我们需要安装 socket.io 和 socket.io-client 两个库,分别用于服务端和客户端。

npm install socket.io
npm install socket.io-client

3.2.2. 创建 socket.io 服务端

在服务端,我们使用 http 模块创建一个 HTTP 服务器,然后在其基础上初始化一个 socket.io 实例。

const http = require('http');
const { Server } = require('socket.io');// 创建HTTP服务器
const server = http.createServer((req, res) => {res.writeHead(200, { 'Content-Type': 'text/plain' });res.end('socket.io Server');
});// 初始化socket.io服务器
const io = new Server(server);io.on('connection', (socket) => {console.log(`A user connected [ID: ${socket.id}]`);// 接收客户端消息socket.on('message', (data) => {console.log(`Received message: ${data}`);socket.emit('response', `Server received your message: ${data}`);});// 监听断开连接socket.on('disconnect', () => {console.log('User disconnected');});
});// 启动服务器
server.listen(3000, () => {console.log('socket.io server is running on port 3000');
});

3.2.3. 创建 socket.io 客户端

客户端可以使用 socket.io-client 库与服务器建立实时连接。以下是一个在浏览器端实现的例子:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>socket.io Client</title><script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
</head>
<body><h1>socket.io Client</h1><input id="messageInput" type="text" placeholder="Type your message"><button id="sendButton">Send</button><div id="response"></div><script>// 创建socket.io客户端const socket = io('http://localhost:3000');// 发送消息const sendButton = document.getElementById('sendButton');const messageInput = document.getElementById('messageInput');sendButton.addEventListener('click', () => {const message = messageInput.value;socket.emit('message', message);});// 接收服务器的响应socket.on('response', (data) => {document.getElementById('response').textContent = data;});</script>
</body>
</html>

3.2.4. 命名空间和房间

1. 使用命名空间

命名空间允许我们将一个 socket.io 实例划分为多个逻辑空间。客户端可以选择连接到不同的命名空间。

// 服务端:命名空间
const chatNamespace = io.of('/chat');chatNamespace.on('connection', (socket) => {console.log('User connected to /chat');socket.on('message', (data) => {chatNamespace.emit('message', data);});
});// 客户端:连接到指定命名空间
const socket = io('http://localhost:3000/chat');

2. 使用房间

房间允许我们将连接分组,可以实现针对特定房间的消息广播。

io.on('connection', (socket) => {// 加入房间socket.join('room1');// 向房间内的所有客户端发送消息socket.to('room1').emit('message', 'A new user has joined room1');socket.on('message', (data) => {io.to('room1').emit('message', data);});
});

3.2.5. 深入探讨

1. socket.io 与 WebSocket 的区别

  • 协议回退:WebSocket 仅支持 WebSocket 协议,而 socket.io 可以在 WebSocket 不可用时回退到 HTTP 长轮询等其他协议,保证更高的连接可靠性。

  • 消息格式:WebSocket 使用帧进行消息传输,socket.io 则在 WebSocket 之上增加了更多的控制帧,使其更适合构建复杂的应用。

  • 事件机制:socket.io 提供了事件驱动的通信模型,便于开发者处理各种业务逻辑。而 WebSocket 是基于较底层的 API。

2. 使用场景

socket.io 非常适合需要实时通信的应用场景,例如:

  • 实时聊天应用:用户可以在多个房间中发送消息,服务器可以立即将消息广播给其他用户。

  • 在线游戏:服务器可以实时同步游戏状态,保证玩家间的互动体验。

  • 实时数据推送:例如股票市场更新、体育赛事直播等需要推送最新信息的应用场景。


文章转载自:

http://Z0CvApbz.rcsbz.cn
http://23D0Sf1S.rcsbz.cn
http://vT3jvhIs.rcsbz.cn
http://yGtLVHZp.rcsbz.cn
http://Z1Xt1Btd.rcsbz.cn
http://W2JdGpgo.rcsbz.cn
http://Kyoop8nl.rcsbz.cn
http://FTK0bdMJ.rcsbz.cn
http://KQKdkXy9.rcsbz.cn
http://3B3agnsp.rcsbz.cn
http://ld3ajaBM.rcsbz.cn
http://0lJ0QfDZ.rcsbz.cn
http://5LLB8ZlD.rcsbz.cn
http://7qsHR0tr.rcsbz.cn
http://t2c5naqa.rcsbz.cn
http://14ExDDBN.rcsbz.cn
http://lE9nsgUN.rcsbz.cn
http://btTa4NKq.rcsbz.cn
http://eOFbzTgU.rcsbz.cn
http://adaIO6lY.rcsbz.cn
http://bkmwZbEI.rcsbz.cn
http://Db4iYaZh.rcsbz.cn
http://qgkOSZj9.rcsbz.cn
http://b0ZoFmzB.rcsbz.cn
http://vOX5hnmn.rcsbz.cn
http://vwFpojfD.rcsbz.cn
http://qwd3KANx.rcsbz.cn
http://nE7Fc5eL.rcsbz.cn
http://ySZsqg4v.rcsbz.cn
http://CDlM3AYv.rcsbz.cn
http://www.dtcms.com/a/388142.html

相关文章:

  • 媒体发稿渠道选择难?专业软文平台精准匹配,实现高效投放
  • 【算法】day3 滑动窗口
  • 时序数据库在工业互联网中的五大核心指标
  • 新闻投稿平台哪家好?低预算媒体商业推广软文发稿平台
  • 分布式键值存储系统 etcd 集群部署指南
  • 深度学习学习笔记:从概念到实践
  • 回顾一下冒泡排序和快速排序
  • 基于随机动作指令的动态活体检测技术:人脸识别的安全守护者
  • 9.17 学习记录
  • 桥接、NAT和仅主机模式【介绍】
  • C语言程序从开发到单片机执行:编译、存储与运行机制详解
  • 利用云手机实现热血江湖游戏多开
  • Leetcode学习(灵神精讲题)167. 两数之和 II - 输入有序数组(相向双指针)
  • 力扣习题哈希表篇:两句话中不常见单词
  • 分布式流处理与消息传递——向量时钟 (Vector Clocks) 算法详解
  • 车载诊断架构 --- 无车辆识别码(VIN)时的车辆声明报文规范
  • 解读智慧政务云计算数据中心建设方案【附全文阅读】
  • 潜水员戴夫团队新作《纳克园 最后的乐园》开发顺利!
  • 第十八章 Arm C1-Premium Core 嵌入式追踪扩展 (ETE) 详解
  • 理解 multipart/form-data 中的 boundary:文件上传的关键
  • rust中的“继承”
  • PAT乙级_1087 有多少不同的值_Python_AC解法_无疑难点
  • 007 Rust字符串
  • 使用 Compose 部署 WordPress
  • Golang语言入门篇006_关键字与保留字详解
  • Class60 Transformer
  • Redis 线上故障案例分析:从救火到防火的实战指南
  • uv虚拟环境起名
  • YASKAWA安川机器人铝材焊接节气之道
  • 2025 AIME Benchmark:AI 在奥数领域的最新进展