UDT協議實現分析——UDT初始化和銷燬

UDT協議是一個用於在高速Internet上傳輸大量數據的基於UDP的可靠傳輸協議。api

咱們能夠將UDT協議的實現看做一個比較複雜的狀態機。更準確的說,是一個主狀態機,外加多個子狀態機。主狀態機是指協議實現中全局惟1、全局共享的狀態與數據結構,主要對應於CUDTUnited類。子狀態機則是對於一次UDT鏈接或一個Listening的UDT Server的抽象,是UDT本身建立的Socket抽象,一個與系統socket類似但又不一樣的概念,主要對應於CUDTSocket和CUDT類。UDT的Socket又能夠分爲3類,分別是Listening socket,read socket和write socket。儘管實際存在3種類型的socket,它們卻都是由相同的幾個類來表示的,但在這幾個類中,它們卻又都有着本身特有的數據結構/狀態。網絡

後面咱們將主要用狀態機的 網絡協議分析方法 來分析UDT。具體而言,會主要從以下的一些方面來分析:數據結構

1. 這個協議定義了多少種類型的網絡消息,每種消息的具體格式是什麼?app

2. 主要的一些動做具體的執行過程,好比創建鏈接,斷開鏈接,心跳,丟失數據包的信息反饋等:socket

(1). 這些動做發起方和接受方各須要傳遞多少消息,傳遞什麼類型的消息?各個消息的具體含義是什麼?每條消息中具體攜帶了些什麼信息,每種信息的含義又是什麼?每條消息都是在什麼時間點發送的?函數

(2). 動做執行過程當中發送的每一條消息對於主狀態機的影響有哪些?它會促使主狀態機的狀態做什麼樣的轉換?ui

(3). 動做執行過程當中發送的每一條消息對於相關聯的具體的一個子狀態機有何種影響?它會促使子狀態機的狀態做什麼樣的轉換?this

3. 協議層面提供了多少接口,即主狀態機提供的public的,給調用者使用的接口都有哪些,好比startup,shutdown,newSocket,listen等。對於這些接口的調用會對主狀態機的狀態產生什麼樣的影響,會促使主狀態機的狀態做什麼樣的轉換?spa

對於這些接口的調用是否會影響到子狀態機?若是會,又會影響哪些,一個仍是多個,對於相應的子狀態機的狀態有些什麼樣的影響,會促使它們做什麼樣的狀態改變?線程

4. Socket(即子狀態機)提供給用戶調用的接口有哪些?對每一個接口的調用對子狀態機的影響是什麼?會促使子狀態機的狀態做什麼樣的轉換?

對於這些接口的調用是否會影響到主狀態機?若是會,又是什麼樣的影響?會促使主狀態機的狀態做什麼樣的轉換?

5. 狀態機的激勵源:

(1). 提供給調用者調用的接口。

(2). 從網絡中傳遞進來的消息。

(3). 狀態機內部起的一些定時執行的Task或其它的線程等。

UDT的初始化與銷燬

這裏從UDT全局初始化及銷燬的部分開始分析。也就是主狀態機的狀態變化。

在調用UDT庫提供的任何功能以前,須要首先調用UDT namespace的startup()函數來對這個庫作初始化。UDT::startup()函數具體的執行過程以下(src/api.cpp):

int CUDTUnited::startup() {
    CGuard gcinit(m_InitLock);

    if (m_iInstanceCount++ > 0)
        return 0;

    // Global initialization code
#ifdef WIN32
    WORD wVersionRequested;
    WSADATA wsaData;
    wVersionRequested = MAKEWORD(2, 2);

    if (0 != WSAStartup(wVersionRequested, &wsaData))
    throw CUDTException(1, 0, WSAGetLastError());
#endif

    //init CTimer::EventLock

    if (m_bGCStatus)
        return true;

    m_bClosing = false;
#ifndef WIN32
    pthread_mutex_init(&m_GCStopLock, NULL);
    pthread_cond_init(&m_GCStopCond, NULL);
    pthread_create(&m_GCThread, NULL, garbageCollect, this);
#else
    m_GCStopLock = CreateMutex(NULL, false, NULL);
    m_GCStopCond = CreateEvent(NULL, false, false, NULL);
    DWORD ThreadID;
    m_GCThread = CreateThread(NULL, 0, garbageCollect, this, 0, &ThreadID);
#endif

    m_bGCStatus = true;

    return 0;
}



int CUDT::startup() {
    return s_UDTUnited.startup();
}



