Network programming for PyS60 (VII)
by Marcelo Barros
Everything is about “protocols” in computer networks, doesn’t it ? In fact they are very important. A protocol is a kind of agreement where rules and roles are definged. They are essential for computer networks, controlling the communication between computer endpoints in all levels (from hardware to applications).
In this post, we will create a simple “homebrew” protocol to receive files over WiFi network using a mono thread server (our first server!). Our protocol has the following format:
So, we have three fields, separated by “\n”:
- An string representing the file name (maximum of 32 characters).
- The size of the file, represented in big endian (most significant byte comes first in memory). When exchanging data between machines with different alignments, endianness matters. While PCs are little endian, machines with MIPS processor are big endian and if you do not pack properly your data it will be misundertood. Size may be useful for checking if all bytes were received or even if we will have enough space for it.
- File contents.
TCP servers have a specific flow. Basically, you need the following sequence of calls to socket API:
- socket(): it is used to create the socket, our “access point” to the socket API
- bind(): if you are creating a server, it is necessary to specify exactly where your server will run or your clients won’t find it. Bind does this task, saying to the operating system that we want to create a new server at (IP,PORT). For multi-homed machines (a machine with several IPs), its possible to use “0.0.0.0″ to say to operating system (OS) that we want to have our server in all available local IPs (but same port).
- listen(): frequently misunderstood, listen is used to reserve resources in your OS. It tell to OS how many simultaneous connection requests must be handled by it at the same time for this socket. Once the connection is accepted by your server, related resources are released, allowing additional connection requests. listen does not limit the amount of connected clients that you may have, this is done by your application. Which number to put in listen ? The answer is application dependent. If you expects few connections per minute, use an small number, like 5 or 10. Many connections per second ? It better to try to estimate the amount of simultaneous connection requests you may have and use this number in listen.
- accept(): everything is defined, your server is up and running. Time to take a breath and wait for your clients, using accept. Your server code will block when accept is called, waiting for possible incoming connections. When a connection arrives, the program continues its normal execution flow just after accept call. It is possible not to block in a call to accept, using some socket options (we will talk about socket option in future posts).
accept() returns two important parameters:
- a socket for describing the incoming connection. Any further communication with the client must be done using this socket.
- client IP address and port, as a tupple.
From this point, you can use recv(), send() to exchange data with your client. Multi thread servers will probably create a new thread, passing the socket to it. This way, it can call accept again and wait for more clients.
As cited in network programming III, since TCP is a byte oriented stream, you need to wait until a complete name and size arrive, so it is necessary some additional work when receiving. As we will see in a future post, the “makefile” function may helps a lot.
Our server code is below:
# -*- coding: utf-8 -*- import sys try: # http://discussion.forum.nokia.com/forum/showthread.php?p=575213 # Try to import 'btsocket' as 'socket' - ignored on versions < 1.9.x sys.modules['socket'] = __import__('btsocket') except ImportError: pass from appuifw import * import socket import os import e32 import struct class FileUpload(object): """ File upload server class """ def __init__(self): self.lock = e32.Ao_lock() self.dir = "e:\\file_upload" if not os.path.isdir(self.dir): os.makedirs(self.dir) self.apo = None self.port = 54321 self.new_line = u"\u2029" app.title = u"File upload" app.screen = "normal" app.menu = [(u"About", self.about)] self.body = Text() app.body = self.body self.lock = e32.Ao_lock() def recv_file(self,cs,addr): """ Given a client socket (cs), receive a new file and save it at self.dir """ data = "" name = "" size = 0 # waiting for file name while True: n = data.find("\n") if n >= 0: name = data[:n] data = data[n+1:] break buf = cs.recv(1024) data = data + buf # waiting for file size (may be useful for limits checking) while True: n = data.find("\n") if n >= 0: # unpack one long (L) using big endian (>) endianness size = struct.unpack(">L",data[:n]) data = data[n+1:] break buf = cs.recv(1024) data = data + buf self.body.add(u"Uploading %s (%d bytes)" % (name,size) + self.new_line) # waiting for file contents fname = os.path.join(self.dir,name) f = open(fname,"wb") while True: f.write(data) data = cs.recv(1024) if not data: break self.body.add(u"Finished." + self.new_line) cs.close() f.close() def server(self,ip,port): """ Starts a mono thread server at ip, port """ s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind((ip,port)) s.listen(1) while True: (cs,addr) = s.accept() self.body.add(u"Connect to %s:%d" % (addr,addr) + self.new_line) self.recv_file(cs,addr) def sel_access_point(self): """ Select and set the default access point. Return the access point object if the selection was done or None if not """ aps = socket.access_points() if not aps: note(u"No access points available","error") return None ap_labels = map(lambda x: x['name'], aps) item = popup_menu(ap_labels,u"Access points:") if item is None: return None apo = socket.access_point(aps[item]['iapid']) socket.set_default_access_point(apo) return apo def about(self): note(u"File upload by Marcelo Barros (email@example.com)","info") def run(self): self.apo = self.sel_access_point() if self.apo: self.apo.start() self.body.add(u"Starting server." + self.new_line) self.body.add(u"IP = %s" % self.apo.ip() + self.new_line) self.body.add(u"Port = %d" % self.port + self.new_line) self.body.add(u"Repository = %s" % (self.dir) + self.new_line) self.server(self.apo.ip(),self.port) self.lock.wait() app.set_tabs( , None ) app.menu =  app.body = None app.set_exit() if __name__ == "__main__": app = FileUpload() app.run()
Finally, this small PC code may be used to send the file. Run it from command line and use the sintaxe “script_name.py server_ip server_port file_to_be_send“.
import struct import sys import os import socket if len(sys.argv) < 4: print "%s server_addr server_port file_to_upload" % sys.argv sys.exit(1) ip = sys.argv port = int(sys.argv) full_name = sys.argv base_name = os.path.basename(full_name) size = os.path.getsize(sys.argv) print "Sending %s to %s:%d ..." % (base_name,ip,port) s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((ip,port)) f = open(full_name,"rb") header = "%s\n%s\n" % (base_name,struct.pack(">L",size)) s.sendall(header) while True: data = f.read(1024) if not data: break s.sendall(data) s.close() f.close()
In the next two pictures we can see these programs working. I used the command ping to test the ad-hoc connection between PC (10.0.0.2) and mobile phone (10.0.0.1), before transferring files.
- Network programming for PyS60 (VIII) Did you do your homework ? So, I would like...
- Network programming for PyS60 (XV) As discussed in post III, TCP sockets have flow...
- Network programming for PyS60 (III) As I promised, today we will start with some programming,...
- Network programming for PyS60 (XIII) In our last post we talked about multicast, a special...
- Network programming for PyS60 (IX) A new element was presented in our last post: exception...
Related posts brought to you by Yet Another Related Posts Plugin.