发送数据包
第一步:确定数据
正如在Creating Pakcets中描述的,找出你需要使用的数据类型,使用bitstream或结构体。
第二部:确定授权(authority这个词我也不太清楚如何翻译,只是理解意思)
你通常会发送动作的触发数据,而不是一系列动作的结果。通常来讲,数据源分为如下三类:
来自做出动作的函数
来自做出动作的函数的触发器。
来自于数据监视器。
每一种方法有有优势也有劣势。
来自于做出动作的函数:
例子:
我有一个称为ShootBullet方法,它带有各种参数,例如子弹的类型,射击源以及射击的方向。每一次进入ShootBullet发送中,目的就是发送一个数据报来告诉网络这个射击事件发生了。
优势:
这种方式很容易维护。ShootBullet或许从许多不同的地方调用(鼠标输入,键盘输入,AI)。并且不用担心跟踪每一个发送数据的地方。在已有的单人游戏很容易实现。
不足:
编程很难。如果我用ShootBullet初始化数据报,那么当网络想要执行这个函数的时候,它要调用这个方法的时候,如果ShootBullet初始化数据报,网络会调用ShootBullet方法,然后会发送另外一个数据报,成为一个反馈循环。那么解决方法有两种,或者另外写一个函数,例如DoShootBullet(sloppy)来专门处理网络发来的数据,或传递一个参数到ShootBullet来告诉它是否是要发送一个数据报。还有就是要考虑授权(authority)。客户端是否可以立刻射击,或者客户端需要来自服务器的授权?如果他需要服务器授权,那么ShootBullet方法需要发送数据包,然后立即返回。除非由网络调用,否则不应该发送数据而是仅仅执行射击的动作。网络也需要额外的数据,例如子弹剩余数,而ShootBullet方法却没有这些数据。有时可以从上下文获得这些数据,但是不是所有时候都可以。用这种方式编程需要一些时间和经验,并且有时很容易产生bug。
从动作函数的触发器获得数据:
例如:
还是使用ShootBullet()方法作为例子。但是这次并不是从ShootBullet方法内部发送数据。这次数据由ShootBullet方法的触发器来发送。例如,当用户点击鼠标时,AI决定射击,或者按下空格等等。
优点:
可以从网络上调用ShootBullet函数,而不用担心形成反馈环。这种情况下,从函数外通常有更多可用的信息。如果网络需要这个数据时,就很容易可以将数据发送出去。
不足:
需要更多的维护。如果我后来加入了其他的方式来射击子弹,那可能会忘记为它发送数据。
从数据解释器发送数据:
例子:
玩家的血量每一次到达0时,发送一个数据报。然而,然后,在血量到达0的地方并没有做这项工作。将它加入到每一个框架都运行的函数中来做,或许是在更新玩家的代码中。当这些代码得知血量到达0时,它会发送数据。然后它会做记录该数据已经发送,不再发送它。
优势:
从网络角度看,逻辑非常清楚。不需要担心反馈,不需要修改做出动作的函数。不需要维护,除非有人修改了我监视的数据。可以实现有效的网络算法,例如每一秒不要发送多次该数据包。
不足:
从设计的角度看很是粗略。仅仅能用于某些类型的数据。当监视的对象重置后,需要加入额外的代码来重置监视代码。要求项目内的其他的编程人员了解这种机制,以防他们修改你所监视的数据。
第三步:确定需要何种可靠性,以及需要的有序流类型。
PakcetPriority.h包含了这些枚举类型。有四个优先级可以选择:IMMEDIATE_PRIORITY, HIGH_PRIORITY, MEDIUM_PRIORITY, LOW_PRIORITY。
每一种优先级的发送次数大约是比它优先级低的快两倍。例如,如果HIGH_PRIORITY发送2条消息,在大致相同的时间内只会发送一条IMMEDIATE_PRIORITY消息。奇怪的是IMMEDIATE_PRIORITY可能会首先到达目的端。
Reliability类型在Detailed Implementation一节介绍了。通常使用RELIABLE_ORDERED作为数据包的可靠性类型。对于所有的有序类型,使用有序流,下面会介绍到。
第四步:调用RakPeerInterface.h中的Send方法。
发送方法不会改变数据,仅仅只做一个数据拷贝,因此从编程人员的角度,到这一步就做完了发送工作。
有序流
有32个有序流用于有序数据包,32有序流用于序列化数据包。可以认为stream是一个相对有序的流,同一个有序类型的数据包相互之间是相对有序的。使用一个例子说明这一点。假设你想要排序所有的聊天消息,排序所有的玩家运动的数据包,排序玩家的开火的数据包,以及序列化所有剩余弹药的数据包。你可能想要所有的聊天数据包按序到达,却不想聊天数据挂起,因为你并没有得到更早发送的玩家运动数据包。玩家运动数据包与聊天消息并没有关系,因此你不会关心他们的到达顺序。因此最好对它们使用不同的有序流,可以将0用于聊天消息,1用于玩家运动数据包。然而,我们认为玩家的开火数据包必须要相对于玩家的运动数据包要有序,谁也不想看到子弹从错误的位置发出。要处理这个问题可以将开火的数据包和玩家的运动数据包放到同一个流(stream),那么如果一个运动数据包比子弹数据包早到达接受方,由于实际上子弹数据包发送的要比运动数据包早,那么运动数据包会在子弹数据包到达并提交上层后才会提交运动数据包。
对于有序的数据包应该丢掉比较老的数据包。例如,如果接收到了数据包2,然后1,最后3,那么结果可能是接收到了2,丢掉1,然后接到3。这中处理对于弹药数据包是比较好的方式,因为弹药仅仅能下降,不会增加。如果你接收了比较老的数据包,那么会看到某个玩家的弹药在射击中增加了,明显是一个错误。因为有序的数据包都是在一个不同的流集合上,那么对有序数据包可以使用任何的流数字,例如0。只要清楚它与聊天数据包没有关系即可,因为聊天数据包使用有序流集合,而不是序列化流。
没有排序,或序列化的数据包,例如UNRELIABLE 和RELIABLE,不会有序列。这些类型的数据包会忽略这个参数。
By 北洋小郭