Outline

In this week’s lab, you will:

  • Learn three approaches to writing concurrent server applications
  • Learn to use select() for I/O multiplexing
  • Write an event-based server that responds to three different types of clients concurrently
  • Learn the basics of threads and their use in writing concurrent applications

Preparation#

Do an upstream pull as usual to get the latest files for this lab in your comp2310-labs repository.

Introduction#

We introduced the tiny web server in the last lab. Tiny is a sequential web server because it can only handle one client at a time. When sequential is communicating with one client, then other clients need to wait for response from Tiny. The same is the situation with the echo client-server application. In this lab, we will explore three approaches toward making client-server web application respond to multiple clients concurrently.

In the lectures, we introduced three approaches for writing concurrent applications:

  • process-based
  • event-based
  • thread-based (this week’s lecture)

The process-based server uses fork() to create a client (child) process to handle each new client. On the other hand, an event-based server uses a programming pattern called I/O multiplexing to achieve concurrency. Specifically, it uses the select() system call for I/O multiplexing. You can read more about the select() system call here. A thread-based server is quite similar to a process-based server except it uses the light-weight abstraction of a thread instead of a process.

Process vs. thread#

We will cover threads in detail in this week’s lecture. For now, a thread-based concurrent server (echoservert.c) is very similar to a process-based server. A thread is a light-weight abstraction of a logical flow. More specifically, threads share the address space of a single process. However, they have their own PC and stack frame. Therefore, instantiating and context switching threads result in a lower overhead compared to processes. Similar to a process-based server and unlike I/O multiplexing, threads in a thread-based server are scheduled/managed by the OS.

Posix threads (Pthreads) is a standard interface for manipulating threads from C programs. The code and local data for a thread are encapsulated in a thread routine. A thread is created with a call to pthread_create,

#include <pthread.h>
int pthread_create(pthread_t *thread,
                   const pthread_attr_t *attr,
                   void *(*start_routine)(void *),
                   void *arg);

The pthread_create function creates a new thread and runs the thread routine start_routine in the context of a new thread and with an input argument of arg. We will always set attr to NULL in this course. When the function returns, argument thread contains the ID of the newly created thread. The new thread can determine its own thread ID by calling the pthread_self function.

The pthread_detach function is used for reclaiming the resources reserved by the thread.

We will study pthreads in more detail in this week’s lecture and next week’s lab. Today, just make sure you compile, run, and understand the echoservert.c program that implements a threaded echo server.

Compiling and running concurrent servers#

The lab repository provides three concurrent servers and one example of the select system call.

Open the lab directory and compile all four programs. Then, run each of the concurrent servers as shown in the lectures. Do you observe what you expect to see? Can you add debugging printf statements to ensure servers are doing what you expect them to do?

Select() demo#

The select.c program is slightly special. It is written to demonstrate I/O multiplexing using select(). The program adds two descriptors to the read set. It then calls Select that suspends the program. When it wakes up, it checks to see if any or both descriptors in the ready set are set to 1 (read and ready sets are bit vectors). If listenfd is set, the program accepts the client and calls the echo() function. Otherwise, the program calls the command() function.

Exercises (Code Understanding)#

Let’s answer a few questions first to check your understanding of the process-based echo server.

  • Why must the parent process close the connected descriptor?
  • What are the consequences of child process forgetting to close listenfd?

Let’s answer a few questions to check your understanding of select.c.

  • Is the select.c program an example of concurrent application? Explain the sources of concurrency in select.c.
  • Notice that we assign read_set to ready_set at the start of the iterative while loop. Why is that?
  • If select.c is run on a multicore processor, will we observe parallelism during execution?
  • Can you change the definition of the echo() function such that the program responds to keyboard input (pending or otherwise) as soon as possible rather than waiting for the connected descriptor to close?
  • Why do we use listenfd+1 in the call to Select?

Practical Exercise#

Write a program that uses I/O multiplexing using the select() function to monitor three types of file descriptors: (1) standard input, (2) echo clients, and (3) HTTP clients. Your program should determine if a specific client intends to obtain an echo response or an HTTP response after accepting the connection. Assume that the only type of HTTP request you serve is a GET request, and the only possible request is for the home page served by the tiny web server. To test the program, you will need to start the tiny web server which is included in your repository.

Almost all the code you need for this exercise is included in the tiny web server and echoservers.c. This exercise should provide you with a hacking experience where you reuse existing code to bring up a new application.

Write your code in server3.c. Type make server3 to compile your code. We have copied echoservers.c in server3.c for your convenience.

bars search times arrow-up