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 inselect.c
. - Notice that we assign
read_set
toready_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 toSelect
?
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.