#!/usr/bin/python3 """Receives and passes pickled objects to an unspecified number of clients.""" import socket, sys, select, pickle sys.path.append('../model') from threading import Timer from mysockets import MySocket from Instruction import Instruction from LocalWorld import LocalWorld from Species import Species import Direction from WorldSpec import WorldSpec sys.path.append('../gui') #import all things gui class WorldServer(MySocket): def __init__(self, port = 32333, width=500, height=500, species=None, db = None): """Constructs a WorldServer object, should take a gui object Socket is at the localhost with default port # 32323""" #socket creation self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server.bind((socket.gethostname(), port)) self.server.listen(1000) #creation of multiple global variables necessary to pass information self.readers = [self.server] self.writers = [] self.stillaccepting = True #creates all of the cases for choosehandler self.cases = [\ (str, self.stringhandler), \ (Instruction, self.handleinstruction),\ (list, self.listhandler),\ (socket, self.rejecthandler),\ (None, self.disconnecthandler)] #sets variables for permissions and the world self.setup_participants = None self.allowed_to_make_changes = None self.world = LocalWorld(species, width, height) self.world.addListener(self) #worldSpecs should also def addListener(self, listener): '''act like a World''' self.listener = listener def main(self, special = None): try: self.acceptclients() #parameters from GUI/command line? self.netsetup(1, 2) #same here self.mainloop(self) finally: self.server.close() def acceptclients(self, maxclients = 4, time = 20): """Until time runs out, server accepts up to maxclients to start game (copied but not removed from main)""" print("accepting clients") t = Timer(time, lambda:1) t.start() #loops when time has not run out and the server has not run out #of space. Reads itself to get the information on connecting sockets #then passes to socket handler while t._stopped == False and len(self.readers) <= maxclients: read, write, err = select.select(self.readers, [], [], 1) for r in read: self.sockethandler(r) t.cancel() print("Done accepting clients!") self.stillaccepting = False def rotate(self, li): '''move the first item in a list to the back and return that value precondition: li is a non-empty list postcondition: the first element of li is now the last element of li the value of the first element is returned ''' val = li.pop(0) li.append(val) return val def netsetup(self, time, total): ''' Parameters: time, number of seconds total, integer number of iterations per listener Preconditions: There must be at least 1 listener Postconditions: It will use a predictable round robin format to determing when people are allowed to make changes to the board. It will tell people when they have their turn to make changes.''' '''use threading.timer to change which client socket is allowed to submit changes. Keep track of this in some sort of data structure, removing a client when its time is elapsed Do something hackish with global variables to allow the Instruction Handler to know what to do with Instruction ''' '''rotate through the list of participants. Start a timer. When the timer expires, rotate the list again, taking the first element to be 'special' (allowed to submit changes), and start a new timer. Stop when we have received enough changes. ''' if len(self.writers) > 0: self.setup_participants = [w for w in self.writers] self.allowed_to_make_changes = self.rotate(self.setup_participants) count = 0 #at the end of time, run rotate, allowing another person to place object(s) #timer = Timer\ #(time, self.rotate, [self.setup_participants]) timer = Timer(time, lambda:1) timer.start() #iterate to allow total turns for each person while count < total*len(self.writers): if timer._stopped: #This cannot be placed in timer because timer doesn't update allowed self.allowed_to_make_changes = self.rotate(self.writers) #timer = Timer\ #(time, self.rotate, [self.setup_participants]) timer = Timer(time, lambda:1) timer.start() #check every port to see any information added read, write, error = select.select(self.readers, [], [], 1) count+=1 #can handle other situations, but will usually pass to handle instruction for s in read: self.sockethandler(s) def mainloop(self, special = None): """Cycles through each client connected to the server, receiving and sending pickled objects, or adding new clients""" '''mainloop: DEPRECATED!''' cat = Species("cat", 'black') catplacer = Instruction(Direction.C, cat) ourList = [catplacer] self.sendtoall(ourList) while True: print("Waiting to read") read, write, error = select.select(self.readers, [], [], 1) print(repr(read)) #Nothing seems to add to response? response = [] for s in read: self.sockethandler(s) print("Waiting to send") read, write2, error = select.select([], self.writers, [], 1) print(repr(write2)) if len(response) > 0: print('ready to send') self.sendtoall(response.pop(0)) self.world.step() def sockethandler(self, sock): ''' Parameters: sock, a socket to read from Preconditions: A successful read statement is sent to this through sock. Postconditions: If a socket is connecting while it can, connect that socket. Otherwise, pass it to an appropiate handler. If it reads itself, it must be getting a new client, so accept it if one can. Otherwise, check if a client is disconnecting, by recieving no object. If it does so, remove it from the list''' if sock == self.server and self.stillaccepting: (client, address) = sock.accept() self.write(client,self.world.toWorldSpec()) self.readers.append(client) self.writers.append(client) print('accepted a client!') #We may want a return statement of some sort so that other clients #know of the existence of the new one. '''rejecthandler is tested for and called separa''' elif sock == self.server and not self.stillaccepting: self.rejecthandler(sock) else: obj = self.read(sock) self.choosehandler(obj, sock) def choosehandler(self, obj, sock): '''compare obj to the first item in each tuple of case and execute the second item if they match. There should probably be a separate handler for sockets If there is not a case, ''' for expected, response in self.cases: try: if isinstance(obj, expected): response(obj, sock) except TypeError as e: if obj == expected: response(sock) def rejecthandler(self, sock): '''method for rejecting clients when the accepting clients function is done''' '''A client will recieve True if it successfully connects, and False if it doesnt''' (client, address) = sock.accept() self.write(client,False) client.close() def disconnecthandler(self, sock): '''method for dealing with clients disconnecting''' print("Client disconnected!") self.readers.remove(sock) self.writers.remove(sock) sock.close() def stringhandler(self, st, sock): #What is this even supposed to do? Do any other procedures intend #to use chatecho? print(st) self.chatecho(st, sock) def chatecho(self, st, sock): '''Chatecho sends the recieved message to all other listening clients''' read, write, err = select.select([], self.writers, [], 1) for w in write: if w != sock: self.write(w, st) def handleinstruction(self, inst, sock): '''Probably will never be recieving instructions''' if sock in self.allowed_to_make_changes: self.world.setSpeciesFromInstruction(inst) return True else: return False def listhandler(self, li, sock): '''Listhandler will recieve a list of changes from local world and then pass these along to its listeners, the clients''' for ele in li: handleinstruction(ele, sock) def sendtoall(self, obj): '''A method for sending things to all listening sockets''' read, write, err = select.select([], self.writers, [], 0) for w in write: self.write(w, obj) def net_read(self): '''use select to see if there are any sockets to read from. While there are sockets to read from, read them and deal with whatever comes out ''' readers, writers, err = select.select(self.readers, [], [], 0) while readers != []: for sock in readers: self.sockethandler(sock) readers, writers, err = select.select(self.readers, [], [], 0) def update(self, changes): self.listener.update(changes) self.sendtoall(changes) def step(self): '''act like a World''' self.world.step() self.net_read() #deal with our sockets #self.world.step() def getSpecies(self, x, y): '''return a species from within the localworld''' return self.world.getSpecies(x, y) def setSpecies(self, x, y, s): self.world.setSpecies(x, y, s) self.sendtoall([Instruction(Direction.C, s, (x,y))]) return True #This guy has problems. Main does not autorun, which I think this is supposed to #allow? if __name__=='__main__': if len(sys.argv) == 2: WorldServer(int(sys.argv[1])).main() else: WorldServer().main()