Establishing a network connection

While the recipes preceding this one have shown off some neat networking features of the Panda3D engine using standard communication protocols, none of them have touched upon the topic of implementing the lower-level custom network protocols needed for synchronizing game objects across players connected to a game server. Starting with this recipe though, we change that situation. The rest of this chapter will be dedicated to how to open a connection and exchange custom crafted data between hosts.

Why games might require special network handling, such that you need to know about lower-level custom networking protocols?

The problems game developers have to solve when developing online multiplayer games are plenty. First of all, each game is a unique case on its own: First, there are different types of games, like shooters, racing games, or online role-playing games. Every game out of any category offers a different set of game modes, gameplay, game mechanics, objects, and ways of interaction. Apart from offering non-standardized experiences, each online game has a different set of requirements for its multiplayer functionality: An MMO has to be able to let thousands of players share the same world at the same time, while a fast-paced shooter has to minimize the communication lag between hosts to allow precise and accurate player movement and hit detection.

These are just a few simple samples, but ultimately it's up to us game developers to find solutions for all of these problems. This means that we need to be in control over how, when and which data is sent to meet our games' requirements.

This recipe marks the beginning of a three-part series. First we will shed some light on the components that are involved in getting Panda3D to talk over a network and how to establish a connection. Second, we will implement a tiny custom protocol for learning how to build and send custom datagrams across a network. Finally, in the third part of the series, we will implement a basic sample for synchronizing the state of a game object as a start for implementing a custom network protocol for a game.

Getting ready

We will build this sample on the foundation created in Setting up the game structure found in Chapter 1. Take a step back to read that recipe before continuing if you're unsure.

Unlike the other recipes in this chapter, we will be building this sample from our basic project setup without using any additional libraries or frameworks.

How to do it...

Let's implement a basic client and server and open a connection between them:

  1. Add the required import statements and the NetCommon class to Application.py:
    from direct.showbase.ShowBase import ShowBase
    from panda3d.core import *
    class NetCommon:
    def __init__(self, protocol):
    self.manager = ConnectionManager()
    self.reader = QueuedConnectionReader(self.manager, 0)
    self.writer = ConnectionWriter(self.manager, 0)
    self.protocol = protocol
    taskMgr.add(self.updateReader, "updateReader")
    def updateReader(self, task):
    if self.reader.dataAvailable():
    data = NetDatagram()
    self.reader.getData(data)
    reply = self.protocol.process(data)
    if reply != None:
    self.writer.send(reply, data.getConnection())
    return task.cont
    
  2. Below NetCommon, implement the Server class:
    class Server(NetCommon):
    def __init__(self, protocol, port):
    NetCommon.__init__(self, protocol)
    self.listener = QueuedConnectionListener(self.manager, 0)
    socket = self.manager.openTCPServerRendezvous(port, 100)
    self.listener.addConnection(socket)
    self.connections = []
    taskMgr.add(self.updateListener, "updateListener")
    def updateListener(self, task):
    if self.listener.newConnectionAvailable():
    connection = PointerToConnection()
    if self.listener.getNewConnection(connection):
    connection = connection.p()
    self.connections.append(connection)
    self.reader.addConnection(connection)
    print "Server: New connection established."
    return task.cont
    
  3. Now it's time to add the implementation of the Client and Protocol classes:
    class Client(NetCommon):
    def __init__(self, protocol):
    NetCommon.__init__(self, protocol)
    def connect(self, host, port, timeout):
    self.connection = self.manager.openTCPClientConnection(host, port, timeout)
    if self.connection:
    self.reader.addConnection(self.connection)
    print "Client: Connected to server."
    def send(self, datagram):
    if self.connection:
    self.writer.send(datagram, self.connection)
    class Protocol:
    def process(self, data):
    return None
    
  4. To finish coding for this recipe, it's time to modify the Application class to look like this:
    class Application(ShowBase):
    def __init__(self):
    ShowBase.__init__(self)
    server = Server(Protocol(), 9999)
    client = Client(Protocol())
    client.connect("localhost", 9999, 3000)
    
  5. Start the program. If everything went right you will be able to see the following lines in the Output area of NetBeans:
Client: Connected to server.
Server: New connection established.

How it works...

To avoid duplication of code, we begin this recipe by adding the NetCommon class to Application.py. In the constructor we can already see some of the main components needed for implementing custom network functionality in Panda3D.

The ConnectionManager class handles opening ports, initiating connections to remote hosts and encapsulates all the low level IO operations involved when communicating over a network. Additionally, we need to create QueuedConnectionReader and a ConnectionWriter, responsible for reading and writing data respectively. QueuedConnectionReader is a subclass of ConnectionReader that buffers all incoming datagrams so they can be processed one after another in the updateReader() task. This task periodically polls the reader object for newly available data. If anything has been received, it is handed to the process() method of a Protocol object, which decides how to react to the received data and which reply to send.

Currently, we only have a Protocol class that doesn't do anything. This will change in the following recipes, where we will use different protocol implementations to define the behaviors of client and server.

The NetCommon class already contains a good part of what's necessary for sending and receiving data over a network, but to create a server we still need to derive a new class and add some additional features. We need a QueuedConnectionListener to listen for new connections on the network port that is opened using the call to openTCPServerRendezvous(). The first parameter defines the port number our server will listen on for connections. The second parameter sets the maximum amount of simultaneous connection attempts. If the number of requests exceeds this value, new connection attempts are simply ignored.

Similar to the QueuedConnectionReader class, QueuedConnectionListener buffers requests for new connections and needs to be polled in a task where new connections are put into a list and registered to the QueuedConnectionReader owned by the class so incoming data is received and processed.

All we need to add to the Client class, on the other hand, are the connect() and send() methods. The former opens a new connection to a server. For this we need to specify the target host and port as well as the maximum time to wait for a reply before considering the connection to be terminated. The latter method is just a wrapper for sending a datagram.

In the constructor of Application we finally create a new Server and Client object and connect them using the internal loopback connection. Both client and server are using our stub protocol that does nothing yet. If you want this to change, go on to the next recipe!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset