CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1...

22
CS 4431 Advanced Project Stage I Specifications and Implementation Marshall Hahn Hafiz Kakar Alex Brown Website: http://flash.lakeheadu.ca/~mhahn

Transcript of CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1...

Page 1: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

CS 4431 Advanced Project Stage I Specifications and Implementation

Marshall Hahn

Hafiz Kakar Alex Brown

Website: http://flash.lakeheadu.ca/~mhahn

Page 2: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

1

Table of Contents

1. Requirements 1.1. Running Requirements 1.2. Project Requirements

2. User Guide

2.1. How to use the program 3. Project Overview

3.1. Pygame’s Main Event Loop 3.2. The Tank Class 3.3. The Threaded Client

3.3.1. The GUI Thread 3.3.2. The Socket Thread

3.4. The Threadpool Server 3.5. Client/Server Interaction

4. Testing

4.1. Project Implementation Table 4.2. Overview of Completed Items 4.3. Overview of Items in Progress 4.4. Overview of Implemented Items 4.5. Overview of Future Items

5. Implementation

5.1. Client Code 5.2. Server Code

Page 3: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

2

1. User Requirements 1.1 Running Requirements

The hardware requirements for running this program are currently unknown. The program has not been tested under any Linux/Unix system, although it is assumed that it will function correctly as long as either of these operating systems support multithreading. Although a network connection is not required to run the program, it is recommended. Python and Pygame packages must also be installed on your system in order to use the program. 1.2 Project Requirements For stage one of the project, the following minimum requirements must be implemented:

• A two ‘player’ or ‘user’ game • A client / server designed to manage the overall running game.

These two requirements have been met. The following additions have been made:

• A specialized tank class to handle all tank-related operations • Angular rotation of tank sprites on screen (described in Section 3.1) • A multithreaded client to handle both visual UI and server communication

(described in Section 3.2) • A threadpool server architecture to handle multiple client connections and reduce

thread overhead in Python (described in section 3.3) 2. User Guide 2.1 How to use the Program To begin, run server.py. A blank console screen will appear. This is the active server, waiting for client connections. Next, open a new console window (in Windows XP, click Start, Run, type CMD, and press enter). Navigate to the directory in which your client and server python files are located. Run the following command at the command prompt: client.py 192.168.0.1 Use the appropriate IP address where the server is located. You can use localhost instead of an IP address if the server is running on the same machine.

Page 4: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

3

3. Project Overview 3.1 PyGame’s Main Event Loop The client itself is made of two distinct programs, one that handles the connection and communication to the server, and the other that handles displaying a graphical user interface to the screen. This latter part is best known as the Main Event Loop which handles user I/O interaction, and drawing to the screen. It is the foundation of all things PyGame. There are several steps that must be executed before the main loop actually begins, most of which are just initializing variables to be used in the event loop, like sprite objects, group objects, etc. The main loop itself then begins to run, executing in the following order:

• The event queue is processed. The event queue contains any input from user-controlled devices (keyboard, mouse, etc). In this case, consider pressing the ‘up’ key to move forward, and the ‘down’ key to move backward. The event queue handles these events as they happen in real time.

• The update() method of each sprite is called. Each sprite has an update method that is called during each loop iteration. When it is called for any one sprite, any updates to the sprite’s position etc are made, so the sprite can be redrawn to the screen in a new spot.

• Each sprite is cleared from its previous position on screen. • Each sprite is redrawn at its new position. (the new position is determined by

values located in each sprite’s class) • The updated sprite may now have new x/y coordinates, or be positioned at a new

angle, etc. This updated information will later be sent to the server (this is described in sections 3.3 and 3.4)

• The end of each loop iteration checks a tank dictionary for new tanks. This will be described further in section 3.3.

3.2 The Tank Class The tank class itself contains several methods, and inherits from the sprite class in pygame. The most important methods in this class are the init class and the update class.

• The init function of the class is called when an object of this class is created. It presets specific variables and prepares the object for drawing.

• The update method of a Tank object is called repeatedly from the main event loop. The main objective of this method is to update a tank’s position on the screen given the values of several variables (that is, x and y coordinate positions, angular speed and movement speed).

The tank’s movement is dependant on the two speed variables. Using the current x/y coordinates of the tank, and their current angular speed, we can adjust the current angle of the sprite. Using the movement speed and the (possibly new) sprite angle, we can

Page 5: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

4

