详细实现
实现
实现RakNet你所要做的就是在程序中获得一个RakPeer的实例。
有一些头问价是你必须需要的:
头文件
#include "MessageIdentifiers.h"
#include "RakPeerInterface.h"
#include "RakNetTypes.h"
MessageIdetifiers.h包含了一个巨大的枚举数据,表示了RakNet用于发送消息的标识符,例如断开连接通知。你可能想要定义自己的消息标识符,你的枚举值必须在MessageIdentifiers.h+1的最大值。RakNetworkFactory.h是一个工厂设计模式的实现,用于获得一个指向RakPeerInterface的指针。在使用DLL中是必要的。RakPeerInterface.h是一个RakPeer类得接口。RakNetTypes.h定义了在RakNet中使用的结构体,包括SystemAddress结构体——系统的唯一标识符,以及当你需要接收数据或需要发送数据时,API返回给你的数据包。
实例
RakNet::RakPeerInterface* peer = RakNet::RakPeerInterface::GetInstance();
这些代码会给你一个peer的实例。通常在特殊的exe中只需要一个实例。
下一步是连接,作为客户端或服务器端
例如:
作为客户端连接
peer->Startup(1, &SocketDescriptor(), 1)
peer ->Connect(serverIP, serverPort, 0, 0);
调用Startup启动网络线程。
第一个参数用于设置连接的最大值。例如一个纯client,这个值使用1.
第二个参数(本例中设置为30)是一个线程休眠定时器。0值对需要快速响应的游戏比较好,例如射击。否则,值30也会有好的响应时间,并且要求非常少的CPU使用。
第三个参数(SocketDescriptor())描述了监听的端口/地址。因为我们想要一个客户端,我们不需要指定任何东西。
调用Connect连接到服务器,第一个参数serverIP是服务器的IP地址或域地址。如果想要连接到我们自己的系统,例如在一个机器上运行两个相同程序,使用“127.0.0.1”或使用localhost。用于自己连接自己的系统。如果是IPv6,使用“::1”。
Connect的下一个参数是serverPort。这个端口是你想要连接到服务器上的端口。如果指定了服务器端口,但是服务器不会从该端口上接收数据,你就无法连接就像是输入了错误的IP地址。IP和端口总是一起工作形成一个完整的地址。如何知道连接那个端口?好吧,这个端口是需要编程人员提前决定的,然后直接硬编码到系统中。如何选择一个端口号?可以选择任意的一个端口号,但是它在机器上没用使用且是在2^16内的一个数字。然后某些确定的端口是保留给已经建立的系统程序,例如网络浏览器,Telnet,和FTP。可以在网上查看这些已经分配的端口,但是快速其模糊的答案是多数位于32000之下的端口是保留的,超过32000任何人都可以使用。
实践中,端口通常使用#define设置,不再改变。例如
#define SERVER_PORT 60005
#define CLIENT_PORT 60006
这种情况下,服务器总知道使用哪个端口响应,客户端也知道连接到服务器的哪一个端口。这样也省去了终端用户输入端口。
注意连接的尝试是异步的。如果连接尝试中连接成功,函数会立即返回CONNECTION_ATTEMPT_STARTED,但是这并不意味着你的连接成功了。当调用RakPeerInterface ::Receive()以及带有第一个自己ID_CONNECTION_ACCEPTION的数据包返回时才知道真正连接成功。连接失败会返回一个ID_CONNECTION_ATTEMPT_FAILED网络标识。
RakNet迅速连接,如果在几秒钟之内没有连接它,那么就不能够连接了。系统会返回一个ID_CONNECTION_ATTEMPT_FAILED标识通知你连接尝试失败。
启动服务器也是相似的。
peer->Startup(maxConnectionsAllowed, &SocketDescriptor(serverPort,0), 1);
peer->SetMaximumIncomingConnections(maxPlayersPerServer);
第一个参数是同时连接到服务器的最大连接数。第二个和第三个参数是监听那个端口。滴啊用SetMaximumIncomingConnections设置允许有多少连接。
如果你的游戏允许服务器充当玩家,记住你可以拥有的玩家的真正数字必须要比你支持的最大的客户端数大1。如果你的服务器是一个专属服务器,或游戏使得客户端和服务器在同一个机器上(不推荐),那么可以游戏的人数也应该相应修改。
端到端连接
RakNet::SocketDescriptor sd(60000,0);
peer->Startup(10, &sd, 1);
peer->SetMaximumIncomingConnections(4);
Startup设置了10个允许连接。一个允许的连接或者是进入或者是向外连接。使用端口60000接收数据。如果你想要允许其他的对等段连接到你的系统,那么SetMaximumIncomingConnections是必须要调用设置的。但是如果你的系统仅仅连接到其他的系统,那么这个函数就不是必须要调用的了。这种情况下,将它设置为4。这个是最大的值,而不是保留值,那么连接到8个对等端也是可能的——你那么仅仅能够获得两个连接直到你断开了一个或更多的对等端。
读取数据包
RakNet::Packet *packet = peer->Receive();
就是这么简单。如果包长是0, 没有数据需要读取,可以继续游戏。否则获取其中的数据。通常要在一个循环中调用这个函数,例如:
RakNet::Packet *packet;
for (packet=peer->Receive(); packet; peer->DeallocatePacket(packet), packet=peer->Receive()) {
}
可以获得两种类型数据:
从引擎到来的消息
从RakNet实例打来的数据,位于同一个计算机或不同计算机。
两种数据用同样的方式来处理。让我们看一下数据包的结构。
namespace RakNet
{
struct Packet
{
/// The system that send this packet.
SystemAddress systemAddress;
/// A unique identifier for the system that sent this packet,
/// regardless of IP address (internal / external / remote system)
/// Only valid once a connection has been established
/// (ID_CONNECTION_REQUEST_ACCEPTED, or
/// ID_NEW_INCOMING_CONNECTION)
/// Until that time, will be UNASSIGNED_RAKNET_GUID
RakNetGUID guid;
/// The length of the data in bytes
unsigned int length;
/// The length of the data in bits
BitSize_t bitSize;
/// The data from the sender
unsigned char* data;
} // Namespace
SystemAddress描述了包的来源。每一个连接的系统都有一个系统自动赋值的唯一的SystemAddress。注意系统地址在整个连接期间会是常量。某些本地的网络消息使用SystemAddress成员ID_REMOTE_DISCONNECTION_NOTIFICATION告诉另外一个客户端已经从你的客户端断开了连接。systemAddress在那种情况下指定了那个客户端。UNASSIGNED_SYSTEM_ADDRESS是一个为“Unknown”保留的值。不应该将systemAddress作为远端系统的一个唯一标识。因为相同的计算机可以有不同的systemAddress对每一个连接。使用RakNetGUID作为一个特殊的RakPeerInterface实例的唯一标识值。
bitSize指出了结构体的数据域有多少位长。
既然获取了一个数据包,就需要确定哪些数据意味着什么。通常第一个字节的数据是一个枚举值,描述了数据类型(参考creating packets获取更多信息)。后面会学习到,数据不总是这种情况。因为相同的数据包,可以获得一个时间戳。让工作更加简单点,提供了一个函数获取有时间戳的数据包的标识:
unsigned char GetPacketIdentifier(Packet *p)
{
if ((unsigned char)p->data[0] == ID_TIMESTAMP)
return (unsigned char) p->data[sizeof(MessageID) + sizeof(RakNet::Time)];
else
return (unsigned char) p->data[0];
}
这个方法返回一个unsigned char类型数据,它对应了MessageIdentifiers.h中定义的枚举值。
网络引擎会为这个客户端返回某些的消息,某些消息仅仅用于服务器,而有的消息用于两者。更加详细的及时消息参考MessageIdentifiers.h。最重要的需要考虑的是ID_NEW_INCOMING_CONNECTION和ID_CONNECTION_REQUEST_ACCEPTED。这些意味着服务器或对等端获得一个新的客户端连接,和客户端或对等端已经成功连接。在这时,可以发送你自己的数据或消息。
如果数据包标识不是预先定义的标识符,那么你那么你获得其他系统发送的数据。你可以解码数据,在游戏中按照适当的方法处理数据。参考Creating packets 获得更多的编码和解码数据的信息。
重要!!!!
处理完数据后,记得要将数据包释放掉。仅仅需要将它传递给DeallocatePakcet。
peer->DeallocatePacket(p);
发送数据
解释如何发送数据最好的方法就是使用一个例子。
const char* message = "Hello World";
对所有连接的系统:
peer->Send((char*)message, strlen(message)+1, HIGH_PRIORITY, RELIABLE, 0, UNASSIGNED_RAKNET_GUID, true);
第一个参数必须是字节流。因为我们发送一个字符串,字符串也就是一个字节流,我们不需要转换,直接发送这个数据。第二个参数是有多少字节要发送。在这个例子中,我们发送字符串的长度,以及一个结束符。
第三个参数是数据包的优先级,使用如下值中的一个:
IMMEDIATE_PRIORITY,
HIGH_PRIORITY
MEDIUM_PRIORITY
LOW_PRIORITY
IMMEDIATE_PRIORITY消息通知RakNet的更新线程立即更新。假设带宽是足够使用的,那么立即发送。HIGH_PRIORITY,MEDIUM_PRIORITY,和LOW_PRIORITY消息是放进一个缓存中。下一次RakNe的更新线程执行时(每10微秒)发送这些消息。这三个优先级可以获得更加有效的带宽,因为如果多个消息可以整合进一个单独的数据包,RakNet会透明地实现整合。
每一个优先级发送出去数据的数量是成二倍关系的,因此如果所有的数据等待发送,8 IMMEDIATE_PRIORITY, 4 HIGH_PRIORITY, 2 MEDIUM_PRIORITY, and 1 LOW_PRIORITY 会按照前述比例发送数据。很明显,如果仅仅LOW_PRIORITY消息等待,这些数据会尽快发送出去。
第四个参数取如下的五个主要值之一。让我们发送数据1,2,3,4,5,6。如下是我们获取数据的序列和子串:
UNRELIABLE - 5, 1, 6
UNRELIABLE_SEQUENCED - 5 (6 was lost in transit, 1,2,3,4 arrived later than 5)
RELIABLE - 5, 1, 4, 6, 2, 3
RELIABLE_ORDERED - 1, 2, 3, 4, 5, 6
RELIABLE_SEQUENCED - 5, 6 (1,2,3,4 arrived later than 5)
得到更多的细节参考PacketPriority.h
第五个参数(本例中使用0)是使用哪个有序流。这个值用于在同一个流中,与其他数据包相关的数据包的排序。这个值现在不重要,要了解更多的知识,参考Sending Packets部分。
第六个参数,(UNASSIGNED_RAKNET_GUID),是要发送到的远端系统。UNASSIGNED_RAKNET_GUID是一个保留值,意味着“不特指某一个”。这个参数意味着两件事情:一个是你的数据包的接受方,或者是你不想要发送的一方,这依据最后一个参数广播的值决定。
第七个参数(在本例为TRUE)是表明是否广播到所有的连接系统或不广播。这个参数与第六个参数结合使用,如果广播标识是TRUE,那么第六个参数指定不发送的客户端。如果它的值为false,那么第六个参数指定了仅仅发送到该客户端。如果我们要光波导么一个人,可以指定该值为UNASSIGNED_RAKNET_GUID。这个在中继数据包时起作用,因为在获得的数据包的Packet::SystemAddress域指定了发送者是谁。我们可以将数据包中继给所有的客户端,不再给发送者发送,这个在有些情况下是有效的,因为我们通常不想要发送相同的数据到发送给我们这个数据的客户端。
关闭
关闭很容易,并且是即时的。仅仅需要调用Shutdown()方法,然后销毁它。
somePeer->Shutdown(300);
Shutdown停止了网络线程。如果你指定了一个大于0的参数,Shutdown会阻塞起来,通知连接到系统的客户端连接终止了。0会造成没有任何声明的Shutdown(),远端系统会在大约10秒之内检测出连接断开,返回一个ID_CONNECTION_LOST.
清理
仅仅将实例传递给DestroyRakPeerInterface即可。或许你想要在程序中间做这项工作来释放内存,但是并不是要求的。
RakNet::RakPeerInterface::DestroyInstance(rakPeer);
By 北洋小郭