Threaded socket server in Python

We’ve already seen how to create a simple socket server in Python, but sometimes we need to handle multiple incoming requests simultaneously, that require processor-heavy computation from the server. That’s what a threaded socket server can achieve.

This tutorial assumes a basic understanding of Python and socket programming. Feel free to read our previous article on creating a simple socket server for an easier introduction to sockets in Python.

Also note that the example given in this article is only one way to implement a threaded socket server; it may not be the best. It is only meant to give an overview of what a threaded socket server is, and how it’s possible to implement one.

The idea

The problem with a socket server running on a single thread is that it can only handle one request at a time. This is fine as long as the server doesn’t need to do any processor intensive task for each client, in which case a multi-threaded approach is usually better. Using a single thread for each client can significantly reduce the impact that processing data for other clients has on a given connection.

Architecture

The basic architecture of a threaded socket server is the following:

  • A main entity listens for incoming connections on a given host/port. We’ll name this class SocketServer. We’ll make it inherit the Python Thread class so we can execute other tasks while it’s running.
  • Every time SocketServer receives a new connection, it will create a new SocketServerThread object. This object will be responsible for the connection. It will handle incoming messages (for this article we’ll just make it print them to the standard output) and client disconnection.
  • SocketServer  should keep track of the SocketServerThreads  it created, so it can properly terminate them when it finished. We’ll simply use an array for that.
  • For the sake of clarity, SocketServer  will assign a unique number to each SocketServerThread . This will allow us to see which  SocketServerThread  receives which data.

SocketServer

This class will contain the following methods:

  • Constructor: Initialize the socket with a host and port.
  • close() : Terminate and wait on each SocketServerThread , and close the main socket.
  • run() : Start the socket server, and run until the thread is stopped. For each incoming socket connection, start a new SocketServerThread .
  • stop() : Stop the execution of the run()  loop.

The code for the SocketServer  is the following:

SocketServerThread

It contains the methods:

  • Constructor: simply set variables used to keep track of the socket address and port, and the number assigned to this thread.
  • run() : Keep accepting incoming data until the thread is terminated, or the client has disconnected (detected by receiving an empty message when select  indicates that rdy_read  is greater than 0). On incoming messages we just print the message out.
  • stop() : Stop the execution of the run()  loop.
  • close() : Properly close the socket.

The code for this class is the following:

Main function

Let’s write a main()  function that will start our server, wait 2 minutes, and stop the server. Stopping the server will allow us to test that the stop()  function does terminate all SocketServerThreads  started by the SocketServer .

Don’t forget to include this at the bottom of your Python script:

Imports

The following import statements are necessary:

Test

For this test we’ll use 4 terminals. One of them will run our threaded socket server, and the other 3 will run nc  (netcat) to send messages to the server. Here is what we should expect from this test:

  • Each new netcat process should result in a new SocketServerThread . This will be shown by the number associated to the SocketServerThread .
  • Each message from a netcat process should be printed out by the correct SocketServerThread
  • The correct SocketServerThread should be stopped when a netcat process is terminated.
  • At the end of the 2 minutes delay, any SocketServerThread that is still active should be terminated, as well as the SocketServer . As a result, the associated netcat processes should return.

Below is a screenshot of the test:

Test of the threaded socket server in Python
Test of the threaded socket server in Python

As we can see, every test case listed above is working.

Conclusion

We’ve learned how to create a threaded socket server in Python, and seen that it’s not complicated and can easily be incorporated in a project that needs it.

Of course, a threaded socket server is not needed it you just want to print out the messages received, but in a situation where the server needs to execute processor-heavy tasks, such a design would greatly improve the performance perceived by each client. As always in software development, the best solution depends on what you need to achieve. Feel free to experiment, use this example in your project, and see what you can improve!

Be the first to comment

%d bloggers like this: