Example Project

Multithreaded server

An important goal for a server application is the ability to handle multiple clients at the same time. The most common way to implement a server that has this capability is to make the server multithreaded. By taking advantage of threads and their ability to do more than one thing at the same time we can dramatically improve the power of our server application.

In these note I will walk you through the steps needed to turn our simple web server into a multithreaded server application.

Basics of threads

There are several libraries available on Linux to implement threads. The simplest and most widely used of these is the POSIX threads, or pthread, library.

To use POSIX threads in a program we start by including the appropriate header file:

#include <pthread.h>

In addition to including this header file we also need to use the -pthread option in the command line that compiles the code for the application.

The only function we will need to use to implement threads in our application is the pthread_create() function, which starts a new thread running.

Here is a snippet of code that illustrates how this function works:

pthread_t thread;
pthread_create(&thread,NULL,serveRequest,&new_socket);

The thread variable is a thread identifier. If we need to interact with the thread after starting it, we will use the thread variable to access the thread.

The third parameter in pthread_create() is a pointer to the thread function. The thread function is a function that takes a single parameter, a generic void* pointer, and returns a void* pointer as its result. When we launch the thread the code in the thread function will start running. When we reach the end of the code in that thread function the thread stops running.

The fourth parameter in pthread_create() is a pointer to the data we want to pass to the thread function.

Threaded miniweb

In the threaded version of the miniweb server I will be using a common pattern for setting up the server. This pattern is the thread per connection pattern.

Servers typically feature an infinite loop that accepts new client connections. In this version of the server application we will launch a new thread each time we accept a connection:

while(1) {
    struct sockaddr_in client;
    int new_socket , c = sizeof(struct sockaddr_in);
    new_socket = accept(server_socket, (struct sockaddr *) &client, (socklen_t*)&c);
    if(new_socket != -1) {
      pthread_t thread;
      pthread_create(&thread,NULL,serveRequest,&new_socket);
    }
  }

I have also modified the serveRequest() function from its original form to make the serveRequest() function work as a thread function.

The original serveRequest() function took the form

void serveRequest(int fd) {
  // fd is a file descriptor for the socket we use to communicate
  // with the client. Read the request from fd and send the
  // response back via fd.
}

The new form of this function is

void* serveRequest(void* pfd) {
  int fd = *((int*) pfd);
  // Do the same as before with fd
  return NULL;
}

Instead of being passed a file descriptor to work with we will instead receive a generic void* pointer that points to the int where the file descriptor is stored. To get the file descriptor we have to type cast the generic pointer to the correct pointer type, dereference that pointer, and then copy the data to a local variable for our use.