RakNet--Reliability types

Jenkins Software

可靠性类型

控制何时如何使用数据包优先级和可靠性类型

// 发送数据的时候,使用这些枚举类型设置数据类型

enum PacketPriority

{

       // 最高优先级。这些0消息立即发送,通常不会进行缓存或与其他数据包聚集 为一个数据报。

       //   HIGH_PRIORITY优先级的数据或者更低优先级的

       // 数据将会被缓存,并且是在10毫秒的时间间隔后被发送。

       IMMEDIATE_PRIORITY,

       // 每发送两个IMMEDIATE_PRIORITY消息,才会发送一个HIGH_PRIORITY消息

       HIGH_PRIORITY,  

       // 每发送两个HIGH_PRIORITY, 才会发送一条MEDIUM_PRIORITY优先级消息.

       MEDIUM_PRIORITY,

       // 每发送两条MEDIUM_PRIORITY消息, 才会发送一条LOW_PRIORITY。

       LOW_PRIORITY,

       NUMBER_OF_PRIORITIES

};

 

 

 

注:上述的情况都是在缓存中有高优先级的消息存在时才会如此,否则如果没有缓存,则到来的数据直接发送。

 

        数据包优先级非常简单。高优先级数据包在中级优先级数据包之前发送,中级优先级数据包在低优先级数据包之前发送。最初提出数据包的优先权花了很长时间才设计清楚,但是实际使用中优先级会扰乱游戏,因为要发送到一些新连接的不太重要的数据(例如地图数据)会占据了游戏数据。

// 这些枚举类型描述了数据包如何传送

enum PacketReliability

{

       UNRELIABLE,

       UNRELIABLE_SEQUENCED,

       RELIABLE,

       RELIABLE_ORDERED,

       RELIABLE_SEQUENCED

       UNRELIABLE_WITH_ACK_RECEIPT,

       UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT,
       RELIABLE_WITH_ACK_RECEIPT,

       RELIABLE_ORDERED_WITH_ACK_RECEIPT,
       RELIABLE_SEQUENCED_WITH_ACK_RECEIPT,

};

 

 

不可靠(UNRELIABLE)

不可靠得数据包直接使用UDP发送。他们到达目的地会出现乱序,或更不到达不了。这中方式对不太重要的数据包比较好,或者是那些发送频率非常高的数据,即使一些数据丢失了,更新到的数据包会弥补丢失的数据包。

优点:这些数据包不需要进行确认,在确认数据包中节省UDP数据包头大小的数据(大约50字节)。这些数据加起来也可以节省很大的带宽。

缺点:数据包不是按序到达,或者数据包根本到达不了,如果发送缓存满了,这些数据包是最先丢弃的那些数据包。

 

不可靠序列化(UNRELIABLE_SEQUENCED)

不可靠序列化数据包与不可靠数据包相同,但是这个条件下仅仅接收最新的数据包。更早的数据包会被忽略。

优点:与不可靠类型一样,开销比较低。不用担心较早的数据包会将游戏数据修改为老数据。

缺点:由于使用UDP进行发送,许多数据包可能会被丢弃,即使他们达到了接收端可能被丢掉。如果发送缓存满,这些数据是首先丢弃的对象。最后的发送的数据包可能不会到达,如果你在一些特定点停止了数据发送,这种情况下本类型数据有可能存在问题。

       注意:三个可靠数据包类型中的一个的传输需要进行连接丢失检测。如果你不需要发送可靠数据包,那么需要实现手工的连接丢失检测。

 

可靠类型(RELIABLE)

可靠类型数据包由可靠层监督的UDP数据包,保证这些数据包可以到达目的地。

优点:可以知道数据最终到达了接收端。最终……

缺点:重传和确认会增加额外的带宽消耗。如果网络非常忙,数据包可能很晚到达。并且没有数据包排序。

 

可靠有序类型(RELIABLE_ORDERED)

        可靠有序类型是由可靠层监督的UDP数据包,确保了按序到达目的端。

        优点:数据包会按照发送的顺序到达接收端。这种方式是最简单的编程方式,编程人员不需要担心由于失序或丢包而出现的一些奇怪的行为。

        缺点:重传和确认会明显增加带宽需求。如果网络过于忙碌,数据包可能到达非常晚。一个迟到的数据包可能会延迟其他更早到达的数据包,造成显著的延迟。然而,这些缺点完全可以通过有序流的合理利用来缓解。

 

可靠序列化类型(RELIABLE_SEQUENCED)

        可靠序列化数据包是由可靠性层监控的UDP数据包,确保按照序列化的方式到达目的地。

        优点:实现可靠的UDP数据包,并且数据包是按照序列化进行排序的,不需要等待较老的数据包。此外这种方式下,数据包到达的数量明显多于不可靠序列化类型方法。他们甚至更加分散。最重要的优势在于无论如何最后发送的数据包会到达,但是在不可靠序列化类型下,最后的数据包有可能丢失。(注:此处为何不可靠序列化类型的最后的数据包会丢失,不太明白。)

 