develop a vector to a new set of x/y coordinate points, as illustrated in the following figure:

Should either the angular speed or the movement speed are set to 0, the angle or forward/backward movement would also be, respectively, 0. During the rotation, the tank’s image itself is actually rotated. Although seemingly simpler to just rotate the tank image by increments as key events are processed, the image itself becomes distorted over time, due to the rotations. To correct this, we always begin rotation with the original image information of the tank, and rotate to the angle required, over and over again. This makes sure the tank image is never distorted. The tank class has one other function designed to grab all important information, the x/y coordinates and current tank angle, and return this value as a two-tuple. This function is called when sending updated tank information to the server. 3.3 The Threaded Client

The client is made up of two threads, one that deals with the GUI and one that deals with the interaction with the server. These two threads must work in tandem to update tanks

Page 6: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

5

that are under the control of other clients, so the movement of each tank appears to be the same on all screens. 3.3.1 – The GUI Thread Everything so far explained describes how the GUI of the client basically works, but it does not cover any interaction with the server, nor does it explain how other client’s tanks are displayed on everyone’s screen. This section will dive into the details of this process.

Step 1 – Connect to the Server

The first step in preparing the client is to initialize a socket and attempt to connect to the server. It’s best not to dive into detail regarding exactly how this is done, but it is important to know what happens during the process. After a connection to the server is established, a request is sent for a unique ID number from the server, to identify the client among any other clients currently connected to the server. Once this information has been obtained from the server (the server will record this information as well), the client’s GUI thread is started. Step 2 – Prepare the GUI, Begin Main Event Loop It is important to note that the thread handling communication with the server continues to run, sending updated client tank information to the server, and receiving a list of all tanks currently in game from the server. Meanwhile, the GUI thread begins to run. During this step, the tank controlled by this client, known hereafter as playertank is created. Its initial values are set and this object is then added into the sprite group RenderUpdates. Please note that details regarding sprites and groups will not be discussed in this outline, nor will general PyGame code implementation. It is assumed the reader understands these concepts. After the playertank and other PyGame variables have been set and initialized, the program will enter the main event loop, as described in Section 3.1.

Step 3 – The Main Event Loop

The general parts of the main event loop are described in section 3.1. The last step of this event loop will now be further described in this section. Because users may be connecting and disconnecting from the server in real time, it is important to keep track of how many tanks there are in play at any given time, and their location. With this information, we can create new tank objects as required, or update existing tanks. This information is kept in a Python dictionary (similar to Java’s hashtables) named TankDictionary. The complete TankDictionary is located server-side. As it is updated

Page 7: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

6

with current tank information from each client, this dictionary is sent back to each client. This particular series of events are maintained and controlled by the server communication thread in the client, which will be discussed in detail in the next subsection, 3.3.2. This other thread will basically handle updating the client’s TankDictionary, ensuring that when the GUI thread uses that dictionary to update all tank information on the screen it will (more or less) be up to date. If the TankDictionary has been updated to contain tank information for a tank that does not yet exist (that is, the client controlling said tank just connected to the server) a new tank object will be created, and thus a new sprite will be created to display to the screen. The information in the dictionary regarding coordinates and angle will be given to the tank object, and the tank will be added to the RenderUpdates group, so that this object’s update function will be called along with all other tanks. If the TankDictionary contains information about a tank that already exists, the information in the dictionary will be sent to the corresponding tank object so that object’s attributes can be updated. If there exists a tank object for which the TankDictionary has no information, that tank is removed from the RenderUpdates group, and will be freed from memory by PyGame’s garbage collection. This will remove the tank from the screen (as its client has disconnected). Note: This last feature has NOT been implemented yet. When the main event loop runs again, the new tanks will be displayed, existing tanks will be displayed in new positions, and (eventually) tanks that should no longer appear on screen will be removed. 3.3.2 – The Socket Thread The socket thread of the client is actually the first thing to run when the client is executed. It is responsible for contacting the server, obtaining a client/tank ID, sending updated PlayerTank information, and obtaining TankDictionary updates. The details involved are fairly simplistic. It is important to note that the socket created by this thread is currently TCP. The reason behind not currently using UDP is for simplicity. We will eventually develop a UDP socket design as the project continues. Please see Section 4.4 for more details.

Page 8: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

7

