詞彙表

📚 Burst Time

Burst time主要分爲CPU burst timeDevice burst time

- CPU burst time

運行特定例程只運算 CPU 的時間 。 CPU burst time 線程的 CPU 核心佔用率爲 100 。

- Device burst time

這是CPU在執行特定例程時等待其他處理完成的時間。Device burst time中執行緒的CPU核心使用率為0。 CPU 等待其他處理完成的典型情況包括讀取/寫入檔案、執行資料庫查詢或等待其他主機的服務回應。

📚 C++ Singleton

Singleton和全局變量在C++語言中存在差異。 全局變量在運行 WinMain()main() 時不會在即將退出時被破壞, 而會在子調用函數中被破壞 。 全局變量的破壞順序只能在一個C++文件的編譯結果中保證,不同C++文件的編譯結果之間的破壞順序不能保證。

但是 C++ singleton 在 WinMain()main() 返回之前被調用 。 此外,實例的創建者會在第一次訪問 singleton 的瞬間被調用,破壞的順序也會被調用者反向調用。 因此,它確保了比全局變量更安全的生成/破壞規則。

➡️ C++ singleton 實現示例
class A
{
    A(){}
public:
    static A& Instance()
    {
        static A inst;
        return inst;
    }
    void Goo() {}
};
 
 
 
void Foo()
{
    A::Instance().Goo();
}

上述實現存在如下風險:如果在短時間內同時從多個線程訪問singleton,則生成器可能會有2次以上的呼叫。

在解決這些問題的同時,我們建議使用無critical section負載的類Proud.CSingleton訪問。

📚 DB Constraints

要進入字段的值如果不滿足特定條件,就指不讓進入。

PrimaryKey Unique Index 其他等等(>,<,=,!=),Constraints由Unique Index, Trigger 等實現。

📚 Fast Heap

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

ProudNet 的 Fast heap 的實現類是 Proud.CFastHeapProud.CFastHeapLookaside allocator 一樣, 只有在所有存儲塊被破壞之後才能移除 Proud.CFastHeap 對象。

Fast heap的使用方法如下。

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

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

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

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

📚 P2P 組

如果兩個客戶端A和B要進行P2P通信,則A和B必須屬於至少一個P2P組。 多人可以在一個聊天視窗中互相聊天,也可以透過建立多個聊天視窗來進行多人聊天。 但是,您無法在您不在的聊天視窗中聊天。

ProudNet在網絡聊天工具中,每個聊天窗口對應P2P組。

但是,創建聊天窗口或進入其他聊天窗口的權限只有服務器纔有。 P2P 組標識符也是 Proud.HostID 類型。

📚 PIDL

PIDL是爲RMI自制的編譯器。

在特定文件中定義協議後設置,會自動創建對象文件。 此時生成的客體與Server和Client一起使用,因此生成Common(公用)Project進行管理非常方便。

📚 Reliable 訊息

Reliable消息(或reliable send)是指發送方發送的消息內容和順序始終由接收方統一接收。 例如,發送信息A、B、C、D、E後,接收方也會按照A、B、C、D、E的順序無損失地接收數據。

雖然具有可信度(reliablility)的優點,但偶爾接收的時間可能比Unreliable 信息慢。

📚 RMI

RMI(Remote Method Invocation, 遠程方法呼叫)是指呼叫其他網絡或其他程序中的函數,通過機器代替人進行定義收發例程和信息(Message)結構的編碼工作,起到將讓開發者感到吃力的編程工作(信息結構定義、發送函數、接收函數製作)簡化爲函數呼叫形式的作用。

發送消息時,調用 RMI 函數的一方稱爲 Proxy,調用方稱爲 Stub。

- 不使用 RMI 時

// Message header ID definitions
#define Message_Knight_Move_ID 12
#define Message_Knight_Attack_ID 13
 
// Message format definitions
struct Message
{
    int m_msgID;
};
struct Message_Knight_Move:public Message
{
    int m_id;
    float m_x,m_y,m_z;
};
struct Message_Knight_Attack:public Message
{
    int m_id;
    int m_target;
    int m_damage;
};
 
// A function which send a formatted message
void Knight_Move(int id,float x,float y,float z)
{
    Message_Knight_Move msg;
    msg.m_msgID=Message_Knight_Move_ID;
 
    msg.m_id=id;
    msg.m_x=x;
    msg.m_y=y;
    msg.m_z=z;
 
    Send(msg);
}
 
// A function which send a formatted message
void Knight_Attack(int id,int target,int damage)
{
    Message_Knight_Attack msg;
    msg.m_msgID=Message_Knight_Attack_ID;
 
    msg.m_id=id;
    msg.m_target=target;
    msg.m_damage=damage;
 
    Send(msg);
}
 
