Game Development Community

A question about using NetInterface...

by Zbynek Novotny · in Torque Game Engine · 03/22/2006 (5:21 am) · 19 replies

Hi everybody,

I'm using the TNL for my project, which deals with distributed virtual environment. At the core is a server and a client (or several clients, to be more specific), and, of course, the purpose of the server is to "host" a scene and keep it in a consistent state while synchronizing the interactions with it from the connected clients.

Currently, the server uses a NetInterface object which handles all the client connections. I have also subclassed the EventConnection and NetEvent classes to provide and customize the required infrastructure for the packet traffic.

My problem is that I'd like the server's net interface to use my subclass of the EventConnection class. I know it works with NetConnections, but I'd like it to create objects of my subclass instead of the generic NetConnection class.

But how do I do it?

I guess the solution is evident, but it somehow eludes me. :(

Thanks for any help.

Cheers, clay

About the author

Recent Threads


#1
03/29/2006 (2:26 pm)
It seemed to work for me. Just inheret and instantiate when connecting.
#2
04/04/2006 (3:07 am)
Thanks for the reply. However, I have another problem I'd like to ask in this thread since it's also related to the NetInterface class (or so I guess).

The problem is that when the client is connecting to the server, the connection gets rejected. From the debugging output, it seems that the client tries to solve the puzzle given to it by the server and sends it back to the server, but the server rejects it, and after another attempt to solve the puzzle and give it to the server, the client's connection remains in the "connection rejected" state, which I suppose means that the server doesn't bother anymore.

Now while I did subclass some of the TNL's classes (the EventConnection on the client side, NetInterface on the server side, and I also have a few subclasses of the NetEvent class as my message classes), there's very little code of my own in the classes that I suppose matter in this instance (in fact I have commented what I could and rolled back to the "factory defaults") in the attempt to pinpoint the culprit of this problem, but so far, all of my efforts have been in vain. :(

So, if anybody could tell me what I should look into or which methods of which classes I should override in my subclasses, I'd be very grateful. TNL seems to be a really handy tool but the documentation (while being a fairly exhaustive reference to the library) could use some additional info on how to actually use the library (or maybe I'm somewhat thick-headed).

Thanks a lot in advance for any responses.

P.S.: If there's any need to, I can post the relevant code.
#3
04/05/2006 (4:45 pm)
Only thing I can think is if you are overwriting the connect request methods and not calling the parent class methods or returning the right value. It might be easier if you did post your connection code.
#4
04/09/2006 (3:26 pm)
Well, ok, here goes... (I'm sorry for this loooong post, I just wanted to provide as much info that might help as possible)

As I said, I have commented out most of the modifications I have made (and removed them here not to clutter up this post), so this is the bare foundation on which I'm going to build.

I have created a NetInterface subclass common for both the server and the client. This class does not override any functions yet, it just defines its own data members and constructors to initialize them (and call the NetInterface ctor)
CommonNetInterface.h
class CommonNetInterface : public TNL::NetInterface
{
        // Attributes
    private:
        MessageQueue    msgQueue;

        // Construction & destruction
    public:
        CommonNetInterface( const TNL::Address &address, MessageQueue &rQueue );
        virtual ~CommonNetInterface();

        // Operations
    public:
        // Nothing yet...
};
I plan to use my Connection class (derived from EventConnection -- using just the EventConnection class seems to break things up with a SIGTRAP signal) on both sides. Its current implementation doesn't have anything worthwhile except for a function that transforms the current connection state into the appropriate text message for easier debugging.
Connection.h
    class Connection : public TNL::EventConnection
    {
            // Attributes
        private:

            // Constructor & destructor
        public:
            Connection();
            virtual ~Connection();

            // Operations
        protected:
            // None yet...

        public:
            std::string  getConnectionStateText();

            TNL_DECLARE_NETCONNECTION( Connection );
    };
In the Connection.cpp file, I call
TNL_IMPLEMENT_NETCONNECTION(Connection, NetClassGroupGame, true);

All the "fun" happens in the threads that actually handle connections. The initConnection(string &serverAddr, const U32 port) initializes the network objects local to the thread like this:
Address bindAddress( IPProtocol, Address::Any, 0 );
serverAddress = new Address( serverAddr.c_str() );
interface = new CommonNetInterface( bindAddress, queue );
interface->setAllowsConnections( true );
interface->setRequiresKeyExchange( false );
connection = new Connection();
The run() method of the thread does this:
U32 ClientConnectionThread::run()
    {
            cout << "Connection thread started..." << endl;
            connection->connect( interface, *serverAddress );
            while( !quitFlag )
            {
                displayConnectionState( connection ); // This just displays the current connection state
                interface->checkIncomingPackets();
                interface->processConnections();
                Platform::sleep(1);
            }
        return 0;
    }
The server's connection handling thread is even simpler. All it does is it first initializes its own object of the CommonNetInterface class like this:
Address bindAddress( IPProtocol, Address::Any, port );
        interface = new commons::CommonNetInterface( bindAddress, queue );  // queue is a reference to the central message queue
        interface->setAllowsConnections( true );
        interface->setRequiresKeyExchange( false );
and then it enters the standard infinite loop
U32 ServerConnectionThread::run()
    {
        cout << "Server connection thread started..." << endl;
        while ( true )
        {
            interface->checkIncomingPackets();
            interface->processConnections();
            Platform::sleep( 1 );
        }
    }

And lastly, the client log output I've been getting:
Quote:
Starting client...
Using address IP:127.0.0.1:5555 to connect to the server...
Starting connection thread...
Connection thread started...
Connection state: AWAITING CHALLENGE RESPONSE
Connection state: COMPUTING PUZZLE SOLUTION
Client puzzle solved in 205 ms.
Connection state: AWAITING CONNECT RESPONSE
Connection state: AWAITING CHALLENGE RESPONSE
Client puzzle solved in 36 ms.
Connection state: AWAITING CONNECT RESPONSE
Connection state: CONNECTION REJECTED

So, as you can see from the code, I'm not doing anything complex that might influence the TNL's connection handshaking process (or at least I hope I don't). Maybe there is something I *should* override in order to get things to work and I'm just overlooking it. I just don't know. So, if you spot anything that I should look into, please let me know.

Thanks a lot in advance and again, sorry for such a long message.

Cheers, Zbynek.
#5
04/09/2006 (5:39 pm)
First, don't use port 0 for the client bind address. That's system only. Try port 25000 or so. See if it works...
#6
04/10/2006 (3:07 pm)
Unfortunately not. I changed the client's listen port to 25554 and the server's to 25555, but it didn't help at all. I also overloaded the processPacket() method of NetInterface in my custom net interface class to display the type of the connection handshake packet received, like this:
void CommonNetInterface::processPacket( const Address &sourceAddress, BitStream *pStream )
    {
        if( !( pStream->getBuffer()[0] & 0x80 ) )
        {
            cout << "Received a connection handshake packet, type " 
                   << displayPacketType( pStream->getBuffer()[0] ) << endl;
        }
        parent::processPacket( sourceAddress, pStream );
    }
(it's an inverted condition from the parent implementation of the function, which I borrowed from the library's sources)
With this addition, I can read from the client log:
Quote:
Starting client...
Using address 127.0.0.1:5555 to connect to the server...
Starting connection thread...
Connection thread started...
Connection state: AWAITING CHALLENGE RESPONSE
Received a connection handshake packet, type CONNECT CHALLENGE RESPONSE
Client puzzle solved in 87 ms.
Connection state: AWAITING CONNECT RESPONSE
Received a connection handshake packet, type CONNECT REJECT
Connection state: AWAITING CHALLENGE RESPONSE
Received a connection handshake packet, type CONNECT CHALLENGE RESPONSE
Client puzzle solved in 91 ms.
Connection state: AWAITING CONNECT RESPONSE
Received a connection handshake packet, type CONNECT REJECT
Connection state: CONNECTION REJECTED
and the server log
Quote:
Port number not given, using default number 5555.
Starting server connection thread...
Server's address is IP:Any:5555
Server connection thread started...
Received a connection handshake packet, type CONNECT CHALLENGE REQUEST
Received a connection handshake packet, type CONNECT REQUEST
Received a connection handshake packet, type CONNECT REQUEST
Received a connection handshake packet, type CONNECT CHALLENGE REQUEST
Received a connection handshake packet, type CONNECT REQUEST
Received a connection handshake packet, type CONNECT REQUEST
If that's any help, I noticed that the handshake process goes fast until it hits the point, at which the client awaits the connect response, which takes significantly longer and it kind of seems that the server doesn't drop back with the connect response (and by looking at thet NetInterfaceConstants I'd say the request times out)... Is there really not anything I should override?
#7
04/10/2006 (4:04 pm)
I don't know. You should be able to just redefine the netinterface and connection without overriding anything and it should work. I can't really see anything wronge in the pieces of code you posted.
#8
04/11/2006 (11:40 am)
Well, looks like I've found the problem, though I fail to grasp the true cause.

I've whipped up the simplest test program I could and then started replacing the generic library class objects with my own described in the previous posts, and after some trial-error attempts, I concluded that the problem lies in this line of code:
TNL_IMPLEMENT_NETCONNECTION(Connection, NetClassGroupGame, true);
When I put this in the .cpp, the client connection will be rejected, however, when I put it in the header file right after the semicolon finishing the Connection class' definition, it all suddenly starts working. The thing is, though, that in an app spanning multiple source files, I end up getting complaints about multiple definitions of whatever this macro expands into.

Any ideas as to why this is causing trouble when placed in the .cpp file but works fine when in the .h file?

Maybe I should have noted that the Connection class is defined inside a namespace, but I made sure to call that macro inside that namespace.
#9
04/11/2006 (1:40 pm)
Doesn't TNL_DECLARE_NETCONNECTION need a second param?
#10
04/11/2006 (2:34 pm)
Well, I have just checked the tnlNetConnection.h file and the TNL_DECLARE_NETCONNECTION macro is defined with only one parameter.
#11
04/11/2006 (3:46 pm)
That's odd. I have all my classes implemented in seperate .cpp files. Multiple defines? You are wrapping your class definitions in #ifndef? Or, you aren't trying to include your .cpp file anywhere are you? Here is the simple example.

chatmessage.h
#ifndef __CHATMESSAGE_H__
#define __CHATMESSAGE_H__

#include "tnl.h"
#include "tnlEventConnection.h"
#include "tnlNetInterface.h"
#include "tnlRPC.h"
#include <stdio.h>

using namespace TNL;

namespace Test {
    class ChatMessageConnection : public EventConnection {
    protected:
       typedef EventConnection Parent;

    public:
        TNL_DECLARE_NETCONNECTION(ChatMessageConnection);

        TNL_DECLARE_RPC(rpcChatMessageToServer, (StringPtr msg));

        TNL_DECLARE_RPC(rpcChatMessageToClient, (StringPtr msg));
    };

}
#endif

chatmessage.cpp
#include "ChatMessage.h"

using namespace TNL;

namespace Test {

    //--------------------------------------------------------------------------
    TNL_IMPLEMENT_NETCONNECTION(ChatMessageConnection, NetClassGroupGame, true);

    //--------------------------------------------------------------------------
    TNL_IMPLEMENT_RPC(ChatMessageConnection, rpcChatMessageToServer, (StringPtr msg), (msg),
        NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirClientToServer, 0)
    {
        // forward message to other clients
        printf("%s\n", msg.getString());
        rpcChatMessageToClient(msg);
    }

    //--------------------------------------------------------------------------
    TNL_IMPLEMENT_RPC(ChatMessageConnection, rpcChatMessageToClient, (StringPtr msg), (msg),
        NetClassGroupGameMask, RPCGuaranteedOrdered, RPCDirServerToClient, 0)
    {
        // received a message
        printf("%s\n", msg.getString());
    }
}
#12
04/13/2006 (2:39 pm)
That's how I have it as well (although I don't use the RPC model). This is the complete code of the Connection class, that's been causing trouble.

Connection.h
#ifndef __CONNECTION_H__
#define __CONNECTION_H__

#include <string>
#include <tnlEventConnection.h>

namespace commons
{
    class Connection : public TNL::EventConnection
    {
        typedef TNL::EventConnection parent;

            // Operations
        public:
            std::string getConnectionStateText();

            TNL_DECLARE_NETCONNECTION( Connection );
    };

};
#endif 	// __CONNECTION_H__

Connection.cpp
#include "Connection.h"

using namespace TNL;
using namespace std;
using namespace commons;

namespace commons
 {
    TNL_IMPLEMENT_NETCONNECTION( Connection, NetClassGroupGame, true );

    string Connection::getConnectionStateText()
    {
        NetConnectionState state = getConnectionState();
        string retVal;
        switch( state )
        {
            case NotConnected:
                retVal = "NOT CONNECTED";
                break;
            case AwaitingChallengeResponse:
                retVal = "AWAITING CHALLENGE RESPONSE";
                break;
            case SendingPunchPackets:
                retVal = "SENDING PUNCH PACKETS";
                break;
            case ComputingPuzzleSolution:
                retVal = "COMPUTING PUZZLE SOLUTION";
                break;
            case AwaitingConnectResponse:
                retVal = "AWAITING CONNECT RESPONSE";
                break;
            case ConnectTimedOut:
                retVal = "CONNECTION TIMED OUT";
                break;
            case ConnectRejected:
                retVal = "CONNECTION REJECTED";
                break;
            case Connected:
                retVal = "CONNECTED";
                break;
            case Disconnected:
                retVal = "DISCONNECTED";
                break;
            case TimedOut:
                retVal = "TIMED OUT";
                break;
            default:
                retVal = "! UNKNOWN STATE VALUE !";
        }
        return retVal;
    }
 };

That's it. Really. I'm starting to suspect the GCC compiler I'm using to compile it... Anyone have any experience with compiling TNL progs using some of the newer versions of GCC (I'm using version 3.4.5-r1). Maybe some flag or something would help.

Perhaps I should also note that this connection class is stored in my own static library, which I link against to build the server and client executables. Although I do simple -L/dir/to/the/lib -llibrary, I don't know if there is any pitfall I should foreclose.
#13
04/22/2006 (10:55 am)
Hello again

so I spent the last week or so trying various things and hacks (of which none helped, unfortunately, so I'm still stuck :( ), so I finally decided to dig into the library itself and put some auxiliary log messages in various places to help me pinpoint the problem.

I put one just after the call to the ClientPuzzleManger::checkSolution in netInterface.cpp to see what error code the manager returns. This is the log of the server:
Quote:
Port number not given, using default number 5555.
Starting server connection thread...
Server's address is IP:Any:5555
Starting scene operating thread...
Server connection thread started...
Received a connection handshake packet, type CONNECT CHALLENGE REQUEST
Received a connection handshake packet, type CONNECT REQUEST
Puzzle result error code = 0
Received a connection handshake packet, type CONNECT REQUEST
Puzzle result error code = 3
Received a connection handshake packet, type CONNECT CHALLENGE REQUEST
Received a connection handshake packet, type CONNECT REQUEST
Puzzle result error code = 0
Received a connection handshake packet, type CONNECT REQUEST
Puzzle result error code = 3
Note: Error code 0 = Success, Error code = 3 equals InvalidClientNonce

I checked the code of ClientPuzzleManager::checkSolution and it seems that it returns the InvalidClientNonce error when the client nonce is already in the nonce table. So, the log seems to indicate that the client connection object somehow re-requests the connection after having successfully identified itself to the server for the first time. The question is -- why is this happening? The code of the Connection class I posted above still stands (and so does the code for the CommonNetInterface class).

Any ideas and comments would be highly appreciated. :)

Cheers, Zbynek.
#14
04/22/2006 (9:32 pm)
I compiled the class you posted in my test app, and it worked fine. I honestly don't know what the problem is. I have tried to reproduce it but haven't been able to. Here is what your class printed out in my app:

Connect Address [<ip | ipx | ipv6>:address:port] : ip:localhost:25000
Test Connection added...
AWAITING CHALLENGE RESPONSE
AWAITING CHALLENGE RESPONSE
AWAITING CONNECT RESPONSE
CONNECTED
CONNECTED
CONNECTED
CONNECTED
CONNECTED
CONNECTED
CONNECTED
CONNECTED
CONNECTED
CONNECTED
#15
04/24/2006 (8:46 am)
Thanks for that. :) I guess I've just found what the problem is, though I can't for the life of me explain or understand why it's acting the way it is. Apparently, the fact that I'm storing the Connection class in a static library and link to it in both my apps causes the connection to simply not work. *shrug*

I even tried to temporarily hack the library (in an attempt to figure the exact cause of the problem) and returned Success even when the puzzle manager failed to add the client nonce to the table, but it didn't work out anyway. It was like the connection didn't accept the server's acknowledgement of a successful connection attempt (unless I missed something else that I screwed up by creating that hack).

Anyway, I just found out that by taking the implementation files of the Connection class out of the static library and putting them in both apps solves this problem. It's really strange but that's how it is. It might be interesting to see if the "connection class in a static library" thing would work for you, Carter. I tried compiling the apps and the lib in Windows using Visual Studio 2005, but with the same result.

Anyway, thanks a lot for your help. I guess I'll have to keep it this way for the time being because I really need to get moving in this project.

Thanks again.
#16
04/24/2006 (2:24 pm)
I'm using codeblocks with the GCC compiler through MinGW. All I have to do is remove the connection files from the target build list and add them to a seperate target. I build that to a library before the origional target. Then tell the origional target to link to it. It works just like above. I don't have any problems.

If you want I can give you the whole project. I origionally used it to test object ghosting, so it has a lot of stuff that isn't related to your problem which might not help the matter.
#17
04/29/2006 (1:02 pm)
Well, if you wouldn't mind, I could use some nice example on ghosting. :)

My email address is znovotny[at]centrum.cz

Thanks a lot for your help.
#18
04/29/2006 (1:05 pm)
Well, if you wouldn't mind, I could use some nice example on ghosting. :)

My email address is znovotny[at]centrum.cz

Thanks a lot for your help.
#19
04/29/2006 (3:01 pm)
Ok, I sent it.