ProudNet 實用程式

使用收藏

STL、ATL等也已經提供收藏類(std.map, std.vector, CFastArray, CAtlArray, CAtlMap等)。 但是ProudNet在STL和ATL都難以使用的情況下,或在要求高速性能的情況下提供有效的類

類別函數註釋

數組類

Proud.CFastArray

內部使用 Fast heap

鏈接列表類

Proud.CFastList

內部使用Fast Heap。 與CFastArray不同,該類可以與創建者、消失者和輻射分配操作員一起使用。

地圖類

Proud.CFastMap

(Key, Value) 使用成對的哈希算法。 與CAtlMap的使用方法非常相似,STL.map的重複序列和一些方法可以相同地使用。

設定類

Proud.CFastSet

與CFastMap不同,Key是僅有的類,其餘與CFastMap相同。

快速內存管理器

ProudNet內置高性能內存管理器,開發者可以利用高性能內存管理器加速應用程序的加工性能。

ProudNet支持的存儲管理器大致如下。

- Lookaside allocator

Lookaside allocator應用了通常的memory pool技術。 如果您必須經常分配/ 釋放相同大小的內存, 推薦使用 Lookaside allocator

主要機制如下。

  • 分配新的存儲塊時,分配新的系統內存。

  • 當解鎖存儲塊時,解鎖的塊將返回至lookaside allocator

  • 重新分配存儲塊時,返還給lookaside allocator的存儲塊將被再利用。

這個過程以非常快的速度運行。 比OS環境下的內存分配速度要快得多。

但也存在缺點。

  • Lookaside allocator總是隻能分配大小相同的內存。

  • 分配給 Lookaside allocator 的內存塊必須在 Lookaside allocator 被破壞之前全部釋放。

使用Lookaside allocator的方法如下。

  • 首先使用 Proud.CLookasideAllocator.New 方法創建對象。 創建全局對象也可以。

  • 將存儲塊分配給 Proud.CLookasideAllocator.Alloc 方法。

  • 解除設置爲 Proud.CLookasideAllocator.Free。 Realloc不存在。

  • 解除所有存儲塊後,破壞 Proud.CLookasideAllocator 對象。

- Fast Heap

ProudNet的Fast heap雖然比Lookaside allocator稍微慢一些,但是比OS環境下的存儲器分配/解除速度更快,可以分配/解除各種大小的存儲器塊。

ProudNet 的 Fastheap 的實現類是 Proud.CFastHeapProud.CFastHeap 還可以在所有存儲塊被破壞後移除 Proud.CFastHeap 對象。

Fast heap的使用方法如下。

  • 首先以Proud.CFastHeap.New方法生成Fast heap對象。 創建全局對象也可以。

  • Proud.CFastHeap.Alloc 方法分配內存塊 。

  • 解除爲Proud.CFastHeap.Free。 可用CFastHeap.Realloc重新分配內存塊。

  • 解除所有存儲塊後,破壞 Proud.CFastHeap 對象。

指定為 C++ 類別的預設分配器

在C++類中容易接受Fast heapLookaside allocator的加速的方法是覆蓋C++的operator new, delete方法。

這樣,在創建和破壞C++類作爲new、delete操作符時,將使用Fast heapLookaside allocator代替系統的內存heap。

在以下示例中,當將類實例化到 operator newdelete 時,高性能內存管理器將允許您分配/ 釋放內存。 它們各有優缺點,請適當選擇使用。

➡️ 例子
class Example
{
    static CLookasideAllocatorPtr gAlloc; // 或者用CFast Heap也OK。
public:
    void* operator new(size_t size)
    {
        return gAlloc->Alloc(size);
    }
    void operator delete(void* ptr, size_t size)
    {
        gAlloc->Free(ptr);
    }
};
 
CLookasideAllocatorPtr Example::gAlloc(CLookasideAllocator::New()); // 或者用CFast Heap也OK。

智能指示器

ProudNet擁有智能指針Proud.RefCount類。