namespace UDT {

int startup() {
    return CUDT::startup();
}

UDT::startup()的調用過程爲:UDT::startup()-> CUDT::startup() -> CUDTUnited::startup()。

這個地方咱們能夠看一下,在UDT中定義全局數據結構管理類——CUDTUnited類對象的方法。從語義上來講,CUDTUnited類對象應該是全局惟一的,一般能夠用singleton模式來實現這種全局性和惟一性,或者在類外定義一個static的對象也能夠。但在UDT中,考慮到CUDTUnited類並不會被導出給用戶做爲接口進行直接調用,而只是會做爲UDT實現的一部分,所以不須要擔憂調皮的用戶會破壞封裝性;同時,CUDTUnited類提供的接口主要是給CUDT調用的,於是被定義爲了CUDT類的private static成員變量(src/core.h):

private:
    static CUDTUnited s_UDTUnited;               // UDT global management base

另外咱們能夠看一下UDT中,類成員變量的命名方式:

1. 最開頭是一個小寫字母,表示變量的做用域,好比s表示靜態成員變量static,m表示類非靜態成員變量member等。

2. 第二個字符是下劃線。

3. 在下劃線以後,是0個、一個、兩個或三個小寫字母,表示變量的數據類型。若是是類(結構)類型變量,沒有這個部分。其它一些常見的用於表示變量數據類型的小寫字符/字符串有,i表示int整型值,b表示bool值,p表示指針類型,ll表示int64_t型值,ull表示uint64_t型值等。

4. 以後則是駝峯方式表示的一個描述變量含義的字符串。

最後,再來具體看一下實際執行初始化的CUDTUnited::startup()函數。在CUDTUnited中用m_iInstanceCount來記錄UDT被引用的次數,每一次調用CUDTUnited::startup()函數時,這個數會被加一,而在調用CUDTUnited::cleanup()函數時,這個數會被減一,以免重複的初始化,並在UDT沒有被任何部分使用到時執行最終的銷燬動做。這也就要求UDT的使用者,必定要成對地調用UDT::startup()和UDT::cleanup()。

1. CUDTUnited::startup()函數會首先增長m_iInstanceCount,並檢查其值,若m_iInstanceCount在遞增前的大於0,代表UDT已經被初始化過了,直接返回,不然,繼續執行後面的初始化動做。

2. 檢查m_bGCStatus的值,若該值爲true,代表UDT已經被初始化,而無需再作進一步的動做,直接返回,爲false,則繼續執行初始化。

3. 設置m_bClosing爲false,以指示GC線程的狀態。CUDTUnited構造函數中會將此值設置爲false,但執行UDT::cleanup()結束時,該值爲false。

4. 初始化用於停掉GC線程的mutex和condition,而後建立並執行GC線程CUDTUnited::garbageCollect(void* p)。

5. 設置m_bGCStatus爲true,以代表UDT的GC線程已經啓動,UDT已可用,而後返回0。

看完了UDT的初始化過程,再來看UDT的銷燬過程,也就是UDT::cleanup():

int CUDTUnited::cleanup() {
    CGuard gcinit(m_InitLock);

    if (--m_iInstanceCount > 0)
        return 0;

    //destroy CTimer::EventLock

    if (!m_bGCStatus)
        return 0;

    m_bClosing = true;
#ifndef WIN32
    pthread_cond_signal(&m_GCStopCond);
    pthread_join(m_GCThread, NULL);
    pthread_mutex_destroy(&m_GCStopLock);
    pthread_cond_destroy(&m_GCStopCond);
#else
    SetEvent(m_GCStopCond);
    WaitForSingleObject(m_GCThread, INFINITE);
    CloseHandle(m_GCThread);
    CloseHandle(m_GCStopLock);
    CloseHandle(m_GCStopCond);
#endif

    m_bGCStatus = false;

    // Global destruction code
#ifdef WIN32
    WSACleanup();
#endif

    return 0;
}


int CUDT::cleanup() {
    return s_UDTUnited.cleanup();
}



int cleanup() {
    return CUDT::cleanup();
}

銷燬過程徹底是初始化過程的逆過程。調用過程爲UDT::cleanup() -> CUDT::cleanup() -> CUDTUnited::cleanup()。在CUDTUnited::cleanup()中:

1. 遞減m_iInstanceCount,並檢查其值,若m_iInstanceCount在遞減後仍然大於0,代表UDT已還存在其它的使用者,直接返回,不然,繼續執行後面的銷燬動做。

2. 檢查m_bGCStatus的值,若該值爲false,代表UDT已經被銷燬,而無需再作進一步的動做,直接返回,爲true,則繼續執行銷燬動做。

3. 設置m_bClosing爲true,以指示GC線程逐步退出執行。而後signal GCStopCond,以便於在GC線程休眠的時喚醒GC線程。

4. 等待GC線程執行結束,而後銷燬用於停掉GC線程的mutex和condition。

5. 設置m_bGCStatus爲false,以代表UDT的GC線程已經被銷燬,UDT已不可用,而後返回0。

總結一下,UDT主狀態機的描述與狀態變化。在CUDTUnited類中,主要用以下的這幾個變量來描述主狀態機的狀態(src\api.h):

private:
    volatile bool m_bClosing;
    pthread_mutex_t m_GCStopLock;
    pthread_cond_t m_GCStopCond;