3.4 The Threadpool Server The server environment chosen for this project is a thread-pool design intended to increase the speed of threads in Python. Due to the fact that Python’s threading abilities were incorporated into the language long after the language’s initial inception, the Python Interpreter tends to be less than ‘thread-safe’. With that in mind, and due to the large overhead of thread usage with Python, one might easily wish to opt out of thread use. However, it is important to understand that to truly work with a multiplayer environment, threading is an option to seriously consider. Designing a threadpool will dramatically reduce this overhead problem associated with Python threads, but the issue of ‘Locking’ remains a problem. In order to synchronize threads to ensure that certain threads are not attempting to access information while others are attempting to change it, we use locking. Creating a Python lock is not seriously complicated, and involves ‘acquiring’ the lock, and so announcing to other threads that the next section of code must complete execution before any other thread can continue, and then ‘releasing’ the lock, allowing any thread to proceed. Because Python does not support thread priority in an substantial fashion, the interpreter has an internal lock in order to prevent thread starvation. Threads usually tend to run for 10 Python bytecodes before this internal lock is release in order to allow other threads to proceed. This produces a distinct issue, since no thread has a higher priority than any other, and it is likely that, unless kept in check, two threads may attempt to access data in parallel, causing all kinds of errors. Python’s Lock, Semaphore, Queue and Condition classes attempt to solve this problem. Although there are no exact numbers, there is a general consensus among the Python programming community that these actions are expensive and tend to slow threads down. Despite these warnings, we will proceed to experiment with threads in hopes that they will serve our needs. 3.4 Client/Server Interaction

Step 1 Server - creates a TCP socket, binds the socket to a specific port (in our case, port 51423), and begins listening for a client requesting connection. Client – creates a TCP socket and attempts to connect to the server (in our case, on port 51423 of some machine with IP address X, where X has been supplied by the user) Step 2 Server – after the connection has been established, the server creates a new thread for this specific client, set to handle this client alone. Wait for client ID request or disconnection. Client – send a request for the server to give this client a unique ID.

Page 9: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

8

Step 3 Server – receive client request for ID, spawn ID and send this to client Client – wait for ID Step 4 Server – switch to UDP and send tank dictionary (switching to UDP has not yet been completed) Client – receive ID, switch to UDP and wait for tank dictionary. Initialize GUI thread. Step 5 Server – wait for client playertank update Client – receive tank dictionary, update local dictionary. Return client playertank update. Step 6 Server – receive playertank update. Update server tank dictionary. Return new tank dictionary to client Client – wait for updated dictionary. Step 7 Return to step 5.

Page 10: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

9

Page 11: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

10

4. Testing 4.1 Project Implementation Table

As of this writing, the implementation of this project is well on its way to completion. A note on how this table works: Items that are Not Started mean just that; development has not yet begun on these items. Items that are set as Working are items that are currently in development that have not been completed or implemented. Completed items are items that have been finished, but, due to their nature (i.e. testing code) that will not be used in the final solution. Implemented items are items that have been completed and have also been implemented into the current project.

Task Implemented Completed Working Not StartedPyGame General Sprite Test X

XXX

XXXXX

XX

XX

XX XX X

XXXXXXXX

PyGame Sprite Movement TestPyGame Sprite Rotation TestPyGame Sprite Groups TestPyGame Sound TestPyGame Complicated Tank Sprite TestPyGame Collision Detection TestTank Firing Shots TestKeyboard and Mouse Controls Test

Client/Server TCP TestsClient/Server UDP TestsClient/Server Broadcasting TestsPython Threading TestsCombining TCP and UDPThreadpool Server TestsCombining PyGame and Client Tests

Maps (Walls Etc)Different WeaponsTank DamageTeam Play (several clients competing on a team)Client/Server Optimization (code optimization)Sound Collecting/CreationSpecial Effects for weaponsServer-Finding System

Page 12: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

11

