Server issue when having more than 2 clients.[URGENT]

Hello I am having an issue with the server when i connect more than 2 clients…

#SERVER.PY
from pandac.PandaModules import *
import direct.directbase.DirectStart
from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.PyDatagramIterator import PyDatagramIterator
from direct.showbase.DirectObject import DirectObject
from direct.gui.DirectGui import *
from direct.task import Task
import sys
import player
import random

######################################3
##
## Config
##

PORT = 7198

######################################3
##
## Defines
##
## Quote Yellow: This are server opcodes. It tells the server
## or client what pkt it is receiving. Ie if the pkt starts
## with 3, the server knows he has to deal with a chat msg


MSG_NONE                   = 0
C_COORDINATE_SEND          = 1
S_COORDINATE_SEND		   = 2
C_DISCONNECT_REQ           = 3
S_NEWPLAYER_SEND		   = 4



###################################################################
##    
## Creating a dictionary for the clients. Thats how we can adress
## them later on. For now they are adressed by their IP, but that
## isn't the best solution. What if 2 clients have the same IP?
## You can find of course any other working solution that will avoid
## this. Feel free to distribute it! As for the Demo its OK as is.
##

PLAYERS = {}

class Server(DirectObject):

    def __init__(self):
        
       ## If you press Escape @ the server window, the server will quit.
        self.accept("escape", self.quit)
        self.lastConnection = None
        
        # Create network layer objects

        # Deals with the basic network stuff
        self.cManager = QueuedConnectionManager()
        
        # Listens for new connections and queue's them
        self.cListener = QueuedConnectionListener(self.cManager, 0)

        # Reads data send to the server
        self.cReader = QueuedConnectionReader(self.cManager, 0)

        # Writes / sends data to the client
        self.cWriter = ConnectionWriter(self.cManager,0)

        # open a server socket on the given port. Args: (port,timeout)
        self.tcpSocket = self.cManager.openTCPServerRendezvous(PORT, 1)

        # Tell the listener to listen for new connections on this socket
        self.cListener.addConnection(self.tcpSocket)

        # Start Listener task 
        taskMgr.add(self.listenTask, "serverListenTask",-40)

        # Start Read task
        taskMgr.add(self.readTask, "serverReadTask", -39)
        
        taskMgr.add(self.repeatTask, 'repeatTask',-38)
        
    def repeatTask(self,task):
        for key,p in PLAYERS.items():
            self.ProcessPlayer(p)
        return Task.cont
        
    def listenTask(self, task):
        """
        Accept new incoming connections from the client
        """
        # Run this task after the dataLoop
        
        # If there's a new connection Handle it
        if self.cListener.newConnectionAvailable():
            rendezvous = PointerToConnection()
            netAddress = NetAddress()
            newConnection = PointerToConnection()
            
            if self.cListener.getNewConnection(rendezvous,netAddress,newConnection):
                newConnection = newConnection.p()
                # tell the Reader that there's a new connection to read from
                self.cReader.addConnection(newConnection)
                p = player.Player()
                p.ip = netAddress.getIpString()
                p.client = newConnection
                p.name = str(random.randint(0,5000))
                p.playerId = random.randint(0,50000)
                for key,p2 in PLAYERS.items():
					while p2.playerId == p.playerId:
						p.playerId = random.randint(0,50000)
                print p.playerId
                PLAYERS[p.client] = p
                self.lastConnection = newConnection
                print "Got a connection!"
            else:
                print "getNewConnection returned false"
        return Task.cont

    def readTask(self, task):
        """
        If there's any data received from the clients,
        get it and send it to the handlers.
        """
        while 1:
            (datagram, data, msgID) = self.nonBlockingRead(self.cReader)
            if msgID is MSG_NONE:
                # got nothing todo
                break
            else:
                # Got a datagram, handle it
                self.handleDatagram(data, msgID,datagram.getConnection())
                
        return Task.cont

    def nonBlockingRead(self,qcr):
        """
        Return a datagram iterator and type if data is available on the
        queued connection reader
        """
        if self.cReader.dataAvailable():
            datagram = NetDatagram()
            if self.cReader.getData(datagram):
                data = PyDatagramIterator(datagram)
                msgID = data.getUint16()
            else:
                data = None
                msgID = MSG_NONE
        else:
            datagram = None
            data = None
            msgID = MSG_NONE
        # Note, return datagram to keep a handle on the data
        return (datagram, data, msgID)

    def handleDatagram(self, data, msgID, client):
        """
        Check if there's a handler assigned for this msgID.
        Since we dont have case statements in python,
        I'm using a dictionary to avoid endless elif statements.
        """
   
   ########################################################
   ##
   ## Of course you can use as an alternative smth like this:
   ## if msgID == CMSG_AUTH: self.msgAuth(msgID, data, client)
   ## elif...
   
        if msgID in Handlers.keys():
            Handlers[msgID](msgID,data,client)
        else:
            print "Unknown msgID: %d" % msgID
            print data
        return

    def receiveCoordinates(self, msgID, data, client):
    
    #########################################################
    ##
    ## Okay... The client sent us some Data. We need to extract
    ## the data the same way it was placed into the buffer.
    ## Its like "first in, first out"
    ##
		p = PLAYERS[client]
		p.x = data.getFloat64()
		p.y = data.getFloat64()
		p.z = data.getFloat64()
		print "ip: %s" %p.ip
		print "X: %s" %p.x
		print "Y: %s" %p.y
		print "Z: %s" %p.z
        
   ## Now that we have the username and pwd, we need to
   ## determine if the client has the right user/pwd-combination.
   ## this variable will be sent later to the client, so lets
   ## create/define it here.
		
    '''    
        ## Creating a buffer to hold the data that is going to be sent.
			pkg = PyDatagram()
   
   ## The first Bytes we send to the client in that paket will be
   ## the ones that classify them as what they are. Here they mean
   ## "Hi Client! I am an Auth Response from the server."
   ## How does the client know that? Well, because both have a
   ## definition saying "SMSG_AUTH_RESPONSE  = 2"
   ## Due to shorter Network Pakets Yellow used Numbers instead
   ## of the whole Name. So you will see a 2 in the paket if you
   ## catch it somewhere...
			pkg.addUint16(SMSG_AUTH_RESPONSE)
        
   ## Now we are sending, if the auth was
   ## successfull ("1") or not ("0" or "2")
			pkg.addUint32(flag)

   ## Now lets send the whole story...
			self.cWriter.send(pkg,client)
	'''

   
    def msgDisconnectReq(self, msgID, data, client):
        pkg = PyDatagram()
        pkg.addUint16(SMSG_DISCONNECT_ACK)
        self.cWriter.send(pkg,client)
        for key, p in PLAYERS.items():
            if(PLAYERS[client].playerId in p.playersNearby):
                p.playersNearby.remove(PLAYERS[client].playerId)
        del PLAYERS[client]
        self.cReader.removeConnection(client)

    def quit(self):
		
        self.cManager.closeConnection(self.tcpSocket)
        sys.exit()
		
    def sendNewPlayer(self, p, otherPlayer):
        pkg = PyDatagram()
        pkg.addUint16(S_NEWPLAYER_SEND)
        pkg.addUint16(otherPlayer.playerId)
        pkg.addString(otherPlayer.name)
        self.cWriter.send(pkg,p.client)
        
    def sendCoordinates(self, p, otherPlayer):
        pkg = PyDatagram()
        pkg.addUint16(S_COORDINATE_SEND)
        pkg.addUint16(otherPlayer.playerId)
        pkg.addFloat64(otherPlayer.x)
        pkg.addFloat64(otherPlayer.y)
        pkg.addFloat64(otherPlayer.z)
        self.cWriter.send(pkg,p.client)
            
    def ProcessPlayer(self,p):
        print p.name
        for key, otherPlayer in PLAYERS.items():
            if p.name != otherPlayer.name:
                if otherPlayer.playerId not in p.playersNearby:
                    p.playersNearby.append(otherPlayer.playerId)
                    self.sendNewPlayer(p, otherPlayer)				
                for otherPlayerId in p.playersNearby:    
                    self.sendCoordinates(p, otherPlayer)
                        

