C# 實現的多線程非同步Socket資料包接收器架構

來源:互聯網
上載者:User

幾天前在博問中看到一個C# Socket問題,就想到筆者2004年做的一個省級交通流量接收伺服器項目,當時的基本求如下: 接收自動觀測裝置通過無線網卡、Internet和Socket上報的交通量資料包 全年365*24啟動並執行自動觀測裝置5分鐘上報一次觀測資料,每筆記錄約2K大小 規劃全省將有100個左右的自動觀測裝置(截止2008年10月還只有30個)

      當時,VS2003才發布年多,筆者也是接觸C#不久。於是Google了國內國外網,希望找點應用C#解決Socket通訊問題的思路和代碼。最後,找 到了兩篇協助最大的文章:一篇是國人寫的Socket接收器架構,應用了獨立的用戶端Socket會話(Session)概念,給筆者提供了一個接收服務 器的總體架構思路;另一篇是美國人寫的,提出了多線程、分段接收資料包的技術方案,描述了多線程、非同步Socket的許多實現細節,該文堅定了筆者採用多 線程和非同步方式處理Socket接收器的技術路線。

     具體實現和測試時筆者還發現,在Internet環境下的Socket應用中,需要系統有極強的容錯能力:沒有辦法控制異常,就必須允許它們存在(附加源 代碼中可以看到,try{}catch{}語句較多)。對此,筆者設計了一個專門的檢查和清理線程,完成無效或逾時會話的清除和資源釋放工作。

     依稀記得,國內架構作者的名稱空間有ibm,認為是IBM公司職員,通過郵件後才知道其人在深圳。筆者向他請教了幾個問題,相互探討了幾個技術關鍵點。可 惜,現在再去找,已經查不到原文和郵件了。只好藉此機會,將本文獻給這兩個素未謀面的技術高人和同行,也盼望拙文或源碼能給讀者一點有用的啟發和協助。

1、主要技術思路

     整個系統由三個核心線程組成,並由.NET線程池統一管理: 偵聽用戶端串連請求線程:ListenClientRequest(),迴圈偵聽用戶端 串連請求。如果有,檢測該用戶端IP,看是否是同一觀測裝置,然後建立一個用戶端TSession對象,並通過Socket非同步呼叫方法 BeginReceive()接收資料包、EndReceive()處理資料包 資料包處理線程:HandleDatagrams(),迴圈檢測資料包隊列_datagramQueue,完成資料包解析、判斷類型、儲存等工作 用戶端狀態檢測線程:CheckClientState(),迴圈檢查用戶端工作階段表_sessionTable,判斷會話對象是否有效,設定逾時會話關閉標誌,清楚無效會話對象及釋放其資源

2、主要類簡介

     系統主要由3個類組成: TDatagramReceiver(資料包接收伺服器):系統的核心進程類,建立Socket串連、處理與儲存資料包、清理系統資源,該類提供全部的public屬性和方法 TSession(用戶端工作階段):由每個用戶端的Socket對象組成,有自己的資料緩衝區,清理線程根據該對象的最近會話時間判斷是否逾時 TDatagram(資料包類):判斷資料包類別、解析資料包

3、關鍵函數和代碼

     下面簡介核心類TDatagramReceiver的關鍵實現代碼。

