Socket.IO 連線錯誤與重連排查示意圖

← INSIGHTS & PERSPECTIVES | 後端開發

Python Socket.IO Client 4.5.1 不會自動重連:503 後的排查與手動重連寫法

記錄 python-socketio 4.5.1 遇到 HTTP 503 後沒有自動重連的排查,並整理用 Timer 手動重連的處理方式。

我遇到的問題是 python-socketio 4.5.1 在 polling 連線收到 HTTP 503 後,client 結束連線流程,但沒有照預期自動重連。當時的處理方式是先關掉套件內建重連,改用 threading.Timer 包一層自己的重連邏輯,讓服務在斷線或連線失敗時持續嘗試重新連上。

問題版本與現象是什麼?

python-socketio 4.5.1 的問題情境是 polling request 收到 HTTP 503 後,client 沒有再進入自動重連。這個行為也出現在 GitHub issue #485 的討論,回報者使用 python-socketio 4.5.1 與 python-engineio 3.12.1。

當時我記下的版本與線索:

項目內容
Python 套件python-socketio
問題版本4.5.1
相關 Engine.IO 版本GitHub issue 中提到 python-engineio 3.12.1
連線方式transports='polling'
主要現象HTTP 503 後沒有自動重連
相關討論GitHub issue #485:Reconnect on HTTP 503

GitHub issue #485 的 log 顯示,client 在送出 PING packet 時收到 HTTP 503,接著 Engine.IO client 記錄 unexpected status code 並 abort;回報者描述 client 直接離開,沒有再嘗試 reconnect(GitHub,2020-05)。

為什麼需要自己寫重連?

python-socketio 文件說明 Client(reconnection=True) 預設會在連線中斷後嘗試重連,但 4.5.1 遇到 503 abort 的情境並沒有如預期恢復。我的取捨是先把內建重連設為 False,把重連時機收斂到自己能控制的程式碼。

官方 v4 API 文件中,socketio.Clientreconnection 預設為 Truereconnection_attempts=0 代表無限次嘗試,reconnection_delay 預設第一段延遲為 1 秒(python-socketio documentation,v4 API)。照文件看,正常的斷線應該可以交給 client 自己處理。

問題在於,我碰到的不是一般網路斷線,而是 server 回了 HTTP 503 之後 client 中止 polling 流程。為了讓服務不要停在斷線狀態,我選擇用最小可控的方式補一層 retry。

手動重連程式碼怎麼寫?

手動重連的核心是讓 connectSocket() 負責連線,失敗時取消舊 Timer,再排下一次嘗試。disconnect event 也用同一個入口重新排程,避免連線失敗與已連線後斷線走兩套不同邏輯。

我當時整理的重連寫法如下,重點是 socketio.Client(reconnection=False)Timer(1.0, connectSocket)

import socketio
from threading import Timer

timer = None
address = "http://127.0.0.1:2027"
sio = socketio.Client(reconnection=False, logger=False, engineio_logger=False)
isConnected = False


def connectSocket():
    global timer
    try:
        sio.connect(address, transports='polling')
    except:
        if timer is not None:
            timer.cancel()
        timer = Timer(1.0, connectSocket)
        timer.start()


def close():
    global sio
    global timer
    global isConnected
    sio.disconnect()
    isConnected = False
    if timer is not None:
        timer.cancel()


@sio.event
def test():
    print('(test)')


def send(data):
    sio.emit('send', data)


@sio.event
def connect():
    global isConnected
    print('(connect)')
    isConnected = True


@sio.event
def disconnect():
    global timer
    global isConnected
    print('(disconnected)')
    sio.disconnect()
    isConnected = False
    if timer is not None:
        timer.cancel()
    timer = Timer(1.0, connectSocket)
    timer.start()

這段程式碼的想法很直接:連不上就一秒後再試,斷線也一秒後再試。close() 則負責在程式主動關閉時取消 timer,避免背景還有排程繼續呼叫 connectSocket()

排查時要注意哪些細節?

排查 python-socketio 4.5.1 重連問題時,不要只看 client 是否觸發 disconnect。需要同時看 HTTP status code、Engine.IO log、transport 類型,以及是初次連線失敗還是已連線後中斷。

我會先確認幾件事:

