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

websocket建立连接过程

1. 客户端发送一个GET的http请求,请求头要包含

connection: upgrade

host:localhost:8000。表明地址

upgrade: websocket。指明升级的协议

sec-websocket-key 。 安全验证密钥

sec-websocket-version。 协议版本

sec-websocket-accept 。对传过来的key进行加密后(sha-1)后base64编码,用于告诉客户端自身合法。

响应状态码为101,标识切换协议。

在 WebSocket 连接建立的 “HTTP 握手阶段”,客户端请求头和服务器响应头包含多个专用字段,这些字段是实现 “协议升级” 和 “连接安全验证” 的核心。以下逐一拆解各字段的作用,结合之前的握手示例(请求 + 响应)展开说明:

一、客户端请求头(发起协议升级)

客户端发送的是 HTTP GET 请求,但通过特殊头字段告知服务器:“我希望将当前 HTTP 连接升级为 WebSocket 连接”。核心字段及作用如下:

字段名示例值核心作用细节说明
GET(请求方法)GET / HTTP/1.1触发协议升级的基础请求WebSocket 握手仅支持 GET 方法,因为无需向服务器提交数据,仅需 “发起连接请求”。路径(/)通常与 WebSocket 服务绑定的路径一致(如 ws://localhost:8080/chat 对应 GET /chat)。
Hostlocalhost:8080指定目标服务器地址与普通 HTTP 请求一致,用于服务器在多域名部署时识别 “客户端要连接哪个服务”(如反向代理场景)。
ConnectionUpgrade声明 “要升级连接类型”必须设置为 Upgrade,告知服务器:“这不是普通 HTTP 请求,而是要升级连接协议的请求”。
Upgradewebsocket声明 “要升级到的协议”核心字段,明确指定升级目标是 websocket 协议(区分于其他可能的升级协议,如 HTTP/2)。
Sec-WebSocket-KeydGhlIHNhbXBsZSBub25jZQ==安全验证的随机密钥1. 客户端生成的 16 字节随机字符串(经 Base64 编码后的值);
2. 服务器需将该值与固定 GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)拼接,再通过 SHA-1 哈希、Base64 编码,生成 Sec-WebSocket-Accept 响应值;
3. 作用:防止 “伪 WebSocket 请求”(如普通 HTTP 请求伪造升级意图),确保服务器确实理解 WebSocket 协议。
Sec-WebSocket-Version13声明支持的 WebSocket 版本必须设置为 13(当前主流标准版本),低版本(如 8、10)已被淘汰。服务器若不支持该版本,会返回 426 Upgrade Required 错误,并在 Sec-WebSocket-Version 头中告知支持的版本。
Origin(可选)http://localhost:8080声明请求来源仅在浏览器环境下发送(如前端页面跨域连接时),服务器可通过该字段做 “跨域权限校验”(如允许 http://example.com 来源的连接)。
Sec-WebSocket-Protocol(可选)chat, game声明支持的子协议客户端告知服务器 “希望使用的 WebSocket 子协议”(如自定义的 chat 协议用于聊天,game 协议用于游戏)。服务器若支持,会在响应头中返回匹配的子协议;若不支持,会忽略该字段。
Sec-WebSocket-Extensions(可选)permessage-deflate声明支持的扩展客户端告知服务器 “希望启用的 WebSocket 扩展”(如 permessage-deflate 用于数据压缩),减少传输体积。服务器需在响应中确认启用的扩展。

二、服务器响应头(同意协议升级)

服务器验证请求头合法后,返回 101 Switching Protocols 响应,标志 “HTTP 连接正式升级为 WebSocket 连接”。核心字段及作用如下:

字段名示例值核心作用细节说明
HTTP/1.1 101 Switching Protocols(状态码)-确认协议升级唯一合法的状态码,含义是 “服务器同意客户端的协议升级请求”。若返回其他状态码(如 400、426),表示握手失败。
ConnectionUpgrade呼应客户端的升级声明必须与客户端的 Connection: Upgrade 一致,确认 “连接类型将升级”。
Upgradewebsocket确认升级到 WebSocket 协议与客户端的 Upgrade: websocket 一致,明确告知客户端 “协议升级目标已确认”。
Sec-WebSocket-Accepts3pPLMBiTxaQ9kYGzzhZRbK+xOo=验证通过的凭证1. 由客户端的 Sec-WebSocket-Key 计算而来(规则:Key + GUID → SHA-1 哈希 → Base64 编码);
2. 客户端收到后会反向验证:若计算结果与该值一致,说明服务器确实理解 WebSocket 协议,握手成功;否则握手失败,关闭连接;
3. 作用:防止 “中间人攻击” 或 “错误连接”,确保通信双方均支持 WebSocket。
Sec-WebSocket-Protocol(可选)chat确认启用的子协议若客户端发送了 Sec-WebSocket-Protocol,服务器需从中选择一个支持的子协议返回(如选 chat);若不支持任何子协议,可忽略该字段(客户端需处理 “无可用子协议” 的情况)。
Sec-WebSocket-Extensions(可选)permessage-deflate确认启用的扩展若客户端发送了 Sec-WebSocket-Extensions,服务器需返回 “同意启用的扩展”(如 permessage-deflate);若不支持,可忽略该字段(客户端不启用扩展)。
Access-Control-Allow-Origin(可选)http://localhost:8080跨域权限控制仅在跨域场景下需要(如前端页面部署在 http://a.com,连接 ws://b.com)。服务器通过该字段允许指定来源的客户端连接,避免跨域限制。

