Sending high scores to a server

In this recipe we are going to take a look at the very basics of implementing a feature that allows us to submit scores to a server that processes and stores the data we are sending. Additionally, we will be able to view the list of submitted scores using a web browser.

The server side of this project will be implemented using the Twisted framework (http://twistedmatrix.com). The libraries contained in Twisted make it very easy to implement servers and clients for all kinds of common and custom network protocols. For our purpose, we are going to implement a little custom web server that will accept POST requests for submitting data and will serve the static scoreboard page.

It is important to note that there are several other Python frameworks like Twisted available. Tornado (http://www.tornadoweb.org) and Diesel (http://dieselweb.org) are just two examples of network programming frameworks similar to Twisted. All of them have their upsides and downsides, but in the end Twisted was chosen here out of pure preference as well as for its ease of use. Not to mention that it is implemented in Python, which makes it a good fit among all the other Python code in this recipe and this book.

Getting ready

Before we can begin working on the following steps, we need to install the Twisted framework first using a collection of Python scripts called "setuptools". This is a set of command line tools that handle installing and managing additional third-party libraries from the Python Package Index hosted on http://pypi.python.org/pypi. This is a great source for Python programming libraries and frameworks, definitely worth some time to browse and explore!

To get set for the following tasks, first follow these steps:

  1. Open your browser and go to http://pypi.python.org/pypi/setuptools.
  2. Scroll down the page until you find the following table of download links:
    Getting ready
  3. Download the file setuptools-0.6c11.win32-py2.6.exe. The exact filename and version may vary slightly because of newer releases but it is important to watch for the string win32-py2.6 in the filename to match the version of Python used in Panda3D.
  4. After the file has finished downloading, launch the executable. This will start an installer program. Click Next until you see the following screen:
    Getting ready
  5. Choose the Python installation that comes with Panda3D, if you happen to have multiple versions of Python installed on your system. Double check the Python Directory field this has to match your Panda3D installation directory!
  6. After the installer has finished, add the directory C:Panda3D-1.7.0pythonScripts to the system search path.
  7. Open a command prompt and enter the command easy_install Twisted. This will download and set up all components needed for using the Twisted framework.

Now that the dependencies are installed and ready, it is time to finish the preparation steps by creating two projects. For the first one, follow Setting up the game structure found in Chapter 1 and name the project PostScore. This will be our client application.

When creating the second project, follow the same recipe again, but only up to step 3, naming the project ScoreServer. This is where we will implement the server side of this sample.

How to do it...

This recipe consists of the following tasks:

  1. Open the main.py file that's part of the ScoreServer project and replace its content with the following code:
    from twisted.web.server import Site
    from twisted.web.resource import Resource
    from twisted.internet import reactor
    import sqlite3
    class ScorePage(Resource):
    def __init__(self):
    Resource.__init__(self)
    self.db = sqlite3.connect("scores.db")
    cursor = self.db.cursor()
    args = ("scores",)
    cursor.execute("select name from sqlite_master where name=?", args)
    if len(cursor.fetchall()) == 0:
    cursor.execute("create table scores (player text, score integer)")
    self.db.commit()
    cursor.close()
    
  2. Below the code you just added, put this piece of code:
    def render_POST(self, request):
    cursor = self.db.cursor()
    args = (request.args["player"][0],)
    cursor.execute("select * from scores where player=?", args)
    if len(cursor.fetchall()) > 0:
    args = (request.args["score"][0], request.args["player"][0])
    cursor.execute("update scores set score=? where player=?", args)
    else:
    args = (request.args["player"][0], request.args["score"][0])
    cursor.execute("insert into scores values (?,?)", args)
    self.db.commit()
    cursor.close()
    return "OK"
    
  3. Then, add the following method:
    def render_GET(self, request):
    networking, Panda3Dhigh scores, sending to servercursor = self.db.cursor()
    cursor.execute("select * from scores order by score desc")
    data = cursor.fetchall()
    cursor.close()
    result = str("
    ".join(["%s %s" % (p, s) for p, s in data]))
    request.setHeader("Content-Type", "text/plain; charset=utf-8")
    return result
    
  4. Now we need to add the following main function that will start up our server:
    if __name__ == "__main__":
    root = Resource()
    root.putChild("score", ScorePage())
    factory = Site(root)
    reactor.listenTCP(80, factory)
    reactor.run()
    
  5. For implementing the client, open the file Application.py and fill in the code below:
    from direct.showbase.ShowBase import ShowBase
    from panda3d.core import *
    class Application(ShowBase):
    def __init__(self):
    ShowBase.__init__(self)
    self.http = HTTPClient.getGlobalPtr()
    self.channel = self.http.makeChannel(False)
    self.channel.beginPostForm(DocumentSpec("http://localhost/score"), "player=Foo&score=1337")
    self.ram = Ramfile()
    self.channel.downloadToRam(self.ram)
    taskMgr.add(self.updateChannel, "updateChannel")
    def updateChannel(self, task):
    if self.channel.run():
    return task.cont
    elif self.channel.isDownloadComplete():
    print self.ram.getData()
    return task.done
    else:
    print "Error posting score."
    
  6. To start the server, right-click the ScoreServer node in the project tree and click on Run, as shown in the following screenshot:
    How to do it...
  7. Repeat the last step to run the PostScore project.
  8. To view the list of scores, open a browser and go to http://localhost/score.
  9. To stop the server, find the entry in NetBean's status bar (at the bottom right of the window, as shown in the screenshot) that reads ScoreServer and Running and click the little x button next to it.
How to do it...

How it works...

First, let's take a look at how our server works. For this we need to understand the programming model of the Twisted framework: At the core of our application we find the reactor object that is responsible for opening ports and polling them in its internal event loop. In our case, port 80 (the standard for a HTTP server) is opened to listen for TCP connections, which leads us directly to the next layer.

When opening the server port, we pass a new object of the class Site that is responsible for processing the data that is received and sent over our newly opened port as HTTP requests. This means that with these steps, we have already created a web server. The only problem is that as our web server currently is, it would not provide any interesting data.

To add resources that can be queried and retrieved, we first add an empty Resource at the site's root folder. We do not want our server to host anything there. The only thing a client will receive if they request at the document root of our server is an error code, specifically 503. This error means that it is forbidden to access the requested resource. Instead, we add a new child to our virtual directory tree called score. This causes our service to be reachable via the URL http://localhost/score.

To be able to accept new scores being submitted by players and present the score list, we add our custom ScorePage subclass of Resource to the aforementioned virtual server directory.

In the constructor of ScorePage, we connect to an SQLite database, check if a table called scores exists, and create it if necessary. SQLite is a very lightweight SQL database implementation that stores its data in a specially formatted local file. It is mainly intended for small, single user applications, which means that for a serious attempt at implementing a server that stores scores, we should think about using a database system that is aimed at bigger scale use cases.

Querying the database requires us to use a cursor object. After executing the query, the cursor holds the results, which we then can retrieve using the fetchall() method. If we made changes to the database's layout or data, we need to commit() these changes, or they will be dropped. Also, after we are done with our queries, we should not forget to close() the cursor to free any resources or handles we might still be holding.

This leaves the render_POST() and render_GET() methods to be discussed on the server side of our project. The render_POST() method is called whenever a client sends data to our server. We then check if a player with the given name has already submitted a score that we need to update or if we need to create a new record in our database. After the data is processed and stored in the database, we're done receiving the request and return the string"OK" to the client to signal that no error occurred.

An HTTP GET request asks the server to return the data it hosts at a given address. The render_GET() method builds this data on the fly as new requests for retrieving the resource located at the score directory of the server are received by the server. Our code queries the database to return all submitted scores, ordered by score in descending order. We build a plain text list of strings, where each line contains a player name and a score, hence the text/plain MIME type is set in the header of the reply that will be sent back to the client. Of course, we could also return a string that contains HTML and omit setting the MIME type so the client (that is most likely going to be a web browser) will interpret it as a web page.

Before going on to discussing the client side, we should stop and think about an important issue that we have not addressed so far in the server code: security. First, our little server does not perform any sanity checks on the data submitted to it. In a production system, make sure to define which ranges and data types are allowed and add checking routines to prevent possible attacks based on submitting malicious data.

The second point we did not address is client authentication and authorization. So far, any program that is able to send a POST request could possibly submit data to our server. Surely, we would only want our game to be able to submit data to prevent cheaters from submitting arbitrarily crafted scores, so some mechanisms for verifying clients and encrypting the submitted data will have to be put into place.

Finally, we can take a look at the client, where we use the HTTPClient to send our request and retrieve the server's reply, which has already been discussed in the recipe Downloading a file from a server found earlier in this chapter. Instead of requesting a document, we use the beginPostForm() method to send data to the server. What's particularly interesting about this call is the second parameter it accepts, which is the data to send.

The data is sent using a key-value form. Each of these key-value pairs takes the following form key=value. We can send multiples of these pairs in one request, as shown in the sample code, using the ampersand (&) sign as a delimiter between each key-value pair.

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

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