網絡多人遊戲架構與編程2

網絡多人遊戲架構與編程2算法

1.0、虛擬現實遊戲是對延遲最敏感的, 由於咱們人類只要頭旋轉了,眼睛就指望看到不一樣的事物。在這些狀況下,保證用戶感受在虛擬現實世界中就要求延遲少於 20 毫秒編程

  格鬥遊戲、 第一人稱射擊遊戲和其餘動做頻繁的遊戲是對延遲第二敏感的。 這些遊戲的延遲範圍能夠從16 毫秒到150毫秒網絡

  RTS遊戲是對延遲容忍度最高的, 這個容忍度一般頗有用, 正如第 6 章所介紹的。 這些遊戲的延遲能夠高達 500 毫秒, 而不影響用戶體驗。多線程

1.一、非網絡延遲。架構

  1)輸入採樣延遲(input sampling latency)。用戶按下一個按鈕到遊戲檢測到這個按鈕的時間可能很長。下圖代表,遊戲循環架構可能致使 Input Sampling Latency 高達接近 2幀的時間。併發

    

  2)渲染流水線延遲(render pipeline latency)。驅動程序將繪製命令插入到緩衝區,GPU在將來的某個時刻執行。若是有許多渲染任務要作,可能會致使滯後 1幀 渲染出來。ide

    

  3)多線程渲染流水線延遲(multithreaded render pipeline latency)。大數據

    

  4)垂直同步(VSync)ui

  5)顯示延遲(display lag)。顯示器可能會對畫面進行調整。this

  6)像素響應時間(pixel response time)。像素改變須要時間,大概幾毫秒。

二、數據包傳輸過程當中,有四種主要的延遲:

  1)處理延遲(processing delay)。網絡路由器的工做包括:讀取數據包、檢查目的IP、找出下一臺機器等。

  2)傳輸延遲(transmission dely)。鏈路層將數據寫入物理層(轉爲物理層信號)的時間。

  3)排除延遲(queuing delay)。

  4)傳播延遲(propagation delay)。例如從東海岸傳播到西海岸的時間。

 

  包含1400字節負載的數據包與包含200字節負載的數據包一般經歷相同時間的處理延遲。若是你發送 7 個包含 200 字節負載的數據包, 最後那個數據包將不得不在隊列中等待前面6 個數據包的處理, 這樣將經歷比一個大數據包更多的累積網絡延遲。

 

三、網絡抖動會致使數據包亂序到達。

四、數據包丟失的狀況。數據包丟失必然會產生,沒法避免。

  1)不可靠的物理介質。電磁干擾可能致使依賴損壞或丟失,如微波爐的工做。

  2)不可靠的鏈路。有時鏈路層信道徹底滿了,必須丟失正在發送的幀。

  3)不可能的網絡層。當路由器隊列滿了,後續到達的包將被丟棄。

五、路由器並不必定丟棄最後到達的報文。例如,有些路由器在丟棄TCP報文以前先丟棄UDP報文,由於它們知道丟棄TCP的報文會自動重傳。

六、TCP的幾大問題,最大問題是強制可靠性。

  1)低優先級數據的丟失干擾高優先級數據的接收。例如,依次發送聲音報文、技能報文,若是聲音報文未收到,則永遠不觸發技能報文,但對玩家來講,聲音播放與否可有可無,但技能必須當即播放。

  2)不相關數據流的想到干擾。例如技能報文、聊天報文使用同一個 TCP 鏈接,則一種報文的丟失會影響另外一個報文。

  3)過期遊戲狀態重傳。

 

  TCP中的 Nagle 算法起了很是很差的做用, 由於它在將數據包發送出去以前能夠延遲長達0.5秒。事實上,使用 TCP 做爲傳輸層協議的遊戲一般禁用 Nagle 算法以免這個問題, 雖然同時放棄了它提供的減小數據包數量的優點。

  最後,TCP 爲管理鏈接和跟蹤全部可能被重傳的數據分配了不少資源。這些分配一般是由操做系統管理的, 遊戲須要時很難經過自定義內存管理器的方式跟蹤和路 由。

 

七、經過UDP,能夠自定義一個系統,在發生丟包時,只發送最新消息,而不是重傳丟失的數據。有些第三方的UDP網絡庫可使用,如 RakNet、Photon。

  

八、自建可靠的UDP系統。

  1)發出數據包。從TCP借用一個技術,給每一個數據包分配一個序列號來實現。

 1 InFlightPacket* DeliveryNotificationManger::WriteSequenceNumber(
 2     OutputMemoryBitStream& inPacket)
 3 {
 4     PacketSequenceNumber sequenceNumber = mNextOutgoingSequenceNumber++;
 5     inPacket.Write(sequenceNumber);
 6     
 7     ++mDispatchedPacketCount;
 8     
 9     mInFlightPackets.emplace_back(sequenceNumber);
10     return &mInFlightPackets.back();
11 }
View Code

  2)收到數據包併發送確認。與TCP不一樣,這裏不承諾按序處理每一個單獨的數據包。僅僅承諾不亂序處理。只回復最新的包。

