Connection object changing on disconnect, why?

I’ve got my code to the point where I’m shifting the connection data into a mysql database, but I’ve run into something interesting:

When I connect to the server I get 00A4EFB0 as the object memory location, but when I disconnect the server thinks my object was at 00A4ED88, which totally buggers the code I was trying to put in that would track a bunch of things in the database based on the user’s connection object.

When a user connects it adds their connection object, ip address, and connection time to a table called client_sessions:

In the image above you can see that it recorded my connection. So now I’m trying to delete that row when I disconnect, but the server thinks I have a different connection object when disconnecting which makes this impossible.

Here’s the relevant codebits:

Listening for client connections

    def tskListenerPolling(self, taskdata):
        
        if self.cListener.newConnectionAvailable():
            rendezvous = PointerToConnection()
            netAddress = NetAddress()
            newConnection = PointerToConnection()
            if self.cListener.getNewConnection(rendezvous,netAddress,newConnection):
                newConnection = newConnection.p()
                self.activeConnections.append(newConnection) # Remember connection
                self.cReader.addConnection(newConnection)     # Begin reading connection
                print str(newConnection) + " connected from " + str(newConnection.getAddress())
                self.activeConnectionsCounter += 1
                
                query = """INSERT INTO client_sessions(ipaddress, connection) VALUES ('"""
                query += str(newConnection.getAddress()) + """', '""" 
                query += str(newConnection) + """')"""       
                self.mysqlInsertQuery(query)
                
                
        return Task.cont

Disconnect code:

    def taskCheckConnections(self, task):
        if(self.cManager.resetConnectionAvailable()):
            connectionPointer = PointerToConnection()
            self.cManager.getResetConnection(connectionPointer)
            lostConnection = connectionPointer.p()

            print str(lostConnection) + " disconnected"
            
            query = "DELETE FROM `client_sessions` WHERE (`connection`='" + str(lostConnection) + "')"
            print "\n" + str(query)   
            self.mysqlDeleteQuery(query)
            self.cManager.closeConnection(lostConnection)
            self.activeConnections.remove(lostConnection)
            
            self.activeConnectionsCounter -= 1
            #print "Current connections: " + str(self.activeConnections)
            for i in self.activeConnections:
                print i
        return Task.again

The mysqlInsertQuery and mysqlDeleteQuery functions do exactly as they say. Essentially they both execute a sql query and then commit it. I’ve tested both functions and they work with no issue.

The output of the print statements in these two tasks is shown in the first screenshot at the top of this post.

Any ideas on how to correct this so I can actually delete the connection out of the database?

Basically what I’m trying to do is have some kind of identifier on a given user, that I can use for various database functions and delete it when they disconnect. I can’t seem to do that with the connection object, so I need a way of linking a connection object to something like an integer.

I can’t use their IP address because they need to be able to establish multiple connections from the same IP address.

Hi, I’ve never been any good at the ConnectionListener stuff, however I can see a simple fix.

Use the (string) tuple of the back address ex: ‘(‘192.168.2.1’, 5000)’

your socket code (somewhere) should return the TCP port on which to send data BACK to the connected client, and you can use that to identify separate clients under the same ip address

the back address is assigned automatically via the OS (I think) and is specific to the connected client, for instance:

client1 connects from google.com to your server port 5000
client2 connects from google.com to your server port 5000

client1 has a back port of 3461
where
client2 has a back port of 3462

the routers/OS inbetween your code should handle all of that on their own… you just have to find out how to get the back port of the connected client within your code, or it should work like this anyway.

I am probably being of no use here, as I have no idea how to find it with ConnectionListener, I’ll look around and post back if I find anything, at least now you get the idea of how to identify multiple clients under the same IP

It would be great if you could point me in the right direction for that.

I tried looking at connection.getSocket, but that returns the memory location of the socket, not the actual address and I have no idea how to get it.

Apologies about the double post, I’ve no idea what happened there… must be firefox messed up or I clicked twice or something :unamused:

The relevant API’s are found in the python reference here:
https://www.panda3d.org/reference/python/class!panda3d.core.Connection

So instead of:
connection.getSocket()

try using:
connection.getAddress()

I have no real code to test this on, so let me know if this works.

That returns the IP address. But that doesn’t help if they connect twice from the same machine as both connections would have the same IP’s tied to them.

I’ve connected twice to my server from my local machine and both connections get 127.0.0.1 as the IP address, I can’t find any way to distinguish them.

Pain in the butt lol. Any other ideas?

I appreciate the help, I just wish there was some obvious answer lol

Hi, I am running out of idea’s (I lack knowledge about the panda3d networking interface, I know tons about python’s socket interface, panda’s is a mystery to me…)

