Continuing on in my series on gevent and Python, this article discusses how to build TCP servers using the infrastructure provided with gevent. If you're just getting started with Gevent, you might want to read the previous articles in this series first:
- Introduction to Gevent
- Gevent, Threads, and Benchmarks
- Gevent and Greenlets
- Greening the Python Standard Library with Gevent
And now that you're all caught up, let's jump into gevent servers...
Basic TCP servers with Gevent StreamServer
Setting up a tcp server in Gevent extremely straightforward:
- You create some handler function that will perform communication over a connected TCP socket.
- You create an instance of
gevent.servers.StreamServer, passing your handler function in its constructor.
- You start the server.
And that's it! By default, each socket gets its own greenlet. Here's a simple example of an echo server we could set up:
from gevent import socket from gevent.server import StreamServer def handle_echo(sock, address): fp = sock.makefile() while True: line = fp.readline() if line: fp.write(line) fp.flush() else: break sock.shutdown(socket.SHUT_WR) sock.close() server = StreamServer( ('', 1234), handle_echo) server.serve_forever()
The only thing here that bears explanation is the
fp = sock.makefile(). What
this does is create a file wrapper around a socket so we can use our normal
flush methods, pretending we're not really dealing
with a socket at all.
One of the nice things about gevent is that it lets you handle many more
simultaneous connections than a threaded implementation of a server would be able
to. In some cases, however, you might still want to limit the number of greenlets
started by a
StreamServer. For those cases, you can either pass an instance of
gevent.pool.Pool or an integer as the
server = StreamServer( ('', 1234), handle_echo, spawn=10000) # limit to 10,000 simultaneous connections
Customizing the listening socket
The StreamServer also supports several other features. For instance, if we wish
to change the
backlog argument to
listen() on the listening socket, we can
backlog to the
server = StreamServer( ('', 1234), handle_echo, backlog=1000)
If we want to do further customization, we can simply create the StreamServer with an already-prepared and listening socket instead of the (IP, port) we were using above:
sock = socket.socket() sock.bind(('', 1234)) sock.listen(256) server = StreamServer( sock, handle_echo) server.serve_forever()
Encrypting your connections
One of the reasons you might consider create the listening socket is to provide an
encrypted SSL socket using
gevent.ssl.socket. This, however, will not work due
to some implementation details in the
ssl module. To create an SSL stream
server, then, you would simply pass the SSL information to the
server = StreamServer( ('', 1234), handle_echo, keyfile='server.key', certfile='server.crt')
And that's pretty much all there is to it. Building gevent-based TCP servers is really pretty simple, and it's nice to know you can scale to thousands of connections without overloading your server. So what do you think? Have you ever built any socket servers that you'd think about porting to gevent? Any other use-cases you've run into? I'd love to hear about it in the comments below!