// Identified a received message 
// and call an appropriate function for message handling
void DoReceivedMessage(Message* msg)
{
    switch(msg->m_msgID)
    {
    case Message_Knight_Move_ID:
    {
        Message_Knight_Move* msg2=
            (Message_Knight_Move*)msg;
 
        Do_Knight_Move(
            msg2->m_id,
            msg2->m_x,
            msg2->m_y,
            msg2->m_z);
    }
    break;
    // ... cases for other message types
    case Message_Knight_Attack_ID:
    {
        Message_Knight_Attack* msg2=
            (Message_Knight_Attack*)msg;
 
        Do_Knight_Attack(
            msg2->m_id,
            msg2->m_target,
            msg2->m_damage);
    }
    break;
    // ... cases for other message types
    }
}c

但是使用RMI可以整理成以下短代碼。

Knight_Move([in] int id,[in] float x,[in] float y,[in] float z);
Knight_Attack([in] int id,[in] int target,[in] int damage);

以上格式爲IDL(Interface Description Language)格式,編譯後生成C++或C#源。 創建的源文件由消息結構聲明、發送函數、接收處理函數等組成,即使開發者不直接創建網絡處理程序,PIDL編譯器也會自動生成。

在創建的文件中,將函數調用轉換爲消息發送到網絡的模塊稱爲proxy,分析通過網絡接收到的消息並調用用戶函數的模塊稱爲stub。

當從主機A調用RMI函數X時,實際上調用X的proxy,proxy將其切換爲網絡消息並將其發送到主機B。 然後主機B接收消息併發送到stub,stub分析後調用用戶創建的函數X。

因爲看起來具有與主機A呼叫主機B上的函數相似的形狀,所以具有Remote Method Invocation(遠程方法呼叫)的意義。

📚 Stored Procedure

Stored Procedure是由DBMS本身可放入的SQL語句製作的程序函數。 訪問數據庫時,查詢語句字符串由應用程序直接創建並扔出,但如果可能,提前創建Stored Procedure保存查詢語句例程,應用程序直接調用Stored Procedure在性能和穩定性(數據庫鎖定策略等)上更有效。

📚 Unreliable 訊息

Unreliable 信息(或unreliable send)可以根據通信線的長度和狀態,接收發送方發送的消息內容和順序不同。

例如,發送消息A、B、C、D、E時,接收方有時會收到A、B、C、D、E,但可能會收到兩次相同的信息(A,B,B,C,C,D和E),或者消息在中間丟失(A,B,D),或者消息會按順序不同到達。(A,C,B,E,D)

但是消息內部的數據不會被破壞。 雖然Unreliable消息具有這些缺點,但送達時間比Reliable 信息快。

📚 UUID 或 GUID

Unique or global universal identifier (UUIDGUID)是16字節大小的數據塊,生成的GUID在概率上是全世界唯一的。

ProudNet DB將UUID分配給每個Gamer、Hero、WorldObject。 雖然因爲UUID是16bytes,所以可以認爲大小比較大,但是UUID具有在地球上不重複的優點,所以在幾個重要案例中很有用。 最具代表性的是服務器整合、玩家賬號移動、玩家ID變更等。

📚 競爭狀態 (Race Condition)

在工學領域,競爭狀態(race condition)是指同時進行兩個以上輸入或操作的狀態。 在這種情況下,有無法得出正常結果的危險,這被稱爲競爭危險

在計算機科學中,競爭狀態是指多個過程試圖同時獲取共享資源的狀態,當同時獲取時,可能會出現破壞數據一致性的結果。 爲了防止這種情況的發生,需要過程協作技術。

📚 數據量化

當通過網絡交換浮動小數點單位的大值時,可能會出現減少數據包量的詭計。 例如,如果角色位置x值僅在100到200之間確定,小數點後兩位數的精度可以忽略不計, 此值可轉換爲 100 * 100 = 10000 或更少的值, 並節省 double (8 字節) 爲 word (2 字節) 。

這個技巧稱為量化(quantization)

該類提供了量化和量化的相反給付功能。

Proud::CQuantizer q(-10000,10000,65535); // - 將10000~10000之間的值以65535等分精度進行量化的功能
double a = 3423.38274f;
int b = q.Quantize(a);      // 量子化
double c= q.Dequantize(b);  // 從量子化值中恢復實際值

📚 Listening Port

主機地址端口組成,服務器必須有一個listening port才能接收客戶端的訪問。

主機地址是互聯網上的地址,由111.222.111.222或mycomputer.mydomain.com 格式組成。 Port的值介於1,000到65500之間。 只要兩個或兩個以上的程序不使用相同的listening port,就可以任意指定。

📚 Marshaling

將RMI呼叫轉換爲信息或從信息中提取用於呼叫RMI的值稱爲marshaling。 ProudNet提供int或float等基本類型的Marshaling功能。

📚 Multicast

將一條信息一次性傳遞給多個主播的multicast, 將消息只傳遞給一個主機叫做unicast

📚 Thread Pool

