The eventlet
library provides a high-level API for event-driven programming, but it does so in a style that mimics the procedural, blocking-IO style that we used in our multithreaded servers. The upshot is that we can effectively take our multithreaded chat server code, make a few minor modifications to it to use eventlet
instead, and immediately gain the benefits of the event-driven model!
The eventlet
library is available in PyPi, and it can be installed with pip
, as shown here:
$ pip install eventlet Downloading/unpacking eventlet
Once it's installed, create a new file called 4.1-chat_server-eventlet.py
and save the following code in it:
import eventlet import eventlet.queue as queue import tincanchat HOST = tincanchat.HOST PORT = tincanchat.PORT send_queues = {} def handle_client_recv(sock, addr): """ Receive messages from client and broadcast them to other clients until client disconnects """ rest = bytes() while True: try: (msgs, rest) = tincanchat.recv_msgs(sock) except (EOFError, ConnectionError): handle_disconnect(sock, addr) break for msg in msgs: msg = '{}: {}'.format(addr, msg) print(msg) broadcast_msg(msg) def handle_client_send(sock, q, addr): """ Monitor queue for new messages, send them to client as they arrive """ while True: msg = q.get() if msg == None: break try: tincanchat.send_msg(sock, msg) except (ConnectionError, BrokenPipe): handle_disconnect(sock, addr) break def broadcast_msg(msg): """ Add message to each connected client's send queue """ for q in send_queues.values(): q.put(msg) def handle_disconnect(sock, addr): """ Ensure queue is cleaned up and socket closed when a client disconnects """ fd = sock.fileno() # Get send queue for this client q = send_queues.get(fd, None) # If we find a queue then this disconnect has not yet # been handled if q: q.put(None) del send_queues[fd] addr = sock.getpeername() print('Client {} disconnected'.format(addr)) sock.close() if __name__ == '__main__': server = eventlet.listen((HOST, PORT)) addr = server.getsockname() print('Listening on {}'.format(addr)) while True: client_sock,addr = server.accept() q = queue.Queue() send_queues[client_sock.fileno()] = q eventlet.spawn_n(handle_client_recv, client_sock, addr) eventlet.spawn_n(handle_client_send, client_sock, q, addr) print('Connection from {}'.format(addr))
We can test this with our multithreaded client to ensure that it works as expected.
As you can see, it's pretty much identical to our multithreaded server, with a few changes made so as to use eventlet
. Notice that we've removed the synchronization code and the lock
around send_queues
. We're still using queues, although they're the eventlet
library's queues, because we want to retain the blocking behavior of Queue.get()
.
There are more examples of using eventlet for programming on the eventlet site at http://eventlet.net/doc/examples.html.