Ok a few things:
I would think that it would return the IP and port of the connection… considering it’s getAddress instead of getIp or something similar… perhaps a design flaw

Perhaps you can use getSocket as you where saying before, and see if the str(value) of that changes upon connect-disconnect.

The reason the object’s memory location is changing is because they are separate instances of PointerToConnection()
So instead, try using connection.getSocket() and storing that as the identifier and (my hope is that) it should stay the same as the socket instance is the same between those upon connect-disconnect

Lemmme know if it works,

~powerpup118

Hi, I am running out of idea’s (I lack knowledge about the panda3d networking interface, I know tons about python’s socket interface, panda’s is a mystery to me…)

Ok a few things:
I would think that it would return the IP and port of the connection… considering it’s getAddress instead of getIp or something similar… perhaps a design flaw

Perhaps you can use getSocket as you where saying before, and see if the str(value) of that changes upon connect-disconnect.

The reason the object’s memory location is changing is because they are separate instances of PointerToConnection()
So instead, try using connection.getSocket() and storing that as the identifier and (my hope is that) it should stay the same as the socket instance is the same between those upon connect-disconnect

Lemmme know if it works,

~powerpup118

Tried what you suggested and this is the result:

I connected to the server from 2 clients on the same computer.

Each time a client connects or disconnects the server prints out the connection, the connection.netAddress(), and the connection.getSocket().

The socket for both connections is identical (0x00A4E278) :frowning:

However the socket appears to change when the connection drops, you can see the 2nd connection becomes 0x00A4E8C0 when it disconnects.

Another interesting thing is that Panda doesn’t appear to retain the IP address once somebody has disconnected. The IP’s for each connection suddenly become 0.0.0.0 when they disconnect.

Here’s the current code:

SERVER - Close Connections

    def taskCheckConnections(self, task):
        if(self.cManager.resetConnectionAvailable()):
            connectionPointer = PointerToConnection()
            self.cManager.getResetConnection(connectionPointer)
            lostConnection = connectionPointer.p()
            
            print "\n" + str(lostConnection) + " disconnected from " + str(lostConnection.getAddress()) + " on socket: " + str(lostConnection.getSocket()) + "\n"
            self.cManager.closeConnection(lostConnection)         
                        
            self.activeConnectionsCounter -= 1

        return Task.again

SERVER - Accept Connections

    def tskListenerPolling(self, taskdata):
        
        if self.cListener.newConnectionAvailable():
            rendezvous = PointerToConnection()
            netAddress = NetAddress()
            newConnection = PointerToConnection()
            if self.cListener.getNewConnection(rendezvous,netAddress,newConnection):

                newConnection = newConnection.p()
                self.activeConnections.append(newConnection) 
                self.cReader.addConnection(newConnection)    
                print "\n" + str(newConnection) + " connected from " + str(newConnection.getAddress()) + " on socket: " + str(newConnection.getSocket()) + "\n"
                self.activeConnectionsCounter += 1

Now for the connection and the getSocket it’s printing out the memory location. Is there a way to get the actual value stored? I’m thinking if I can get the actual value of the socket instead of the pointer to the memory location I can probably use that.

The real problem here is that you are trying to use str(connection) as a unique identifier. But the address that prints in the str() is the address of the Python wrapper, not the address of the underlying C++ object.

Instead of using str(connection), just use connection.this. This is the address of the underlying C++ object, and it won’t change from one reference to the next.

David

That works, woohoo! Thanks so much drwr, that’s been driving me nuts lol.

Next question guys - I was thinking about this all day and was thinking it would be better to build the server in just standard python code without the Panda libraries. The client software needs Panda, but I’m not sure if the Server would actually need all that stuff (it’s not like a server is rendering 3d objects to the screen, etc).

Would it be safe to assume that running the server without DirectObject and the other panda-specific libraries would be a hellova lot more resource friendly?

Or am I wrong in thinking that and would benefit a lot from using the panda libraries in the Server code?

  • Sorry that I was so little help on the above, I wish I knew more about the ConnectionListener stuffs. :slight_smile:
  • I should think that building the server in just python (and using panda on the clients) is certainly do-able.

Are you referring to installation of panda? or actual system resources? You can disable rendering to a window (stops the system resources problem) by putting this in your code before import DirectStart:

from pandac.PandaModules import ConfigVariableString
ConfigVariableString("window-type","none").setValue("none")
import direct.directbase.DirectStart #- only after import

You can probably (depending on the case) also use panda for certain stuff on your server (AI positions, for instance)

Do note that it is possible to just use python’s socket interface in both the client and server (in fact I’m doing this, I have major reasons for this however, personal opinions anywho :wink: )
[/code]
[/quote]

