找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2531

积分

1

好友

352

主题
发表于 3 天前 | 查看: 13| 回复: 0

[知识是人生的灯塔,只有不断学习,才能照亮前行的道路]

本文将探讨如何在 Nginx 中实现 WebSocket 的反向代理。在开始配置之前,我们先来简单了解一下 WebSocket 协议,知其然亦知其所以然。

NGINX反向代理架构示意图

Websocket 介绍

WebSocket 提供了一种在单个 TCP 连接上进行全双工通信的方法,这对于实时应用(如聊天室、在线游戏等)非常有用,是目前广泛使用的一种 Web 通讯方式。与 HTTP 协议相比,它最大的优势在于服务端可以主动推送数据,无需客户端轮询,从而能及时地向客户端反映服务端的变化。

WebSocket 具有以下核心特点:

  • 持久连接:连接建立后无需重复握手,数据以帧的形式传输。
  • 低延迟:相比 HTTP 长轮询,WebSocket 减少了数据传输延迟。
  • 高效性:传输数据量更小,适合高频实时通信。

目前主流的开发语言如 Java、Python、Node.js、Go 等,都提供了对 WebSocket 的原生支持,开发成本低,易于上手。

WebSocket 协议帧的基本结构如下图所示:

WebSocket协议帧结构详解

让我们来解析一下这个帧结构中的关键字段:

  • FIN:用于表示这是当前帧的最后一部分。当 FIN 设置为 1 时,表示这是一个完整的消息;如果为 0,则表示还有更多的数据片段需要接收。
  • RSV1RSV2RSV3:保留位,用于自定义扩展,值为 0 时表示不使用这些扩展。
  • Opcode:用于表示帧的类型,例如文本、二进制数据或连接关闭等。若接收到未知的 opcode,则会关闭连接。常见值有:
    • 0x0:表示一个继续帧(Continuation Frame)。
    • 0x1:表示一个文本帧(Text Frame)。
    • 0x2:表示一个二进制帧(Binary Frame)。
    • 0x8:表示一个连接关闭帧(Connection Close Frame)。
    • 0x9:表示一个 Ping 帧。
    • 0xA:表示一个 Pong 帧。
  • MASK 掩码位:表示帧中数据是否经过掩码处理。客户端发出的数据帧必须经过掩码处理(值为 1),服务端发出的帧则无需掩码(值为 0)。当值为 1 时,会附带一个 4 字节的掩码密钥用于解码数据。
  • Payload length:表示负载数据的字节数。它可能是 7 位、7+16 位或 7+64 位:
    • 如果值为 0 到 125,则直接表示负载长度。
    • 如果值为 126,则后续两个字节表示实际的负载长度。
    • 如果值为 127,则后续八个字节表示实际的负载长度。
  • Extended Payload Length:扩展负载长度字段,当 Payload length 为 126 或 127 时使用,用于存放实际的长度值。
  • Masking-key:掩码密钥,当 MASK 位为 1 时存在,占用 4 个字节。
  • Payload Data:实际传输的数据内容。

知识扩展:WebSocket 也有一些局限性,例如数据分片是有序的,不支持原生的多路复用(但可通过扩展实现),且协议原生不支持压缩(同样可通过 RFC 7692 扩展实现)。

Websocket 反向代理指令

在 Nginx 中配置 WebSocket 反向代理,主要依赖于 ngx_http_proxy_module 模块。其配置思路与 HTTP 反向代理相似,但有几个关键指令必须设置,以确保协议能够正确升级:

# 设置 HTTP 版本为 1.1,这是 WebSocket 握手所必需的
proxy_http_version 1.1;

# 设置 WebSocket 协议升级头
proxy_set_header Upgrade $http_upgrade;

# 将 connection 头设置为 "upgrade",以完成 WebSocket 的升级握手
proxy_set_header Connection "upgrade";

WebSocket协议握手升级请求与响应头部示例

上图展示了 WebSocket 握手过程中关键的请求与响应头部。除了上述 Nginx 需要设置的头部,WebSocket 协议本身还定义了一些特定的头部:

  • Sec-WebSocket-Version:客户端声明其使用的 WebSocket 协议版本(如 13)。
  • Sec-WebSocket-Extensions:用于协商本次连接要使用的扩展,如压缩。
  • Sec-WebSocket-Key:客户端发送的一个随机生成的密钥,用于服务端验证。
  • Sec-WebSocket-Protocol:用于协商应用子协议(如 chat)。
  • Sec-WebSocket-Accept:服务端对客户端 Key 计算后的响应密钥,用于完成握手验证。

实践演示

接下来,我们通过一个完整的例子来演示如何配置 Nginx 反向代理 WebSocket。

步骤 01. 准备 WebSocket 服务端环境

我们使用 Python 的 websockets 库快速创建一个简单的回声服务端,它会将客户端发送的消息原样返回。

# 安装必要的依赖
pip install websockets -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

# 创建 WebSocket 服务器脚本
tee websocket_server.py <<EOF
import asyncio
import logging
from datetime import datetime
import websockets
from websockets.server import serve

# 设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

