Python 多執行緒 TCP Client 完整實作教學(自動重連 + 封包記錄)

更新 發佈閱讀 10 分鐘

本篇教學將帶你實作一個:

  • ✅ 多執行緒 TCP Client
  • ✅ 支援自動斷線重連
  • ✅ 非阻塞收發機制
  • ✅ Queue 非同步發送
  • ✅ JSON 封包格式
  • ✅ 自動依照日期分類儲存封包 Log
  • ✅ Windows Port 被占用處理

這是一個適合工業設備通訊(例如 AVI 檢測系統)或客製化 TCP 協議應用的完整架構範例。


一、整體架構設計

我們的程式包含三個主要類別:

Request        → 封包物件
LogHandler → 訊息回呼機制
TCPClient → 核心 TCP 通訊引擎

架構流程如下:

外部程式

SendData()

tx_queue

TCP Thread

sendall()

Server

Server 回傳

recv()

rx_queue

外部程式處理

二、Request 封包設計

class Request:
def __init__(self, cmd, data):
self.Cmd = cmd
self.Data = data

def to_json(self):
return json.dumps({"Cmd": self.Cmd, "Data": self.Data}, ensure_ascii=False)

設計重點

  • 將封包格式統一為:
{
"Cmd": "PUT_AVIRESULT_REQ",
"Data": {
"PN": "ABC123",
"SN": "0001"
}
}
  • ensure_ascii=False
    → 支援中文

這樣可以確保整個系統的封包格式一致。


三、LogHandler 訊息回呼系統

class LogHandler:
def __init__(self):
self.callbacks = {}

def register(self, name, callback):
self.callbacks[name] = callback

def emit(self, msg):
for callback in self.callbacks.values():
callback(msg)

為什麼需要它?

讓 TCPClient 不直接依賴 UI 或 print。

例如在 PyQt 中可以這樣註冊:

client.logHandlers.register("ui", lambda msg: print(msg))

這樣 TCPClient 就變成:

可嵌入 GUI、Console、Server 程式的通用元件

這是一種 解耦設計(Decoupling Design)


四、TCPClient 核心解析


1️⃣ 初始化

class TCPClient:
def __init__(self):
self.remote_addr = ("127.0.0.1", 9999)
self.local_addr = None
self.sock = None
self._isconnected = False
self._running = False
self.tx_queue = queue.Queue()
self.rx_queue = queue.Queue()
self.logHandlers = LogHandler()
self.log_root = "C:/AVI_Packet"

重點

變數功能tx_queue發送佇列rx_queue接收佇列_running執行緒是否運行_isconnected是否已連線


五、多執行緒設計

def start(self):
if self._running: return
self._running = True
self.thread = threading.Thread(target=self._tcp_thread, daemon=True)
self.thread.start()

使用:

daemon=True

代表主程式結束時,TCP thread 會自動關閉。


六、自動重連機制(核心重點)

if not self._isconnected:

重連流程:

  1. 關閉舊 socket
  2. 建立新 socket
  3. 設定 SO_REUSEADDR
  4. 綁定 local port
  5. 設定 timeout
  6. connect
  7. 切回 non-blocking

1️⃣ 為什麼要 shutdown?

self.sock.shutdown(socket.SHUT_RDWR)
self.sock.close()

確保:

  • 舊 socket 立即釋放
  • 不會殘留 TIME_WAIT

2️⃣ Windows Port 10048 問題處理

self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

如果 Port 被佔用:

self.sock.bind((self.local_addr[0], 0))

改用系統自動分配 Port。

這是實務上非常重要的穩定性優化。


3️⃣ 為什麼 connect 之後要設為 non-blocking?

self.sock.setblocking(False)

如果不這樣:

recv() 會卡死

七、收發邏輯解析


1️⃣ 發送資料(優先處理)

if not self.tx_queue.empty():

轉換 JSON:

if isinstance(obj, Request):
json_str = obj.to_json()

然後發送:

