free web stats RakNet & Irrlicht - Step 3

A Primer For RakNet Using Irrlicht

By Dave Andrews (http://www.daveandrews.org)



Intro | Step 1 | Step 2 | Step 3 | Conclusion

Step 3: The Client (With Networking Code)

Worry not, we are not going to re-write the entire client. We're only going to fill in the functions that were left empty before, and then recompile. We are going to be implementing the following functions in the ClientConnection class, and then it will work as expected: the constructor, destructor, SendLineToServer, ListenForPackets, and HandlePacket.

To begin, we must add a member to the ClientConnection class. Before, we did not have a pointer to a RakClientInterface object, which is very much like the RakServerInterface, except it acts only as a client.  So change the private section of the ClientConnection class to this:

private:
RakClientInterface * client;
list<line2d<s32> > lineList;

All we have done here is add a pointer to a RakClientInterface object to the members of the ClientConnection object. The RakClientInterface is what allows us to connect to a remote server and send packets to the server.

Also, add the following definition to the top of the client program file:

const unsigned char PACKET_ID_LINE = 100;

This is just like the server's definition of the constant. The client and the server must have an understanding between each other of exactly what packet identifiers to use, so they will be able to understand the custom packets sent back and forth. If we did not use the same value for our packet identifiers, the server would have no idea what custom packets were coming in, or worse confuse them with predefined packet types.

    ClientConnection(char * serverIP, char * portString)
: client(NULL)
{
client = RakNetworkFactory::GetRakClientInterface();

client->Connect(serverIP, atoi(portString), 0, 0,
0);
}

Replace the constructor of the ClientConnection with this code. Now that you have written the server, you should be able to understand the RakNetworkFactory line. It will basically just create a client connection for us.

The second line is what does the actual connecting to the server. The parameters are simple, the first is the IP of the server, in a string. The second parameter is the port to connect to, except it is accepted as an unsigned short instead of a string, so we have to convert it. Third is the port to open on the local client computer. We specify 0 because we don't care what port is opened locally. The fourth parameter is deprecated, set it to 0. Finally is the thread sleep timer value, just like we set on the server. They do not have to be the same, I just set it to 0 because we are calling Sleep() in the main loop.

And that's how the client connects!

    ~ClientConnection()
{
client->Disconnect(300);
RakNetworkFactory::DestroyRakClientInterface(client);
}

Here is the disconnection code, which runs when the ClientConnection destructs. As you can see, we send it a time of 300 milliseconds to make sure all packets have been sent and received, just like we did when we closed down the server. After that time, the client is disconnected. We then safely destroy the RakClientInterface object to make sure there's no memory leaks.

    void SendLineToServer(s32 x1, s32 y1, s32 x2, s32 y2)
{
RakNet::BitStream dataStream;

dataStream.Write(PACKET_ID_LINE);
dataStream.Write(x1);
dataStream.Write(y1);
dataStream.Write(x2);
dataStream.Write(y2);

client->Send(&dataStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0);
}

Remember our dealings with BitStream in the server program? The client uses them as well to send packets to the server. As you can see, we first add the packet identifier to the stream of bytes we'll be sending, because the first byte is the packet identifier. We then add the line's point data.

When we send the packet, we send it with HIGH_PRIORITY. The priority ordering tells the client interface what order to send packet data. The RELIABLE_ORDERED parameter means we want the data to arrive in exactly the order it was sent, which defeats one of the downsides of UDP. RakNet has internal methods of ordering packets correctly, to avoid receiving them out of order.
 
    void ListenForPackets()
{
Packet * p = client->Receive();

if(p != NULL) {
HandlePacket(p);

client->DeallocatePacket(p);
}
}

Listen for packets is almost exactly like the loop in the server application was. We first try to receive packets from the client connection, and if there are any it will be stored in Packet * p. If there are no packets, then p will be NULL.

If there is a packet to handle, then we will handle it with the function below, and then deallocate it just like the server did. As in the server, it is extremely important that you deallocate packets that have been received.

    void HandlePacket(Packet * p)
{
RakNet::BitStream dataStream((const char*)p->data, p->length, false);
unsigned char packetID;

dataStream.Read(packetID);

switch(packetID) {
case PACKET_ID_LINE:
int x1, y1, x2, y2;

dataStream.Read(x1);
dataStream.Read(y1);
dataStream.Read(x2);
dataStream.Read(y2);

AddLineLocal(x1, y1, x2, y2);
break;
}
}

And here is the HandlePacket function, the last function of the RakNet-Enabled client. As you can see, it's very similar to the server's HandlePacket function.

It creates a BitStream from the packet data, just like the server did when it received a packet. It then checks the packet identifier to see if it's an incoming line. If the packet received is a line, it will add the line to the local list for drawing.

And now you have a complete server and client application. Try it out! Load up an instance of the server and two or three simultaneous clients and start drawing on one of them. You will see that the server relays the lines drawn on one client to all the other clients!

Back To Step 2 | On To Conclusion...


The content, code, and images of this page is copyright (c) Dave Andrews. Any reproduction or redistribution of this material is prohibited without consent from the author.