async def echo_handler(websocket):
    """处理 WebSocket 连接,将客户端发送的消息原样返回"""
    client_address = websocket.remote_address
    logger.info(f"客户端连接成功: {client_address}")

    try:
        async for message in websocket:
            # 记录接收到的消息
            logger.info(f"收到来自 {client_address} 的消息: {message}")
            # 构建响应消息(原样返回客户端发送的内容)
            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
            response = f"[{timestamp}] 服务端消息: {message}"
            # 发送响应
            await websocket.send(response)
            logger.info(f"发送响应到 {client_address}: {response}")

    except websockets.exceptions.ConnectionClosed as e:
        logger.info(f"客户端断开连接: {client_address}, 原因: {e}")
    except Exception as e:
        logger.error(f"处理客户端 {client_address} 时发生错误: {e}")
        await websocket.close(code=1011, reason=str(e))

async def main():
    """启动 WebSocket 服务器"""
    host = "0.0.0.0"
    port = 8765

    logger.info(f"启动 WebSocket 服务器在 ws://{host}:{port}")
    logger.info("按 Ctrl+C 停止服务器")
    # 启动服务器
    async with serve(
        echo_handler,
        host,
        port,
        ping_interval=20,
        ping_timeout=20,
        close_timeout=10,
    ) as server:
        await server.wait_closed()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        logger.info("服务器已停止")
EOF

# 启动 WebSocket 服务器
python3 websocket_server.py

步骤 02. 测试 WebSocket 服务

在另一台 Linux 服务器上,可以使用 websocat 工具测试刚启动的服务是否正常。

# 下载并安装 websocat 工具
wget https://github.com/vi/websocat/releases/download/v1.14.0/websocat.x86_64-unknown-linux-musl
mv websocat.x86_64-unknown-linux-musl /usr/local/bin/websocat
chmod +x /usr/local/bin/websocat

# 连接测试
websocat ws://127.0.0.1:8765

使用websocat工具测试WebSocket服务连通性

步骤 03. 配置 Nginx 反向代理

现在,我们来配置 Nginx 作为反向代理,并使其同时支持 ws (WebSocket) 和 wss (WebSocket Secure) 协议。这里我们复用之前学过的 SSL/TLS 配置。

tee /usr/local/nginx/conf.d/server.conf <<'EOF'
# 定义后端 WebSocket 服务地址
upstream websocket_backend {
  server 10.20.172.214:8765;
}

server {
  listen 80;
  # 监听 443 端口,启用 SSL
  listen 443 ssl;
  server_name server.weiyigeek.top;
  default_type text/html;

  # 开启 HTTP/2 支持
  http2 on;

  # 日志文件
  access_log /var/log/nginx/server.log main;
  error_log /var/log/nginx/server.err.log debug;

  # SSL 证书配置
  ssl_certificate /usr/local/nginx/certs/server.crt;
  # 使用加密的私钥文件(需提供密码文件)
  ssl_certificate_key /usr/local/nginx/certs/server_encrypted.key;
  ssl_password_file /usr/local/nginx/certs/ssl_password.txt;

  # 支持的 SSL/TLS 协议版本
  ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
  # 加密套件
  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE:ECDH:AES:HIGH:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:!NULL:!aNULL:!eNULL:!EXPORT:!PSK:!ADH:!DH:!DES:!MD5:!RC4;

  ssl_session_cache shared:SSL:10m;
  ssl_session_timeout 10m;
  ssl_prefer_server_ciphers on;

  # 强制使用 HTTPS 访问
  add_header Strict-Transport-Security "max-age=31536000;includeSubDomains;preload" always;

  # WebSocket 反向代理核心配置
  location ^~ /ws {
    proxy_pass http://websocket_backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 60s; # 设置长连接超时时间
    proxy_set_header X-Real-IP $remote_addr;
    # 缓冲区配置,应对可能的消息突增
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;
  }
}
EOF

步骤 04. 验证配置并测试

应用 Nginx 配置,并使用 websocat 分别通过 HTTP 和 HTTPS 测试反向代理。

# 检查配置并重载 Nginx
nginx -t && nginx -s reload

# 测试非 SSL 的 WebSocket 反向代理
websocat ws://server.weiyigeek.top/ws

# 测试 SSL 加密的 WebSocket (WSS) 反向代理
websocat -k wss://server.weiyigeek.top/ws

通过Nginx反向代理测试WebSocket及WSS连接

知识扩展:除了命令行工具,我们也可以在 Web 前端使用 JavaScript 连接到这个 WebSocket 服务。

// Connect to the WebSocket echo server
const socket = new WebSocket('wss://server.weiyigeek.top/ws');

// Connection opened
socket.addEventListener('open', (event) => {
  console.log('Connected to echo server');
  socket.send('Hello, WebSocket Echo Server!');
});

// Listen for echoed messages
socket.addEventListener('message', (event) => {
  console.log('Echoed back:', event.data);
});

// Handle errors and close events
socket.addEventListener('error', (event) => {
  console.error('WebSocket error:', event);
});
socket.addEventListener('close', (event) => {
  console.log('Disconnected from echo server');
});

总结

本文介绍了 WebSocket 协议的基础原理,并通过一个完整的实践演示了如何使用 Nginx 配置 WebSocket 反向代理,涵盖了从搭建简易 Python 服务端到配置支持 ws/wss 协议的全过程。掌握这一配置,对于构建实时聊天、在线协作、游戏等需要双向通信的 Web 应用至关重要。希望这篇实践指南能帮助你顺利部署自己的 WebSocket 服务。如果你想与更多开发者交流此类网络与系统配置经验,欢迎到 云栈社区 参与讨论。




上一篇:Python多态核心机制:从行为一致性到实践指南
下一篇:x86架构下绕过火绒防护的Windows IIS提权实战记录与思路解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-14 18:54 , Processed in 0.501243 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表