Skip navigation.

Async python Socket Server using gobject

Recently I created a small command line python utility to process some stuff on a TCP socket connection. I used the standard python SocketServer class because it was dead simple to use for my purposes.

However, as the requirements of the utility grew (a GUI), I hit the main problem with SocketServers - they block - which is not so good for GUIs.

So, instead of using nasty old threading, I worked out how to use gobject.io_add_watch() to do everything in the gobject/gtk main loop. This makes it very simple to have this run in a gtk UI without needing threads.

The basic pattern is shown below. The first function, server(), sets up the socket (bind, listen) and waits for connections, but doesn't block. Instead it sets a watch and when a new connection comes in it calls a listener(). This accepts the new connection and creates a handler(). The handler is called whenever input is available on the connection. Note that listener and handler return True (typically) so that the io_watch keeps running. After a connection is closed, the listener is called again to create a new connection.

It took me a while to work this out and I couldn't find all of this in one place on the web, so here it is. Hope it helps someone.

import gobject, socket
def server(host, port):
	'''Initialize server and start listening.'''
	sock = socket.socket()
	sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	sock.bind((host, port))
	print "Listening..."
	gobject.io_add_watch(sock, gobject.IO_IN, listener)
def listener(sock, *args):
	'''Asynchronous connection listener. Starts a handler for each connection.'''
	conn, addr = sock.accept()
	print "Connected"
	gobject.io_add_watch(conn, gobject.IO_IN, handler)
	return True
def handler(conn, *args):
	'''Asynchronous connection handler. Processes each line from the socket.'''
	line = conn.recv(4096)
	if not len(line):
		print "Connection closed."
		return False
		print line
		return True
if __name__=='__main__':
	server('', 8080)


There's also the Tasks tutorial, which shows how to do this without using callbacks or threads.

I hadn't seen that.

I hadn't seen that. Probably because I was not looking here. :) My utility is for work and I didn't want to depend on roxlib. It's also too bad that the (py)gobject handlers don't directly accept python generators as their callbacks.

P.S. it appears that <code> blocks are eating blank lines. Is there a way to fix that?

You CAN use generators!

To use a generator as the callback just do some of the dirty work yourself. Instead of passing the callback directly, pass the generator's next() method, thus:

def handler():
  for x in range(100):
    print x
    yield True
gobject.timeout_add(100, handler().next)

OK, it's not pretty. It would still be better for pygobject to recognize that the callback is a generator and do this automatically. But it works AFAICS.

Very usefull Tip

Thanx for that piece of code.
It fit perfectly for what i was searching for.
I adapted it a bit to specify the handler, putting this code in a class and being
able to use it inside an application.

Keep me in touch if you want it back.


Thanks for sharing this. It really helped me.

Syndicate content