self.sock.sendall((json_str.strip() + "\r\n").encode('utf-8'))

為什麼加 \r\n

因為我們用它當作封包分隔符。


八、接收資料設計

raw_rx = self.sock.recv(4096)

如果:

if not raw_rx:

代表:

Server 已關閉連線

然後拆分:

msgs = raw_rx.decode('utf-8').split("\r\n")

每筆封包丟入:

self.rx_queue.put(m)

九、斷線判斷機制

捕捉:

except (socket.error, ConnectionResetError)

任何錯誤都視為:

斷線

然後:

_isconnected = False

下一輪自動重連。


十、封包自動存檔系統

C:/AVI_Packet/
2026/
0226/
ABC_SN_timestamp.json

建立路徑:

folder_path = os.path.join(self.log_root,
now.strftime("%Y"),
now.strftime("%m%d"))

根據 Cmd 命名檔案:

Cmd檔名REGISTER_REQREGISTER_時間.jsonPUT_AVIRESULT_REQPN_SN_時間.json

這對於:

  • 產線追蹤
  • 問題追查
  • 資料留存

非常實用。


十一、智慧休眠設計

if not active_work:
time.sleep(0.01)
time.sleep(0.005)

目的:

  • 降低 CPU 使用率
  • 不影響即時性

這比無限 while True 空轉好很多。


十二、完整使用範例

client = TCPClient()

client.setRemote("127.0.0.1", 9999)

client.logHandlers.register("console", print)

client.start()

req = Request("PUT_AVIRESULT_REQ", {
"PN": "ABC123",
"SN": "0001"
})

client.SendData(req)

十三、這個架構的優點

✔ 穩定自動重連


✔ 不會卡住 UI ✔ 支援 GUI / Console ✔ 可處理高頻封包 ✔ 可追蹤歷史封包 ✔ Windows 相容優化



十四、可進一步優化方向

  1. 增加心跳機制
  2. 增加最大重試次數
  3. 加入 SSL/TLS
  4. 封包加上序號
  5. 使用 asyncio 重寫(高併發版本)

十五、總結

這是一個:

工業等級 TCP Client 架構

它解決了:

  • Port 被占用
  • 斷線殘留
  • 阻塞卡死
  • 重連不穩定
  • 封包無紀錄

等常見問題。

