The Address Book as ClientServer

Back in the files topic we built a version of our address book using a dictionary. Let's reuse that example but this time we will build a client/server version.

Notice that the original code broke one of the good practice rules we discussed earlier: I included User Interface code in my helper functions. If we were to try to use this code we would get messages from the child process mixed up with messages from the parent. We need to tweak the code slightly so that we can turn it into a reusable module. The main thing is to remove any print statements from the functions and pass the data in as arguments. We also want to return a result from each function.

Once we have done that we can import the code and access the helper functions without executing the main() function. The functions that we will make available are therefore:

• readBook(filename)

• saveBook(book,filename)

• removeEntry(book, name)

The modified code looks like this:

def readBook(filename='addbook.dat'): import os book = {}

if os.path.exists(filename): store = file(filename,'r') for line in store:

name,entry = line.strip().split(':') book[name] = entry else:

store = file(filename,'w') # create new empty file store.close() return book def saveBook(book, filename = "addbook.dat"): store = file(filename,'w') for name,entry in book.items(): line = "%s:%s" % (name,entry) store.write(line + '\n') store.close()

def addEntry(book, name, data): book[name] = data return 'Added entry for ' + name def removeEntry(book, name): del(book[name])

return 'Deleted entry for ' + name def findEntry(book, name): if name in book.keys():

result = "%s : %s" % (name, book[name]) else: result = "Sorry, no entry for: " + name return result

Note that I've ignored the user interface functions because we don't need them here but you might want to try making the necessary modifications to allow it to still function as a standalone program as well as serve as a module for the client/server version as an exercise.

Once you've fixed up the code and got it working as a stand-alone program once more (or just saved the code above as address_srv.py, which is what I've done, we can proceed to writing our client/server code.

The client/server code will comprise our standard structure of creating pipes and forking the process. In the child process we will read the incoming pipe and interpret the data as a command followed by the arguments supplied and then call the relevant database function. In the parent, or client process, we will present the user with a menu and depending on the choice, request any needed additional data before sending the combined data string to the child or server process. The client will then read back the response and present it to the user.

The main program looks like this:

import os, signal, address_srv fromClient,toServer = os.pipe() fromServer,toClient = os.pipe()

addresses = address_srv.readBook() while True:

s = os.read(fromClient,102 4) cmd,data = s.split(':') if cmd == "add":

name = details[0]

s = address_srv.addEntry(addresses, name, entry) address_srv.saveBook(addresses) elif cmd == "rem":

s = address_srv.removeEntry(addresses, data) elif cmd == "fnd":

s = address_srv.findEntry(addresses, data) else: s = "ERROR: Unrecognized command: " + cmd os.write(toClient,s)

1) Add Entry

2) Delete Entry

3) Find Entry

while True: print menu try: choice = int(raw_input('Choose an option[1-4] ')) except: continue if choice == 1:

name = raw_input('Enter the name: ') num = raw_input('Enter the House number: ') street= raw_input('Enter the Street name: ') town = raw_input('Enter the Town: ') phone = raw_input('Enter the Phone number: ') data = "%s,%s %s, %s, %s" % (name,num,street,town,phone) cmd = "add:%s" % data elif choice == 2:

name = raw_input('Enter the name: ') cmd = 'rem:%s' % name elif choice == 3:

name = raw_input('Enter the name: ') cmd = 'fnd:%s' % name elif choice == 4:

break else:

print "Invalid choice, must be between 1 and 4." continue os.write(toServer, cmd)

print "\nRESULT: ", os.read(fromServer,102 4)

os.kill(pid, signal.SIGTERM)

Obviously we could tidy that up a bit more by using some functions for the if/elif chains but the example is small enough for that not to be necessary. A couple of points to note are the use of break to stop the loop and continue to go round to the top of the loop again. We didn't discuss these in the looping topic but hopefully their use is obvious and if not the documentation describes them in more detail.

An obvious extension to this exercise would be to use the database version of the address book as the server instead of the dictionary version. I'll leave that as an exercise for the keen students among you!

The big snag with this form of client/server programming is that the server can only talk to the client that started it. It would be much better if the server could be started first and then multiple clients attach to it. That's exactly what we will do at the end of the next topic where we introduce networking.

0 0

Post a comment

  • Receive news updates via email from this site