三、关键字段的 “协同逻辑”(为什么需要这些字段?)

WebSocket 握手的核心是 “安全地完成协议升级”,避免与普通 HTTP 请求混淆,以下是关键字段的协同逻辑:

  1. Connection: Upgrade + Upgrade: websocket
    这对字段是 “升级信号”,明确告知服务器 “这不是普通 HTTP 请求”,而是要切换到 WebSocket 协议。
  2. Sec-WebSocket-Key + Sec-WebSocket-Accept
    这对字段是 “安全验证”,确保服务器确实支持 WebSocket(而非普通 HTTP 服务器误响应),同时防止客户端连接到错误的服务。
  3. Sec-WebSocket-Version
    确保客户端与服务器使用相同的 WebSocket 标准版本,避免版本不兼容导致通信失败。

四、握手失败的常见场景(字段错误导致)

若请求 / 响应头字段不合法,握手会立即失败,连接关闭。常见场景包括:

  • 客户端未发送 Upgrade: websocket 或 Connection: Upgrade → 服务器视为普通 HTTP 请求,返回 400 错误;
  • 客户端 Sec-WebSocket-Version 不是 13 → 服务器返回 426 错误,并告知支持的版本;
  • 服务器计算的 Sec-WebSocket-Accept 与客户端预期不符 → 客户端判定服务器不支持 WebSocket,关闭连接;
  • 跨域场景下服务器未返回 Access-Control-Allow-Origin → 浏览器因跨域限制,拒绝建立连接。

通过以上字段的协作,WebSocket 才能安全、可靠地完成从 HTTP 到 WebSocket 协议的升级,为后续的双向实时通信奠定基础。

代码demo

服务端

const WebSocket = require('ws');
const http = require('http');
const fs = require('fs');// 创建 HTTP 服务器提供客户端页面
const server = http.createServer((req, res) => {if (req.url === '/') {res.writeHead(200, { 'Content-Type': 'text/html' });res.end(fs.readFileSync('client.html'));} else {res.writeHead(404);res.end();}
});// 创建 WebSocket 服务器,附加到 HTTP 服务器
const wss = new WebSocket.Server({ server });// 监听连接事件
wss.on('connection', (ws) => {console.log('客户端已连接');// 向客户端发送欢迎消息ws.send(JSON.stringify({ type: 'system', message: '欢迎连接到 WebSocket 服务器!' }));// 监听客户端消息ws.on('message', (data) => {console.log(`收到客户端消息: ${data}`);// 解析客户端消息try {const message = JSON.parse(data);// 回复客户端ws.send(JSON.stringify({type: 'reply',message: `服务器已收到: ${message.content}`,timestamp: new Date().toISOString()}));} catch (e) {ws.send(JSON.stringify({type: 'error',message: '无效的消息格式'}));}});// 监听连接关闭ws.on('close', () => {console.log('客户端已断开连接');});// 监听错误ws.on('error', (error) => {console.error('WebSocket 错误:', error);});
});// 启动服务器
const PORT = 8080;
server.listen(PORT, () => {console.log(`服务器运行在 http://localhost:${PORT}`);console.log(`WebSocket 服务已启动,等待连接...`);
});

客户端:

<!DOCTYPE html>
<html><head><title>WebSocket 示例</title><style>.container {max-width: 800px;margin: 0 auto;padding: 20px;}#messages {border: 1px solid #ccc;height: 400px;overflow-y: auto;margin-bottom: 20px;padding: 10px;}.message {margin: 5px 0;padding: 8px;border-radius: 4px;}.system {background-color: #f0f0f0;}.incoming {background-color: #e1f5fe;}.outgoing {background-color: #e8f5e9;text-align: right;}.error {background-color: #ffebee;}#inputArea {display: flex;gap: 10px;}#messageInput {flex-grow: 1;padding: 8px;}button {padding: 8px 16px;}</style>
</head><body><div class="container"><h1>WebSocket 通信示例</h1><div id="connectionStatus">未连接</div><div id="messages"></div><div id="inputArea"><input type="text" id="messageInput" placeholder="输入消息..."><button onclick="connectWebSocket()">连接</button><button onclick="disconnectWebSocket()">断开</button><button onclick="sendMessage()">发送</button></div></div><script>let ws;const messagesDiv = document.getElementById('messages');const statusDiv = document.getElementById('connectionStatus');const messageInput = document.getElementById('messageInput');// 连接 WebSocket 服务器function connectWebSocket() {// 关闭已有的连接if (ws) {ws.close();}// 创建 WebSocket 连接// 注意:ws:// 对应 HTTP,wss:// 对应 HTTPSws = new WebSocket('ws://localhost:8080');// 连接建立事件ws.onopen = () => {console.log('WebSocket 连接已建立');statusDiv.textContent = '已连接';statusDiv.style.color = 'green';addMessage('系统消息:连接已建立', 'system');};// 接收消息事件ws.onmessage = (event) => {console.log('收到消息:', event.data);const message = JSON.parse(event.data);addMessage(message.message, message.type === 'error' ? 'error' : 'incoming');};// 连接关闭事件ws.onclose = (event) => {console.log(`WebSocket 连接已关闭,代码: ${event.code}, 原因: ${event.reason}`);statusDiv.textContent = '已断开';statusDiv.style.color = 'red';addMessage(`系统消息:连接已关闭 (${event.code})`, 'system');ws = null;};// 错误事件ws.onerror = (error) => {console.error('WebSocket 错误:', error);addMessage(`错误:${error.message}`, 'error');};}// 断开连接function disconnectWebSocket() {if (ws) {ws.close(1000, '客户端主动断开');}}// 发送消息function sendMessage() {if (!ws || ws.readyState !== WebSocket.OPEN) {alert('请先建立连接');return;}const message = messageInput.value.trim();if (!message) return;// 发送消息到服务器ws.send(JSON.stringify({content: message,timestamp: new Date().toISOString()}));// 在本地显示发送的消息addMessage(`我: ${message}`, 'outgoing');messageInput.value = '';}// 添加消息到界面function addMessage(text, type) {const messageDiv = document.createElement('div');messageDiv.className = `message ${type}`;messageDiv.textContent = text;messagesDiv.appendChild(messageDiv);// 滚动到底部messagesDiv.scrollTop = messagesDiv.scrollHeight;}// 监听回车键发送消息messageInput.addEventListener('keypress', (e) => {if (e.key === 'Enter') {sendMessage();}});</script>
</body></html>

请求头:

GET ws://localhost:8080/ HTTP/1.1
Host: localhost:8080
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/139.0.0.0 Safari/537.36
Upgrade: websocket
Origin: http://localhost:8080
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
Sec-WebSocket-Key: hoR5iuo6igGV8GdVrg6/hw==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

响应头

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: sYx+sebTITkvLKI5+SW8icTNWkc=

http://www.xdnf.cn/news/1387153.html

相关文章:

  • AI供应链优化+AI门店排班:蜜雪冰城降本20%、瑞幸提效的AI商业落地实战
  • 港科大开放世界长时域具身导航!LOVON:足式机器人开放词汇目标导航
  • LeetCode Hot 100 Python (1~10)
  • 1 分钟 Maya 动画渲染要多久?5 天还是 5 小时
  • linux系统学习(15.启动管理)
  • 第一百零二章:AI的“未来电影制片厂CEO”:多模态系统落地项目实战(完整 AI 视频创作平台)
  • 极飞科技AI智慧农业实践:3000亩棉田2人管理+产量提15%,精准灌溉与老农操作门槛引讨论
  • 组件的生命周期:`useEffect` 的威力与副作用处理
  • 随机森林的 “Bootstrap 采样” 与 “特征随机选择”:如何避免过拟合?(附分类 / 回归任务实战)
  • 华为云CCE的Request和Limit
  • Ethercat主从站移植时的问题记录(二)—无法进入OP状态且从站PDI中断不触发
  • 什么是Jmeter? Jmeter工作原理是什么?
  • linux上安装methylkit -- 安全下车版 (正经版: Linux环境下安装methylKit的实践与避坑指南)
  • springboot java开发的rocketmq 顺序消息保证
  • CAN总线(Controller Area Network Bus)控制器局域网总线(二)
  • 无人机图传模块原理及作用——开启飞行视野的关键技术
  • 第二阶段WinForm-9:委托复习
  • 应用转生APP:无需Root权限的应用双开和Xposed模块加载工具
  • 计算机是如何运行的
  • [AI人脸替换] docs | 环境部署指南 | 用户界面解析
  • c++ template
  • OpenCSG月度更新2025.8
  • 电影交流|基于SprinBoot+vue的电影交流平台小程序系统(源码+数据库+文档)
  • C++基础(④链表反转(链表 + 迭代 / 递归))
  • 公司内网部署离线deepseek+docker+ragflow本地模型实战
  • SpringBoot整合Spring WebFlux弃用自带的logback,使用log4j2,并启动异步日志处理
  • Python计算点云的均值、方差、标准差、凸点(顶点)、质心和去中心化
  • Docker03-知识点整理
  • Go Vendor 和 Go Modules:管理和扩展依赖的最佳实践
  • 项目一系列-第9章 集成AI千帆大模型