bool DeliveryNotificationManager::ProcessSequenceNumber(
    InputMemoryBitStream& inPacket)
{
    PacketSequenceNumber sequenceNumber;
    inPacket.Read(sequenceNumber);
    if (sequenceNumber == mNextExpectedSequenceNumber)
    {
        // 是指望的包,加入到待發ACK隊列
        mNextExpectedSequenceNumber = sequenceNumber + 1;
        AddPendingAck(sequenceNumber);
        return true;
    }
    // 過期包,丟棄
    else if (sequenceNumber < mNextExpectedSequenceNumber)
    {
        return false;
    }
    // 超新包,加入待發ACK隊列,更新mNextNumber
    else if (sequenceNumber > mNextExpectedSequenceNumber)
    {
        // 這裏有個問題,當 a,b包近 b,a序到達時,只會發b的ack,而不會發a的ack
        // 因此會有對方收到了a,但卻沒有回覆a的狀況發生。
        mNextExpectedSequenceNumber = sequenceNumber + 1;
        AddPendingAck(sequenceNumber);
        return true;
    }
}    

   下面是寫 ack 的方法。

void DeliveryNotificationManager::WritePendingAcks(
    OutputMemoryBitStream& inPacket)
{
    bool hasAcks = (mPendingAcks.size()>0);
    // 1. write hasAcks
    inPacket.Write(hasAcks);
    if(hasAcks)
    {
        // 2. write AckRange
        mPendingAcks.front().Write(inPacket);
        mPendingAcks.pop_front();
    }
}

  3)處理確認。ACK包亂序時,如依次回覆確認包 A,B,C,當客戶端端先收到C,則A,B將會被看成Fail處理,雖然服務端正確收到並處理了A,B,C。

void DeliveryNotificationManger::ProcessAcks(InputMemoryBitStream& inPacket)
{
    bool hasAcks;
    inPacket.Read(hasAcks);
    
    if (hasAcks)
    {
        AckRange ackRange;
        ackRange.Read(inPacket);
        
        // ACK的 Start
        PacketSequenceNumber nextAckdSequenceNumber = ack.Range.GetStart();
        
        // ACK的 End
        uint32_t onePastAckedSequenceNumber = nextAckdSequenceNumber + acRange.GetCount();
        
        while(nextAckdSequenceNumber<OnePastAckedSequenceNumber && !mInFlightPacket.empty())
        {
            const auto& nextInFlightPacket = mInFlightPacket.front();
            PacketSequenceNumber nextInFlightPacketSequenceNumber = nextInFlightPacket.GetSequenceNumber();
            
            // 1. 確認包已超越 mNextInFlightPacketSequenceNumber,代表沒有確認包,反饋丟包
            if (nextInFlightPacketSequenceNumber < nextAckdSequenceNumber)
            {
                auto copyOfInFlightPacket = nextInFlightPacket;
                mInFlightPackets.pop_front();
                HandlePacketDeliveryFailure(copyOfInFlightPacket);
            }
            // 2. 確認包等於 mNextInFlightPacketSequenceNumber,代表收到確認包,反饋收到包
            else if (nextInFlightPacketSequenceNumber == nextAckdSequenceNumber)
            {
                HandlePacketDeliverySuccess(nextInFlightPacket);
                
                mInFlightPackets.pop_front();
                ++nextAckdSequenceNumber;
            }
            // 3. 確認包小於 mNextInFlightPacketSequenceNumber,直接將確認包跌至 nextAckdSequenceNumber)
            else if (nextInFlightPacketSequenceNUmber > nextAckdSequenceNumber)
            {
                nextAckdSequenceNumber = nextInFlightPacketSequenceNumber;
            }
        }
        
    }
}
View Code

   綜上,自定義UDP層有個特色,就是隻處理最新SEQ,ACK的包。  

  4)超時機制。

void DeliveryNotificationManager::ProcessTimedOutPackets()
{
    uint64_t timeoutTime = Timing::sInstance.GetTimeMS() - kAckTimeout;
    while (!mInFlightPackets.empty())
    {
        const auto& nextInFlightPacket = mInFlightPackets.front();
        
        // 此方法有個條件,全部的請求必須有統一超時時間
        if(nextInFlightPacket.GetTimeDispatched()<timeoutTime)
        {
            HandlePacketDeliveryFailure(nextInFlightPacket);
            mInFlightPackets.pop_front();
        }
        else
        {
            break;
        }
        
    }
}
View Code

   5)每個包有本身的 HandleFail、HandleSucc 的實現。

void DeliveryNotificationManager::HandlePacketDeliveryFailure(
    const InFlightPacket& inFlightPacket)
{
    ++mDroppedPacketCount;
    inFlightPacket.HandleDeliveryFailure(this);
}

void DeliveryNotificationManager::HandlePacketDeliverySuccess(
    const InFlightPacket& inFlightPacket)
{
    ++mDeliveredPacketCount;
    inFlightPacket.HandleDeliverySuccess(this);
}
View Code

 九、沉默終端(dumb terminal)的三個問題:

  1)延遲問題。

  2)跳躍(無插值)問題。

    

  3)瞄準問題。瞄準的始終是過去幾百毫秒的位置。  

十、

十一、

十二、

1三、

相關文章
相關標籤/搜索