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 请求混淆,以下是关键字段的协同逻辑:

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

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

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

  • 客户端未发送 Upgrade: websocketConnection: 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=
相关推荐
VVVVWeiYee14 分钟前
IP v 6
网络·网络协议·tcp/ip
蟾宫曲4 小时前
网络编程 04:TCP连接,客户端与服务器的区别,实现 TCP 聊天及文件上传,Tomcat 的简单使用
java·服务器·网络·tcp/ip·tomcat·端口
key_Go5 小时前
03.《交换的底层逻辑:从基础到应用》
运维·服务器·网络
淮北4947 小时前
linux系统学习(15.启动管理)
运维·服务器·网络·c++·vscode·学习
程序员小凯7 小时前
网络编程基础
网络·http·https
跨境猫小妹9 小时前
亚马逊巴西战略升级:物流网络重构背后的生态革新与技术赋能之路
网络·重构·跨境电商·亚马逊
mit6.8249 小时前
[p2p-Magnet] 队列与处理器 | DHT路由表
网络·网络协议·p2p
北极光SD-WAN组网9 小时前
突破传统企业组网瓶颈:某科技公司智能组网服务项目深度解析
网络·科技
我也要当昏君10 小时前
5.2 I/O软件
java·网络·算法