4.2 – Overview of Completed Items All completed items, as explained, are items specifically made or designed to test feasibility of ideas, or to learn parts of Python or PyGame in order to understand the inner workings of these two platforms. These items were never intended to become part of the end solution. 4.3 – Overview of Items in Progress Items in progress, or Working Items, are items that have not yet been completed or implemented. Many of these items include future ideas for the project, such as implementing sound or special effects, Team Play etc. Several of these future ideas are already in working order, to test their feasibility (i.e. how difficult they would be to implement) or simply just to understand how to do (such as in the case of sound, we try simply to make a Python program make a sound). A specific note regarding one working item, combining TCP and UDP, is an important part of the client/server model. Because UDP performs much faster than TCP, it is important that UDP is implemented as described in sections 3.3.2 and 3.3.3. Although this item is a work in progress, it is still not ready for implementation. The fully TCP client/server model, however, is fully functional, if not a little slow for our needs. Therefore, the current code implementation included with this document includes only the TCP client/server model. It is estimated that the UDP/TCP implementation will be working by the end of phase 2. 4.4 – Overview of Implemented Items Implemented items are those that are to be employed for use in the project solution. These items are completed and were developed specifically for use in the project. They were not designed to be ‘test code’ for learning or feasibility. Currently implemented items can be found in the coded implementation in sections 5.1 and 5.2. 4.5 – Overview of Future Items Future Items are those items that have yet to be started. They are basically ideas on the drawing board to add to the project, but are not part of the minimum project requirements. This particular part of the list grows and shrinks as ideas are developed or dropped from the list of desired requirements.

Page 13: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

12

5. Implementation 5.1 – Client Code

try: import sys import os import math import time import pygame import socket import pickle from pygame.locals import * from threading import * except ImportError, err: print "Could not load module: %s" % (err) sys.exit(2) if not pygame.font: print 'Warning, fonts disabled' if not pygame.mixer: print 'Warning, sound disabled' # START PYGAME CODE HERE # def load_image(name, colorkey=None): fullname = os.path.join('data', name) try: image = pygame.image.load(fullname) except pygame.error, message: print 'Cannot load image:', fullname raise SystemExit, message image = image.convert() if colorkey is not None: if colorkey is -1: colorkey = image.get_at((0,0)) image.set_colorkey(colorkey, RLEACCEL) return image, image.get_rect() # tank class class Tank(pygame.sprite.Sprite): """Class Tank self.tank_id - id of this tank object self.xy_center - center of sprite rectangle as tuple (x, y) self.angle - angle of attack in x-y plane self.original_image - storage of tank image (used to rotate image) self.move_speed - current move speed of sprite (used during update method) self.angle_speed - angular turn speed""" def __init__(self, tank_id=0, xy_center=(0,0), angle=0): """method '__init__' initializes Tank object Inputs: tank_id - integer identifying this object xy_center - (x, y) tuple indecating center of rectangle angle - taken as degrees, to be converted to radians""" pygame.sprite.Sprite.__init__(self) # initialize sprite self.image, self.rect = load_image('tank.bmp', -1) # load image

Page 14: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

13

self.original_image = self.image # set self.original_image self.tank_id = tank_id # set self.tank_id self.xy_center = xy_center # set self.xy_center self.angle = angle * (180 / math.pi) # set self.angle (change to radians) self.move_speed = 0 # set self.move_speed self.angle_speed = 0 # set self.angle_speed self.rect.center = xy_center # set rectangle center self.rotate() screen = pygame.display.get_surface() self.area = screen.get_rect() def update(self): global serv_tank_dict global cv cv.acquire() if serv_tank_dict.has_key(self.tank_id): if self.tank_id <> playertank_id: update = serv_tank_dict[self.tank_id] self.xy_center = update[0] self.rect.center = self.xy_center self.angle = update[1] self.rotate() #sprint "Updating Tank: ", self.tank_id, " to ", update cv.release() if self.angle_speed > 0: self.angle = self.angle + self.angle_speed if self.angle > (360 * (math.pi / 180)): self.angle = 0 self.rotate() elif self.angle_speed < 0: self.angle = self.angle + self.angle_speed if self.angle < 0: self.angle = (360 * (math.pi / 180)) self.rotate() if self.move_speed != 0: angle = self.angle x = self.move_speed * math.cos(angle) y = self.move_speed * math.sin(angle) new_rect = self.rect.move(x, y) # the following block checks for and handles collisions #inflated_rect = self.rect.inflate(-12, -12) #if inflated_rect.collidelist(collidelist) == -1: # new_rect = self.rect.move(x, y) #else: # new_rect = self.rect.move(-x * 5, -y * 5) # self.move_speed = 0 if new_rect.right < 0: new_rect.left = 640 elif new_rect.left > 640: new_rect.right = 0 elif new_rect.bottom < 0: new_rect.top = 480 elif new_rect.top > 480: new_rect.bottom = 0

Page 15: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

14

