网络多人游戏架构与编程2

网络多人游戏架构与编程2

1.0、虚拟现实游戏是对延迟最敏感的, 因为我们人类只要头旋转了,眼睛就期望看到不同的事物。在这些情况下,保证用户感觉在虚拟现实世界中就要求延迟少于 20 毫秒

格斗游戏、 第一人称射击游戏和其他动作频繁的游戏是对延迟第二敏感的。 这些游戏的延迟范围可以从 16 毫秒到150毫秒

RTS游戏是对延迟容忍度最高的, 这个容忍度通常很有用, 正如第 6 章所介绍的。 这些游戏的延迟可以高达 500 毫秒, 而不影响用户体验。

1.1、非网络延迟。

1)输入采样延迟(input sampling latency)。用户按下一个按钮到游戏检测到这个按钮的时间可能很长。下图表明,游戏循环架构可能导致 Input Sampling Latency 高达接近 2帧的时间。

网络多人游戏架构与编程2

2)渲染流水线延迟(render pipeline latency)。驱动程序将绘制命令插入到缓冲区,GPU在未来的某个时刻执行。如果有许多渲染任务要做,可能会导致滞后 1帧 渲染出来。

网络多人游戏架构与编程2

3)多线程渲染流水线延迟(multithreaded render pipeline latency)。

网络多人游戏架构与编程2

4)垂直同步(VSync)

5)显示延迟(display lag)。显示器可能会对画面进行调整。

6)像素响应时间(pixel response time)。像素改变需要时间,大概几毫秒。

2、数据包传输过程中,有四种主要的延迟:

1)处理延迟(processing delay)。网络路由器的工作包括:读取数据包、检查目的IP、找出下一台机器等。

2)传输延迟(transmission dely)。链路层将数据写入物理层(转为物理层信号)的时间。

3)排除延迟(queuing delay)。

4)传播延迟(propagation delay)。例如从东海岸传播到西海岸的时间。

包含1400字节负载的数据包与包含200字节负载的数据包通常经历相同时间的处理延迟。如果你发送 7 个包含 200 字节负载的数据包, 最后那个数据包将不得不在队列中等待前面6 个数据包的处理, 这样将经历比一个大数据包更多的累积网络延迟。

3、网络抖动会导致数据包乱序到达。

4、数据包丢失的情况。数据包丢失必然会产生,无法避免。

1)不可靠的物理介质。电磁干扰可能导致依赖损坏或丢失,如微波炉的工作。

2)不可靠的链路。有时链路层信道完全满了,必须丢失正在发送的帧。

3)不可能的网络层。当路由器队列满了,后续到达的包将被丢弃。

5、路由器并不一定丢弃最后到达的报文。例如,有些路由器在丢弃TCP报文之前先丢弃UDP报文,因为它们知道丢弃TCP的报文会自动重传。

6、TCP的几大问题,最大问题是强制可靠性。

1)低优先级数据的丢失干扰高优先级数据的接收。例如,依次发送声音报文、技能报文,如果声音报文未收到,则永远不触发技能报文,但对玩家来说,声音播放与否无关紧要,但技能必须立即播放。

2)不相关数据流的想到干扰。例如技能报文、聊天报文使用同一个 TCP 连接,则一种报文的丢失会影响另一个报文。

3)过时游戏状态重传。

TCP中的 Nagle 算法起了非常不好的作用, 因为它在将数据包发送出去之前可以延迟长达 0.5秒。事实上,使用 TCP 作为传输层协议的游戏通常禁用 Nagle 算法以避免这个问题, 虽然同时放弃了它提供的减少数据包数量的优势。

最后,TCP 为管理连接和跟踪所有可能被重传的数据分配了很多资源。这些分配通常是由操作系统管理的, 游戏需要时很难通过自定义内存管理器的方式跟踪和路 由。

7、通过UDP,可以自定义一个系统,在发生丢包时,只发送最新消息,而不是重传丢失的数据。有些第三方的UDP网络库可以使用,如 RakNet、Photon。

网络多人游戏架构与编程2

8、自建可靠的UDP系统。

1)发出数据包。从TCP借用一个技术,给每个数据包分配一个序列号来实现。