System resources mostly. That code is useful, but it makes the program take more resources.

When I run the server with the window enabled, it sucks up 45% of my CPU and about 65mb of RAM. When I disbale the window with the code you provided, it sucks up over 80mb of RAM.

Why does disabling the window take up an extra 15mb of RAM? Shouldn’t it consume less if it’s not drawing the DirectStart object window?

I don’t know why it uses more RAM without rendering to the window; one possibility is that you are loading a lot of models with big textures. The first time you render a texture, its memory image is copied from RAM to graphics memory, and then removed from RAM. So if you never render a texture, its memory image will remain in RAM.

If that’s what’s causing the difference here, you can arrange to never load the texture images in the first place with:

preload-textures 0

in your Config.prc file.

David

The code isn’t loading textures or anything. All it does is open a Connection Manager. I think it’s the DirectObject class.

I tried runninng a simply python script and it only took up a few mb of RAM. Then I tried plugging in the DirectObject class and it suddenly sucked up a huge amount of memory.

Is there a way to get a task manager running without using DirectObject?

That’s probably just the Panda3D libraries being pulled in. It’s possible to build a version of Panda that’s more modular so that you can run the task manager without pulling in the entire set of Panda3D libraries, though we haven’t spent a lot of effort doing this recently.

Unless you have issues with your virtual memory management system, though (and some virtualization environments do have trouble with virtual memory, unfortunately), it really shouldn’t be an issue how much memory your process consumes, since the unused code will never get paged in from disk in the first place. It’s just a number that doesn’t have much to do with actual resource consumption.

David

Alright, thanks for the input :slight_smile:

Here’s a brief follow-up in case anybody is in the same position as me.

When you open the connection manager, add this:

self.Connections = {}

That creates an empty library (associative array in python).

Then when somebody connects to your server, use this code for your listen task:

        if self.cListener.newConnectionAvailable():
            print "CONNECTION"
            rendezvous = PointerToConnection()
            netAddress = NetAddress()
            newConnection = PointerToConnection()
            if self.cListener.getNewConnection(rendezvous,netAddress,newConnection):
                newConnection = newConnection.p()
                self.activeConnections.append(newConnection)
                self.Connections[str(newConnection.this)] = rendezvous
                self.cReader.addConnection(newConnection)   
                print "\nSOMEBODY CONNECTED"
                print "Connection: " + str(newConnection)
                print "IP Address: " + str(newConnection.getAddress())
                print "Socket: " + str(newConnection.getSocket())
                print "Connection ID: " + str(newConnection.this)
                print "\n"             
        return Task.cont  

You can skip all the print commands if you want, I put that in for debugging purposes. The only difference in this code from what the manual shows you is that I add an element to the self.Connections library in the form of connection.this => rendezvous (which is the pointer to the connection).

What that does is give you an array that links the connection.this value (which does not change at all during a client connection) to the connection pointer.

Then here’s the code for disconnecting:

    def ConnectionManagerTASK_Check_For_Dropped_Connections(self, task):
        if(self.cManager.resetConnectionAvailable()):
            connectionPointer = PointerToConnection()
            self.cManager.getResetConnection(connectionPointer)
            lostConnection = connectionPointer.p()
            print "\nSOMEBODY DISCONNECTED"
            print "Connection: " + str(lostConnection) 
            print "IP Address: " + str(lostConnection.getAddress())
            print "Socket: " + str(lostConnection.getSocket())
            print "ConnectionID: " + str(lostConnection.this)
            print "\n"
            del self.Connections[str(lostConnection.this)]
            self.cManager.closeConnection(lostConnection)
        return Task.cont

Again, you can ignore the print commands. I put it there for debugging purposes. The only difference from the manual is that it deletes the element from the self.Connections library.

Viola, you now have an associate array with your connections and a unique identifier for everybody connected to your server.

Now you can just take the connection.this integer and plug it into your database so that it maps the connection.this value to the client account in your database.

The last step is to send a message to your specific client connection, which is what all this code was for.

When your server needs to send a message, pull the connection.this integer from your database. So lets say you want to broadcast a message to everybody in your game that is in a specific zone, first construct the datagram:

                myResponse = PyDatagram()
                myResponse.addString("DIE PIRATE SCUM!")

Then run your mysql query to identify which players are in the zone, i.e.: “SELECT connectionID FROM client_connections WHERE current_zone=‘4’”

Then for each client return, you run this command:

                self.cWriter.send(myResponse, self.Connections[row[0]])

That will end out a datagram with a string “DIE PIRATE SCUM” to every client that is connected and currently in zone #4.

I’m going to post the entire code for this in the code snippets section shortly, just fixing some minor issues first.