Learning Python Network Programming

(Sean Pound) #1
Chapter 8

Let's step through the code, as it's quite different from our previous servers. We
begin by defining our server behavior in a subclass of the asyncio.Protocol
abstract class. We're required to override the three methods connection_made(),
data_received(), and connection_lost(). By using this class we can instantiate
a new server scheduled on the event loop, which will listen on a socket and behave
according to the contents of these three methods. We perform this instantiation in the
main section further down with the loop.create_server() call.


The connection_made() method is called when a new client connects to our socket,
which is equivalent to socket.accept() receiving a connection. The transport
argument that it receives is a writable stream object, that is, it is an asyncio.
WriteTransport instance. We will use this to write data to the socket, so we hang on
to it by assigning it to the self.transport attribute. We also grab the client's host
and port by using transport.get_extra_info('peername'). This is the transport's
equivalent of socket.getpeername(). We then set up a rest attribute to hold the
leftover data from tincanchat.parse_recvd_data() calls, and then we add our
instance to the global clients list so that the other clients can broadcast to it.


The data_received() method is where the action happens. This function is called
every time the Protocol instance's socket receives any data. This is equivalent to
poll.poll() returning a POLLIN event, and then us performing a recv() on the
socket. When called, this method is passed the data that is received from the socket
as the data argument, which we then parse using tincanchat.parserecvd
data(), as we have done before.


We then iterate over any received messages, and for each one, send it to every client
in the clients list by calling the write() method on the clients' transport objects.
The important thing to note here is that the Transport.write() call is non-blocking
and so returns immediately. The send just gets submitted to the event loop, to be
scheduled for completion soon. Hence the broadcast itself completes quickly.


The connection_lost() method is called when the client disconnects or the
connection is lost, which is equivalent to a socket.recv() returning an empty
result, or a ConnectionError. Here, we just remove the client from the clients
global list.


In the main module code we acquire an event loop, and then create an instance of
our Protocol server. The call to loop.run_until_complete() runs the initialization
phase of our server on the event loop, setting up the listening socket. Then we call
loop.run_forever(), which starts our server listening for incoming connections.

Free download pdf