留言
avatar-img
螃蟹_crab的沙龍
161會員
317內容數
本業是影像辨識軟體開發,閒暇時間進修AI相關內容,將學習到的內容寫成文章分享。 興趣是攝影,踏青,探索未知領域。 人生就是不斷的挑戰及自我認清,希望老了躺在床上不會後悔自己什麼都沒做。
螃蟹_crab的沙龍的其他內容
2024/08/11
避免 thread 競速(Race Condition)是多執行緒編程中常見的挑戰之一。 Race Condition 發生在多個執行緒同時訪問和修改共享資源時,因為執行緒之間的執行順序無法預測,可能會導致數據的不一致性或意外行為。 本文主要介紹如何使用Lock來避免此狀況出現。 首先先看沒
Thumbnail
2024/08/11
避免 thread 競速(Race Condition)是多執行緒編程中常見的挑戰之一。 Race Condition 發生在多個執行緒同時訪問和修改共享資源時,因為執行緒之間的執行順序無法預測,可能會導致數據的不一致性或意外行為。 本文主要介紹如何使用Lock來避免此狀況出現。 首先先看沒
Thumbnail
2024/03/22
在Python中,queue是一個非常有用的模块。 它提供了多種佇列(queue)實現,用於在多線程環境中安全地交換信息或者數據。 佇列(queue)是一種先進先出(FIFO)的數據結構,允許在佇列的一端插入元素,另一端取出元素。(FIFO 是First In, First Out 的縮寫)
Thumbnail
2024/03/22
在Python中,queue是一個非常有用的模块。 它提供了多種佇列(queue)實現,用於在多線程環境中安全地交換信息或者數據。 佇列(queue)是一種先進先出(FIFO)的數據結構,允許在佇列的一端插入元素,另一端取出元素。(FIFO 是First In, First Out 的縮寫)
Thumbnail
2024/03/21
當你需要在 Python 中執行多個任務,但又不希望它們相互阻塞時,可以使用 threading 模組。 threading 模組允許你在單個程序中創建多個執行緒,這些執行緒可以同時運行,從而實現並行執行多個任務的效果。
Thumbnail
2024/03/21
當你需要在 Python 中執行多個任務,但又不希望它們相互阻塞時,可以使用 threading 模組。 threading 模組允許你在單個程序中創建多個執行緒,這些執行緒可以同時運行,從而實現並行執行多個任務的效果。
Thumbnail
看更多
你可能也想看
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
你是怎麼在用 AI 工具的呢? 回頭看這幾年 AI 的發展歷程,我們從最早期的文字接龍,一路見證了全方位數位助理的誕生。中間經歷了無數次的技術變革,從 Text-to-Text 的文本生成,到跨越感官的 Text-to-Image、Video 與 Audio。而今,這項技術迎來了最關鍵的轉折點「A
Thumbnail
你是怎麼在用 AI 工具的呢? 回頭看這幾年 AI 的發展歷程,我們從最早期的文字接龍,一路見證了全方位數位助理的誕生。中間經歷了無數次的技術變革,從 Text-to-Text 的文本生成,到跨越感官的 Text-to-Image、Video 與 Audio。而今,這項技術迎來了最關鍵的轉折點「A
Thumbnail
一、為什麼需要 3x3 矩陣?避開單一指標的盲點 在海量的全球股市數據中,投資人最常犯的錯誤是「只看加權指數」或「只看單一均線」。然而,指數的漲跌往往被權值股(如美股的 Mag 7 或台股的台積電)所掩蓋。 為了看清市場整體的「健康程度」,我開發了這套 3x3 分佈矩陣分析引擎: 橫向維度(時
Thumbnail
一、為什麼需要 3x3 矩陣?避開單一指標的盲點 在海量的全球股市數據中,投資人最常犯的錯誤是「只看加權指數」或「只看單一均線」。然而,指數的漲跌往往被權值股(如美股的 Mag 7 或台股的台積電)所掩蓋。 為了看清市場整體的「健康程度」,我開發了這套 3x3 分佈矩陣分析引擎: 橫向維度(時
Thumbnail
本文說明在安裝實體具有多核 GPU 的環境下,可以透過 Python 「多執行緒的」程式,讓 CPU 及 GPU 依照特性,各自同時進行運算,得到最好的算力配置。
Thumbnail
本文說明在安裝實體具有多核 GPU 的環境下,可以透過 Python 「多執行緒的」程式,讓 CPU 及 GPU 依照特性,各自同時進行運算,得到最好的算力配置。
Thumbnail
IDE 升級後出現了一樣的錯誤,手上程式碼沒有 pylint black-format 檢查上不了 gitlab,我又點開了那個很小很小的 x 符號,裡面 logs 提示的解決方式是升級..
Thumbnail
IDE 升級後出現了一樣的錯誤,手上程式碼沒有 pylint black-format 檢查上不了 gitlab,我又點開了那個很小很小的 x 符號,裡面 logs 提示的解決方式是升級..
Thumbnail
題目會給我們一個山形的輸入陣列,和目標值target,要求我們找出目標值所在的陣列索引。如果出現兩次,返回比較小的那一個,也就是比較靠左的那個索引值。 山形的意思就是說,從最左側到山頂最大值都是遞增,從山頂最大值到右側都是遞減。
Thumbnail
題目會給我們一個山形的輸入陣列,和目標值target,要求我們找出目標值所在的陣列索引。如果出現兩次,返回比較小的那一個,也就是比較靠左的那個索引值。 山形的意思就是說,從最左側到山頂最大值都是遞增,從山頂最大值到右側都是遞減。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News