Building a Multi-Connection Server
00:00 In the next few lessons, you’ll learn how to write an echo server and client which can work with multiple messages from multiple connections.
00:09 This more interesting example will use a selector to handle multiple connections and repeated calls to send and receive as needed. It will also make use of command-line arguments.
00:19 You’ll be specifying the host and port on the command line when you start the programs. Real Python has a tutorial on command-line arguments should you wish to learn more about them. You’ll look at the server program first. Let’s look at the code to create the listening socket.
00:38
Here is the multiconn-server
program. It’s importing socket
as you might expect, but there are a few others as well. It’s importing selectors
since you’ll need one. It’s also importing sys
.
00:51
This will allow us to use a system call if the program is not started with the host and port on the command line. You can also see an import types
.
01:01
The server uses the types
module to create a simple data type to manage the data coming from the client. Next, you’ll see where the program creates a default selector to contain the sockets that are created.
01:15 Following that are a couple of functions that will be described later. Scroll down past them to see where the script will start.
01:26 The first thing you should notice is a diagnostic check that the server program started with the host and port. Including the name of the server, there should be three arguments, so a check is done for that, and if they’re not all there, the program exits after printing why.
01:43 Then it’s time for phase one, create a listening socket. Many of these commands should look familiar. We create the socket indicating version four address and TCP socket streams.
01:57 Bind the socket to the provided host and port. Tell the socket to start listening for a connection, but this time you don’t want the process to block. The program needs to continue running, as it’s expecting requests from more than one client. Finally, and this is new, the socket is registered onto the selector.
02:19
The selector will notify the program when the socket has read some input, indicated by EVENT_READ
, which is why the socket doesn’t need to block the process while waiting to accept a connection.
02:34
So what’s new? You don’t want this socket to block waiting on its .accept()
call. Blocking would defeat the purpose of having a program do multiple things concurrently.
02:45
The selector sel
should monitor the socket and alert the program when it’s ready to be processed. Since it’s a listening socket, it only needs to respond to read events, and as mentioned before, this program is much more useful having the host and port numbers passed as command-line arguments.
03:07 Next is the event loop the server has to create. The program needs to detect if a socket is ready to be processed. It could either be the listening socket, meaning it’s ready to accept a connection, or could be a previously created servicing socket having received data.
03:24 This version of the server won’t use resource management. It will have instructions to close the socket once it receives a message from the client indicating it’s safe to do so.
03:34
You can see the beginning of the while True
loop. This program repeatedly checks if the selector has returned a socket that needs attention. For each one it finds, it checks to see if it’s the listening socket or a processing socket and calls the appropriate helper function to handle it.
03:52
If the selector has no events to process, it will block. That’s indicated by the parameter timeout
being set to None
. An event consists of a key-mask pair.
04:05
The key has information about the data and the socket, and the mask has information about the type of event that has happened, such as EVENT_READ
.
04:15 That information is added to a socket designated to process data. If the socket doesn’t actually contain data, that means it was a listening socket. A connection from the client has been requested, and the server needs to accept it. If the key does contain data, then it was a socket processing the communication, and it needs to be serviced to echo the data back.
04:43
Let’s review these. Using timeout=None
is telling this method to block. If there are no sockets to be processed, then the program has nothing to do and should pause here until the selector responds that there is a socket event to handle.
04:58
The .select()
method returns a key-mask pair. The key contains information about the socket as it was registered to the selector, and the mask has information about the type of activity the socket reacted to, reading or writing.
05:12
Included in the information about the socket is the field data
, which indicates what was received from the socket. If the data
field was empty, then this is a listening socket ready to process a new connection.
05:25
That will be done with the accept_wrapper()
function, which you’ll see next. If the data contained information, then it’s a socket that needs echo processing.
05:35
That’s done with the service_connection()
function, which you’ll see later.
05:42 If the server is accepting a request, then again, a new socket will be created to handle the connection from the client as its address, and a diagnostic message is printed.
05:53
The socket shouldn’t blocked since it’s going to be registered on the selector. Here’s where the server specifies what the data will look like. It’s going to be a simple object with the client’s address, a field for the data it received, inb
, and a field for the data being prepared to echo back, outb
.
06:12 You want the selector to detect when a socket is ready to read or write, and create a suitable event mask to indicate that. Then finally, this new socket is registered on the selector.
06:27
Let’s review some of the important things you saw in that code. Just as in the previous version, you want to save the new socket and address from the .accept()
method call, but this time, you don’t want .accept()
to block. If it blocked, you wouldn’t be able to create or respond to any other interactions until this method was ready.
06:46 This statement creates a simple object of an unnamed class with three fields. These fields will be used by the server to keep track of the data received and sent as it processes an echo request.
06:59
The selector will want to know what type of events to listen to. This events
variable will be used to tell the selector to respond to both read and write requests from the sockets it’s managing.
07:11 And finally, this is where we put the socket onto the selector, identifying its socket, the type of events to respond to, and the type of data to associate with it.
07:24 Finally, the server needs to read all of the data being sent from the client, which could be of any arbitrary length, and then echo that data back to the client.
07:34
This is done in the service_connection()
function. First, the function extracts the socket and data from the arguments. If the socket is ready to be read, then a one-kilobyte chunk of data is appended to the data in the object’s outb
field. Once that chunk has been read, it will be echoed back since the socket is normally ready to write.
07:55
The socket calls the .send()
method to send whatever amount of data the client is ready to accept, and the method call returns the actual number of bytes sent.
08:05
It might not be the entire string of data received, so the program uses a slice operation to remove just those bytes from the outb
string. This process repeats until the client sends something without data.
08:18
Then the recv_data
variable will be empty, which will make the if
condition false, and then the socket will be removed from the selector and closed.
08:32 A keystroke to end the process, typically Control + C, can be detected, making sure to close the selector before exiting the program.
08:44 Let’s look again at what’s happening here. When the program is receiving data, as in the first version, the program is going to read up to one kilobyte from the client.
08:54 If it received actual data, then it will append that data to any previous data that it’s received.
09:01 If the last message from the client didn’t contain data, then the communication is done. The program needs to remove this socket from the selector and then close it.
09:11 Remember, this version isn’t using Python’s resource management.
09:17 And now let’s go over the writing portion of the echo process. First, you want to make sure there is still data to be sent back. There should be if the program gets here, but it’s nice to check so the next statements don’t fail. Next, you want to echo that data back. The argument includes the entire data string, but only so much will be sent depending on what the client is willing to accept.
09:40 This method will actually return the number of bytes sent so the program can keep track. It then uses a slice operation to strip off the part of the data it actually did send so that what’s left can be sent the next time this function is called.
09:58 In the next lesson, you’ll look at the client, which will send messages to this server.
Become a Member to join the conversation.