??大家好,我是 展菲,目前在上市企業從事人工智能項目研發管理工作,平時熱衷于分享各種編程領域的軟硬技能知識以及前沿技術,包括iOS、前端、Harmony OS、Java、Python等方向。在移動端開發、鴻蒙開發、物聯網、嵌入式、云原生、開源等領域有深厚造詣。
圖書作者:《ESP32-C3 物聯網工程開發實戰》
圖書作者:《SwiftUI 入門,進階與實戰》
超級個體:COC上海社區主理人
特約講師:大學講師,谷歌亞馬遜分享嘉賓
科技博主:華為HDE/HDG
我的博客內容涵蓋廣泛,主要分享技術教程、Bug解決方案、開發工具使用、前沿科技資訊、產品評測與使用體驗。我特別關注云服務產品評測、AI 產品對比、開發板性能測試以及技術報告,同時也會提供產品優缺點分析、橫向對比,并分享技術沙龍與行業大會的參會體驗。我的目標是為讀者提供有深度、有實用價值的技術洞察與分析。
展菲:您的前沿技術領航員
👋 大家好,我是展菲!
📱 全網搜索“展菲”,即可縱覽我在各大平臺的知識足跡。
📣 公眾號“Swift社區”,每周定時推送干貨滿滿的技術長文,從新興框架的剖析到運維實戰的復盤,助您技術進階之路暢通無阻。
💬 微信端添加好友“fzhanfei”,與我直接交流,不管是項目瓶頸的求助,還是行業趨勢的探討,隨時暢所欲言。
📅 最新動態:2025 年 3 月 17 日
快來加入技術社區,一起挖掘技術的無限潛能,攜手邁向數字化新征程!
文章目錄
- 前言
- 問題場景復現
- Node.js 端的連接代碼
- 瀏覽器端的配置對比
- 問題分析
- 解決方案
- 1. 確認版本兼容
- 2. 模擬瀏覽器的請求頭
- 3. 使用 ws 庫手動握手
- 4. 用瀏覽器環境跑(例如 Puppeteer + Tampermonkey)
- 總結
前言
在做逆向或者接口復現的時候,很多人都會遇到一個奇怪的問題:同樣是調用 socket.io
客戶端,在瀏覽器里能和服務器正常握手通信,但在 Node.js 里跑就會報 transport close
錯誤。
這類問題常常讓人懷疑是不是請求頭、版本、路徑的問題,但即使把各種參數都調了個遍,依然解決不了。下面我結合一個真實案例,聊聊為什么會出現這種現象,以及應該怎么排查和解決。
問題場景復現
我想在 Node.js 中復現某個網站的一個功能,該功能依賴 socket.io
建立 websocket 連接。
但是我發現,瀏覽器端運行一切正常,而在 Node.js 里卻始終報錯:
WebSocket連接關閉: transport close
Node.js 端的連接日志大致如下:
[2025-09-09T06:26:32.787Z] 接收到Engine.IO包: {type: 'open',data: '{"sid":"87b1232f-2644-4954-8dcb-4bed0c8f9591","upgrades":["websocket"],"pingInterval":5000,"pingTimeout":30000}'
}
2025-09-09T06:26:32.789Z socket.io-client:socket sending connect packet with query token=123123123&key=110133478422868
WebSocket連接已打開
[2025-09-09T06:26:32.942Z] 接收到Engine.IO包: { type: 'message', data: '0/lookTime' }
WebSocket連接關閉: transport close
在瀏覽器端,用同樣的 socket.io
參數就能連上并保持長連接。
Node.js 端的連接代碼
我最開始的 Node.js 配置如下:
const io = require("socket.io-client")const url = 'wss://foo-socketio.foo.com/lookTime'
const query = `token=${token}&key=${key}`const socket = io.connect(url, {path: "/socket.io", forceNew: true,reconnection: true,reconnectionDelayMax: 1000,reconnectionAttempts: 5,transports: ["websocket"],extraHeaders: {Host: 'foo-socketio.foo.com',Origin: 'https://foo.com',"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)","Sec-WebSocket-Version": "13"},query
})socket.on("connect", () => {console.log("Node.js 成功連接")
})socket.on("connect_error", (err) => {console.error("連接失敗:", err)
})socket.on("disconnect", (reason) => {console.warn("連接斷開:", reason)
})
可以看到,雖然連接已經建立,但在握手確認階段立刻斷開,報 transport close
。
瀏覽器端的配置對比
對比瀏覽器源碼,可以看到它用的是類似的配置:
var url = "wss://foo-socketio.foo.com/lookTime"
var opts = {path: "/socket.io",forceNew: true,reconnection: true,"reconnection limit": 1000,reconnectionAttempts: Infinity,transports: ["websocket"],query: "token=" + this.token + "&key=" + this.key
}
this.chatWebsock = io.connect(url, opts)
很顯然,配置上沒有明顯差別。那為什么瀏覽器能連上,Node.js 卻不行?
問題分析
經過排查,我總結了幾個可能的原因:
-
瀏覽器和 Node.js 的 socket.io-client 實現不完全相同
瀏覽器端的socket.io-client
更貼近真實的瀏覽器環境,和服務器協商時帶上的 header、cookie、CORS 行為都更自然。
Node.js 端雖然也能跑,但它在某些服務器環境下會暴露出兼容性問題。 -
握手階段缺少關鍵 header 或 cookie
很多服務端會在握手時校驗請求來源,比如Origin
、Sec-WebSocket-Key
、Sec-WebSocket-Protocol
等。如果缺少或格式不對,就會被直接拒絕。瀏覽器會自動生成這些 header,而 Node.js 中必須手動加。 -
socket.io 的版本差異
你可能在項目里安裝了socket.io-client@4.x
,而服務端跑的是2.x
或3.x
,這時候就容易 handshake 失敗。瀏覽器里的客戶端腳本一般是跟隨服務端下發的,所以能成功,而 Node.js 里可能裝了個不兼容版本。 -
服務器對環境有區分
有些服務端邏輯會檢測User-Agent
或運行環境,如果不是瀏覽器(例如 “Node.js” UA),直接斷開。
解決方案
結合以上分析,可以嘗試以下幾種辦法:
1. 確認版本兼容
安裝一個和瀏覽器端一致的版本:
npm install socket.io-client@2.4.0
或者明確指定與服務器端日志顯示一致的版本。
2. 模擬瀏覽器的請求頭
在 Node.js 中盡量補齊和瀏覽器一致的 header,尤其是 Sec-WebSocket-Key
和 Sec-WebSocket-Protocol
。
例如:
extraHeaders: {Origin: 'https://foo.com',"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)","Sec-WebSocket-Version": "13","Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits"
}
3. 使用 ws 庫手動握手
如果 socket.io-client
在 Node.js 中始終不行,可以退而求其次,直接用 ws
庫手動實現 websocket 連接,構造和瀏覽器一致的握手請求。
例如:
const WebSocket = require("ws")
const ws = new WebSocket("wss://foo-socketio.foo.com/socket.io/?EIO=3&transport=websocket&token=xxx&key=yyy", {headers: {Origin: "https://foo.com","User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
})ws.on("open", () => console.log("連接成功"))
ws.on("message", (msg) => console.log("消息:", msg.toString()))
ws.on("close", () => console.log("連接關閉"))
這樣能更精確地調試出到底是哪一步握手沒對上。
4. 用瀏覽器環境跑(例如 Puppeteer + Tampermonkey)
如果服務端真的綁定了瀏覽器特性,可以直接在瀏覽器里運行代碼:
- 用 油猴插件 注入
- 或者用 Puppeteer 啟動一個無頭瀏覽器運行 websocket 邏輯
這樣能最大限度保證環境一致。
總結
- 瀏覽器端能連上,Node.js 連不上,大概率是 handshake 的環境差異造成的。
- 先確認
socket.io-client
的版本是否和服務端兼容; - 再補齊和瀏覽器一致的請求頭;
- 如果還不行,就用
ws
模擬或者干脆跑在瀏覽器環境。
這類問題其實不是 Node.js 代碼錯了,而是因為 瀏覽器和 Node.js 的運行環境存在差異,導致某些服務器實現只認瀏覽器的 handshake。