智能指針是指,只要存在參照生成客體的變數,就能起到保障該客體的存在本身的作用。 而且,只有當參照該客體的變數不再存在時,該客體纔會被破壞。

它還可以解決由於開發人員創建的錯誤(dangling)而引用已被銷毀的物件的問題或未銷毀物件的問題(leak)。

智能指針變量每次複製時客體參考計數增加1,下圖中Object Instance一直存在到參考計數爲0。

➡️ 示例代碼
class A {...};
 
void Foo()
{
    // 創建 A 對象
    Proud::RefCount<A> a(new A);
    
    // 變量b與變量a共享。 A的參考計數爲2。
    Proud::RefCount<A> b = a;
    
    // 變量 a 已解除 。 然而,A沒有被破壞,因爲參考計數仍然是1。
    a = Proud::RefCount<A>();
    
    // 變量b已解除 。 不再有變量引用A,因此A被破壞。 (即已調用 delete)
    b = Proud::RefCount<A>();
}

有時候想在多處參照客體的智能指示器的狀態下強制破壞客體。

例如,如果智能指示器參考擁有打開文件手柄的對象,則有時需要立即破壞擁有文件手柄的對象。

但如果智能指示器到處參照客體,就無法知道客體被破壞的時間,因此可能會遇到難關。 在這種情況下,可以使用Dispose Pattern明確執行多處參考對象的破壞。

- Dispose Pattern

Dispose Pattern是一種程序模式,即使不知道一個以上智能指示器所參照的對象被破壞的時間,也能獲得明確破壞客體的效果。

如果要將Dispose Pattern運用到智能類客體上,作爲客體的成員變數,具有"自我狀態",這意味着客體是否已經被破壞,無法使用。 如果自己的狀態是"已經破壞的狀態",那麼在參考客體時就會發生錯誤,否則就要讓其正常執行。

下面是運用Dispose Pattern的例子。

Dispose Pattern 是 在Java或C#等智能指示器Garbage Collector的程序設計語言中也有涉及。

➡️ 示例代碼
class A
{
    // 如果是true,意味着該客體處於破壞(dispose)狀態。
    bool m_disposed;
    public:
    
    A()
    {
        // 剛生成的對象處於未dispose狀態。
        m_disposed = false;
    }
 
    ~A()
    {
        Dispose();
    }
 
    void Dispose()
    {
        if(m_disposed == false)
        {
            // 進行對象破壞相關的實際執行。
            // 例如,關閉手中的文件手柄。
            ...
        
            m_disposed = true;
        }
    }
};
 
typedef Proud::RefCount<A> APtr;
 
void Foo()
{
    APtr a(new A); // 創建對象
    APtr b = a;     // 兩個智能指針變量共享一個對象
    
    a->Dispose();   // 強行破壞客體。
    
    // 現在a、b客體都處於破壞狀態。
    // 因此,不能訪問a、b所參考的對象。
    ...
}

線程實用程式

ProudNet提供了一些線程實用類。

類別註釋

Proud.Thread

線程可以很容易地生成和破壞。

Proud.CriticalSection

可以創建critical section。

Proud.CriticalSectionLock

可以lock和unlock。

字符串類

ProudNet使字符串類Proud.String, Proud.StringA使字符串像ATL或STL的字符串類一樣可以簡便地處理。

使用 .Net Framework 的程序具有名爲 System.string 的符號。 因此.Net Framework混用時,可能需要註明System或Proud中的一個命名空間。

舉例如下。

Proud::String a;  // 空字符串
a = L"123";         // 爲字符串添加值 。
puts(a);            // a本身直接提供字符串緩衝器。
a += L"abc";        // 向字符串添加另一個字符串
if(L"123abc" == a)  // 如何比較字符串中的內容
{
    a.Replace(L"123", "def");   // 字符串內容替換
}

- 創建字符串功能(format)

Proud.StringT 提供了創建字符串的功能, 如 sprintf()

