在 .NET Framework 早期,System.Net.Sockets 命名空間提供了低階的 Socket API,讓開發者能直接操作 TCP/UDP 通訊。這篇文章將帶你完整走過一個跨平台(當時指 Windows 9x/NT)的點對點文字聊天程式實作,涵蓋命令列參數解析、非同步 Socket 連線、自訂封包協定、WinForms UI 與事件驅動架構。雖然現代開發慣用 SignalR、gRPC 或 WebSocket,但理解底層 Socket 運作仍是網路程式設計的基石。
整個程式僅包含單一 Wintalk.cs 檔案,卻完整實作了三大核心類別:
/L 監聽、/C 連線)、啟動 Talker 與 TalkForm。Form 的 UI 類別,包含訊息顯示區、輸入區、狀態列,並訂閱 Talker 的事件通知。IDisposable 的通訊核心,封裝 Socket、StreamReader/Writer、自訂協定(R{長度}:{內容} 全量更新、〈A{長度}:{內容}〉增量更新)、背景執行緒接收迴圈與事件回呼機制。// 無參數時預設為 Server,監聽 5150 埠
// /L [port] 指定監聽埠
// /C [address] [port] 指定連線目標
ParseArgs 使用 Dns.Resolve 解析主機名稱,支援 IPv4。若監聽埠被佔用(錯誤碼 10048),會自動切換為 Client 模式連回本機,這是一個有趣的「自動配對」設計。
為了在單一 TCP 連線上可靠傳送可變長度文字,Talker.SendTalk 設計了簡單協定:
R{length}:{text} — 當輸入內容非單純追加時使用。A{length}:{text} — 僅傳送新增字元,減少頻寬。接收端 ReceiveTalk 以狀態機逐字元解析,支援多字節字元(Big5/GB2312 環境下中文字佔 2 位元組),並透過 NotificationCallback 將完整訊息推送給 UI 執行緒。
// Talker 定義事件
public event NotificationCallback Notifications;
// TalkForm 訂閱並處理
talker.Notifications += HandleTalkerNotifications;
// UI 更新需在 UI 執行緒,但此範例直接在背景執行緒存取控制項
// 現代寫法應搭配 Control.Invoke/BeginInvoke
範例直接在背景執行緒更新 TextBox,在 .NET 1.x/2.0 可能因 CheckForIllegalCrossThreadCalls = false 預設關閉而運作,但在現代 .NET 必須改用 Invoke 或 SynchronizationContext。
Talker 實作 IDisposable 並搭配終結子(~Talker),確保 Socket、StreamReader、StreamWriter 正確關閉,避免連線殘留。
若要在 .NET 6+ 環境運行,建議進行下列現代化調整:
ThreadPool.QueueUserWorkItem 改為 Task.Run 或 async/await 配合 NetworkStream.ReadAsync。CancellationToken 控制接收迴圈結束,取代例外處理流程。Invoke 或 Progress<T> 執行。Encoding.UTF8),避免跨平台亂碼。// 終端機 1(Server)
dotnet run -- /L 5150
// 終端機 2(Client)
dotnet run -- /C 127.0.0.1 5150
兩視窗啟動後,下方輸入框鍵入文字即時雙向傳送,上方唯讀區顯示對方訊息,狀態列顯示 Listening / Connected。
這個範例雖然年代久遠,但完整展示了:
Bind → Listen → Accept / Connect)理解這些基礎,對後續學習高階網路框架(Kestrel、gRPC、WebSocket)大有裨益。