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=