by Brent Rossen Using .net 2003
This tutorial is based on the SimpleEventConnection tutorial at OpenTNL.org, much of the code is identical. A few corrections
and improvements have been made. No Ogre specific calls are made in this file to keep the code as general as possible.
Please, use this forum thread (http://www.ogre3d.org/phpBB2/viewtopic.php?p=156411) to discuss problems, suggestions, etc.
INSTALLATION & USAGE:
Download OpenTNL from: http://www.opentnl.org/download.php (TNL code snapshots), unzip tnl.zip into your Project folder
(should unzip into a folder called "tnl"). Open the tnl solution (tnl/tnl.sln) and compile in both debug and then release mode,
so you have both.
Visual Studio Sections for Linking and Setup
Linker - Input - Additional Dependencies: libtomcryptd.lib tnld.lib wsock32.lib (libtomcrypt.lib and tnl.lib for release mode)
Linker - Input - Ignore Specific Libraries: LIBCD.lib (LIBC.lib for release), this may not be necessary for all applications,
will give compiler error if it is
Linker - General - Additional Library Directories: tnl/lib
C++ - Language - Enable Runtime Type Info - Yes (/GR)
A Few Notes
1) Make sure the interface for SimpleEventConnection is identical on both the server and client
Both must have all methods implemented, but only the client needs to actually perform client
functions and only the server needs server functions, they just both need to call the implement
macro for all methods
2) Enter the correct cmdAddress and port in the startXConnection() method (127.0.0.1:28001 is for running locally,
any other unused port works too)
3) Call startServerConnection() or startClientConnection() to get things started, the server should be started first
4) Call checkMessages() regularly to keep messages going back and forth smoothly
5) All "TODO:" notes are marked as things you should do and depend on your implementation
6) Please note that the server must call the broadcast functions, or find the appropriate
SimpleEventConnection as is done in the broadcast functions to call the client rpc methods
Put this in server.bat file to run in console. Close console window when finished.
SimpleNetTest.exe -server
Put this in client.bat file to run in console. It will close when done running. Navigate to project folder in a console window (Run>"cmd") and type the same if you want the window to stay open.
SimpleNetTest.exe -client
You should receive "Hello Clients (Server)!" back and forth as well as SetAnimation, PlayAudio, and SetObjectPosition calls
A Brief Explanation of the Original Use:
I am a Graduate Researcher at the University of Florida. We are working on an Interpersonal Simulator using ogre and have
a separation between the "Brain" of the on screen avatar and the "Frontend" (OGRE, FMOD, etc). So, the cleanest
way to maintain this separation was to have them communicate through network protocol. That way, if we want to
run the brains and renderer on different machines, or possibly have multiple rendering systems, we have that option.
OpenTNL was my choice for this. This setup is not intended to scale into an MMO! You need to thoroughly review
the OpenTNL documentation for a project of that magnitude. I wish you luck with OGRE and OpenTNL!
SimpleNet.h
#include "../tnl/tnl.h" #include "../tnl/tnlEventConnection.h" #include "../tnl/tnlNetInterface.h" #include "../tnl/tnlRPC.h" #include <stdio.h> #include <string> using namespace TNL; // make sure we can simply use the TNL classes. bool gQuit = false; // a flag used when the client wants to quit. class SimpleEventConnection : public EventConnection { typedef EventConnection Parent; private: RefPtr<NetInterface> theNetInterface; int messageLimit; //built in limit so the client will quit after 14 messages public: SimpleEventConnection(); // 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, (StringPtr messageString)); // declare the server to client message TNL_DECLARE_RPC(rpcMessageServerToClient, (StringPtr theMessageString)); // declare the backend to frontend methods TNL_DECLARE_RPC(rpcSetAnimation, (StringPtr animation)); TNL_DECLARE_RPC(rpcPlayAudio, (StringPtr audioFileName)); TNL_DECLARE_RPC(rpcSetObjectPosition, (StringPtr name, float x, float y, float z));//items or cameras void confirmMessageReceipt(const char * message); void checkMessages(); void startClientConnection(); void startServerConnection(); //server calls void rpcBroadcastMessageClients(StringPtr message); void rpcBroadcastSetAnimation(StringPtr animation); void rpcBroadcastPlayAudio(StringPtr audioFileName); void rpcBroadcastSetObjectPosition(StringPtr name, float x, float y, float z); }; SimpleEventConnection::SimpleEventConnection(){ messageLimit = 18; } //SimpleEventConnection implementations TNL_IMPLEMENT_NETCONNECTION(SimpleEventConnection, NetClassGroupGame, true); /** Client to server message Note, a ServerToClient method can be called within this ClientToServer method. The rpcMessageServerToClient will be executed in the server application, and will automatically be routed to the client connection who called it. To reiterate, the client who called rpcMessageClientToServer will have rpcMessageServerToClient called on it, that method will execute on the client side. This can be a bit confusing at first, but you will get used to it. **/ TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcMessageClientToServer, (StringPtr messageString), (messageString), NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirClientToServer, 0) { // display the message the client sent printf("Got message from client: "); printf(messageString); } /** The server to client messages confirmMessageReceipt is used so that you only need to turn it off in one place All of these server to client rpc functions have a corresponding broadcast function it is important that you understand why the broadcast is used. If you tried calling these directly from the newConnection object you would get an error because you need to know which client to send the message to! **/ TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcMessageServerToClient, (StringPtr messageString), (messageString), NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirServerToClient, 0) { //confirm receipt of server call std::string str = "Messsage To Client Received: "; str += messageString.getString(); str += "\n"; confirmMessageReceipt(str.c_str()); messageLimit--; if(messageLimit <= 0){ rpcMessageClientToServer("Disconnecting. Good bye!\n"); gQuit = true; } } TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcSetAnimation, (StringPtr animation), (animation), NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirServerToClient, 0) { //confirm receipt std::string str = "Animation Request Received: "; str += animation.getString(); str += "\n"; confirmMessageReceipt(str.c_str()); //TODO: play animation } TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcPlayAudio, (StringPtr audioFileName), (audioFileName), NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirServerToClient, 0) { //confirm receipt std::string str = "Audio Request Received: "; str += audioFileName.getString(); str += "\n"; confirmMessageReceipt(str.c_str()); //TODO: play audio } TNL_IMPLEMENT_RPC(SimpleEventConnection, rpcSetObjectPosition, (StringPtr objectName, float x, float y, float z), (objectName, x, y, z), NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirServerToClient, 0) { //confirm receipt std::string str = "Position Set Received For: "; str += objectName.getString(); str += "\n"; confirmMessageReceipt(str.c_str()); //TODO: move objects } void SimpleEventConnection::confirmMessageReceipt(const char * message){ // display the message the server sent printf(message); //TODO: comment this out when you no longer want message confirmations rpcMessageClientToServer(message); } void SimpleEventConnection::checkMessages(){ theNetInterface->checkIncomingPackets(); theNetInterface->processConnections(); } void SimpleEventConnection::startClientConnection(){ Address cmdAddress("127.0.0.1:28001"); Address bindAddress(IPProtocol, Address::Any, 0); // create a new NetInterface bound to any interface and any port theNetInterface = new NetInterface(bindAddress); // create a new SimpleEventConnection and tell it to connect to the // server at cmdAddress. connect(theNetInterface, cmdAddress); // post an RPC, to be executed when the connection is established rpcMessageClientToServer("Client Connection Requested"); } void SimpleEventConnection::startServerConnection(){ Address cmdAddress("127.0.0.1:28001"); // create a server net interface, bound to the cmdAddress theNetInterface = new NetInterface(cmdAddress); // notify the NetInterface that it can allow connections theNetInterface->setAllowsConnections(true); } //broadcast functions send the message to all n registered clients void SimpleEventConnection::rpcBroadcastMessageClients(StringPtr message){ TNL::Vector<TNL::NetConnection* > con_list = theNetInterface->getConnectionList(); for(int i = 0; i < con_list.size(); i++){ SimpleEventConnection *con = (SimpleEventConnection *)con_list[i]; con->rpcMessageServerToClient(message); } } void SimpleEventConnection::rpcBroadcastSetAnimation(StringPtr animation){ TNL::Vector<TNL::NetConnection* > con_list = theNetInterface->getConnectionList(); for(int i = 0; i < con_list.size(); i++){ SimpleEventConnection *con = (SimpleEventConnection *)con_list[i]; con->rpcSetAnimation(animation); } } void SimpleEventConnection::rpcBroadcastPlayAudio(StringPtr audioFileName){ TNL::Vector<TNL::NetConnection* > con_list = theNetInterface->getConnectionList(); for(int i = 0; i < con_list.size(); i++){ SimpleEventConnection *con = (SimpleEventConnection *)con_list[i]; con->rpcPlayAudio(audioFileName); } } void SimpleEventConnection::rpcBroadcastSetObjectPosition(StringPtr name, float x, float y, float z){ TNL::Vector<TNL::NetConnection* > con_list = theNetInterface->getConnectionList(); for(int i = 0; i < con_list.size(); i++){ SimpleEventConnection *con = (SimpleEventConnection *)con_list[i]; con->rpcSetObjectPosition(name, x, y, z); } } //runs either the server or client code depending on your call to argv int main(int argc, const char **argv) { if(argc != 3) { printf("usage: simpletnltest <-server|-client>"); return 1; } bool isClient = !strcmp(argv[1], "-client");//check to see if we want to start a client or server SimpleEventConnection *newConnection = new SimpleEventConnection; if(isClient) { //This is your client protocol and should be run in your client application newConnection->startClientConnection(); // post an RPC, to be executed when the connection is established newConnection->rpcMessageClientToServer("Client Connection Requested"); } else { //This is your server protocol and should be run in your server application newConnection->startServerConnection(); } // now just loop, processing incoming packets and sending outgoing packets // until the global quit flag is set. while(!gQuit) { if(isClient){ newConnection->rpcMessageClientToServer("Hello Server!\n"); } else{ newConnection->rpcBroadcastMessageClients("Hello Clients!"); newConnection->rpcBroadcastPlayAudio("audio.ogg"); newConnection->rpcBroadcastSetAnimation("Dance"); newConnection->rpcBroadcastSetObjectPosition("ogreHead", 0, 200, -300); } //if you use SimpleEventConnection newConnection->checkMessages(); Platform::sleep(10); //you may not need to sleep if you have a place to call check messages regularly, //such as each frame } return 0; }