GLib friendly sockets with Gio

filed under python and gtk

Working with Gtk a bit more lately, I found myself in need of non-blocking sockets. You can't easily use the socket module in Python Gtk programs because they will either be blocking, or you'll have to write a lot of glue to use them with GLib's event loop in a non-blocking fashion.

The GNOME project provides a socket abstraction library called Gio. This library interacts well with GLib (and thus Gtk), and allows you to perform asynchronous reads and writes on sockets.

However, I couldn't find an example of how to use Gio in Python, so I thought I'd provide a working example here.

The following example asynchonously connects to a UNIX socket, writes given bytes to the socket synchronously, then reads from the socket until close and fires a given callback with the response.

from io import BytesIO
from gi.repository import Gio, GLib

def message_client(bytes, callback):
    request_id = next(request_counter)

    addr = Gio.UnixSocketAddress.new('/path/to/unix-socket')
    client = Gio.SocketClient.new()

    buf = io.BytesIO()

    def connect_callback(obj, result, user_data):
        conn = client.connect_finish(result)
        ostream = conn.get_output_stream()

        # FIXME: could also be async for super async
        ostream.write_bytes(GLib.Bytes(r))

        istream = conn.get_input_stream()
        istream.read_bytes_async(8192, 1, None, read_callback, conn)

    def read_callback(obj, result, conn):
        istream = conn.get_input_stream()
        bytes = istream.read_bytes_finish(result)
        if not bytes.get_data():
            conn.close(None)

            response = buf.getvalue()
            if callback is not None:
                callback(response)
        else:
            buf.write(bytes.get_data())
            istream.read_bytes_async(8192, 1, None, read_callback, conn)

    client.connect_async(addr, None, connect_callback, None)

If you want to connect to a TCP server rather than a UNIX server, replace the addr line above with the following:

addr = Gio.InetSocketAddress.new_from_string('127.0.0.1', 1234)
Related Posts