网络多人游戏架构与编程2网络多人游戏架构与编程2
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。

网络多人游戏架构与编程2网络多人游戏架构与编程2
void DeliveryNotificationManger::ProcessAcks(InputMemoryBitStream& inPacket)
{
    bool hasAcks;
    inPacket.Read(hasAcks);
</span><span>if</span><span> (hasAcks)
{
    AckRange ackRange;
    ackRange.Read(inPacket);

    </span><span>//</span><span> ACK&#x7684; Start</span>
    PacketSequenceNumber nextAckdSequenceNumber =<span> ack.Range.GetStart();

    </span><span>//</span><span> ACK&#x7684; End</span>
    uint32_t onePastAckedSequenceNumber = nextAckdSequenceNumber +<span> acRange.GetCount();

    </span><span>while</span>(nextAckdSequenceNumber<onepastackedsequencenumber>mInFlightPacket.empty())
    {
        <span>const</span> auto&amp; nextInFlightPacket =<span> mInFlightPacket.front();
        PacketSequenceNumber nextInFlightPacketSequenceNumber </span>=<span> nextInFlightPacket.GetSequenceNumber();

        </span><span>//</span><span> 1. &#x786E;&#x8BA4;&#x5305;&#x5DF2;&#x8D85;&#x8D8A; mNextInFlightPacketSequenceNumber&#xFF0C;&#x8868;&#x660E;&#x6CA1;&#x6709;&#x786E;&#x8BA4;&#x5305;&#xFF0C;&#x53CD;&#x9988;&#x4E22;&#x5305;</span>
        <span>if</span> (nextInFlightPacketSequenceNumber &lt;<span> nextAckdSequenceNumber)
        {
            auto copyOfInFlightPacket </span>=<span> nextInFlightPacket;
            mInFlightPackets.pop_front();
            HandlePacketDeliveryFailure(copyOfInFlightPacket);
        }
        </span><span>//</span><span> 2. &#x786E;&#x8BA4;&#x5305;&#x7B49;&#x4E8E; mNextInFlightPacketSequenceNumber&#xFF0C;&#x8868;&#x660E;&#x6536;&#x5230;&#x786E;&#x8BA4;&#x5305;&#xFF0C;&#x53CD;&#x9988;&#x6536;&#x5230;&#x5305;</span>
        <span>else</span> <span>if</span> (nextInFlightPacketSequenceNumber ==<span> nextAckdSequenceNumber)
        {
            HandlePacketDeliverySuccess(nextInFlightPacket);

            mInFlightPackets.pop_front();
            </span>++<span>nextAckdSequenceNumber;
        }
        </span><span>//</span><span> 3. &#x786E;&#x8BA4;&#x5305;&#x5C0F;&#x4E8E; mNextInFlightPacketSequenceNumber&#xFF0C;&#x76F4;&#x63A5;&#x5C06;&#x786E;&#x8BA4;&#x5305;&#x8DCC;&#x81F3; nextAckdSequenceNumber)</span>
        <span>else</span> <span>if</span> (nextInFlightPacketSequenceNUmber &gt;<span> nextAckdSequenceNumber)
        {
            nextAckdSequenceNumber </span>=<span> nextInFlightPacketSequenceNumber;
        }
    }

}

}

View Code

综上,自定义UDP层有个特点,就是只处理最新SEQ,ACK的包。

4)超时机制。

网络多人游戏架构与编程2网络多人游戏架构与编程2
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 的实现。

网络多人游戏架构与编程2网络多人游戏架构与编程2
void DeliveryNotificationManager::HandlePacketDeliveryFailure(
    const InFlightPacket& inFlightPacket)
{
    ++mDroppedPacketCount;
    inFlightPacket.HandleDeliveryFailure(this);
}

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

View Code

9、沉默终端(dumb terminal)的三个问题:

1)延迟问题。

2)跳跃(无插值)问题。

网络多人游戏架构与编程2

3)瞄准问题。瞄准的始终是过去几百毫秒的位置。

10、

11、

12、

13、

Original: https://www.cnblogs.com/tekkaman/p/11287719.html
Author: Tekkaman
Title: 网络多人游戏架构与编程2

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/549322/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球