3.1  系統啟動

      系統啟動方法StartReceiver()首先清理資源、建立資料庫連接、初始化若干計數值,然後建立伺服器端偵聽Socket對象,最後調用靜態方法ThreadPool.QueueUserWorkItem()線上程池中建立3個核心處理線程。 Code

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->/// <summary>
///  啟動接收器
/// </summary>
public bool StartReceiver()
{
    try
    {
        _stopReceiver = true;

        this.Close();

        if (!this.ConnectDatabase()) return false;

        _clientCount = 0;
        _datagramQueueCount = 0;
        _datagramCount = 0;
        _errorDatagramCount = 0;
        _exceptionCount = 0;

        _sessionTable = new Hashtable(_maxAllowClientCount);
        _datagramQueue = new Queue<TDatagram>(_maxAllowDatagramQueueCount);

        _stopReceiver = false;  // 迴圈中均要該標誌

        if (!this.CreateReceiverSocket())  //建立伺服器端 Socket 對象
        {
            return false;
        }

        // 偵聽用戶端串連請求線程, 使用委託推斷, 不建 CallBack 對象
        if (!ThreadPool.QueueUserWorkItem(ListenClientRequest))
        {
            return false;
        }

        // 處理資料包隊列線程
        if (!ThreadPool.QueueUserWorkItem(HandleDatagrams))
        {
            return false;
        }

        // 檢查客戶工作階段狀態, 長時間未通訊則清除該對象
        if (!ThreadPool.QueueUserWorkItem(CheckClientState))
        {
            return false;
        }

        _stopConnectRequest = false;  // 啟動接收器,則自動允許串連
    }
    catch
    {
        this.OnReceiverException();
        _stopReceiver = true;
    }
    return !_stopReceiver;
}

      下面是建立偵聽Socket對象的方法代碼。 Code

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->/// <summary>
/// 建立接收伺服器的 Socket, 並偵聽用戶端串連請求
/// </summary>
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
    {
        this.OnReceiverException();
        return false;
    }
}

3.2  偵聽用戶端串連請求

      伺服器端迴圈等待用戶端串連請求。一旦有請求,先判斷用戶端串連數是否超限,接著檢測該用戶端IP地址,一切正常後建立TSession對象,並調用非同步方法呼叫接收用戶端Socket資料包。

      代碼中,Socket讀到資料時的回調AsyncCallback委託方法EndReceiveData()完成資料接收工作,正常情況下啟動另一個非同步BeginReceive()調用。

      .NET中,每個非同步方法呼叫都有自己的獨立線程,非同步處理其實也基於多線程機制的。下面代碼中的非同步套非同步呼叫,既佔用較大的系統資源,也給處理帶來意想不到的結果,更是出現異常時難以控制和處理的關鍵所在。 Code

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->/// <summary>
/// 迴圈偵聽用戶端請求,由於要用線程池,故帶一個參數
/// </summary>
private void ListenClientRequest(object state)
{
    Socket client = null;
    while (!_stopReceiver)
    {
        if (_stopConnectRequest)  //  停止用戶端串連請求
        {
            if (_receiverSocket != null)
            {
                try
                {
                    _receiverSocket.Close();  // 強制關閉接收器
                }
                catch
                {
                    this.OnReceiverException();
                }
                finally
                {
                    // 必須為 null,否則 disposed 對象仍然存在,將引發下面的錯誤
                    _receiverSocket = null;
                }
            }
            continue;
        }
        else
        {
            if (_receiverSocket == null)
            {
                if (!this.CreateReceiverSocket())
                {
                    continue;
                }
            }
        }

        try
        {
            if (_receiverSocket.Poll(_loopWaitTime, SelectMode.SelectRead))
            {
                // 頻繁關閉、啟動時,這裡容易產生錯誤(提示通訊端只能有一個)
                client = _receiverSocket.Accept();

                if (client != null && client.Connected)
                {
                    if (this._clientCount >= this._maxAllowClientCount)
                    {
                        this.OnReceiverException();

                        try
                        {
                            client.Shutdown(SocketShutdown.Both);
                            client.Close();
                        }
                        catch { }
                    }
                    else if (CheckSameClientIP(client))  // 已存在該 IP 地址
                    {
                        try
                        {
                            client.Shutdown(SocketShutdown.Both);
                            client.Close();
                        }
                        catch { }
                    }
                    else
                    {
                        TSession session = new TSession(client);
                        session.LoginTime = DateTime.Now;

                        lock (_sessionTable)
                        {
                            int preSessionID = session.ID;
                            while (true)
                            {
                                if (_sessionTable.ContainsKey(session.ID))  // 有可能重複該編號
                                {
                                    session.ID = 100000 + preSessionID;
                                }
                         &nbs

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.