News Project Frequently Asked Questions Documentation Downloads Mailing Lists How to Contribute Logo

Changing the way games are made and played.

Simple Torque Network Library Tutorial: Hello World

Simple Torque Network Library Tutorial: Hello World

This simple tutorial walks the user through the creation of a very simple networked example program using the RPC functionality of the EventConnection class. This command-line program can be run in either client or server modes. As a client it will attempt to connect to a server at the specified address and send it a message every second. When the server receives a message from a client, it will respond back with a "Hello, World!" message.

See Also RPC in the Torque Network Library for a more complete description of RPC in the Torque Network Library.

First, create a new source file, "simpleNet.cpp", containing the following lines to start:

#include "tnl.h" #include "tnlEventConnection.h" #include "tnlNetInterface.h" #include "tnlRPC.h" #include <stdio.h> bool gQuit = false; // a flag used when the client wants to quit. using namespace TNL; // make sure we can simply use the TNL classes.

If the compiler complains of missing include files, be sure the "tnl" directory is in the include path.

Next, create a custom subclass of EventConnection for the example program to communicate across, and declare the client-to-server and server-to-client RPC methods:

class SimpleEventConnection : public EventConnection { typedef EventConnection Parent; public: // Let the network system know this is a valid network connection. TNL_DECLARE_NETCONNECTION(SimpleEventConnection); // declare the client to server message TNL_DECLARE_RPC(rpcMessageClientToServer, (const char *theMessageString)); // declare the server to client message TNL_DECLARE_RPC(rpcMessageServerToClient, (const char *theMessageString)); };

The first macro invoked, TNL_DECLARE_NETCONNECTION declares members necessary to make this class work as a connection in TNL. The RPC methods are then declared using the TNL_DECLARE_RPC macro. The first parameter to the macro is the name of the RPC method, which can be invoked like any other member function on the object. The second argument is a parenthesized argument list to the RPC. Although these functions are both declared to take only a single string, RPC method parameter lists can contain all the basic TNL types, as well as StringTableEntry, ByteBuffer and Vector objects.

Next, define the implementation of the SimpleEventConnection class:

TNL_IMPLEMENT_NETCONNECTION(SimpleEventConnection, NetClassGroupGame, true); TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcMessageClientToServer, (const char *messageString), NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirClientToServer, 0) { // display the message the client sent printf("Got message from client: %s\n", messageString); // send a hello world back to the client. rpcMessageServerToClient("Hello, World!"); } TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcMessageServerToClient, (const char *messageString), NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirServerToClient, 0) { // display the message the server sent printf("Got a message from server: %s\n", messageString); // once the client has heard back from the server, it should quit. gQuit = true; }

The TNL_IMPLEMENT_NETCONNECTION macro specifies two properties. The first, NetClassGroupGame declares that this connection traffics only in NetEvent, NetObject and RPC instances that have the NetClassGroupGame mask bit set in their implementations. The second property, as declared by the boolean true specifies that remote hosts can request a connection of type SimpleEventConnection from this process.

The TNL_IMPLEMENT_RPC macro behaves like a standard function signature declaration, but with some important additions. The first three arguments, class name, method name and arguments are fairly self-explanatory. The fourth argument is the connection type mask, which specifies which types of connections the specified RPC can travel over. Since RPC's (unlike NetEvent and NetObject subclasses) are declared for only a single connection class, this mask should always be the mask associated with the connection type declared in TNL_IMPLEMENT_NETCONNECTION.

The fifth argument to the macro specifies the guarantee type of the RPC, which can be one of RPCGuaranteedOrdered, RPCGuaranteed or RPCUnguaranteed. The sixth parameter is the direction the RPC is allowed to travel, which when specified can make an application more secure from attacks that modify packets in transit. The last argument is a version number, which can be specified so that older clients can still connect to servers offering newer RPCs.

The function body of the RPC similar to any other function body with one important difference. When an RPC method is invoked, the function body code is executed on the remote side of the connection. So if the client calls the method rpcMessageClientToServer("Hello?"), the code for the body will execute on the server.

Now that the connection class is defined, all that is left to do is create the main() function that will start up the instance as either a client or a server.

int main(int argc, const char **argv) { if(argc != 3) { printf("usage: simpletnltest <-server|-client> <connectAddress>"); return 1; } bool isClient = !strcmp(argv[1], "-client"); // convert the command-line address into TNL address form Address cmdAddress(argv[2]); RefPtr<NetInterface> theNetInterface; if(isClient) { Address bindAddress(IPProtocol, Address::Any, 0); // create a new NetInterface bound to any interface, any port (0) theNetInterface = new NetInterface(bindAddress); // create a new SimpleEventConnection and tell it to connect to the // server at cmdAddress. SimpleEventConnection *newConnection = new SimpleEventConnection; newConnection->connect(theNetInterface, cmdAddress); // post an RPC, to be executed when the connection is established newConnection->rpcMessageClientToServer("Hello??"); } else { // create a server net interface, bound to the cmdAddress theNetInterface = new NetInterface(cmdAddress); // notify the NetInterface that it can allow connections theNetInterface->setAllowsConnections(true); } // now just loop, processing incoming packets and sending outgoing packets // until the global quit flag is set. while(!gQuit) { theNetInterface->checkIncomingPackets(); theNetInterface->processConnections(); Platform::sleep(1); } return 0; }

Once the code is written, use your compiler to build simpletnltest, making sure to link in the tnl library, along with whatever network libraries your platform requires (wsock32.lib on Windows, for example).

Also, make sure Run-Time Type Checking is turned ON for your compiler or the program will not function correctly (option /GR in MSVC, for example).

To test the program, open up two console shells, and in the first, type:

simpletnltest -server

This will create an instance of simpletnltest running as a server and bound to port 28000 on the local host machine. In the second window, run another instance as a client, set to connect to the server like this:

simpletnltest -client

Once the client runs, the server display should read:

Got a message from client: Hello?

and the client window should read:

Got a message from server: Hello, World!

The server will continue to accept new connections from clients until it is terminated manually.

For an example program that uses more of TNL's advanced features, including RPCs with bit compressed arguments, a custom NetInterface subclass, and the NetObject replication system, look at the TNLTest project and documentation.