檢查項目為什麼要看
logger=True / engineio_logger=True看 PING、PONG、polling request 與 abort 位置
HTTP status code503 表示 server 或中間層暫時不可用,不等同 client 主動斷線
transport這個案例用 polling,和 WebSocket upgrade 失敗是不同問題
reconnect 是否真的被觸發不要只看 disconnect,也要看後續是否有新的 connect request
server / proxy log503 可能來自應用伺服器、反向代理或負載平衡器

如果只是暫時性的網路波動,內建重連通常比較適合;如果是 4.5.1 這種特定錯誤路徑沒有恢復,手動重連才是務實的補洞方式。

這個寫法有哪些限制?

Timer 手動重連能讓服務恢復,但不是完整的連線管理框架。實務上要避免重複 timer、無限密集重試、程式關閉後還在背景重連,以及多執行緒同時呼叫 emit() 的競態。

這段程式碼是解決當下問題的最小做法,不是我會直接拿去當長期通用模組的版本。若要放進正式服務,我會補上:

  1. 明確捕捉例外,不使用裸 except
  2. 加上最大延遲或 backoff,避免 server 故障時每秒固定打一次。
  3. connect() 成功後清掉舊 timer 狀態。
  4. 區分「主動關閉」與「意外斷線」,避免 close() 後又被 disconnect() 排回重連。
  5. emit() 加上 connected 狀態檢查,避免斷線期間送資料失敗。

官方文件也提醒,emit() 不是 thread safe;如果多個 thread 同時透過同一個 client 發送訊息,應該用 Lock 等同步機制保護封包順序(python-socketio documentation,v4 API)。

什麼時候應該升級而不是補手動重連?

如果專案允許升級套件,優先測試新版 python-socketio 會比長期維護自製重連更好。手動重連適合無法立即升級、但服務又需要持續連線的短期修補。

我會用這個順序判斷:

情境建議
只能留在 4.5.1先補手動重連,並把 log 打清楚
可以升級但風險未知在測試環境重放 503、timeout、server restart
服務需要長期穩定升級後保留監控,不依賴單一 retry 寫法
server 經常回 503先查 server、proxy、負載平衡器,不要只在 client 重試

新版 python-socketio 文件仍保留 client 端重連選項,也說明 accidental disconnection 後 client 會嘗試 reconnect;如果升級後行為符合預期,就應該移除臨時性的 Timer retry,讓連線生命週期回到套件管理。

常見問題

Qpython-socketio 4.5.1 為什麼不會自動重連?

我遇到的情境是 polling request 收到 HTTP 503 後,Engine.IO client 中止流程,沒有再走預期的 reconnect。GitHub issue #485 也描述了相同方向的問題:client 在 503 後直接離開,沒有再嘗試連線。

Qreconnection=True 不是預設值嗎?

是,python-socketio v4 文件中 socketio.Clientreconnection 預設值是 True。但 4.5.1 在 HTTP 503 abort 這條路徑上,實際行為和一般斷線重連不同,所以我才改成 reconnection=False,自己接管重連。

Q為什麼範例使用 transports='polling'

當時的連線情境就是 polling,所以我保留這個設定來對應實際問題。python-socketioconnect() 可以指定 pollingwebsocket transport;若不指定,通常會先連 polling,再嘗試升級 WebSocket。

Q手動重連間隔一秒會不會太短?

一秒適合用在單機或低流量排查,不一定適合正式環境。正式服務建議改成 exponential backoff、最大延遲與停止條件,避免 server 503 時 client 同時重試造成更大壓力。

Q這段程式碼可以直接用在正式環境嗎?

這段程式碼可以當作修補方向,但正式環境至少要補例外分類、關閉狀態、timer 去重、重試上限或 backoff。若多 thread 會呼叫 emit(),還要加同步保護。

Q升級 python-socketio 後還需要 Timer 重連嗎?

升級後應先用測試重放 503、server restart、timeout 等情境。若新版內建重連已能正確恢復,就不需要保留 Timer 重連,避免自製邏輯和套件內建生命週期互相干擾。

參考資料

延伸閱讀

最後更新

本文最後更新於 2026-08-28。首次整理日期為 2022-12-16,本次更新補上 GEO 結構、FAQ、參考資料與延伸閱讀。

關於作者

Claire Chang | 企業 AI 導入與流程轉型顧問。專注於 AI Agent 架構設計、ERP 系統整合與企業 AI 治理。

首次發布:2022-12-16