Docs官方網站 ↗
中文

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.CFastHeap。 Proud.CFastHeap 還可以在所有存儲塊被破壞後移除 Proud.CFastHeap 對象。

Fast heap的使用方法如下。

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

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

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

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

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

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

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

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

➡️ 例子
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"。

- Unicode-多字節相互轉換

- 字符串處理性能

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爲此提供日誌留存功能。

Windows也有留下事件日誌的功能。

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的形式提供延遲測量功能。

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

(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. 安裝成功完成後,會顯示如下圖所示。

- 刪除

可以透過Tools → Extensions and Updates選單將其刪除。

對於 Visual Studio 2010, Tools → Extension 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++。

請輸入搜尋關鍵字。