Wiley.Mobile.Python.Rapid.prototyping.of.applications.on.the.mobile.platform.Dec.2007 (779889), страница 36
Текст из файла (страница 36)
The first part, Example 80, introduces somecore data structures and the client registration mechanism.Example 80: Generic JSON gateway (1/2)import SocketServer, threading, jsonconn = {}186MOBILE NETWORKINGconn_lock = threading.Lock()class ThreadingServer(SocketServer.ThreadingMixIn,SocketServer.TCPServer):allow_reuse_address = Trueclass Handler(SocketServer.StreamRequestHandler):def handle(self):print "A new client connected", self.client_addressmsg = json.read_stream(self.rfile)if "!name" in msg:name = msg["!name"]wlock = threading.Lock()conn_lock.acquire()conn[name] = (wlock, self.wfile)conn_lock.release()print "Client registered (%s)" % namereply = {"ok": u"registered"}self.wfile.write(json.write(reply))self.wfile.flush()else:reply = {"err": u"invalid name"}self.wfile.write(json.write(reply))returnhandle_connection(self, name)The core of the server is the dictionary conn, which is shared by allhandler threads.
It maps registered names to the corresponding connections. Any phone that wants to participate in peer-to-peer networkingmust open a connection and register a name with the server.Since multiple threads may add and remove keys from the dictionaryin parallel, its integrity must be protected with a lock (conn lock). Athread may access the dictionary only when it has acquired the lock.To make the access mutually exclusive, the lock can be held by onlyone thread at a time.
Do not worry if locking issues confuse you. It is anotoriously difficult subject.When a new client connects to the gateway, it first sends a messagecontaining the key ‘!name’ to it. This key maps to a name that identifiesthis phone. Note that the gateway does not check whether another phonewith the same name has registered already.
The gateway just blindlyreplaces the old connection with a new one. The gateway sends a replycontaining the key ‘ok’ to the client if registration was successful.This means that anyone can hijack an existing connection to thegateway and pretend to be the original phone. If the gateway was tobe used in an uncontrolled setting, some other approach for handlingalready registered names would need to be implemented. This protocolis not meant for secure communication.Example 81: Generic JSON gateway (2/2)def handle_connection(self, name):PEER-TO-PEER NETWORKING187while True:try:msg = json.read_stream(self.rfile)except:msg = {"!close": True}if "!close" in msg:print "Client exits (%s): %s" % name, self.client_address)conn_lock.acquire()if name in conn:del conn[name]conn_lock.release()breakelif "!dst" in msg:wfile = Noneconn_lock.acquire()if msg["!dst"] in conn:wlock, wfile = conn[msg["!dst"]]conn_lock.release()if wfile:wlock.acquire()try:wfile.write(json.write(msg))wfile.flush()finally:wlock.release()server = ThreadingServer((’’, 9000), Handler)print "JSON gateway is running!"print "Waiting for new clients..."server.serve_forever()The second part of the gateway contains the function handle connection(), which is used to handle a connection between a single phone and the gateway.
Note that, because of threading, manyhandle connection() functions are active simultaneously in thegateway, each handling an individual phone.Multiple clients may want to push a message to the same client atthe same time. Since separate messages must not be mixed up in theconnection, only one thread may write to a connection at once. This isensured by a connection-specific lock, wlock.Note that messages are read with the function json.read streaminstead of json.read, which has been used in the previous examples.This function parses a message byte by byte from the connection andblocks until it has completely read one message.
With this function,the client can use a single permanent connection to send and receivemultiple messages.The server is oblivious to errors in message sending. If the recipientmoves away, say, from a WiFi hotspot while a message is being sent, themessage is quietly lost.
This also happens if the recipient for a message isunknown. Thus, any application using this gateway must understand thatany message may be silently lost.188MOBILE NETWORKINGIf an error occurs while a message is being read, the gateway deregistersthe client, assuming that it has lost its network connection and will reconnect when a network becomes available again. However, note that ifanother client registers to the gateway using the same name, subsequentmessages will be routed to this new, possibly malicious, client.This server is run in a similar way to the previous example servers,such as the simple TCP server (Example 69). Naturally, you need to startthe server before any phones can start to communicate with each other.The gateway server can easily be extended with new features.
Forexample, it might be useful to be able to distribute a message to multiplerecipients. Broadcasting like this should not be difficult to implement:Just add a new protocol key, say "!broad" and loop through the conndictionary, pushing the message to each active connection.8.6.2 Instant MessengerA natural application for peer-to-peer communication is instant messaging.
With this application, any number of phones can take and sendphotos and short text messages to each other over the Internet or alocal wireless network. If all participants have WiFi-capable phones andthey use a free wireless LAN, you can use this application to share anunlimited number of photos and text messages with your friends at nocost!Technically, this example shows how to build applications on top of theJSON gateway. The example presents a generic client-side counterpart forthe JSON gateway, which can easily be used by peer-to-peer applicationsof your own.This code also exemplifies how to handle network events in an asynchronous manner in PyS60 using threads.
We do not explain threading indetail in this book, but you can use the following functions in applicationsof your own. As a non-trivial exercise, you could make the Bluetoothchat application in Section 7.3 asynchronous by modifying the instantmessenger code to use Bluetooth for communication.The example is divided into three parts. Example 82 contains the reusable communication infrastructure. Examples 83 and 84 are related tothe user interface of the instant messaging application.
As usual, youshould combine these three parts to make the final application.Example 82: Instant messenger (1/3)import appuifw, e32, camera, thread, socket, json, graphicsSERVER = ("192.168.0.2", 9000)def send_message(msg):global to_servertry:PEER-TO-PEER NETWORKING189to_server.write(json.write(msg))to_server.flush()thread_handle_message({"note": u"Message sent!"})except Exception, ex:print "Connection error", exto_server = Nonedef read_message():global to_servertry:msg = json.read_stream(to_server)thread_handle_message(msg)except Exception:print "Broken connection"to_server = Nonedef connect():global to_server, keep_talking, connconn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)conn.connect(SERVER)to_server = conn.makefile("rw", 8192)send_message({"!name": me})ret = json.read_stream(to_server)if "err" in ret:thread_handle_message({"note": u"Login failed"})thread_show_note(u"Login failed: %s" % ret["err"], "error")keep_talking = Falseraise "Login failed"else:thread_handle_message({"note": u"Login ok"})def communicate():global to_server, keep_talking, thread_send_message, app_lockthread_send_message = e32.ao_callgate(send_message)to_server = Nonewhile keep_talking:if to_server:read_message()else:try:connect()thread_handle_message({"note": u"Waiting for\messages..."})except:print "Could not connect to server"to_server = Noneif keep_talking:e32.ao_sleep(10)if conn:conn.close()if to_server:to_server.close()The combination of an interactive user interface and asynchronous,two-way communication with a server is a tricky one.
The applicationmust listen to both the user and the server simultaneously, as either ofthem may need attention at any time.190MOBILE NETWORKINGTo fulfill this need, the communication infrastructure is run in aseparate thread from the UI. Thus, this example uses two threads: themain thread that handles the UI and the communication thread thatcommunicates with the JSON gateway. As with the gateway code, above,extra attention is needed to keep the threads from messing each other up.The core of the communication thread is the function communicate().It keeps waiting for incoming messages in an infinite loop until the application quits. Each iteration begins with a check that the connection is stillalive.
If it is not, the function connect() is called which tries to register the client to the gateway. If the registration fails, it will be tried againafter a ten-second delay. Remember to change the SERVER constant inExample 82 to point at the actual server that is running the JSON gateway.Once the connection has been set up, the function read message()is used to receive a new message. When a new message arrives, it isprocessed by an application-specific function, thread handlemessage(). This function is actually a wrapper, produced by thee32.ao callgate() function, for the function handle message().A wrapper like this is needed whenever a thread must call a functionthat accesses low-level resources, such as network connections (sockets)or the user interface, which have been initialized by another thread.In this case, handle message() is used to access some UI resources,for instance, appuifw.note(), which have been initialized by the mainthread.