# create a server object
serverHandler = Server()

#install msg handlers
## For detailed information see def handleDatagram(self, data, msgID, client):
Handlers = {
    C_COORDINATE_SEND    : serverHandler.receiveCoordinates,
    C_DISCONNECT_REQ : serverHandler.msgDisconnectReq,
    }
	

			




## The loop again... otherwise the program would run once and thats it ;)
run()
#CLIENT.PY
from pandac.PandaModules import *
import direct.directbase.DirectStart
from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.PyDatagramIterator import PyDatagramIterator
from direct.showbase.DirectObject import DirectObject
from direct.gui.DirectGui import *
from direct.task import Task
import sys
import cplayer


######################################3
##
## Config
##

IP = '127.0.0.1'
PORT = 7198


######################################3
##
## Defines
##
## Quote Yellow: This are server opcodes. It tells the server
## or client what pkt it is receiving. Ie if the pkt starts
## with 3, the server knows he has to deal with a chat msg

MSG_NONE                   = 0
C_COORDINATE_SEND           = 1
S_COORDINATE_SEND           = 2
C_DISCONNECT_REQ           = 3
S_NEWPLAYER_SEND           = 4

OTHER_PLAYERS = []

class Client(DirectObject):

    def __init__(self):
        #self.accept("escape", self.sendMsgDisconnectReq)
        
        # Create network layer objects
        ## This is madatory code. Don't ask for now, just use it ;)
        ## If something is unclear, just ask.
   
        self.cManager = QueuedConnectionManager()
        self.cListener = QueuedConnectionListener(self.cManager, 0)
        self.cReader = QueuedConnectionReader(self.cManager, 0)
        self.cWriter = ConnectionWriter(self.cManager,0)

        self.Connection = self.cManager.openTCPClientConnection(IP, PORT,1)
        self.cReader.addConnection(self.Connection)

        # Start tasks
        taskMgr.add(self.readTask, "serverReaderPollTask", -39)
        self.sendMsgAuth()
    ########################################
    ##
    ## Addition:
    ## If in doubt, don't change the following. Its working.
    ## Here are the basic networking code pieces.
    ## If you have questions, ask...
    ##

    def readTask(self, task):
        while 1:
            (datagram, data, msgID) = self.nonBlockingRead(self.cReader)
            if msgID is MSG_NONE:
                break
            else:
                self.handleDatagram(data, msgID)
                
        return Task.cont

    def nonBlockingRead(self,qcr):
        """
        Return a datagram iterator and type if data is available on the
        queued connection reader
        """
        if self.cReader.dataAvailable():
            datagram = NetDatagram()
            if self.cReader.getData(datagram):
                data = PyDatagramIterator(datagram)
                msgID = data.getUint16()
            else:
                data = None
                msgID = MSG_NONE
        else:
            datagram = None
            data = None
            msgID = MSG_NONE
        # Note, return datagram to keep a handle on the data
        return (datagram, data, msgID)
    
    def sendMsgDisconnectReq(self):
        #####################################################
        ##
        ## This is not used right now, but can be used to tell the 
        ## server that the client is disconnecting cleanly.

        pkg = PyDatagram()
        
        ## Will be a short paket... we are just sending
        ## the Code for disconnecting. The server doesn't
        ## need more information anyways...
        pkg.addUint16(C_DISCONNECT_REQ)
        self.send(pkg)

    def handleDatagram(self, data, msgID):
        if msgID in Handlers.keys():
            Handlers[msgID](msgID,data)
        else:
            print "Unknown msgID: %d" % msgID
            print data
        return

    def sendMsgAuth(self):
        
   #########################################################
   ##
   ## This handles the sending of the auth request.
   ##
   
   ## 1st. We need to create a buffer
        pkg = PyDatagram()
        
   ## 2nd. We put a UInt16 type Number in it. Here its CMSG_AUTH
   ## what means that the corresponding Value is "1"
        pkg.addUint16(C_COORDINATE_SEND)
   
   ## 3rd. We add the coordinates to the buffer after the UInt.
        x=123
        y=456
        z=78910
        pkg.addFloat64(x)
        pkg.addFloat64(y)
        pkg.addFloat64(z)
   
   ## Now that we have a Buffer consisting of a Number and 2 Strings
   ## we can send it.
        self.send(pkg)
        
    def send(self, pkg):
        self.cWriter.send(pkg, self.Connection)

    def quit(self):
        self.cManager.closeConnection(self.Connection)
        sys.exit()
        
    def receiveCoordinates(self, msgID, data):
    
    #########################################################
    ##
    ## Okay... The client sent us some Data. We need to extract
    ## the data the same way it was placed into the buffer.
    ## Its like "first in, first out"
    ##
        id = data.getUint16()
        p = None
        for p2 in OTHER_PLAYERS:
            if p2.playerId == id:
                p = p2
        if p != None:
            p.x = data.getFloat64()
            p.y = data.getFloat64()
            p.z = data.getFloat64()
            #print "X: %s" %p.x
            #print "Y: %s" %p.y
            #print "Z: %s" %p.z
        
        
    def receiveNewPlayer(self, msgID, data):
    
    #########################################################
    ##
    ## Okay... The client sent us some Data. We need to extract
    ## the data the same way it was placed into the buffer.
    ## Its like "first in, first out"
    ##
        id = data.getUint16()
        print id
        name = data.getString()
        print "New Player Connected! ID: %s" %id
        print "New Player Name: %s" %name
        p = cplayer.CPlayer()
        p.name = name
        p.playerId = id
        OTHER_PLAYERS.append(p)

