首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 多執行緒非同步 Socket 資料包接收器框架實戰

     編輯:關於C#

專案背景:省級交通流量監測系統

  • 需求:接收 100 台自動觀測設備透過無線網卡/Internet 以 Socket 上報的交通量封包。
  • 頻率:全年 365×24 運行,每台設備 5 分鐘上報一次,單筆約 2KB。
  • 環境:VS2003/.NET 1.1 時代,C# 初期生態,參考國內外兩篇關鍵文章(Session 概念框架 + 多執行緒分段接收技術)。

核心架構:三大執行緒 + ThreadPool 統一管理

  1. 監聽執行緒 ListenClientRequest():循環 Accept(),檢查 IP 重複,建立 TSession,啟動 BeginReceive()。
  2. 封包處理執行緒 HandleDatagrams():循環取出 _datagramQueue,解析、驗證、入庫。
  3. 狀態檢查執行緒 CheckClientState():掃描 _sessionTable,三階段清理(Shutdown → Close → Remove),釋放資源。

關鍵類別設計

類別職責
TDatagramReceiver核心進程:Socket 生命週期、執行緒協調、資源管理、對外公共 API
TSession客戶端會話封裝:Socket、接收緩衝區、最後活動時間、狀態機
TDatagram封包實體:型別判斷、解析、資料載體

核心程式碼深度解析

1. 系統啟動 StartReceiver()

public bool StartReceiver()
{
    try
    {
        _stopReceiver = true; this.Close(); // 清理舊資源
        if (!ConnectDatabase()) return false;
        
        // 重置計數器
        _clientCount = _datagramQueueCount = _datagramCount = 
        _errorDatagramCount = _exceptionCount = 0;
        
        _sessionTable = new Hashtable(_maxAllowClientCount);
        _datagramQueue = new Queue<TDatagram>(_maxAllowDatagramQueueCount);
        _stopReceiver = false;
        
        if (!CreateReceiverSocket()) return false;
        
        // ThreadPool 佇列三大核心執行緒
        if (!ThreadPool.QueueUserWorkItem(ListenClientRequest)) return false;
        if (!ThreadPool.QueueUserWorkItem(HandleDatagrams)) return false;
        if (!ThreadPool.QueueUserWorkItem(CheckClientState)) return false;
        
        _stopConnectRequest = false;
    }
    catch { OnReceiverException(); _stopReceiver = true; }
    return !_stopReceiver;
}

關鍵點:使用委托而非 Callback 物件、Interlocked 原子計數、_stopReceiver 旗標統一控制迴圈退出。

2. 建立監聽 Socket CreateReceiverSocket()

private bool CreateReceiverSocket()
{
    try
    {
        _receiverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        _receiverSocket.Bind(new IPEndPoint(IPAddress.Any, _tcpSocketPort));
        _receiverSocket.Listen(_maxAllowListenQueueLength);
        return true;
    }
    catch { OnReceiverException(); return false; }
}

3. 監聽迴圈 ListenClientRequest() - 異步套異步模式

private void ListenClientRequest(object state)
{
    while (!_stopReceiver)
    {
        // 停止連線請求處理
        if (_stopConnectRequest) { /* 關閉 socket、清理 */ continue; }
        if (_receiverSocket == null && !CreateReceiverSocket()) continue;

        try
        {
            if (_receiverSocket.Poll(_loopWaitTime, SelectMode.SelectRead))
            {
                Socket client = _receiverSocket.Accept();
                if (client != null && client.Connected)
                {
                    // 連線數上限、IP 重複檢查
                    if (_clientCount >= _maxAllowClientCount || CheckSameClientIP(client))
                    { /* 關閉拒絕 */ }
                    else
                    {
                        var session = new TSession(client) { LoginTime = DateTime.Now };
                        lock (_sessionTable)
                        {
                            // 確保 Session ID 唯一
                            while (_sessionTable.ContainsKey(session.ID))
                                session.ID = 100000 + session.ID;
                            _sessionTable.Add(session.ID, session);
                            Interlocked.Increment(ref _clientCount);
                        }
                        OnClientRequest();
                        
                        // 關鍵:異步接收,回調 EndReceiveData
                        try { session.ClientSocket.BeginReceive(session.ReceiveBuffer, 0,
                            session.ReceiveBufferLength, SocketFlags.None, EndReceiveData, session); }
                        catch { session.DisconnectType = TDisconnectType.Exception; session.State = TSessionState.NoReply; }
                    }
                }
            }
        }
        catch { /* 例外處理、關閉 client */ }
        // Thread.Sleep(適當毫秒) 降低 CPU 佔用
    }
}

