1. 项目概述:为什么 WSS 配置成了 GatewayWorker 项目上线前的“最后一道关卡”
GatewayWorker 是国内 PHP 社区广泛采用的长连接服务解决方案,它用 Swoole 实现了 Gateway、BusinessWorker、Register 三进程模型,把 WebSocket 连接管理、业务逻辑处理、服务注册发现做了清晰分层。很多团队在开发实时通知、在线客服、协同编辑、IoT 设备心跳上报这类功能时,第一反应就是上 GatewayWorker——因为它文档齐、生态熟、配合 ThinkPHP 或 Laravel 做业务层非常顺手。但真正走到部署阶段,90% 的人会卡在同一个地方:本地ws://跑得飞起,一上生产环境切到wss://就连不上,控制台报错net::ERR_CONNECTION_CLOSED或WebSocket connection to 'wss://...' failed,Gateway 日志里却安静得像没这回事。
这个问题不是偶发,而是有明确技术动因的:WSS 不是简单给 WebSocket 加个 SSL,它是建立在 TLS 协议之上的完整加密通道,要求从客户端发起请求,到反向代理(如 Nginx),再到 GatewayWorker 的 Worker 进程,每一环都必须正确识别、透传、终止或协商 TLS。而 GatewayWorker 本身不内置 HTTPS 服务能力——它的 Worker 进程只监听纯 TCP 端口(比如 2121),所有 TLS 解密工作必须由前置组件完成。这就引出了最典型的配置失配:Nginx 配置了 SSL,但没正确转发 WebSocket 升级头;或者 Nginx 转发了,但 GatewayWorker 的gateway.php配置里ssl相关参数全为 false;又或者证书路径写错、权限不足、Swoole 版本不支持 TLS 1.3……每一个看似微小的疏漏,都会让整个 WSS 链路在握手阶段就静默失败。
我最近在一个在线教育平台的直播互动模块中复现了全部典型问题。这个项目需要支撑 5000+ 并发学生端实时接收答题倒计时、教师端广播白板操作、后台监控连接健康度——对连接稳定性、首包延迟、断线重连成功率要求极高。我们前期在测试环境用ws://跑通了全部逻辑,但正式灰度前切换 WSS 时,花了整整三天才定位到根源:Nginx 的proxy_set_header Upgrade $http_upgrade;这一行被注释掉了,而开发同学误以为“SSL 配好了,WebSocket 就自动好了”。这种认知偏差,在中小团队中极其普遍。所以这篇纪实不讲原理推导,也不堆砌 RFC 文档,只聚焦真实生产环境里你马上会遇到的配置项、报错日志、验证命令和绕过陷阱的实操路径。无论你是刚接触 GatewayWorker 的 PHP 开发者,还是负责部署的运维同学,只要你的项目需要对外提供wss://接口,这篇内容就能帮你省下至少 8 小时排查时间。
2. 整体架构与配置思路:三层解耦是 WSS 可靠性的底层前提
要理解 GatewayWorker 的 WSS 配置为什么容易出错,必须先厘清它的标准生产部署结构。这不是一个单体服务,而是一个典型的“前端卸载 + 后端专注”的三层协作模型:
最外层:反向代理(Nginx / Apache / Caddy)
它是用户直接访问的入口,承担 SSL 证书加载、HTTP/HTTPS 请求终止、WebSocket 协议升级协商、负载均衡、静态资源托管等职责。它不处理任何业务逻辑,只做“流量翻译官”。中间层:Gateway 进程
它运行在独立端口(如 8282),负责维持与所有客户端的长连接,做心跳检测、连接数统计、IP 黑白名单、消息广播等网关级功能。它通过register进程与 BusinessWorker 通信,但自身不解析业务数据。最内层:BusinessWorker 进程
它监听另一个端口(如 2121),只接收来自 Gateway 的已认证、已解包的业务消息(例如{"type":"submit_answer","data":{...}}),执行数据库操作、调用第三方 API、触发事件总线等。它完全不知道 TLS 是什么,只认 TCP 流。
WSS 的核心难点,就在于这三层之间存在两个关键“协议转换点”:
客户端 ↔ Nginx:这是标准的 HTTPS + WebSocket Upgrade 流程。客户端发送
GET /websocket HTTP/1.1+Upgrade: websocket+Sec-WebSocket-Key头,Nginx 必须原样透传这些头,并返回101 Switching Protocols,同时将后续二进制帧流持续转发给后端。Nginx ↔ GatewayWorker:这里必须是明文 TCP 连接。Nginx 解密完 TLS 后,把原始的 HTTP Upgrade 请求(不含 TLS 层)以明文形式发给 Gateway 进程监听的端口。Gateway 进程收到的是干净的 WebSocket 握手请求,它再按标准流程响应
101,并升级为 WebSocket 连接。
提示:很多人误以为 GatewayWorker 也要开 TLS 端口,于是去改
gateway.php里的'ssl' => true并填证书路径。这是根本性错误。GatewayWorker 的 SSL 配置仅在一种场景下有效:当你跳过 Nginx,让客户端直连 Gateway 进程(即wss://your-domain.com:8282)。但生产环境绝不能这么做——没有反向代理,你就失去了证书自动续期、DDoS 缓冲、HTTP/2 支持、日志审计等关键能力。所以,'ssl' => true在标准架构中应永远为false。
那么正确的配置责任划分就非常清晰了:
- Nginx 全权负责 TLS 终止、证书管理、Upgrade 头透传
- GatewayWorker 专注 WebSocket 协议处理,只与 Nginx 建立明文连接
- BusinessWorker 完全无感,照常收发 JSON 消息
这种解耦带来的最大好处是可维护性:换证书只需改 Nginx 配置,调优 WebSocket 参数只需改 Gateway 配置,升级业务逻辑只需重启 BusinessWorker——三者互不影响。而所有“踩坑”,几乎都源于打破了这个边界:比如在 Nginx 里忘了加Upgrade头,导致 Gateway 收到的是普通 HTTP 请求而非 WebSocket 握手;或者在 Gateway 配置里误启了 SSL,结果它试图用明文端口去读取证书文件,启动直接报错。
3. 核心配置项逐项拆解:Nginx 与 GatewayWorker 的 7 个关键开关
WSS 配置的成败,取决于 7 个具体配置项是否全部正确。少一个,连接就建立不了;错一个,日志里就找不到线索。下面我按实际部署顺序,把每个开关的作用、常见错误、验证方法、修复方案说透。
3.1 Nginx Server 块:SSL 证书与基础 HTTPS 启用
这是整个 WSS 的起点。Nginx 必须能正常响应https://your-domain.com的普通 HTTPS 请求,否则 WebSocket 根本没机会发起。
server { listen 443 ssl http2; server_name your-domain.com; # 证书路径必须绝对准确,且 Nginx 用户(通常是 www-data 或 nginx)有读取权限 ssl_certificate /etc/nginx/ssl/your-domain.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/your-domain.com/privkey.pem; # 强烈建议启用 OCSP Stapling,提升 TLS 握手速度 ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/nginx/ssl/your-domain.com/chain.pem; # TLS 版本与加密套件,兼顾安全与兼容性 ssl_protocols 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; ssl_prefer_server_ciphers off; # 其他常规配置... root /var/www/html; index index.php; }常见错误与排查:
- 证书路径错误:
ls -l /etc/nginx/ssl/your-domain.com/看文件是否存在,sudo -u www-data cat /etc/nginx/ssl/your-domain.com/fullchain.pem | head -n 1测试 Nginx 用户能否读取。如果报Permission denied,执行sudo chmod 644 /etc/nginx/ssl/your-domain.com/*.pem。 - 证书链不完整:用
openssl s_client -connect your-domain.com:443 -servername your-domain.com 2>/dev/null | openssl x509 -noout -text | grep "CA Issuers"检查是否包含中间证书。若缺失,fullchain.pem应为cert.pem+chain.pem拼接。 - Nginx 未重载:改完配置必须
sudo nginx -t && sudo systemctl reload nginx,只reload不restart,避免连接中断。
注意:不要在
ssl_certificate路径里用相对路径或~符号,Nginx 启动时工作目录不固定,必须用绝对路径。
3.2 Nginx Location 块:WebSocket 升级头的精准透传
这是 WSS 失败率最高的环节。Nginx 默认不会透传Upgrade和Connection这两个关键头,必须显式声明。
location /websocket { # 关键!必须开启代理缓冲,否则大消息会截断 proxy_buffering off; # 关键!透传 WebSocket 升级所需的两个头 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 关键!告诉后端这是 WebSocket 请求,不是普通 HTTP proxy_http_version 1.1; # 后端 Gateway 进程地址,必须是明文 HTTP(非 HTTPS) proxy_pass http://127.0.0.1:8282; # 超时设置,WebSocket 是长连接,不能沿用默认的 60s proxy_read_timeout 86400; proxy_send_timeout 86400; # 其他可选优化 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }为什么这三行缺一不可?
proxy_http_version 1.1:WebSocket Upgrade 协议要求 HTTP/1.1,HTTP/1.0 不支持。proxy_set_header Upgrade $http_upgrade:$http_upgrade是 Nginx 内置变量,值为客户端发来的Upgrade头内容(通常是websocket)。如果写死成"websocket",当客户端不带此头时会出错;如果省略,后端收不到升级指令。proxy_set_header Connection "upgrade":Connection: upgrade是 HTTP/1.1 协议规定的升级触发头,必须原样传递。
常见错误与排查:
- 忘记
proxy_buffering off:GatewayWorker 发送大消息(如初始同步数据)时,Nginx 缓冲区满会导致连接被静默关闭。现象是客户端能连上,但收不到第一条消息。tail -f /var/log/nginx/error.log会看到upstream sent too big header类似错误。 proxy_pass写成https://:proxy_pass https://127.0.0.1:8282是致命错误。Gateway 进程监听的是明文端口,Nginx 会尝试用 HTTPS 去连它,必然失败。必须是http://。proxy_read_timeout过短:设为 60 秒是常见错误。WebSocket 连接空闲时,Nginx 会在 60 秒后主动断开,导致客户端频繁重连。设为86400(24 小时)是稳妥选择,实际超时由 Gateway 的心跳机制控制。
3.3 GatewayWorker 配置:gateway.php中的 4 个核心参数
GatewayWorker 的配置文件Applications/YourApp/Config/gateway.php是第二道防线。这里不需要开 SSL,但必须确保与 Nginx 的通信方式完全匹配。
return [ 'gateway' => [ 'class' => '\GatewayWorker\Gateway', 'transport' => 'tcp', // 必须是 tcp,不是 ssl 'ip' => '127.0.0.1', 'port' => 8282, 'count' => 4, 'lan_ip' => '127.0.0.1', 'start_port' => 2900, 'ping_interval' => 30, 'ping_not_response_limit' => 0, 'ping_data' => '{"type":"ping"}', // 关键!必须为 false,表示不启用内置 SSL 'ssl' => false, // 关键!必须与 Nginx 的 proxy_pass 地址一致 'registerAddress' => '127.0.0.1:1238', ], 'businessWorker' => [ 'class' => '\GatewayWorker\BusinessWorker', 'count' => 4, // 关键!监听端口必须与 gateway.php 中 registerAddress 的端口一致 'registerAddress' => '127.0.0.1:1238', ], 'register' => [ 'class' => '\GatewayWorker\Register', 'ip' => '127.0.0.1', 'port' => 1238, ] ];关键参数详解:
'transport' => 'tcp':明确指定传输层为 TCP。虽然默认就是 TCP,但显式写出可避免歧义。'ssl' => true时 transport 才是'ssl'。'ssl' => false:再次强调,这是标准架构下的铁律。设为true会导致 Gateway 启动时报failed to open stream: No such file or directory(找不到证书),或SSL operation failed with code 1(证书格式错误)。'registerAddress':Gateway 和 BusinessWorker 都必须指向同一个 Register 进程地址。这是三进程通信的“电话总机”,地址不一致会导致业务消息无法送达。'ping_interval':心跳间隔。设为 30 秒是经验值,太短增加无效流量,太长(如 120 秒)可能导致 Nginxproxy_read_timeout先于心跳触发断连。
验证方法:
启动 GatewayWorker 后,执行netstat -tuln | grep ':8282',确认有tcp6 0 0 *:8282 *:* LISTEN。再执行curl -i -N -H "Connection: upgrade" -H "Upgrade: websocket" http://127.0.0.1:8282/websocket,如果返回HTTP/1.1 101 Switching Protocols,说明 Gateway 本身 WebSocket 握手正常,问题一定出在 Nginx 层。
3.4 Swoole 版本与 OpenSSL 支持:被忽视的底层依赖
GatewayWorker 依赖 Swoole 扩展,而 Swoole 的 WebSocket 服务器能力与 OpenSSL 版本强相关。很多“配置全对却连不上”的案例,根源在此。
- Swoole 4.5.0+:开始支持 TLS 1.3,推荐使用 4.8.x 或 5.x 系列。
- OpenSSL 版本:必须 ≥ 1.1.1。Ubuntu 18.04 自带 OpenSSL 1.1.1,但 CentOS 7 默认是 1.0.2k,不支持 TLS 1.3,会导致 Nginx 与 Gateway 通信异常。
验证命令:
# 查看 Swoole 版本与编译参数 php --ri swoole | grep -E "(Version|OpenSSL)" # 查看系统 OpenSSL 版本 openssl version # 检查 Swoole 是否链接了正确的 OpenSSL ldd $(php-config --extension-dir)/swoole.so | grep ssl输出示例(正确):
swoole support => enabled Version => 5.0.3 OpenSSL => OpenSSL 1.1.1w 11 Sep 2023 ...错误场景与修复:
- 如果
php --ri swoole显示OpenSSL => Disabled,说明编译 Swoole 时没找到 OpenSSL 开发库。Ubuntu 上执行sudo apt install libssl-dev,CentOS 上执行sudo yum install openssl-devel,然后重新编译安装 Swoole。 - 如果
OpenSSL版本是1.0.2k,必须升级系统 OpenSSL 或更换发行版。强行用旧版 OpenSSL 会导致 TLS 握手失败,Nginx 错误日志出现upstream prematurely closed connection while reading response header from upstream。
3.5 客户端连接 URL:协议、域名、端口的三位一体校验
客户端代码里的一行new WebSocket("wss://..."),是整个链条的发起点,也是最容易写错的地方。
// ✅ 正确:协议 wss,域名与 Nginx server_name 一致,路径与 location 匹配 const ws = new WebSocket("wss://your-domain.com/websocket"); // ❌ 错误1:协议写成 ws(本地调试可用,生产必须 wss) const ws = new WebSocket("ws://your-domain.com/websocket"); // ❌ 错误2:域名不匹配(如用了 IP 或子域名,但 Nginx 没配 server_name) const ws = new WebSocket("wss://192.168.1.100/websocket"); // ❌ 错误3:路径不匹配(Nginx location 是 /ws,客户端写了 /websocket) const ws = new WebSocket("wss://your-domain.com/ws");验证方法:
在浏览器开发者工具 Network 标签页,过滤WS,点击连接,看 Initiator 列是否显示wss://your-domain.com/websocket。如果显示ws://,检查前端构建配置(如 Vue CLI 的devServer.proxy或 Webpack 的resolve.alias)是否在生产环境注入了错误的协议。
3.6 防火墙与安全组:云服务器的隐形拦路虎
本地curl能通,但线上连不上?90% 是防火墙挡的。云服务器(阿里云、腾讯云、AWS)的安全组默认只放行 80/443,而 GatewayWorker 的 8282 端口是内部通信端口,绝对不能开放给公网。但很多人误操作,把 8282 加到了安全组白名单,结果 Nginx 无法访问本地 8282,因为流量被安全组规则拦截了。
正确做法:
- Nginx 与 GatewayWorker 在同一台机器:无需开放 8282 端口,
127.0.0.1访问走本地回环,不受安全组影响。 - Nginx 与 GatewayWorker 在不同机器(如 Nginx 在 LB,Gateway 在应用服务器):必须在应用服务器的安全组中,仅允许 Nginx 服务器的内网 IP 访问 8282 端口,禁止
0.0.0.0/0。
验证命令:
在 Nginx 服务器上执行:
# 测试能否 telnet 通 Gateway 服务器的 8282 端口 telnet gateway-server-ip 8282 # 如果不通,检查 Gateway 服务器防火墙 sudo ufw status verbose # Ubuntu sudo firewall-cmd --list-all # CentOS 8+ # 确保有类似 allow 8282/tcp from nginx-ip 的规则3.7 日志分级与关联:快速定位故障环节的黄金法则
WSS 故障排查,本质是日志串联。必须养成习惯:一次连接失败,同时查看三处日志。
| 日志位置 | 查看命令 | 关键线索 |
|---|---|---|
| Nginx error.log | sudo tail -f /var/log/nginx/error.log | upstream timed out(超时)、upstream refused(端口拒绝)、no live upstreams(后端宕机) |
| GatewayWorker logs | tail -f Applications/YourApp/Logs/gateway-worker.log | Gateway started(启动成功)、onConnect(连接建立)、onClose(连接关闭)、SSL handshake failed(如果误启了 SSL) |
| 浏览器 Console | F12 → Console | WebSocket connection to 'wss://...' failed(客户端层面)、Failed to construct 'WebSocket': An insecure WebSocket connection may not be initiated from a page loaded over HTTPS(协议混用) |
实战技巧:
- 在 GatewayWorker 的
Events.php中加入详细日志:public static function onConnect($client_id) { \GatewayWorker\Lib\Gateway::log("Client {$client_id} connected from {$_SERVER['REMOTE_ADDR']}"); // 记录 User-Agent,判断是否是爬虫或异常客户端 \GatewayWorker\Lib\Gateway::log("User-Agent: {$_SERVER['HTTP_USER_AGENT']}"); } - 在 Nginx 配置中添加自定义日志格式,记录 WebSocket 升级状态:
log_format websocket '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' '"$http_upgrade" "$http_connection"'; access_log /var/log/nginx/websocket.log websocket;
4. 实操过程全记录:从零部署一个可验证的 WSS 服务
现在,我们把前面所有知识点串起来,走一遍完整的、可立即复现的部署流程。以下步骤基于 Ubuntu 22.04 + PHP 8.1 + Swoole 5.0.3 + Nginx 1.18 环境,所有命令均可复制粘贴执行。
4.1 环境准备:安装基础依赖与 Swoole
# 更新系统 sudo apt update && sudo apt upgrade -y # 安装 PHP 及常用扩展 sudo apt install -y php8.1-cli php8.1-mbstring php8.1-curl php8.1-xml php8.1-zip # 安装 OpenSSL 开发库(编译 Swoole 必需) sudo apt install -y libssl-dev # 安装 Composer curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer # 安装 Swoole(推荐 PECL,自动处理依赖) sudo pecl install swoole # 安装过程中,如果提示 "Enable OpenSSL support? [no] :",输入 yes 并回车 # 启用 Swoole 扩展 echo "extension=swoole.so" | sudo tee /etc/php/8.1/cli/conf.d/20-swoole.ini echo "extension=swoole.so" | sudo tee /etc/php/8.1/fpm/conf.d/20-swoole.ini # 重启 PHP-FPM sudo systemctl restart php8.1-fpm验证 Swoole:
php --ri swoole | grep -E "(Version|OpenSSL)" # 输出应包含 Version => 5.0.3 和 OpenSSL => OpenSSL 1.1.1w4.2 下载并初始化 GatewayWorker 项目
# 创建项目目录 mkdir -p /var/www/gateway-demo && cd /var/www/gateway-demo # 使用 Composer 创建 GatewayWorker 项目(官方推荐方式) composer create-project workerman/gatewayworker:4.1.0 . # 初始化应用结构 php start.php start -d # 此时应看到 "Gateway start success",但这是临时启动,我们马上要配置 # 停止临时服务 php start.php stop4.3 配置 Nginx:生成可直接使用的 server 块
创建/etc/nginx/sites-available/gateway-demo:
upstream gateway_backend { server 127.0.0.1:8282; } server { listen 80; server_name gateway-demo.local; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name gateway-demo.local; # 为本地测试生成自签名证书(生产环境请用 Let's Encrypt) ssl_certificate /etc/nginx/ssl/gateway-demo.local.crt; ssl_certificate_key /etc/nginx/ssl/gateway-demo.local.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off; # WebSocket 专用 location location /websocket { proxy_buffering off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://gateway_backend; proxy_read_timeout 86400; proxy_send_timeout 86400; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 其他静态资源 root /var/www/gateway-demo/Web; index index.html; }生成自签名证书(仅用于本地测试):
sudo mkdir -p /etc/nginx/ssl sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/nginx/ssl/gateway-demo.local.key \ -out /etc/nginx/ssl/gateway-demo.local.crt \ -subj "/C=CN/ST=Beijing/L=Beijing/O=Demo/CN=gateway-demo.local"启用站点:
sudo ln -sf /etc/nginx/sites-available/gateway-demo /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx4.4 配置 GatewayWorker:修改gateway.php与start.php
编辑/var/www/gateway-demo/Applications/GatewayDemo/Config/gateway.php:
return [ 'gateway' => [ 'class' => '\GatewayWorker\Gateway', 'transport' => 'tcp', 'ip' => '127.0.0.1', 'port' => 8282, 'count' => 2, 'lan_ip' => '127.0.0.1', 'start_port' => 2900, 'ping_interval' => 30, 'ping_not_response_limit' => 0, 'ping_data' => '{"type":"ping"}', // 关键!必须为 false 'ssl' => false, 'registerAddress' => '127.0.0.1:1238', ], 'businessWorker' => [ 'class' => '\GatewayWorker\BusinessWorker', 'count' => 2, 'registerAddress' => '127.0.0.1:1238', ], 'register' => [ 'class' => '\GatewayWorker\Register', 'ip' => '127.0.0.1', 'port' => 1238, ] ];编辑/var/www/gateway-demo/start.php,确保use GatewayWorker\Autoloader;在顶部,并在start()函数前添加:
// 设置时区,避免日志时间错乱 date_default_timezone_set('Asia/Shanghai');4.5 启动服务并验证连接
# 启动 GatewayWorker(守护进程模式) cd /var/www/gateway-demo php start.php start -d # 查看进程是否运行 ps aux | grep GatewayWorker # 查看 Gateway 日志 tail -f /var/www/gateway-demo/Applications/GatewayDemo/Logs/gateway-worker.log # 应看到 "Gateway started" 和 "Register started" # 测试 Nginx 到 Gateway 的连通性(在服务器本地执行) curl -i -N -H "Connection: upgrade" -H "Upgrade: websocket" http://127.0.0.1:8282/websocket # 应返回 HTTP/1.1 101 Switching Protocols # 测试 WSS 连接(在另一台机器或浏览器中) # 访问 https://gateway-demo.local,打开开发者工具 Console,执行: # const ws = new WebSocket("wss://gateway-demo.local/websocket"); # ws.onopen = () => console.log("Connected!"); # ws.onerror = (e) => console.error("Error:", e);如果浏览器报错ERR_CONNECTION_REFUSED:
- 检查
sudo netstat -tuln | grep ':8282',确认 Gateway 进程在监听。 - 检查
sudo nginx -t,确认 Nginx 配置语法正确。 - 检查
/var/log/nginx/error.log,看是否有connect() failed (111: Connection refused),说明 Nginx 连不上 8282。
如果浏览器报错ERR_SSL_PROTOCOL_ERROR:
- 检查
curl -I https://gateway-demo.local,确认 HTTPS 基础服务正常。 - 检查证书路径是否正确,
ls -l /etc/nginx/ssl/看文件是否存在。
4.6 前端验证页面:一个最小可运行的 HTML
在/var/www/gateway-demo/Web/index.html中写入:
<!DOCTYPE html> <html> <head> <title>GatewayWorker WSS Test</title> </head> <body> <h1>GatewayWorker WSS 连接测试</h1> <div id="status">等待连接...</div> <button onclick="connect()">连接</button> <button onclick="send()" disabled id="sendBtn">发送消息</button> <div><textarea id="msg" rows="2" cols="50">Hello from browser!</textarea></div> <script> let ws; function connect() { const url = "wss://gateway-demo.local/websocket"; ws = new WebSocket(url); ws.onopen = function() { document.getElementById('status').innerText = '✅ 已连接'; document.getElementById('sendBtn').disabled = false; }; ws.onmessage = function(event) { console.log('收到:', event.data); document.getElementById('status').innerText = '📩 收到: ' + event.data; }; ws.onclose = function() { document.getElementById('status').innerText = '❌ 连接已关闭'; document.getElementById('sendBtn').disabled = true; }; ws.onerror = function(error) { console.error('WebSocket 错误:', error); document.getElementById('status').innerText = '❌ 连接错误: ' + error; }; } function send() { const msg = document.getElementById('msg').value; if (ws && ws.readyState === WebSocket.OPEN) { ws.send(msg); document.getElementById('status').innerText = '📤 已发送: ' + msg; } } </script> </body> </html>访问https://gateway-demo.local,点击“连接”,即可看到实时连接状态和消息收发。
5. 常见问题与排查技巧实录:那些让你抓狂的“幽灵错误”
在多个项目的 WSS 部署中,我整理出一份高频问题速查表。这些问题往往没有明确报错,或者报错信息极具误导性,必须结合上下文才能定位。
5.1 问题速查表:症状、原因、验证命令、修复方案
| 症状 | 可能原因 | 验证命令 | 修复方案 |
|---|---|---|---|
浏览器控制台报net::ERR_CONNECTION_CLOSED,Nginx error.log 为空 | Nginx 的proxy_pass指向了错误的端口(如 2121 而非 8282),或 Gateway 进程未启动 | sudo netstat -tuln | grep ':8282'curl -i http://127.0.0.1:8282/websocket | 确认gateway.php中port与 Nginxproxy_pass端口一致;执行php start.php start -d启动 Gateway |
连接能建立,但 30 秒后自动断开,onClose日志频繁出现 | Nginxproxy_read_timeout过短,或 Gatewayping_interval与 Nginx timeout 不匹配 | grep "proxy_read_timeout" /etc/nginx/sites-available/*grep "ping_interval" /var/www/gateway-demo/Applications/*/Config/gateway.php | 将 Nginxproxy_read_timeout设为86400,Gatewayping_interval设为30,确保 Nginx timeout > 3 × ping_interval |
Nginx error.log 报upstream prematurely closed connection | GatewayWorker 进程崩溃退出,或 Swoole 版本与 OpenSSL 不兼容 | sudo systemctl status php8.1-fpmphp --ri swoole | grep OpenSSL | 升级 Swoole 至 5.x,确保 OpenSSL ≥ 1.1.1;检查 Gateway 日志是否有 `Segmentation fault |