    pthread_mutex_t m_InitLock;
    int m_iInstanceCount;				// number of startup() called by application
    bool m_bGCStatus;					// if the GC thread is working (true)

    pthread_t m_GCThread;
#ifndef WIN32
    static void* garbageCollect(void*);
#else
    static DWORD WINAPI garbageCollect(LPVOID);
#endif

    std::map<UDTSOCKET, CUDTSocket*> m_ClosedSockets;   // temporarily store closed sockets

    void checkBrokenSockets();
    void removeSocket(const UDTSOCKET u);

能夠發現,依賴於GC線程的狀態,UDT主狀態機主要有4個狀態,分別是INIT,STARTING,RUNNING和CLOSING,在UDT中,主要用m_bGCStatus和m_bClosing這兩個bool類型值來描述。這個狀態機只提供了兩個函數給調用者,以影響這個狀態機的狀態,也就是UDT::startup()和UDT::cleanup()函數。這個狀態機的幾個狀態與m_bGCStatus和m_bClosing值的對應關係,及狀態轉換過程以下圖所示:

在使用UDT的程序啓動起來時,或者執行了UDT::cleanup()函數以後,能夠認爲UDT處於INIT狀態,也就是初始狀態。在CUDTUnited的構造函數中,會將m_bGCStatus和m_bClosing這兩個值都初始化爲false,而UDT::cleanup()函數結束時,m_bGCStatus的值則爲false,m_bClosing的值爲true。

調用了UDT::startup()以後,m_bGCStatus的值仍然爲false,m_bClosing的值首先被設置爲false,而後啓動garbageCollect線程。garbageCollect線程的啓動須要必定的時間,在這段時間內能夠認爲UDT主狀態機從INIT狀態轉換到了STARTING狀態。

UDT::startup()會等待garbageCollect線程啓動,在garbageCollect線程啓動以後,m_bClosing的值仍然爲false,m_bGCStatus的值被設置爲true,以表示UDT已經可用了。這裏能夠認爲UDT主狀態機從STARTING狀態切換到了RUNNING狀態。

在UDT主狀態機進入RUNNING狀態以前,用戶是沒法建立Sokcet的,也就是尚未任何的子狀態機被建立,於是還無需考慮這些狀態的轉換對於子狀態機的影響。

使用UDT傳輸了數據以後,須要調用UDT::cleanup()函數來作清理動做。

調用UDT::cleanup()以後,m_bGCStatus的值仍然爲true,但m_bClosing的值首先被設置爲true,garbageCollect線程也會被喚醒。這個時候garbageCollect的處理可能比較複雜,可能已經建立了多個Socket,而Socket所處的狀態可能也多種多樣,於是可能會耗費一些時間。在這段時間內,能夠認爲UDT主狀態機由RUNNING狀態轉換爲了CLOSING狀態。這個狀態轉換會對還在使用的Socket的狀態作一個強制的轉換,也就是說這個轉換對子狀態機的狀態有巨大的影響,但此處先不討論這種影響。

UDT::cleanup()函數會等待garbageCollect線程清理結束。在garbageCollect線程結束以後,m_bGCStatus被設置爲false,以代表UDT不可用。此時m_bGCStatus的值爲false,m_bClosing的值爲false。能夠認爲,UDT主狀態機的狀態由CLOSING狀態又回到了INIT狀態。

可見,UDT主狀態機狀態改變的激勵源,主要有UDT::startup()函數,UDT::cleanup()函數,和garbageCollect線程的執行。

UDT::startup()和UDT::cleanup()函數中都同時檢查了m_iInstanceCountm_bGCStatus的值,這裏彷佛有點多餘了。

Done。

相關文章
相關標籤/搜索