設計亮點:Poll 非阻塞檢查、lock 保護共享資料結構、異常時標記狀態交由清理執行緒處理。

4. 封包處理 HandleDatagrams()

private void HandleDatagrams(object state)
{
    while (!_stopReceiver)
    {
        HandleOneDatagram();
        // 連線斷線自動重連邏輯
        if (!_stopReceiver && _sqlConnection.State == ConnectionState.Closed)
        {
            OnReceiverWork();
            try { _sqlConnection.Open(); } catch { OnReceiverException(); }
        }
    }
}

private void HandleOneDatagram()
{
    TDatagram dg = null;
    lock (_datagramQueue)
        if (_datagramQueue.Count > 0) { dg = _datagramQueue.Dequeue(); Interlocked.Decrement(ref _datagramQueueCount); }
    if (dg == null) return;
    dg.Clear(); dg = null; // 釋放參考
}

5. 會話清理 CheckClientState() - 三階段狀態機

private void CheckClientState(object state)
{
    while (!_stopReceiver)
    {
        var now = DateTime.Now;
        // 建立副本避免長時間持有鎖
        Hashtable snapshot;
        lock (_sessionTable) { snapshot = new Hashtable(_sessionTable); }
        
        foreach (TSession session in snapshot.Values)
        {
            Monitor.Enter(session);
            try
            {
                switch (session.State)
                {
                    case TSessionState.NoReply:
                        session.State = TSessionState.Closing;
                        session.ClientSocket?.Shutdown(SocketShutdown.Both); break;
                    case TSessionState.Closing:
                        session.State = TSessionState.Closed;
                        session.ClientSocket?.Close(); break;
                    case TSessionState.Closed:
                        lock (_sessionTable) { _sessionTable.Remove(session.ID); Interlocked.Decrement(ref _clientCount); }
                        OnClientRequest(); session.Clear(); break;
                    case TSessionState.Normal:
                        if ((now - session.LastDataReceivedTime).TotalSeconds > _maxSocketDataTimeout)
                        { session.DisconnectType = TDisconnectType.Timeout; session.State = TSessionState.NoReply; }
                        break;
                }
            }
            finally { Monitor.Exit(session); }
        }
        snapshot.Clear();
        // Thread.Sleep(間隔)
    }
}

關鍵模式:快照隔離(複製 Hashtable 再遍歷)、狀態機驅動清理(NoReply→Closing→Closed)、Monitor 細粒度鎖(鎖 Session 而非整張表)。

容錯與穩定性實踐

  • 大量 try-catch:網路環境不可控,允許例外存在並標記狀態,而非讓進程崩潰。
  • 資源釋放三部曲:Shutdown(觸發掛起的非同步完成) → Close(釋放 Handle) → Remove(移除管理結構)。
  • ID 生成防衝突:時間基礎 + 迴圈檢查。
  • 資料庫連線自動重連:處理執行緒中檢測 ConnectionState.Closed 並重開。

效能數據與結語

  • 普通 PC (P4 2.0) 連續運行 30+ 天零故障。
  • 單封包處理耗時 ~0.5 秒,優化空間大(程式碼優化、更好硬體)。
  • 完整系統含轉發服務、查詢客戶端、報表、發布、監控等模組,總計 60K+ 行。

此框架展示了早期 .NET 在高併發網路服務上的工程化實踐:ThreadPool 統一調度、非同步 I/O、狀態機資源管理、快照隔離鎖策略。雖然現代開發傾向 async/await、Channels、Kestrel 等高階抽象,但核心思想——關注點分離、失敗隔離、背壓控制——至今仍具參考價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved