戰(zhàn):Nginx 與 Gateway 協(xié)同部署指南)
1. 項(xiàng)目概述為什么 WSS 配置成了 GatewayWorker 項(xiàng)目上線前的“最后一道關(guān)卡”GatewayWorker 是國(guó)內(nèi) PHP 社區(qū)廣泛采用的長(zhǎng)連接服務(wù)解決方案它用 Swoole 實(shí)現(xiàn)了 Gateway、BusinessWorker、Register 三進(jìn)程模型把 WebSocket 連接管理、業(yè)務(wù)邏輯處理、服務(wù)注冊(cè)發(fā)現(xiàn)做了清晰分層。很多團(tuán)隊(duì)在開發(fā)實(shí)時(shí)通知、在線客服、協(xié)同編輯、IoT 設(shè)備心跳上報(bào)這類功能時(shí)第一反應(yīng)就是上 GatewayWorker——因?yàn)樗臋n齊、生態(tài)熟、配合 ThinkPHP 或 Laravel 做業(yè)務(wù)層非常順手。但真正走到部署階段90% 的人會(huì)卡在同一個(gè)地方本地ws://跑得飛起一上生產(chǎn)環(huán)境切到wss://就連不上控制臺(tái)報(bào)錯(cuò)net::ERR_CONNECTION_CLOSED或WebSocket connection to wss://... failedGateway 日志里卻安靜得像沒這回事。這個(gè)問(wèn)題不是偶發(fā)而是有明確技術(shù)動(dòng)因的WSS 不是簡(jiǎn)單給 WebSocket 加個(gè) SSL它是建立在 TLS 協(xié)議之上的完整加密通道要求從客戶端發(fā)起請(qǐng)求到反向代理如 Nginx再到 GatewayWorker 的 Worker 進(jìn)程每一環(huán)都必須正確識(shí)別、透?jìng)鳌⒔K止或協(xié)商 TLS。而 GatewayWorker 本身不內(nèi)置 HTTPS 服務(wù)能力——它的 Worker 進(jìn)程只監(jiān)聽純 TCP 端口比如 2121所有 TLS 解密工作必須由前置組件完成。這就引出了最典型的配置失配Nginx 配置了 SSL但沒正確轉(zhuǎn)發(fā) WebSocket 升級(jí)頭或者 Nginx 轉(zhuǎn)發(fā)了但 GatewayWorker 的gateway.php配置里ssl相關(guān)參數(shù)全為 false又或者證書路徑寫錯(cuò)、權(quán)限不足、Swoole 版本不支持 TLS 1.3……每一個(gè)看似微小的疏漏都會(huì)讓整個(gè) WSS 鏈路在握手階段就靜默失敗。我最近在一個(gè)在線教育平臺(tái)的直播互動(dòng)模塊中復(fù)現(xiàn)了全部典型問(wèn)題。這個(gè)項(xiàng)目需要支撐 5000 并發(fā)學(xué)生端實(shí)時(shí)接收答題倒計(jì)時(shí)、教師端廣播白板操作、后臺(tái)監(jiān)控連接健康度——對(duì)連接穩(wěn)定性、首包延遲、斷線重連成功率要求極高。我們前期在測(cè)試環(huán)境用ws://跑通了全部邏輯但正式灰度前切換 WSS 時(shí)花了整整三天才定位到根源Nginx 的proxy_set_header Upgrade $http_upgrade;這一行被注釋掉了而開發(fā)同學(xué)誤以為“SSL 配好了WebSocket 就自動(dòng)好了”。這種認(rèn)知偏差在中小團(tuán)隊(duì)中極其普遍。所以這篇紀(jì)實(shí)不講原理推導(dǎo)也不堆砌 RFC 文檔只聚焦真實(shí)生產(chǎn)環(huán)境里你馬上會(huì)遇到的配置項(xiàng)、報(bào)錯(cuò)日志、驗(yàn)證命令和繞過(guò)陷阱的實(shí)操路徑。無(wú)論你是剛接觸 GatewayWorker 的 PHP 開發(fā)者還是負(fù)責(zé)部署的運(yùn)維同學(xué)只要你的項(xiàng)目需要對(duì)外提供wss://接口這篇內(nèi)容就能幫你省下至少 8 小時(shí)排查時(shí)間。2. 整體架構(gòu)與配置思路三層解耦是 WSS 可靠性的底層前提要理解 GatewayWorker 的 WSS 配置為什么容易出錯(cuò)必須先厘清它的標(biāo)準(zhǔn)生產(chǎn)部署結(jié)構(gòu)。這不是一個(gè)單體服務(wù)而是一個(gè)典型的“前端卸載 后端專注”的三層協(xié)作模型最外層反向代理Nginx / Apache / Caddy它是用戶直接訪問(wèn)的入口承擔(dān) SSL 證書加載、HTTP/HTTPS 請(qǐng)求終止、WebSocket 協(xié)議升級(jí)協(xié)商、負(fù)載均衡、靜態(tài)資源托管等職責(zé)。它不處理任何業(yè)務(wù)邏輯只做“流量翻譯官”。中間層Gateway 進(jìn)程它運(yùn)行在獨(dú)立端口如 8282負(fù)責(zé)維持與所有客戶端的長(zhǎng)連接做心跳檢測(cè)、連接數(shù)統(tǒng)計(jì)、IP 黑白名單、消息廣播等網(wǎng)關(guān)級(jí)功能。它通過(guò)register進(jìn)程與 BusinessWorker 通信但自身不解析業(yè)務(wù)數(shù)據(jù)。最內(nèi)層BusinessWorker 進(jìn)程它監(jiān)聽另一個(gè)端口如 2121只接收來(lái)自 Gateway 的已認(rèn)證、已解包的業(yè)務(wù)消息例如{type:submit_answer,data:{...}}執(zhí)行數(shù)據(jù)庫(kù)操作、調(diào)用第三方 API、觸發(fā)事件總線等。它完全不知道 TLS 是什么只認(rèn) TCP 流。WSS 的核心難點(diǎn)就在于這三層之間存在兩個(gè)關(guān)鍵“協(xié)議轉(zhuǎn)換點(diǎn)”客戶端 ? Nginx這是標(biāo)準(zhǔn)的 HTTPS WebSocket Upgrade 流程。客戶端發(fā)送GET /websocket HTTP/1.1Upgrade: websocketSec-WebSocket-Key頭Nginx 必須原樣透?jìng)鬟@些頭并返回101 Switching Protocols同時(shí)將后續(xù)二進(jìn)制幀流持續(xù)轉(zhuǎn)發(fā)給后端。Nginx ? GatewayWorker這里必須是明文 TCP 連接。Nginx 解密完 TLS 后把原始的 HTTP Upgrade 請(qǐng)求不含 TLS 層以明文形式發(fā)給 Gateway 進(jìn)程監(jiān)聽的端口。Gateway 進(jìn)程收到的是干凈的 WebSocket 握手請(qǐng)求它再按標(biāo)準(zhǔn)流程響應(yīng)101并升級(jí)為 WebSocket 連接。提示很多人誤以為 GatewayWorker 也要開 TLS 端口于是去改gateway.php里的ssl true并填證書路徑。這是根本性錯(cuò)誤。GatewayWorker 的 SSL 配置僅在一種場(chǎng)景下有效當(dāng)你跳過(guò) Nginx讓客戶端直連 Gateway 進(jìn)程即wss://your-domain.com:8282。但生產(chǎn)環(huán)境絕不能這么做——沒有反向代理你就失去了證書自動(dòng)續(xù)期、DDoS 緩沖、HTTP/2 支持、日志審計(jì)等關(guān)鍵能力。所以ssl true在標(biāo)準(zhǔn)架構(gòu)中應(yīng)永遠(yuǎn)為false。那么正確的配置責(zé)任劃分就非常清晰了Nginx 全權(quán)負(fù)責(zé) TLS 終止、證書管理、Upgrade 頭透?jìng)鱃atewayWorker 專注 WebSocket 協(xié)議處理只與 Nginx 建立明文連接BusinessWorker 完全無(wú)感照常收發(fā) JSON 消息這種解耦帶來(lái)的最大好處是可維護(hù)性換證書只需改 Nginx 配置調(diào)優(yōu) WebSocket 參數(shù)只需改 Gateway 配置升級(jí)業(yè)務(wù)邏輯只需重啟 BusinessWorker——三者互不影響。而所有“踩坑”幾乎都源于打破了這個(gè)邊界比如在 Nginx 里忘了加Upgrade頭導(dǎo)致 Gateway 收到的是普通 HTTP 請(qǐng)求而非 WebSocket 握手或者在 Gateway 配置里誤啟了 SSL結(jié)果它試圖用明文端口去讀取證書文件啟動(dòng)直接報(bào)錯(cuò)。3. 核心配置項(xiàng)逐項(xiàng)拆解Nginx 與 GatewayWorker 的 7 個(gè)關(guān)鍵開關(guān)WSS 配置的成敗取決于 7 個(gè)具體配置項(xiàng)是否全部正確。少一個(gè)連接就建立不了錯(cuò)一個(gè)日志里就找不到線索。下面我按實(shí)際部署順序把每個(gè)開關(guān)的作用、常見錯(cuò)誤、驗(yàn)證方法、修復(fù)方案說(shuō)透。3.1 Nginx Server 塊SSL 證書與基礎(chǔ) HTTPS 啟用這是整個(gè) WSS 的起點(diǎn)。Nginx 必須能正常響應(yīng)https://your-domain.com的普通 HTTPS 請(qǐng)求否則 WebSocket 根本沒機(jī)會(huì)發(fā)起。server { listen 443 ssl http2; server_name your-domain.com; # 證書路徑必須絕對(duì)準(zhǔn)確且 Nginx 用戶通常是 www-data 或 nginx有讀取權(quán)限 ssl_certificate /etc/nginx/ssl/your-domain.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/your-domain.com/privkey.pem; # 強(qiáng)烈建議啟用 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; # 其他常規(guī)配置... root /var/www/html; index index.php; }常見錯(cuò)誤與排查證書路徑錯(cuò)誤ls -l /etc/nginx/ssl/your-domain.com/看文件是否存在sudo -u www-data cat /etc/nginx/ssl/your-domain.com/fullchain.pem | head -n 1測(cè)試 Nginx 用戶能否讀取。如果報(bào)Permission denied執(zhí)行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應(yīng)為cert.pemchain.pem拼接。Nginx 未重載改完配置必須sudo nginx -t sudo systemctl reload nginx只reload不restart避免連接中斷。注意不要在ssl_certificate路徑里用相對(duì)路徑或~符號(hào)Nginx 啟動(dòng)時(shí)工作目錄不固定必須用絕對(duì)路徑。3.2 Nginx Location 塊WebSocket 升級(jí)頭的精準(zhǔn)透?jìng)鬟@是 WSS 失敗率最高的環(huán)節(jié)。Nginx 默認(rèn)不會(huì)透?jìng)鱑pgrade和Connection這兩個(gè)關(guān)鍵頭必須顯式聲明。location /websocket { # 關(guān)鍵必須開啟代理緩沖否則大消息會(huì)截?cái)?proxy_buffering off; # 關(guān)鍵透?jìng)?WebSocket 升級(jí)所需的兩個(gè)頭 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; # 關(guān)鍵告訴后端這是 WebSocket 請(qǐng)求不是普通 HTTP proxy_http_version 1.1; # 后端 Gateway 進(jìn)程地址必須是明文 HTTP非 HTTPS proxy_pass http://127.0.0.1:8282; # 超時(shí)設(shè)置WebSocket 是長(zhǎng)連接不能沿用默認(rèn)的 60s proxy_read_timeout 86400; proxy_send_timeout 86400; # 其他可選優(yōu)化 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.1WebSocket Upgrade 協(xié)議要求 HTTP/1.1HTTP/1.0 不支持。proxy_set_header Upgrade $http_upgrade$http_upgrade是 Nginx 內(nèi)置變量值為客戶端發(fā)來(lái)的Upgrade頭內(nèi)容通常是websocket。如果寫死成websocket當(dāng)客戶端不帶此頭時(shí)會(huì)出錯(cuò)如果省略后端收不到升級(jí)指令。proxy_set_header Connection upgradeConnection: upgrade是 HTTP/1.1 協(xié)議規(guī)定的升級(jí)觸發(fā)頭必須原樣傳遞。常見錯(cuò)誤與排查忘記proxy_buffering offGatewayWorker 發(fā)送大消息如初始同步數(shù)據(jù)時(shí)Nginx 緩沖區(qū)滿會(huì)導(dǎo)致連接被靜默關(guān)閉?,F(xiàn)象是客戶端能連上但收不到第一條消息。tail -f /var/log/nginx/error.log會(huì)看到upstream sent too big header類似錯(cuò)誤。proxy_pass寫成https://proxy_pass https://127.0.0.1:8282是致命錯(cuò)誤。Gateway 進(jìn)程監(jiān)聽的是明文端口Nginx 會(huì)嘗試用 HTTPS 去連它必然失敗。必須是http://。proxy_read_timeout過(guò)短設(shè)為 60 秒是常見錯(cuò)誤。WebSocket 連接空閑時(shí)Nginx 會(huì)在 60 秒后主動(dòng)斷開導(dǎo)致客戶端頻繁重連。設(shè)為8640024 小時(shí)是穩(wěn)妥選擇實(shí)際超時(shí)由 Gateway 的心跳機(jī)制控制。3.3 GatewayWorker 配置gateway.php中的 4 個(gè)核心參數(shù)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}, // 關(guān)鍵必須為 false表示不啟用內(nèi)置 SSL ssl false, // 關(guān)鍵必須與 Nginx 的 proxy_pass 地址一致 registerAddress 127.0.0.1:1238, ], businessWorker [ class \GatewayWorker\BusinessWorker, count 4, // 關(guān)鍵監(jiān)聽端口必須與 gateway.php 中 registerAddress 的端口一致 registerAddress 127.0.0.1:1238, ], register [ class \GatewayWorker\Register, ip 127.0.0.1, port 1238, ] ];關(guān)鍵參數(shù)詳解transport tcp明確指定傳輸層為 TCP。雖然默認(rèn)就是 TCP但顯式寫出可避免歧義。ssl true時(shí) transport 才是ssl。ssl false再次強(qiáng)調(diào)這是標(biāo)準(zhǔn)架構(gòu)下的鐵律。設(shè)為true會(huì)導(dǎo)致 Gateway 啟動(dòng)時(shí)報(bào)failed to open stream: No such file or directory找不到證書或SSL operation failed with code 1證書格式錯(cuò)誤。registerAddressGateway 和 BusinessWorker 都必須指向同一個(gè) Register 進(jìn)程地址。這是三進(jìn)程通信的“電話總機(jī)”地址不一致會(huì)導(dǎo)致業(yè)務(wù)消息無(wú)法送達(dá)。ping_interval心跳間隔。設(shè)為 30 秒是經(jīng)驗(yàn)值太短增加無(wú)效流量太長(zhǎng)如 120 秒可能導(dǎo)致 Nginxproxy_read_timeout先于心跳觸發(fā)斷連。驗(yàn)證方法啟動(dòng) GatewayWorker 后執(zhí)行netstat -tuln | grep :8282確認(rèn)有tcp6 0 0 *:8282 *:* LISTEN。再執(zhí)行curl -i -N -H Connection: upgrade -H Upgrade: websocket http://127.0.0.1:8282/websocket如果返回HTTP/1.1 101 Switching Protocols說(shuō)明 Gateway 本身 WebSocket 握手正常問(wèn)題一定出在 Nginx 層。3.4 Swoole 版本與 OpenSSL 支持被忽視的底層依賴GatewayWorker 依賴 Swoole 擴(kuò)展而 Swoole 的 WebSocket 服務(wù)器能力與 OpenSSL 版本強(qiáng)相關(guān)。很多“配置全對(duì)卻連不上”的案例根源在此。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 默認(rèn)是 1.0.2k不支持 TLS 1.3會(huì)導(dǎo)致 Nginx 與 Gateway 通信異常。驗(yàn)證命令# 查看 Swoole 版本與編譯參數(shù) php --ri swoole | grep -E (Version|OpenSSL) # 查看系統(tǒng) 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 ...錯(cuò)誤場(chǎng)景與修復(fù)如果php --ri swoole顯示OpenSSL Disabled說(shuō)明編譯 Swoole 時(shí)沒找到 OpenSSL 開發(fā)庫(kù)。Ubuntu 上執(zhí)行sudo apt install libssl-devCentOS 上執(zhí)行sudo yum install openssl-devel然后重新編譯安裝 Swoole。如果OpenSSL版本是1.0.2k必須升級(jí)系統(tǒng) OpenSSL 或更換發(fā)行版。強(qiáng)行用舊版 OpenSSL 會(huì)導(dǎo)致 TLS 握手失敗Nginx 錯(cuò)誤日志出現(xiàn)upstream prematurely closed connection while reading response header from upstream。3.5 客戶端連接 URL協(xié)議、域名、端口的三位一體校驗(yàn)客戶端代碼里的一行new WebSocket(wss://...)是整個(gè)鏈條的發(fā)起點(diǎn)也是最容易寫錯(cuò)的地方。// ? 正確協(xié)議 wss域名與 Nginx server_name 一致路徑與 location 匹配 const ws new WebSocket(wss://your-domain.com/websocket); // ? 錯(cuò)誤1協(xié)議寫成 ws本地調(diào)試可用生產(chǎn)必須 wss const ws new WebSocket(ws://your-domain.com/websocket); // ? 錯(cuò)誤2域名不匹配如用了 IP 或子域名但 Nginx 沒配 server_name const ws new WebSocket(wss://192.168.1.100/websocket); // ? 錯(cuò)誤3路徑不匹配Nginx location 是 /ws客戶端寫了 /websocket const ws new WebSocket(wss://your-domain.com/ws);驗(yàn)證方法在瀏覽器開發(fā)者工具 Network 標(biāo)簽頁(yè)過(guò)濾WS點(diǎn)擊連接看 Initiator 列是否顯示wss://your-domain.com/websocket。如果顯示ws://檢查前端構(gòu)建配置如 Vue CLI 的devServer.proxy或 Webpack 的resolve.alias是否在生產(chǎn)環(huán)境注入了錯(cuò)誤的協(xié)議。3.6 防火墻與安全組云服務(wù)器的隱形攔路虎本地curl能通但線上連不上90% 是防火墻擋的。云服務(wù)器阿里云、騰訊云、AWS的安全組默認(rèn)只放行 80/443而 GatewayWorker 的 8282 端口是內(nèi)部通信端口絕對(duì)不能開放給公網(wǎng)。但很多人誤操作把 8282 加到了安全組白名單結(jié)果 Nginx 無(wú)法訪問(wèn)本地 8282因?yàn)榱髁勘话踩M規(guī)則攔截了。正確做法Nginx 與 GatewayWorker 在同一臺(tái)機(jī)器無(wú)需開放 8282 端口127.0.0.1訪問(wèn)走本地回環(huán)不受安全組影響。Nginx 與 GatewayWorker 在不同機(jī)器如 Nginx 在 LBGateway 在應(yīng)用服務(wù)器必須在應(yīng)用服務(wù)器的安全組中僅允許 Nginx 服務(wù)器的內(nèi)網(wǎng) IP 訪問(wèn) 8282 端口禁止0.0.0.0/0。驗(yàn)證命令在 Nginx 服務(wù)器上執(zhí)行# 測(cè)試能否 telnet 通 Gateway 服務(wù)器的 8282 端口 telnet gateway-server-ip 8282 # 如果不通檢查 Gateway 服務(wù)器防火墻 sudo ufw status verbose # Ubuntu sudo firewall-cmd --list-all # CentOS 8 # 確保有類似 allow 8282/tcp from nginx-ip 的規(guī)則3.7 日志分級(jí)與關(guān)聯(lián)快速定位故障環(huán)節(jié)的黃金法則WSS 故障排查本質(zhì)是日志串聯(lián)。必須養(yǎng)成習(xí)慣一次連接失敗同時(shí)查看三處日志。日志位置查看命令關(guān)鍵線索Nginx error.logsudo tail -f /var/log/nginx/error.logupstream timed out超時(shí)、upstream refused端口拒絕、no live upstreams后端宕機(jī)GatewayWorker logstail -f Applications/YourApp/Logs/gateway-worker.logGateway started啟動(dòng)成功、onConnect連接建立、onClose連接關(guān)閉、SSL handshake failed如果誤啟了 SSL瀏覽器 ConsoleF12 → ConsoleWebSocket connection to wss://... failed客戶端層面、Failed to construct WebSocket: An insecure WebSocket connection may not be initiated from a page loaded over HTTPS協(xié)議混用實(shí)戰(zhàn)技巧在 GatewayWorker 的Events.php中加入詳細(xì)日志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 升級(jí)狀態(tài)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. 實(shí)操過(guò)程全記錄從零部署一個(gè)可驗(yàn)證的 WSS 服務(wù)現(xiàn)在我們把前面所有知識(shí)點(diǎn)串起來(lái)走一遍完整的、可立即復(fù)現(xiàn)的部署流程。以下步驟基于 Ubuntu 22.04 PHP 8.1 Swoole 5.0.3 Nginx 1.18 環(huán)境所有命令均可復(fù)制粘貼執(zhí)行。4.1 環(huán)境準(zhǔn)備安裝基礎(chǔ)依賴與 Swoole# 更新系統(tǒng) sudo apt update sudo apt upgrade -y # 安裝 PHP 及常用擴(kuò)展 sudo apt install -y php8.1-cli php8.1-mbstring php8.1-curl php8.1-xml php8.1-zip # 安裝 OpenSSL 開發(fā)庫(kù)編譯 Swoole 必需 sudo apt install -y libssl-dev # 安裝 Composer curl -sS https://getcomposer.org/installer | sudo php -- --install-dir/usr/local/bin --filenamecomposer # 安裝 Swoole推薦 PECL自動(dòng)處理依賴 sudo pecl install swoole # 安裝過(guò)程中如果提示 Enable OpenSSL support? [no] :輸入 yes 并回車 # 啟用 Swoole 擴(kuò)展 echo extensionswoole.so | sudo tee /etc/php/8.1/cli/conf.d/20-swoole.ini echo extensionswoole.so | sudo tee /etc/php/8.1/fpm/conf.d/20-swoole.ini # 重啟 PHP-FPM sudo systemctl restart php8.1-fpm驗(yàn)證 Swoolephp --ri swoole | grep -E (Version|OpenSSL) # 輸出應(yīng)包含 Version 5.0.3 和 OpenSSL OpenSSL 1.1.1w4.2 下載并初始化 GatewayWorker 項(xiàng)目# 創(chuàng)建項(xiàng)目目錄 mkdir -p /var/www/gateway-demo cd /var/www/gateway-demo # 使用 Composer 創(chuàng)建 GatewayWorker 項(xiàng)目官方推薦方式 composer create-project workerman/gatewayworker:4.1.0 . # 初始化應(yīng)用結(jié)構(gòu) php start.php start -d # 此時(shí)應(yīng)看到 Gateway start success但這是臨時(shí)啟動(dòng)我們馬上要配置 # 停止臨時(shí)服務(wù) php start.php stop4.3 配置 Nginx生成可直接使用的 server 塊創(chuàng)建/etc/nginx/sites-available/gateway-demoupstream 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; # 為本地測(cè)試生成自簽名證書生產(chǎn)環(huán)境請(qǐng)用 Lets 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; } # 其他靜態(tài)資源 root /var/www/gateway-demo/Web; index index.html; }生成自簽名證書僅用于本地測(cè)試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 /CCN/STBeijing/LBeijing/ODemo/CNgateway-demo.local啟用站點(diǎn)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.phpreturn [ 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}, // 關(guān)鍵必須為 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()函數(shù)前添加// 設(shè)置時(shí)區(qū)避免日志時(shí)間錯(cuò)亂 date_default_timezone_set(Asia/Shanghai);4.5 啟動(dòng)服務(wù)并驗(yàn)證連接# 啟動(dòng) GatewayWorker守護(hù)進(jìn)程模式 cd /var/www/gateway-demo php start.php start -d # 查看進(jìn)程是否運(yùn)行 ps aux | grep GatewayWorker # 查看 Gateway 日志 tail -f /var/www/gateway-demo/Applications/GatewayDemo/Logs/gateway-worker.log # 應(yīng)看到 Gateway started 和 Register started # 測(cè)試 Nginx 到 Gateway 的連通性在服務(wù)器本地執(zhí)行 curl -i -N -H Connection: upgrade -H Upgrade: websocket http://127.0.0.1:8282/websocket # 應(yīng)返回 HTTP/1.1 101 Switching Protocols # 測(cè)試 WSS 連接在另一臺(tái)機(jī)器或?yàn)g覽器中 # 訪問(wèn) https://gateway-demo.local打開開發(fā)者工具 Console執(zhí)行 # const ws new WebSocket(wss://gateway-demo.local/websocket); # ws.onopen () console.log(Connected!); # ws.onerror (e) console.error(Error:, e);如果瀏覽器報(bào)錯(cuò)ERR_CONNECTION_REFUSED檢查sudo netstat -tuln | grep :8282確認(rèn) Gateway 進(jìn)程在監(jiān)聽。檢查sudo nginx -t確認(rèn) Nginx 配置語(yǔ)法正確。檢查/var/log/nginx/error.log看是否有connect() failed (111: Connection refused)說(shuō)明 Nginx 連不上 8282。如果瀏覽器報(bào)錯(cuò)ERR_SSL_PROTOCOL_ERROR檢查curl -I https://gateway-demo.local確認(rèn) HTTPS 基礎(chǔ)服務(wù)正常。檢查證書路徑是否正確ls -l /etc/nginx/ssl/看文件是否存在。4.6 前端驗(yàn)證頁(yè)面一個(gè)最小可運(yùn)行的 HTML在/var/www/gateway-demo/Web/index.html中寫入!DOCTYPE html html head titleGatewayWorker WSS Test/title /head body h1GatewayWorker WSS 連接測(cè)試/h1 div idstatus等待連接.../div button onclickconnect()連接/button button onclicksend() disabled idsendBtn發(fā)送消息/button divtextarea idmsg rows2 cols50Hello 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 ? 連接已關(guān)閉; document.getElementById(sendBtn).disabled true; }; ws.onerror function(error) { console.error(WebSocket 錯(cuò)誤:, error); document.getElementById(status).innerText ? 連接錯(cuò)誤: error; }; } function send() { const msg document.getElementById(msg).value; if (ws ws.readyState WebSocket.OPEN) { ws.send(msg); document.getElementById(status).innerText 已發(fā)送: msg; } } /script /body /html訪問(wèn)https://gateway-demo.local點(diǎn)擊“連接”即可看到實(shí)時(shí)連接狀態(tài)和消息收發(fā)。5. 常見問(wèn)題與排查技巧實(shí)錄那些讓你抓狂的“幽靈錯(cuò)誤”在多個(gè)項(xiàng)目的 WSS 部署中我整理出一份高頻問(wèn)題速查表。這些問(wèn)題往往沒有明確報(bào)錯(cuò)或者報(bào)錯(cuò)信息極具誤導(dǎo)性必須結(jié)合上下文才能定位。5.1 問(wèn)題速查表癥狀、原因、驗(yàn)證命令、修復(fù)方案癥狀可能原因驗(yàn)證命令修復(fù)方案瀏覽器控制臺(tái)報(bào)net::ERR_CONNECTION_CLOSEDNginx error.log 為空Nginx 的proxy_pass指向了錯(cuò)誤的端口如 2121 而非 8282或 Gateway 進(jìn)程未啟動(dòng)sudo netstat -tuln | grep :8282curl -i http://127.0.0.1:8282/websocket確認(rèn)gateway.php中port與 Nginxproxy_pass端口一致執(zhí)行php start.php start -d啟動(dòng) Gateway連接能建立但 30 秒后自動(dòng)斷開onClose日志頻繁出現(xiàn)Nginxproxy_read_timeout過(guò)短或 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設(shè)為86400Gatewayping_interval設(shè)為30確保 Nginx timeout 3 × ping_intervalNginx error.log 報(bào)upstream prematurely closed connectionGatewayWorker 進(jìn)程崩潰退出或 Swoole 版本與 OpenSSL 不兼容sudo systemctl status php8.1-fpmphp --ri swoole | grep OpenSSL升級(jí) Swoole 至 5.x確保 OpenSSL ≥ 1.1.1檢查 Gateway 日志是否有 Segmentation fault