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 )。 |
Host | localhost:8080 | 指定目标服务器地址 | 与普通 HTTP 请求一致,用于服务器在多域名部署时识别 “客户端要连接哪个服务”(如反向代理场景)。 |
Connection | Upgrade | 声明 “要升级连接类型” | 必须设置为 Upgrade ,告知服务器:“这不是普通 HTTP 请求,而是要升级连接协议的请求”。 |
Upgrade | websocket | 声明 “要升级到的协议” | 核心字段,明确指定升级目标是 websocket 协议(区分于其他可能的升级协议,如 HTTP/2 )。 |
Sec-WebSocket-Key | dGhlIHNhbXBsZSBub25jZQ== | 安全验证的随机密钥 | 1. 客户端生成的 16 字节随机字符串(经 Base64 编码后的值); 2. 服务器需将该值与固定 GUID( 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 )拼接,再通过 SHA-1 哈希、Base64 编码,生成 Sec-WebSocket-Accept 响应值;3. 作用:防止 “伪 WebSocket 请求”(如普通 HTTP 请求伪造升级意图),确保服务器确实理解 WebSocket 协议。 |
Sec-WebSocket-Version | 13 | 声明支持的 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),表示握手失败。 |
Connection | Upgrade | 呼应客户端的升级声明 | 必须与客户端的 Connection: Upgrade 一致,确认 “连接类型将升级”。 |
Upgrade | websocket | 确认升级到 WebSocket 协议 | 与客户端的 Upgrade: websocket 一致,明确告知客户端 “协议升级目标已确认”。 |
Sec-WebSocket-Accept | s3pPLMBiTxaQ9kYGzzhZRbK+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 请求混淆,以下是关键字段的协同逻辑:
Connection: Upgrade
+Upgrade: websocket
:
这对字段是 “升级信号”,明确告知服务器 “这不是普通 HTTP 请求”,而是要切换到 WebSocket 协议。Sec-WebSocket-Key
+Sec-WebSocket-Accept
:
这对字段是 “安全验证”,确保服务器确实支持 WebSocket(而非普通 HTTP 服务器误响应),同时防止客户端连接到错误的服务。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=