- 客户端发送一个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:// 对应 HTTPS
ws = 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=