生成1條線程後,清除的過程會產生大量的通量,因此,如果運行中的線程較多,操作系統可能會超負荷運行。

因此,要保持儘可能少的線程,儘量減少線程的去除和生成過程,爲此,需要設置由一定數量組成的1個線程集合,只在必要時導入使用,必要時返回集合。 這個過程叫做thread pool

即,thread pool是指由多個線程預先準備好的多個線程組成的一個集合

- 例子

客戶端A、B、C被服務器容納的狀態下,分別因RMI或事件在Proud.CNetServer內的queue中等待的狀態。

A1,A2,A3 -> 客戶端 A 的事件或 RMI B1,B2,B3 -> 客戶端B的事件或RMI線程池共有兩個線程。 這時根據規則執行如下。

  • A1、A2、A3不能同時運行。

  • B1、B2、B3和C1、C2、C3同樣不是同時運行的。

  • A1、A2、A3之一和B1、B2、B3之一、C1、C2、C3之一可以同時運行。

  • 由於線程池中只有2個線程,A、B、C中的2個線程被篩選並回饋,但回饋程序首先完成線程的線程對未篩選的客戶端進行RMI或事件回饋。

📚 推測導航(dead reckoning)

ProudNet爲了表現遊戲角色的流暢位置,提供推測導航(dead reckoning)工具。

推測導航大致以以下方式操作。

  • 將移動角色的位置和速度從主機A傳送到主機B。 此時傳輸週期爲每秒2~10次。

  • 傳輸週期最好是動態的。 只有當角色的加速度較大時,縮短傳輸週期才能同步更精確的運動。 加速度大的情況是,角色的速度急劇變化時或角色撞到其他物體,移動方向急劇變化時等。 (見下表)

  • 在主機B中,通過pinging從主機A獲得消息到達所需的時間(延遲)。

  • 當主機B接收到角色的位置、速度信息時,用以下公式預測主機A角色的實際位置。 P: 預測位置, V: 接收速度, T: 延遲, P0: 接收位置 P = (V * T) + P0

到這裏可以預測角色的位置,但租用計算出的位置值會導致角色的位置斷斷續續的問題。 使用 Proud.CPositionFollower 來解決這個問題。

Proud.CPositionFollower的作用是移動追蹤者(follower)使其在規定時間內到達移動目標位置。 特別是製作成可以直線追蹤移動的目標位置,可以減少其他主機角色位置的突出現象。

下面將描述實現推測導航的其餘步驟。

  • 在主機 B 中,將預測的位置和速度值輸入到 Proud.CPositionFollower 對象中。 此時輸入目標位置、速度。

  • 在主機B中,獲取Proud.CPositionFollower客體的示蹤劑位置,對主機A的角色位置進行渲染。

- 導航示例

P(t=0,1,2)是預測點,紅線是Proud.CPositionFollower校正的角色位置。 通常,最好將主機A中角色的位置發送週期和跟蹤者在Proud.CPositionFollower中到達目標位置的時間限制設定爲相同,角色位置發送週期因情況而異。

建議示例

情況發送的數據類型平均發送週期快速增加的加速度示例

大型戰RPG遊戲(MMORPG)玩家角色

位置(xyz),速度(xyz),觀望方向(z)

0.3

角色的移動方向轉換,站立,圓點,波普

飛機或車輛

位置(xyz),速度(xyz),加速度(xyz), 觀望方向(xyz)

0.3

障礙物碰撞、急轉彎

第一人稱射擊遊戲(FPS)玩家角色

位置(xyz), 觀望方向(xyz)

0.03

角色移動方向改變後,狙擊槍發射瞬間

發送週期短時,可能會有發送量過大的危險,建議使用發送量自動調節功能 (Throttling)

- 曲線型跟蹤器(Spline based follower)

Proud.CPositionFollower不僅提供直線,還提供曲線追擊目標的follower。 它是一種三階函數形式的曲線型跟蹤器(spline based follower)。 這顯示了它們以比直線型follower更光滑的形式追趕。 但是,並不總是呈現光滑的面貌,所以最好在遊戲玩法上邊測試邊選擇。

爲了獲得曲線型跟蹤器,請使用以下方法。

  • Proud.CPositionFollower.GetSplineFollowerPosition

  • Proud.CPositionFollower.GetSplineFollowerVelocity

角度的校正處理使用以下方法。

  • Proud.CPositionFollower: 位置補正作用

  • Proud.CAngleFollower: 角度的校準作用

📚 打孔 (Hole Punching)

路由器還具有路由器的特性,以製作Routing Table的P2P通信爲目的,事先與對方交換數據包,在各自的路由器上製作Routing Table。 確切的名稱是 STUN (Simple Traversal of User Datagram Protocol Through Network Address Translators)

打孔方法如下:

  • Full Cone NAT

  • Restricted Cone

  • Port Restricted Cone

  • Symmetric Cone

Last updated