After having built the groundwork for opening network connections, it's time to implement our first self-defined network protocol. To achieve this goal, we are going to implement new classes derived from Protocol
that will piece together custom datagrams. We will then send these datagrams back and forth between our client and server to have them a nice little chat.
This recipe directly continues where the last one left off. So if you didn't read that part yet, take one step back and start from the beginning to create the prerequisites for this recipe and better understand what will be shown here.
Follow these steps to implement your own network protocol:
Application.py
and add the following import
statements to the top of the file:from direct.distributed.PyDatagram import PyDatagram from direct.distributed.PyDatagramIterator import PyDatagramIterator
Protocol
class:def printMessage(self, title, msg): print "%s %s" % (title, msg) def buildReply(self, msgid, data): reply = PyDatagram() reply.addUint8(msgid) reply.addString(data) return reply
Protocol
class, add this:class ServerProtocol(Protocol): def process(self, data): it = PyDatagramIterator(data) msgid = it.getUint8() if msgid == 0: return self.handleHello(it) elif msgid == 1: return self.handleQuestion(it) elif msgid == 2: return self.handleBye(it) def handleHello(self, it): self.printMessage("Server received:", it.getString()) return self.buildReply(0, "Hello, too!") def handleQuestion(self, it): self.printMessage("Server received:", it.getString()) return self.buildReply(1, "I'm fine. How are you?") def handleBye(self, it): self.printMessage("Server received:", it.getString()) return self.buildReply(2, "Bye!")
ClientProtocol
class below ServerProtocol:
class ClientProtocol(Protocol): def process(self, data): it = PyDatagramIterator(data) msgid = it.getUint8() if msgid == 0: return self.handleHello(it) elif msgid == 1: return self.handleQuestion(it) elif msgid == 2: return self.handleBye(it) def handleHello(self, it): self.printMessage("Client received:", it.getString()) return self.buildReply(1, "How are you?") def handleQuestion(self, it): self.printMessage("Client received:", it.getString()) return self.buildReply(2, "I'm fine too. Gotta run! Bye!") def handleBye(self, it): self.printMessage("Client received:", it.getString()) return None
Application
class to resemble the code below:class Application(ShowBase): def __init__(self): ShowBase.__init__(self) server = Server(ServerProtocol(), 9999) client = Client(ClientProtocol()) client.connect("localhost", 9999, 3000) data = PyDatagram() data.addUint8(0) data.addString("Hello!") client.send(data)
Client: Connected to server. Server: New connection established. Server received: Hello! Client received: Hello, too! Server received: How are you? Client received: I'm fine. How are you? Server received: I'm fine too. Gotta run! Bye! Client received: Bye!
As we can see in this sample, sending and receiving data over a network connection is really easy, thanks to the API provided by Panda3D. To send data, we need to create a new PyDatagram
and add data fields using methods like addString()
and addUint8()
before passing it to a ConnectionWriter
instance for sending. We are not limited to sending strings and 8 bit unsigned integers, though. PyDatagram
features a whole lot of these add*()
methods for floating point numbers and integers of various bit widths, for example.
To retrieve data that has been received over a network connection we have to pass it to a PyDatagramIterator
. With the help of this class we are able to unpack the data fields from the datagram. Of course, this works quick and easy, but there's a catch to it that is very important to keep in mind: When retrieving data from a datagram using a PyDatagramIterator
, the fields need to be accessed in exactly the same order as they were added to the PyDatagram
before sending!
Using this knowledge, we were able to build a simple communication protocol that sends a numerical message id and a string. The receiver displays the string it got on the console and sends a reply containing a new message id and string based on the numerical id it received.