self.rect = new_rect self.xy_center = self.rect.center def rotate(self): angle_d = self.angle * (-180 / math.pi) # convert angle to degrees self.xy_center = self.rect.center self.image = pygame.transform.rotate( self.original_image, angle_d) self.rect = self.image.get_rect() self.rect.center = self.xy_center def update_move_speed(self, new_speed): self.move_speed = new_speed def update_angle_speed(self, new_speed): self.angle_speed = new_speed def getTankInfo(self): return (self.tank_id, self.xy_center, self.angle) # main function (with event loop) def mainloop(): #globalize variables global KILLTHREADS global serv_tank_dict local_tank_dict = {} global playertank global playertank_id global cv print "I AM ", playertank_id # initialize screen pygame.init() screen = pygame.display.set_mode((640, 480)) pygame.display.set_caption('Pygame Test 1') # fill background background = pygame.Surface(screen.get_size()) background = background.convert() background.fill((0, 0, 0)) # init player tank xy = (200, 200) move_speed = 0 angle = 0 cv.acquire() playertank = Tank(playertank_id, (0, 0), 0) KILLTHREADS = 0 cv.notify() cv.release() # init/re-init sprite and collide lists

Page 16: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

15

collidelist = pygame.sprite.Group() # contains all sprites EXCLUDING playertank spritelist = pygame.sprite.Group() # contains ALL SPRITES spritelist.add(playertank) # add playertank to initial list # list of all tanks (only player tank at this point in time) sprites = pygame.sprite.RenderUpdates() sprites.add(playertank) # init clock clock = pygame.time.Clock() # blit everything screen.blit(background, (0, 0)) pygame.display.flip() tanklist = {} # main event loop while 1: clock.tick(60) # curb framerate to 60fps for event in pygame.event.get(): if event.type == QUIT: KILLTHREADS = 1 return elif event.type == KEYDOWN: if event.key == K_UP: playertank.update_move_speed(-3) elif event.key == K_DOWN: playertank.update_move_speed(3) elif event.key == K_LEFT: playertank.update_angle_speed(-0.05) elif event.key == K_RIGHT: playertank.update_angle_speed(0.05) elif event.type == KEYUP: if event.key == K_LEFT or event.key == K_RIGHT: playertank.update_angle_speed(0) elif event.key == K_UP or event.key == K_DOWN: playertank.update_move_speed(0) # get tank dictionary and insert info into lists tmp_serv_tank_dict = serv_tank_dict for n in sprites.spritedict.keys(): if tmp_serv_tank_dict.has_key(n.tank_id): del tmp_serv_tank_dict[n.tank_id] for n in tmp_serv_tank_dict: data = tmp_serv_tank_dict[n] newTank = Tank(n, data[0], data[1]) tanklist[n] = newTank print "New Tank: ", newTank.getTankInfo() sprites.add(newTank) # get new spritelist from server as necessary # TO BE COMPLETED LATER sprites.update()

Page 17: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

16

rectlist = sprites.draw(screen) pygame.display.update(rectlist) sprites.clear(screen, background) # END PYGAME CODE HERE # # START CLIENT CODE HERE # def client_main(): # globalize variables global KILLTHREADS global serv_tank_dict global playertank global playertank_id global cv try: #print 'Creating Socket Object' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) except socket.error, e: print "Error creating socket: %s" % e sys.exit(1) try: port = int(textport) except ValueError: try: port = socket.getservbyname(textport, 'tcp') except socket.error, e: print "Couldn't connect to port %s" % e sys.exit(1) #print 'Connecting to %s:%d' % (host, port) try: s.connect((host, port)) except socket.gaierror, e: print "Address-related error connecting to server: %s" % e sys.exit(1) except socket.error, e: print "Connection error: %s" % e sys.exit(1) # initial server contact, get TANK ID cv.acquire() try: send_data = 'id_req'

Page 18: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

17

s.sendall(pickle.dumps(send_data)) except socket.error, e: print "Error sending data: %s" % e sys.exit(1) #print 'Waiting to recieve data' count = 0 try: buf = s.recv(2048) except socket.error, e: print "Error receiving data: %s" % e sys.exit(1) count += len(buf) #print 'Recieved: ', pickle.loads(buf), '\n' #print 'Received %d bytes' % count playertank_id = pickle.loads(buf) # start main loop thread (graphic client) KILLTHREADS = 2 thread_mainloop.start() cv.notify() cv.release() while KILLTHREADS == 2: time.sleep(0.05) # server contact loop while 1: #print '(', playertank_id, ') Sending Message' if KILLTHREADS == 1: break; try: send_data = playertank.getTankInfo() s.sendall(pickle.dumps(send_data)) except socket.error, e: print "Error sending data: %s" % e sys.exit(1) #print 'Waiting to recieve data' count = 0 try: buf = s.recv(2048) except socket.error, e: print "Error receiving data: %s" % e sys.exit(1) if not len(buf): break count += len(buf) data = pickle.loads(buf) #print 'Received %d bytes' % count cv.acquire() serv_tank_dict = data

Page 19: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

18

cv.release() #print tank_dict time.sleep(.01) print 'Shutting down socket' try: s.shutdown(1) except socket.error, e: print "Error sending data (detected by shutdown): %s" % e sys.exit(1) # END CLIENT CODE HERE # # GLOBAL VARIABLES # host = sys.argv[1] textport = '51423' cv = Condition() equeue = [] KILLTHREADS = 0 serv_tank_dict = {} playertank = None playertank_id = 0 # start client socket connection thread thread_client = Thread(target = client_main) thread_client.setDaemon(2) thread_client.start() # create main loop thread (graphic client) thread_mainloop = Thread(target = mainloop) thread_mainloop.setDaemon(0) # end client socket connection thread thread_client.join() # end main loop thread if main loop exits thread_mainloop.join()

Page 20: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

19

5.2 – Server Code #!/usr/bin/env python # Thread Pool - Chapter 21 - threadpool.py import socket, traceback, os, sys, time, pickle from threading import * host = '' # Bind to all interfaces port = 51423 MAXTHREADS = 2 lockpool = Lock() busylist = {} waitinglist = {} queue = [] sem = Semaphore(0) tank_dict = {} def handleconnection(clientsock): """Handle an incoming client connection.""" lockpool.acquire() print "Received new client connection." try: if len(waitinglist) == 0 and (activeCount() - 1) >= MAXTHREADS: # Too many connections. Just close it and exit. clientsock.close() return if len(waitinglist) == 0: startthread() queue.append(clientsock) sem.release() finally: lockpool.release() def startthread(): # Called by handleconnection when a new thread is needed. # Note: lockpool is already acquired when this function is called. print "Starting new client processor thread" t = Thread(target = threadworker) t.setDaemon(1) t.start() def threadworker(): global waitinglist, lockpool, busylist name = currentThread().getName() try: lockpool.acquire() try: waitinglist[name] = 1 finally: lockpool.release() processclients() finally: # Clean up if the thread is dying for some reason. # Can't lock here -- we may already hold the lock, but it's OK print "** WARNING** Thread %s died" % name if name in waitinglist: del waitinglist[name] if name in busylist: del busylist[name]

Page 21: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

20

# Start a replacement thread. startthread() def processclients(): global sem, queue, waitinglist, busylist, lockpool, tank_dict name = currentThread().getName() while 1: sem.acquire() lockpool.acquire() try: clientsock = queue.pop(0) del waitinglist[name] busylist[name] = 1 finally: lockpool.release() try: print "[%s] Got connection from %s" % \ (name, clientsock.getpeername()) # send tank id number clientsock.sendall(pickle.dumps(clientsock.getpeername())) # send and recieve tank data while 1: data = clientsock.recv(4096) if data.startswith('DIE'): sys.exit(0) if not len(data): break # lock to set tank_dict lockpool.acquire() data = pickle.loads(data) #print "sending ", len(data), " bytes to ", clientsock.getpeername() # if tank already exists in dict, just update it, otherwise create entry if data <> 'id_req': tank_dict[data[0]] = (data[1], data[2]) lockpool.release() clientsock.sendall(pickle.dumps(tank_dict)) #print tank_dict except (KeyboardInterrupt, SystemExit): raise except: traceback.print_exc() # Close the connection try: key = clientsock.getpeername() clientsock.close() print "client connection closed: ", key except KeyboardInterrupt:

Page 22: CS 4431 Advanced Project Stage I Specifications and ...flash.lakeheadu.ca/~mhahn/Part 1 Writeup.pdf · • Angular rotation of tank sprites on screen (described in Section 3.1) •

21

raise except: traceback.print_exc() lockpool.acquire() try: del busylist[name] del tank_dict[key] waitinglist[name] = 1 finally: lockpool.release() def listener(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) s.listen(1) while 1: try: clientsock, clientaddr = s.accept() except KeyboardInterrupt: raise except: traceback.print_exc() continue handleconnection(clientsock) listener()