class TCP(): def __init__(self, _core): print("TCP Protocol Startup...") self.core = _core self.config = self.core.server.config def start(self): self.setupTCP() self.startTCPTasks() def setupTCP(self): self.tcpManager = QueuedConnectionManager() self.tcpReader = QueuedConnectionReader(self.tcpManager, 0) self.tcpWriter = ConnectionWriter(self.tcpManager, 0) self.tcpListener = QueuedConnectionListener(self.tcpManager, 0) self.tcpSocket = self.tcpManager.openTCPServerRendezvous( self.config.TCPPORT, self.config.BACKLOG) self.tcpListener.addConnection(self.tcpSocket) print("Started Server on: ", self.config.HOSTNAME, self.config.TCPPORT) def startTCPTasks(self): taskMgr.add(self.tcpListenerTask, "tcpListenerTask", 0) print("TCP Listener Started") taskMgr.add(self.tcpReaderTask, "tcpReaderTask", -10) print("TCP Reader Started") taskMgr.add(self.tcpDisconnectionHandler, "tcpDisconnectionHandler", 20) print("TCP Disconnection Handler Started") # TCP Listener Task def tcpListenerTask(self, task): """ Accept new incoming connection from clients, related to TCP """ # Handle new connection if self.tcpListener.newConnectionAvailable(): rendezvous = PointerToConnection() netAddress = NetAddress() newConnection = PointerToConnection() if self.tcpListener.getNewConnection(rendezvous, netAddress, newConnection): newConnection = newConnection.p() # Tell the reader about the new TCP connection self.tcpReader.addConnection(newConnection) # Handle the connection depending on persistent or not if self.core.server.isPersistent: self.core.handleConnection(generateUUID(), newConnection, netAddress) else: self.core.createPlayerObject(generateUUID(), newConnection, netAddress) print("Server: New Connection from -", str(netAddress.getIpString())) else: print("Server: Connection Failed from -", str(netAddress.getIpString())) return Task.cont def tcpReaderTask(self, task): """ Handle any data from clients by sending it to the Handlers. """ while 1: (datagram, data, opcode) = self.tcpNonBlockingRead(self.tcpReader) if opcode is MSG_NONE: # Do nothing or use it as some 'keep_alive' thing. break else: # Handle it self.core.packetManager.handlePacket(opcode, data, datagram.getAddress()) return Task.cont # TCP NonBlockingRead?? def tcpNonBlockingRead(self, qcr): """ Return a datagram collection and type if data is available on the queued connection udpReader """ if self.tcpReader.dataAvailable(): datagram = NetDatagram() if self.tcpReader.getData(datagram): data = DatagramIterator(datagram) opcode = data.getUint8() else: data = None opcode = MSG_NONE else: datagram = None data = None opcode = MSG_NONE # Return the datagram to keep a handle on the data return (datagram, data, opcode) # TCP Disconnection Handler def tcpDisconnectionHandler(self, task): # Check for resets if self.tcpManager.resetConnectionAvailable(): resetConnection = PointerToConnection() self.tcpManager.getResetConnection(resetConnection) for client in self.core.server.clients: if self.core.server.clients[ client].connection == resetConnection.p(): del self.core.server.clients[client] self.tcpReader.removeConnection(resetConnection.p()) print("Removed Connection:", resetConnection.p()) print('Current Clients:', self.core.server.clients) break return Task.cont def sendPacket(self, _pkt, _connection): self.tcpWriter.send(_pkt, _connection) def sendBroadcast(self, _pkt, _skipif=None): for client in self.serverManager.clients: if _skipif == client: pass else: conn = self.serverManager.clients[client].connection self.tcpWriter.send(_pkt, conn)
class TCP(): def __init__(self, _core): print ("TCP Protocol Startup...") self.core = _core self.config = self.core.server.config def start(self): self.setupTCP() self.startTCPTasks() def setupTCP(self): self.tcpManager = QueuedConnectionManager() self.tcpReader = QueuedConnectionReader(self.tcpManager, 0) self.tcpWriter = ConnectionWriter(self.tcpManager, 0) self.tcpListener = QueuedConnectionListener(self.tcpManager, 0) self.tcpSocket = self.tcpManager.openTCPServerRendezvous(self.config.TCPPORT, self.config.BACKLOG) self.tcpListener.addConnection(self.tcpSocket) print ("Started Server on: ", self.config.HOSTNAME, self.config.TCPPORT) def startTCPTasks(self): taskMgr.add(self.tcpListenerTask, "tcpListenerTask", 0) print ("TCP Listener Started") taskMgr.add(self.tcpReaderTask, "tcpReaderTask", -10) print ("TCP Reader Started") taskMgr.add(self.tcpDisconnectionHandler, "tcpDisconnectionHandler", 20) print ("TCP Disconnection Handler Started") # TCP Listener Task def tcpListenerTask(self, task): """ Accept new incoming connection from clients, related to TCP """ # Handle new connection if self.tcpListener.newConnectionAvailable(): rendezvous = PointerToConnection() netAddress = NetAddress() newConnection = PointerToConnection() if self.tcpListener.getNewConnection(rendezvous, netAddress, newConnection): newConnection = newConnection.p() # Tell the reader about the new TCP connection self.tcpReader.addConnection(newConnection) # Handle the connection depending on persistent or not if self.core.server.isPersistent: self.core.handleConnection(generateUUID(), newConnection, netAddress) else: self.core.createPlayerObject(generateUUID(),newConnection, netAddress) print ("Server: New Connection from -", str(netAddress.getIpString())) else: print ("Server: Connection Failed from -", str(netAddress.getIpString())) return Task.cont def tcpReaderTask(self, task): """ Handle any data from clients by sending it to the Handlers. """ while 1: (datagram, data, opcode) = self.tcpNonBlockingRead(self.tcpReader) if opcode is MSG_NONE: # Do nothing or use it as some 'keep_alive' thing. break else: # Handle it self.core.packetManager.handlePacket(opcode, data, datagram.getAddress()) return Task.cont # TCP NonBlockingRead?? def tcpNonBlockingRead(self, qcr): """ Return a datagram collection and type if data is available on the queued connection udpReader """ if self.tcpReader.dataAvailable(): datagram = NetDatagram() if self.tcpReader.getData(datagram): data = DatagramIterator(datagram) opcode = data.getUint8() else: data = None opcode = MSG_NONE else: datagram = None data = None opcode = MSG_NONE # Return the datagram to keep a handle on the data return (datagram, data, opcode) # TCP Disconnection Handler def tcpDisconnectionHandler(self, task): # Check for resets if self.tcpManager.resetConnectionAvailable(): resetConnection = PointerToConnection() self.tcpManager.getResetConnection(resetConnection) for client in self.core.server.clients: if self.core.server.clients[client].connection == resetConnection.p(): del self.core.server.clients[client] self.tcpReader.removeConnection(resetConnection.p()) print ("Removed Connection:", resetConnection.p()) print ('Current Clients:', self.core.server.clients) break return Task.cont def sendPacket(self, _pkt, _connection): self.tcpWriter.send(_pkt, _connection) def sendBroadcast(self, _pkt, _skipif=None): for client in self.serverManager.clients: if _skipif == client: pass else: conn = self.serverManager.clients[client].connection self.tcpWriter.send(_pkt, conn)
class Server: def __init__(self): self.port = 5555 self.addr = "127.0.0.1" self.backlog = 7 self.active_connections = dict() self.start = False self.connect() def connect(self) -> None: # Handle connections and terminations self.manager = QueuedConnectionManager() # Wait for clients connection requests self.listener = QueuedConnectionListener(self.manager, 0) # Buffers incoming data from active connection self.reader = QueuedConnectionReader(self.manager, 0) # Transmit PyDatagrams to active connection self.writer = ConnectionWriter(self.manager, 0) # Open TCP Rendezvous to accept client connections with a limit self.socket = self.manager.openTCPServerRendezvous( self.port, self.backlog) self.listener.addConnection(self.socket) print("Server listening on port %s...." % str(self.port)) # Listen for mew incoming connections taskMgr.add(self.handle_incoming_connections, "Poll the connection listener", -39) # Listen for new datagrams taskMgr.add(self.handle_connection_data, "Poll the connection reader", -40) # Listen for dropped connections taskMgr.add(self.handle_dropped_connections, "Poll the dropped connection listener", -41) # See if game can be started taskMgr.add(self.start_game, "Start Game", -42) def start_game(self, task_data: Task) -> Task: if len(self.active_connections) == self.backlog: self.start = True return Task.cont def handle_incoming_connections(self, task_data: Task) -> Task: if self.listener.newConnectionAvailable(): rendezvous = PointerToConnection() net_addr = NetAddress() new_connection = PointerToConnection() if self.listener.getNewConnection(rendezvous, net_addr, new_connection): new_connection = new_connection.p() # Keep track of our active connections self.active_connections[str( new_connection.this)] = new_connection # Start reading the new connection self.reader.addConnection(new_connection) print("%s just connected" % str(new_connection)) return Task.cont def handle_connection_data(self, task_data: Task) -> Task: if self.reader.dataAvailable(): # Catch the incoming data datagram = NetDatagram() if self.reader.getData(datagram): name = self.handle_client_message(datagram) broadcast = f"Everyone, welcome {name} to the game!" self.broadcast_message(broadcast) return Task.cont def handle_dropped_connections(self, task_data: Task) -> Task: if self.manager.resetConnectionAvailable(): connection_pointer = PointerToConnection() self.manager.getResetConnection(connection_pointer) lost_connection = connection_pointer.p() print("% s disconnected from server" % str(lost_connection)) del self.active_connections[str(lost_connection.this)] self.manager.closeConnection(lost_connection) return Task.cont def handle_client_message(self, message: str) -> str: iterator = PyDatagramIterator(message) return iterator.getString() def get_connections_count(self) -> int: return len(self.active_connections) def send_personal_message(self, message: str, client: PointerToConnection) -> None: datagram = self.create_new_datagram(message) self.writer.send(datagram, client) def broadcast_message(self, message: str) -> None: datagram = self.create_new_datagram(message) for client in self.active_connections: self.writer.send(datagram, self.active_connections[client]) def create_new_datagram(self, message: str) -> PyDatagram: new_datagram = PyDatagram() new_datagram.addString(message) return new_datagram def terminate_all_clients(self) -> None: for client in self.active_connections: self.reader.removeConnection(client) self.active_connections = list() def terminate_specific_client(self, client: PointerToConnection) -> None: self.reader.removeConnection(client) del self.active_connections[str(client)]
class Client(DirectObject): def __init__(self, host, port, timeout=3000, compress=False, connectionStateChangedHandler=None): DirectObject.__init__(self) self.connectionStateChangedHandler = connectionStateChangedHandler self.myConnection = None self.host = host self.port = port self.timeout = timeout self.compress = compress self.cManager = QueuedConnectionManager() self.cReader = QueuedConnectionReader(self.cManager, 0) self.cWriter = ConnectionWriter(self.cManager, 0) # By default, we are not connected self.connected = False self.passedData = [] self.connect(self.host, self.port, self.timeout) def cleanup(self): self.removeAllTasks() def startPolling(self): self.doMethodLater(0.1, self.tskDisconnectPolling, "clientDisconnectTask") def connect(self, host, port, timeout=3000): # Connect to our host's socket self.myConnection = self.cManager.openTCPClientConnection( host, port, timeout) if self.myConnection: self.myConnection.setNoDelay(True) self.myConnection.setKeepAlive(True) print "Connected" self.cReader.addConnection( self.myConnection) # receive messages from server self.connected = True # Let us know that we're connected self.startPolling() if self.connectionStateChangedHandler: self.connectionStateChangedHandler.handleConnection() else: print "Unable to connect" if self.connectionStateChangedHandler: self.connectionStateChangedHandler.handleFailure() def tskDisconnectPolling(self, task): if not self.connected: return Task.done # TODO: Hacky sending nothing to force disconnect triggers #self.sendData() # Also checking for dataAvailable on reader will trigger the connection disconnected self.cReader.dataAvailable() # TODO: Confirm this works for client side (to both game server and master server) while self.cManager.resetConnectionAvailable(): connPointer = PointerToConnection() self.cManager.getResetConnection(connPointer) connection = connPointer.p() # Remove the connection we just found to be "reset" or "disconnected" self.cReader.removeConnection(connection) # Let us know that we are not connected self.connected = False print "disconnected" if self.connectionStateChangedHandler: self.connectionStateChangedHandler.handleDisconnection() return Task.again def processData(self, netDatagram): myIterator = PyDatagramIterator(netDatagram) return self.decode(myIterator.getString()) def encode(self, data, compress=False): # encode(and possibly compress) the data with rencode return rencode.dumps(data, compress) def decode(self, data): # decode(and possibly decompress) the data with rencode return rencode.loads(data) def sendData(self, data=None): myPyDatagram = PyDatagram() myPyDatagram.addString(self.encode(data, self.compress)) self.cWriter.send(myPyDatagram, self.myConnection) def passData(self, data): self.passedData.append(data) def getData(self): data = self.passedData self.passedData = [] while self.cReader.dataAvailable(): datagram = NetDatagram() if self.cReader.getData(datagram): data.append(self.processData(datagram)) return data
class Server(DirectObject): # TODO: Perhaps a better way to do this? handleNewConnection = None handleLostConnection = None def __init__(self, port, backlog=1000, compress=False): DirectObject.__init__(self) self.port = port self.compress = compress self.cManager = QueuedConnectionManager() self.cListener = QueuedConnectionListener(self.cManager, 0) self.cReader = QueuedConnectionReader(self.cManager, 0) self.cWriter = ConnectionWriter(self.cManager, 0) self.passedData = [] self.connect(port, backlog) self.startPolling() def connect(self, port, backlog): # Bind to our socket tcpSocket = self.cManager.openTCPServerRendezvous(port, backlog) self.cListener.addConnection(tcpSocket) def startPolling(self): self.addTask(self.tskListenerPolling, "serverListenTask", -40) self.addTask(self.tskDisconnectPolling, "serverDisconnectTask", -39) def tskListenerPolling(self, task): if self.cListener.newConnectionAvailable(): rendezvous = PointerToConnection() netAddress = NetAddress() newConnection = PointerToConnection() if self.cListener.getNewConnection(rendezvous, netAddress, newConnection): newConnection = newConnection.p() newConnection.setNoDelay(True) newConnection.setKeepAlive(True) if self.handleNewConnection: self.handleNewConnection(newConnection) self.cReader.addConnection( newConnection) # Begin reading connection return Task.cont def tskDisconnectPolling(self, task): while self.cManager.resetConnectionAvailable(): connPointer = PointerToConnection() self.cManager.getResetConnection(connPointer) connection = connPointer.p() # Remove the connection we just found to be "reset" or "disconnected" self.cReader.removeConnection(connection) if self.handleLostConnection: self.handleLostConnection(connection) return Task.cont def processData(self, netDatagram): myIterator = PyDatagramIterator(netDatagram) return self.decode(myIterator.getString()) def encode(self, data, compress=False): # encode(and possibly compress) the data with rencode return rencode.dumps(data, compress) def decode(self, data): # decode(and possibly decompress) the data with rencode return rencode.loads(data) def sendData(self, data, con): myPyDatagram = PyDatagram() myPyDatagram.addString(self.encode(data, self.compress)) self.cWriter.send(myPyDatagram, con) def passData(self, data, connection): self.passedData.append((data, connection)) def getData(self): data = self.passedData self.passedData = [] while self.cReader.dataAvailable(): datagram = NetDatagram() if self.cReader.getData(datagram): data.append( (datagram.getConnection(), self.processData(datagram))) return data
class SocketTCP(ShowBase): def __init__(self, _parent=None): ShowBase.__init__(self, windowType = 'none') print ("TCP Protocol Startup...") #self.parent = _parent #self.config = self.parent.server.config # tmp config self.tcpport = 6000 self.backlog = 10 self.hostname = '127.0.0.1' self.sendPacketQueue = None def startAll(self): self.setupTCP() self.startTCPTasks() def setupTCP(self): self.tcpManager = QueuedConnectionManager() self.tcpReader = QueuedConnectionReader(self.tcpManager, 0) self.tcpWriter = ConnectionWriter(self.tcpManager, 0) self.tcpListener = QueuedConnectionListener(self.tcpManager, 0) self.tcpSocket = self.tcpManager.openTCPServerRendezvous(self.tcpport, self.backlog) self.tcpListener.addConnection(self.tcpSocket) print ("Started Server on: ", self.hostname, self.tcpport) def startTCPTasks(self): taskMgr.add(self.tcpListenerTask, "tcpListenerTask", 0) print ("TCP Listener Started") taskMgr.add(self.tcpReaderTask, "tcpReaderTask", -10) print ("TCP Reader Started") taskMgr.add(self.tcpDisconnectionHandler, "tcpDisconnectionHandler", 20) print ("TCP Disconnection Handler Started") taskMgr.add(self.tcpQueuedSendHandlerTask, "tcpQueuedSendhandlerTask", -11) print ("TCP Queued Send Handler Started") # TCP Listener Task def tcpListenerTask(self, task): """ Accept new incoming connection from clients, related to TCP """ # Handle new connection if self.tcpListener.newConnectionAvailable(): rendezvous = PointerToConnection() netAddress = NetAddress() newConnection = PointerToConnection() if self.tcpListener.getNewConnection(rendezvous, netAddress, newConnection): newConnection = newConnection.p() # Tell the reader about the new TCP connection self.tcpReader.addConnection(newConnection) #self.core.createPlayerObject(generateUUID(),newConnection, netAddress) print ("Server: " + str(generateUUID), str(netAddress.getIpString())) else: print ("Server: Connection Failed from -", str(netAddress.getIpString())) return Task.cont def tcpReaderTask(self, task): """ Handle any data from clients by sending it to the Handlers. """ while 1: (datagram, data, opcode) = self.tcpNonBlockingRead(self.tcpReader) if opcode is MSG_NONE: # Do nothing or use it as some 'keep_alive' thing. break else: # Handle it #self.core.packetManager.handlePacket(opcode, data, datagram.getAddress()) print("Set Packet Handler to handle packets!") return Task.cont # TCP NonBlockingRead?? def tcpNonBlockingRead(self, qcr): """ Return a datagram collection and type if data is available on the queued connection udpReader """ if self.tcpReader.dataAvailable(): datagram = NetDatagram() if self.tcpReader.getData(datagram): data = DatagramIterator(datagram) opcode = data.getUint8() else: data = None opcode = MSG_NONE else: datagram = None data = None opcode = MSG_NONE # Return the datagram to keep a handle on the data return (datagram, data, opcode) # TCP Disconnection Handler def tcpDisconnectionHandler(self, task): # Check for resets if self.tcpManager.resetConnectionAvailable(): resetConnection = PointerToConnection() self.tcpManager.getResetConnection(resetConnection) print(str(resetConnection.p())) #for client in self.core.server.clients: # if self.core.server.clients[client].connection == resetConnection.p(): # del self.core.server.clients[client] # self.tcpReader.removeConnection(resetConnection.p()) # print ("Removed Connection:", resetConnection.p()) # print ('Current Clients:', self.core.server.clients) # break return Task.cont def sendPacket(self, _pkt, _connection): self.tcpWriter.send(_pkt, _connection) def sendBroadcast(self, _pkt, _skipif=None): for client in self.serverManager.clients: if _skipif == client: pass else: conn = self.serverManager.clients[client].connection self.tcpWriter.send(_pkt, conn) def addPacketToSendQueue(self, _data): # _data should contain packet and connection pass def addPacketToBroadcastQueue(self, _pkt, _connectionList): # _data should contain the pkt data and _connectionList should be the valid connections for that broadcast # Could have a grouping system that put clients together and broadcasts go per group pass def tcpQueuedSendHandlerTask(self, task): if not self.sendPacketQueue.isEmpty(): self.addPacketToSendQueue(self.sendPacketQueue.removeFromQue()) return Task.cont