Proud::String a;
a.Format(L"%d %d %s", 1, 2, L"hahaha");
// 現在a="12 hahaha"。

- 字符串處理性能

Copy-on-write

// Proud.StringT 的 copy-on-write 功能只有在需要字符串時才顯示副本。 
// 在此之前,我們會互相共享字符串數據 。
 
Proud::String a = L"abc"; // a 擁有字符串'abc'
Proud::String b = a; // b共享與a相同的字符串數據
Proud::String c = b; // 現在a、b、c都共享相同的字符串數據。
c = L"bcd"; // a、b仍然共享字符串數據"abc",但c不再共享,單獨擁有"bcd"
b = c; // b放棄與a共享"abc",共享c擁有的"bcd"

字符串長度測量

Proud.StringT.GetLength在調用後立即返回預先測量的字符串長度。 即與strlen()不同。

Proud.StringT 和 int 和 float 一樣, 不使用 thread safe 。 因此,同時從多個執行緒存取同一個字串物件是不安全的(除非所有執行緒都只是讀取)。

這一點與ATL或STL的字符串類別相同。

Timer Queue

Timer Queue是在線程池中執行tick event的模塊,在Windows XP,2000年以後版本的操作系統上提供名爲Windows Timer Queue的API。

每隔一段時間運行用戶指定的函數, 該函數在線程池中的一個線程中運行。 如果所有線程正在運行(running state) 函數的執行將保留到線程中出現完成歷史任務線程爲止。

Timer Queue中呼叫的用戶函數是從線程池中的線程中選擇一個,如果之前正在運行的用戶函數處於未運行狀態,也有無事可做的線程(idle state),則選擇該線程並執行用戶函數。

假設有以下工作清單。

黑箭頭爲0.1s,A、B、C、D、E是每0.1s應做的工作項目。 A、D在0.1秒內結束,B在0.1秒內結束,C、E在0.1秒內無法結束。

如果是服務器主環方法,則這些操作項目如下圖所示。 由於在一個線程中運行所有任務項目,D、E不能按時啓動。

然而,在Timer Queue方法中,動員了另一個線程來運行D,並且E及時運行在先前完成C的線程中。

及時啓動必要任務,必要時進一步調動線程。

Timer Queue主要在服務器程序中使用,因爲用戶函數可能同時在兩個或多個線程中運行。

實現並列性,但需承受並列性所具有的危險性,使用前請判斷該功能是否必要。

如果錯誤地使用 Timer Queue 服務器過程的線程會爆炸性地增加,對性能產生不利影響。 如果沒有必須使用 Timer Queue的原因, 請在 Proud.CTimerThread服務器上使用計時器環路, RMI, 事件處理

- 如何使用計時器隊列

您需要存取 Proud.CTimerQueue 類別。 這個類別是一個singleton。

如果將調用函數和調用週期設置爲 Proud::NewTimerParam 結構,並將其作爲參數添加到 Proud.CTimerQueue.NewTimer 中,則會獲得 Proud.CTimerQueueTimer 對象。 然後,在破壞 Proud.CTimerQueueTimer 對象之前,指定的用戶函數每隔一段時間運行一次。

➡️ 示例代碼
VOID NTAPI UserFunction(void* context, BOOLEAN TimerOrWaitFired)
{
    int *pCallCount = static_cast<int *>(context);
 
    // 用戶函數
    std::cout << "UserFunction : " << ++(*pCallCount) << std::endl;
}
 
int _tmain(int argc, TCHAR* argv[])
{
    // 宣佈 NewTimerParam 結構域變量 。
    NewTimerParam p1;
 
    // 用於測試計數的變量聲明
    int callCount = 0;
 
    // 用戶函數設置 。
    p1.m_callback = UserFunction;
 
    // 設置要接收的參數 。
    p1.m_pCtx = &callCount;
 
    // 設定爲1秒後開始回電。
    p1.m_DueTime = 1000;
 
    // 設置爲0.1秒回撥。
    p1.m_period = 100;
 
    // 每隔一段時間,在線程池中每0.1秒調用一次用戶函數。
    Proud::CTimerQueueTimer* ret = Proud::CTimerQueue::GetSharedPtr()->NewTimer(p1);
 
    std::cout << "PRESS ANY KEY TO EXIT" << std::endl;
 
    // 等待用戶回撥
    _getch();
 
    // 破壞計時器客體。 破壞後用戶函數不再被調用。
    delete ret;
}