######################################################################
##
## OK! After all of this preparation lets create a Instance of the 
## Client Class created above. Call it as you wish. Make sure that you
## use the right Instance name in the dictionary "Handlers" as well...
##

aClient = Client()
    
######################################################################
##
## That is the second piece of code from the
## def handleDatagram(self, data, msgID): - Method. If you have
## trouble understanding this, please ask.
##

Handlers = {
    S_COORDINATE_SEND  : aClient.receiveCoordinates,
    S_NEWPLAYER_SEND  : aClient.receiveNewPlayer,
    }

#######################################################################
##
## As Examples for other Instance names:
##
## justAExample = Client()
##
## Handlers = {
##    SMSG_AUTH_RESPONSE  : justAExample.msgAuthResponse,
##    SMSG_CHAT           : justAExample.msgChat,
##    SMSG_DISCONNECT_ACK : justAExample.msgDisconnectAck,
##    }

    
########################################################################
##
## We need that loop. Otherwise it would run once and then quit.
##
run()
#PLAYER.py
class Player():
	x = None
	y = None
	z = None
	name = None
	client = None
	ip = None
	playerId = None
	playersNearby = []
#CPLAYER.PY
class CPlayer():
	x = None
	y = None
	z = None
	name = None
	playerId = None

Help would be greatly appreciated! :smiley:

It might help us if you told us what the issue is.

the third client gets no info printed in the command prompt

prntscr.com/77e17f here is the issue

I need urgent help please

Looking at the screenshot, I note the “setForegroundWindow() failed” error message in the third client. My guess, then, is that the third client is failing to start up properly, and thus isn’t available to receive messages.

I’m afraid that I don’t know enough to guess with much confidence as to why this is, however.

That said, are these clients all being run on the same machine? If so, have you tried running them on separate machines, in case the issue arises from that?

That did not fix the issue. :frowning:

I would recommend that you try and find other solutions that covers the basic panda3d networking first that addresses this issue and adapt them back to your code. I think there are other good examples out here that covers just that.

I’ve been in your situation before and it really wasted most of my time figuring something out and banging my head against the wall.

Good Luck!

PS: Maybe you can join the Toontown project, where they are accepting volunteers and in compensation you will gain experience and knowledge how the panda3d engine works. :wink: I’m thinking about this as well hahah