确认(ACK)

        *_WITH_ACK_RECEIPT

        通过制定包含了_WITH_ACK_RECEIPT的数据包可靠性类型,可以要求RakPeerInterface通知你当一个消息已经被远端系统确认了,或者传送超时了。

        调用RakPeerInterface::Send() 或者RakPeerInterface::SendLists()返回一个四字节的无符号整数代表了一个发送的消息的ID。当使用_WITH_ACK_RECEIPT时,调用RakPeerInterface::Receive()函数也会返回这个相同的ID。在数据包中字节0会是ID_SND_RECEIPT_ACKED 或 ID_SND_RECEIPT_LOSS。字节1-4会包含Send()或SendLists()返回的相同数字。

        ID_SND_RECEIPT_ACKED 意味着消息到达接受方。对于可靠发送,你就会得到这个值。

        对于UNRELIABLE_WITH_ACK_RECEIPT和 UNRELIABLE_SEQUENCED_ WITH_ACK_RECEIPT两个类型的数据,如果发送失败,会返回ID_SND_RECEIPT_LOSS值。返回这个值意味着对消息的确认没有在消息重发指定的时间门限内到达(大约为ping的几倍)。它所暗含的情况包括如下几种:

        1. 在传输中消息丢失

        2. 消息达到接受方,但是确认在传输中丢失。

        3. 消息到达,确认也到达,但是是在重发时间门限值之后到来。

        情况最多是前两种。

 

下面是一个读取返回值得例子:

packet = rakPeer->Receive();
while (packet)
{
        switch(packet->data[0])
        {
                case ID_SND_RECEIPT_ACKED:
                        memcpy(&msgNumber, packet->data+1, 4);
                        printf("Msg #%i was delivered.\n", msgNumber);
                        break;
                case ID_SND_RECEIPT_LOSS:
                        memcpy(&msgNumber, packet->data+1, 4);
                        printf("Msg #%i was probably not delivered.\n", msgNumber);
                        break;
        }
        sender->DeallocatePacket(packet);
        packet = sender->Receive();
}

 

 

        使用这个值得原因就是了解不可靠类型消息是否到达接受方。有时候如果不可靠消息丢失,要重发不可靠消息,由于这些数据都是时新(实时的)数据,不能使用可靠类型。要实现这样的功能,在发送不可靠数据的时候,需要创建一个Send()或SendList()返回的值到接受方返回值之间的一个映射。如果接受方的返回值为ID_SND_RECEIPT_LOSS,那么就需要重新发送本条返回消息值所对应的数据。

 

高级发送类型

        在Resends上发送最新的值。

        当RakNet重新发送消息时,它仅仅能够发送你最初给它传递的值。对于不断变化的数据(例如位置),你可能就想发送最新的一些值。这样的话,使用UNRELIABLE_WITH_ACK_RECEIPT类型发送数据。调用RakPeer::GetNextSendReceipt(),将值传递到RakPeer::Send()。在内存中存储一个消息类型和发送回复之间的一个关联。如果接收到的值是ID_SND_RECEIPT_ACKED,则将这个关联删除掉(也就是将相应的消息删除掉)。如果获取的返回值是ID_SND_RECEIPT_LOSS值,使用最新的值重新发送这条消息。

        如果想让数据序列化,那么将自己的序列值与消息写到一起。远端应该存储接收到的最大的序列值。如果到达的消息的序列号比最高的序列值更低,那么这条消息都是更早的消息,可以忽略。

下面是一个使用unsigned char序列号的一个代码例子。只要你发送的数据不多于127,那么就不会出现失序:

typedef unsigned char SequenceNumberType;

bool GreaterThan(SequenceNumberType a, SequenceNumberType b)
{
        // a > b?
        const SequenceNumberType halfSpan =(SequenceNumberType) (((SequenceNumberType)(const SequenceNumberType)-1)/(SequenceNumberType)2);
        return b!=a && b-a>halfSpan;
}

 

 

序列化的数据,而不是使用序列化的消息

        RakNet的序列化只对整个消息的序列化有用。然而,有时想要在更高一层的粒度实现序列化。例如,需要位置和生命值的序列化。

        消息A包含生命值

        消息B包含生命值和位置信息

        消息C包含位置值。

        按照正常的序列化规则,如果消息按照A,C,B到达,消息B会被丢弃。然而,这样的话你会丢失掉很有用的信息,因为消息B包含了最新的生命值,且这个值可以使用。

可以通过写入自己的序列化编号给你要序列化的变量来实现子序列化。(像上面描述的一样)。然后根据自己的需要使用非序列化的发送的类型发送数据,UNRELIABLE,UNRELIABLE_WITH_ACK_RECEIPT,RELIABLE,等等。尽管这样需要更多带宽和处理开销,它具有的优势是每一次更新可以尽快处理掉。

 

See Also

 

Index
Sending Packets
posted @ 2012-09-17 01:08  杂草  阅读(374)  评论(0编辑  收藏  举报