留下日誌

在開發網絡遊戲的過程中,必然需要留下各種執行記錄(日誌)的功能。 ProudNet爲此提供日誌留存功能。

ProudNet的登錄功能以非同步方式運行,因此,在調用要留下登錄的方法後,方法立即返回。 此外,將單獨線程的實際日誌記錄在文件或數據庫中。

- 日誌記錄到文件

Proud::CLogWriter 類是允許將日誌寫入文件的類 。

➡️ 示例代碼
// 生成CLogWriter。
CAutoPtr<Proud::CLogWriter> logwriter;
logwriter.Attach(Proud::CLogWriter::New(L"log.txt"));
 
// 讓我們來寫日誌。 我們提供了兩個 WriteLine 函數。
logwriter->WriteLine( Proud::TraceID::TID_System, L"是系統日誌。 );
logwriter->WriteLine( "%是第d個日誌。", 1 );
 
// 換一個新的日誌文件。 如果創建新文件失敗, 則以 Proud::Exception 例外處理 。
logwriter->SetFileName(L"log2.txt);

- 日誌記錄到數據庫

Proud::CDbLogWriter 類是允許將日誌寫入DB 的類 。

爲此,您必須運行 Sample/DbmsSchema 文件夾中的 LogTable.sql,以預先生成 LogTable 。 DBMS的建立參考樣品數據庫建立的程序。

➡️ 示例代碼
// 要接收錯誤的函數 。
class CTestLogWriterDelegate : public ILogWriterDelegate
{
    virtual void OnLogWriterException(Proud::AdoException& Err) override
    {
        // ...
    }
};
 
CTestLogWriterDelegate g_dblogDelegate;
 
void main()
{
    // ...
    // CDbLog Parameter 填充值。
    Proud::CDbLogParameter dbparam;
    dbparam.m_dbmsConnectionString = L"Data Source=localhost;Database=Log-Test;Trusted_Connection=yes";
    dbparam.m_loggerName = L"LoggerName";
    dbparam.m_dbLogTableName = L"DbLog";
 
    // 生成CDbLogWriter。
    CAutoPtr<Proud::CDbLogWriter> dbLogWriter;
    dbLogWriter.Attach(Proud::CDbLogWriter::New(dbparam, &g_dblogDelegate));
    
    // 加入ProudNet不提供的新Field吧。
    // 注意!!用戶想要加入想要的字段時,必須在DBMS的Log-Test表上提前生成字段。
    // 在DBMS中創建TestField,將datatype稱爲int時,如下文所示生成CPropNode並放入Write Line即可。
    Proud::CProperty newnode;
    Proud::String TestField = L"TestField";
    Proud::CVariant TestValue = 123;
    newnode.Add(TestField, TestValue);
    dbLogWriter->WriteLine(L"日誌內容。", &newnode);
    
    // ...
}

延遲測量功能

ProudNet以StopWatch的形式提供延遲測量功能。

如果是可用的版本,則必須使用比現有延遲測量函數更準確的該功能,而不是服務器的GetLastPingGetRecentPing

(1) 開始延遲測量

調用 StartRoundTripLatencyTest 將開始要測量延遲的目標和 relay 。

➡️ 示例代碼
StartRoundTripLatencyTestParameter testParameter;
testParameter.testDuration = 40 * 1000; // 指定在 RoundTripLatency Test 中何時調用 StopRoundTripLatency Test 的變量。 單位是毫秒。 默認是500ms。
testParameter.pingIntervalMs = 400; // 指定在RoundTripLatency Test途中發送PING的週期的變數。 單位是毫秒。 默認是300ms。
ErrorType startError = netClient->StartRoundTripLatencyTest(HostID_Server, testParameter);
switch(startError)
{
    case ErrorType_Ok:
        std::cout << "success" << std::endl;
        break;
    case ErrorType_InvalidHostID:
        std::cout << "HostID是自己或不是連接的Peer時" << std::endl;
    break;
}

(2) 延遲測量結束

如果您想在先前指定的測試Duration之前停止測試, 請調用StopRoundTripLatencyTest函數 。 如果直到testDuration結束爲止不呼叫StopRoundTripLatencyTest,則將自動停止測量。

➡️ 示例代碼
ErrorType stopError = StopRoundTripLatencyTest(HostID_Server);
switch(stopError)
{
    case ErrorType_Ok:
        std::cout << "success" << std::endl;
        break;
    case ErrorType_InvalidHostID:
        std::cout << "非連接Peer時" << std::endl;
        break;
}

(3) 獲取延遲測量值

➡️ 示例代碼
RoundTripLatencyTestResult testResult;
ErrorType getError = GetRoundTripLatency(HostID_Server, testResult);
switch(getError)
{
    case ErrorType_Ok:
        std::cout << "success" << std::endl;
        std::cout << "測量期間的乒乓平均值 : " << testResult.latencyMs 
<< ", 測量期間的乒乓標準差 : " << testResult.standardDeviationMs 
<< ", 測量期間運行的乒乓次數 : " << testResult.totalTestCount << std::endl;
        break;
    case ErrorType_InvalidHostID:
        std::cout << "非連接Peer時" << std::endl;
        break;
    case ErrorType_ValueNotExist:
        std::cout << "從未使用過乒乓時" << std::endl; // 在這種情況下,RoundTripLatencyTestResult的總TestCount爲0。
        break;
}

PIDL 編譯器插件

這是 Visual Studio 的附加元件,可協助您輕鬆設定 PIDL (ProudNet IDL) 檔案的自訂建置。

- 安裝

1. 運行 <安裝路徑>\ProudNet\util\PIDL-addon.vsix

2. 選擇要安裝附加元件的 Visual Studio 並繼續安裝。

3. 安裝成功完成後,會顯示如下圖所示。

- 刪除

可以透過ToolsExtensions and Updates選單將其刪除。

對於 Visual Studio 2010, ToolsExtension Manager

- 新增PIDL檔案視窗

調用 PIDL 新增視窗。 選擇選擇項目右鍵Add new PIDL file

  1. 輸入 PIDL 文件名 。

  2. 顯示添加 PIDL 文件的路徑 。

  3. 添加外部 PIDL 文件 。

  4. 創建新的 PIDL 文件 。

  5. 關閉 PIDL 附加窗口 。

- 添加 PIDL 文件

  1. 輸入要添加的 PIDL 文件名

  2. 點擊 New PIDL

如上圖所示,添加了PIDL文件,該文件默認設置Custom Build。 與默認設置不同時,可通過文件屬性更改功能進行更改。

- PIDL 文件屬性窗口

調用 PIDL 屬性窗口 。

選擇 PIDL 文件右鍵點擊Properties 選擇

  1. 檢查/ 更改 PIDL 文件中的 Custom Build Tool 設置 。 PIDL Compiler Location: 設置 PIDL 編譯器位置 。 PIDL Compiler Location is Relative: 輸入路徑的絕對/相對與否值。 Output Directory (All Languages): 設置輸出路徑 。 Output Directory is Relative (All Languages): 輸入路徑的絕對/相對與否值。

  1. 各語言設置窗口:可按各C++、C#、Java、Unreal Script語言生成。

  2. 特定於語言的設定視窗: Generate Code: C++ 默認 Yes, 其餘語言是 False (可根據需要配置) C++ Implementation File Extension: 僅限C++。

Last updated