[网络开发]RakNet文档翻译(3)——Creating Packets

作者: NickYang 分类: 技术文章,程序开发 发布时间: 2012-05-06 01:40

如何将你的数据编码到一个数据包中?

运行RakNet的系统通过人们所熟知的数据包进行通讯,实际上所有在Internet上运行的系统都如此。更准确的说,在UDP协议下,它用的是数据报。每一个通过RakNet创建的数据报中都包含了一条或者多条信息。消息可以是通过你创建的,例如位置信息,血量信息,或者其他通过RakNet内部创建的,例如ping消息。按照惯例,消息的第一个字节包含了一个0-255之间的数字标示符,它被用来标识消息的类型。RakNet已经在自己内部或者为插件使用了大量消息,这些可以在MessageIdentifiers.h文件中看到。

这个例子中,我们放置了一个定时炸弹在游戏里,我们需要以下的数据:

1.炸弹的位置信息,包含三个浮点数,x,y,z,你可能有自己定义的可以替代三个浮点数的向量。

2.一些所有系统都可以访问炸弹的方法。NetworkIDObject类是一个非常好的方法。让我们假设一个炸弹类继承自NetworkIDObject类。然后我们需要存储炸弹的NetworkID(更多的信息请查看Receiving Packets, Sending Packets, 后面会讲到)

3.谁拥有这个炸弹。这样我们就知道有人踩中了它,该给谁积分。创建引用到玩家身上,最好是系统内存地址,这样就可以通过GetExternalID()来获得改内存地址,也就是拥有者。

4.当这个炸弹被放置后,这个炸弹会在我们倒计时10秒钟后自动爆炸,因此获得正确的时间是非常重要的,这样就不会出现在不同的电脑上爆炸时间不同的问题。幸好RakNet已经有了TimeStamping来处理这个问题。

 

使用一个结构体或者字节流?

任何你想发送的数据最终都会变成字节流发送,将你的数据变成字节流有两种简单的方法。一种是创建一个结构体然后将它转化为(char*),另一种方法是使用内建的BitStream类。

第一种方法的优点是改变结构体非常容易,同时你也可以确切的看到你想要发送的数据。由于发送者和接收者都可以共享源文件中定义的结构体,所以可以避免转化错误。同样也没有让数据乱序,也不会出现使用错误的数据类型。缺点是你经常不得不修改结构体并且重编译许多文件。这样你就失去了可以总是用字节流类来自动执行的便利,同时Raknet不能自动为结构体成员转化字节序。(译者注:网络传输字节序与内存字节序是相反的,所以发送的时候需要转化一下。)

第二种方法的优点是你不需要改变任何外部文件。简单的创建字节流,写入你想要发送的任何排序的数据,然后发送它。可以使用“压缩”版本的读写方法来写入较少的比特数据,例如它写入bool值,只需要一个比特位。当某些情况下是true或者false,你可以动态的写入数据。使用Serialize(), Write()或者Read()写入,BitStream可以自动将数据成员转化为网络字节序。BitStream的缺点是你现在使用它很容易犯错误,比如读写的方法不完全相同,错误的排序,错误的数据类型,或者其他错误。

我们将要使用两种方法来创建数据包。

 

用结构体创建数据包

正如我可能前面提到的,RakNet有一个标识数据包类型的约定惯例。数据段的第一个字节是一个单字节枚举,它标识了数据包的类型,接下来是数据传输。数据包中包含了一个时间戳,第一个字节包含了ID_TIMESTAMP,接下来的4个字节是真正的时间戳值,然后下一个字节是数据包类型的标识,接下来才是真正传输的数据。

没有时间戳的情况

注意 #pragma pack( push, 1 ) #pragma pack( pop ),他强制你的编译器(在本例子中是VC++),将结构体按照1字节对齐。检查你的编译器文档学习更多。

 

有时间戳的情况

注意:当发送数据时,RakNet假设时间戳是网络字节序的。你应该使用BitStream::EndianSwapBytes()函数将时间戳数据进行转序。在接受时间戳数据的系统上,使用

如果使用的是BitSteam这个步骤可以省略。

填充数据包,对于我们的定时炸弹,我们想试用有时间戳的方法。因此最终的结果看起来应该如下所示:

像我上面的注释写到,我们必须定义一个自己数据包的枚举,当数据流到达接收函数时,我们就知道我们关注的数据包是哪一个了。你应该从ID_USER_PACKET_ENUM开始定义枚举,就像下面的例子:

注意:结构体中不应该直接或者间接包含指针。

结构体或者类中包含指针貌似是一个普遍的错误,人们认为指向数据的指针应该可以在网络中被发送。也不是没有这种情况,它会被当做一个指针地址被发送出去的。

 

嵌套结构体

使用嵌套结构体没有任何问题,不过请保持第一个字节总是决定了数据包的类型。

使用字节流来创建数据包

我们继续使用上面的炸弹例子,使用字节流将他发送出去,我们使用与前面相同的数据。

如果哦我们想将myBitStream发送到RakPeerInterface::Send,这时候它会在内部被转化为结构体。现在让我们试着做一点改进。因为一些原因让我们假设定时炸弹的坐标为0,0,0。我们可以替换为下面的。

这个方法在网络传输中可以节省3float,而是用1位数据代替。

 

通常的错误

当用bitstream写第一个字节的时候,必须转化为MessageID或者 unsigned char类型,如果你仅是直接写入枚举数据类型,那将会是一个整数(4字节)

正确的方法是:

错误的方法是:

第二种情况下,RakNet中读取出来的第一个字节是0,这个是为ID_INTERNAL_PING保留的,千万记住。

 

写入字符串

可以使用BitStream的数组来写入字符串。一种是先写入长度,然后再写入数据,例如:

解码是类似的,然而,效率却不高。RakNet存在一个内建的StringCompressor函数…stringCompressor。它是一个全局实例,使用它写入字串变成了:

不仅是字符串编码,所以数据包嗅探器不是很容易读取字符串,而且它压缩了字符串。解码一个字串可以使用:

在这个例子里,256是读写的最大长度。在EncodeString中,如果你的字串长度小于256,他将会写入整个字串。如果超过256个字符,它会被截短,它将会被当成一个256个字符的数据进行解码,包含结束符。

RakNet还有一个字符串类,RakNet::RakString,在RakString.h中可以找到

RakStringstd::string约快3倍。

RakString支持Unicode

 

程序员们请注意:

1.可以直接将结构体写入BitsStream,只需要简单的将他转化为(char*)。使用memcpy拷贝你的结构体。在结构体中,因为不能包含指针,所以不允许将指针写入bitstream

如果你经常使用字符串,你可以使用StringTable代替。它和StringCompressor类似,但是可以发送代表一个已知字串的两字节数据。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

一条评论
  • 大笨兔

    2012 年 5 月 8 日 18:33

    不懂啊,还要努力学习了

    活跃 火狐浏览器 Windows 7

发表评论

电子邮件地址不会被公开。 必填项已用*标注