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
Introduction#
We previously introduced the echo and tiny web servers in Lab 8. These were both examples of sequential web servers that can only handle one client at a time. When a sequential server is communicating with one client, other clients will need to wait for that client to disconnect before they can be served.
In this lab, we will explore different approaches toward making client-server web applications respond to multiple clients concurrently. The lectures, introduced three approaches for writing concurrent server applications:
- Process-based: this uses
forkto create a child process to handle each new client. - Thread-based: this is similar to the process-based approach, but it uses the
light-weight abstraction of a
threadrather than a process. - Event-based: this uses a programming pattern called I/O multiplexing via
the
selectsystem call to achieve concurrency.
Process vs. Thread#
You were introduced to pthreads in the Week 8 Lectures/previous lab, and processes in the Week 3 Lectures/Lab 4.
As a brief summary of the difference between processes and threads, a thread
is a light-weight abstraction of a logical flow. More specifically, threads
share the address space of a single process, while each has their own PC and
stack frame. This means that creating and context switching between threads
result in a lower overhead compared to processes. Threads are managed by the OS.
Exercise 1: Process-Based Echo Server#
For this first exercise, you will not have to write any code! Instead, your task
is to read the provided code for a process-based concurrent echo server and make
sure you understand how it works. The process-based server uses fork() to
create a client (child) process to handle each new client. Pay particular
attention to where each file descriptor is closed in both the parent and child
processes.
To compile the process-based server, run make echoserverp in the lab10
directory. Run make echoclient to build the echo client program, and run in
the same way as you did in
Lab 8.
Run the process based echo server locally. Try and connect to it from multiple
instances of the echoclient launched from different terminals simultaneously.
Can you leave one client hanging and still get a response on another?
Discuss the following questions with either your fellow classmates or your tutor 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?
Exercise 2: Thread-Based Echo Server#
You have also been provided with unfinished code for a thread-based echo server
in the echoservert.c file. Your second exercise in this lab is to complete
this code so that you have a working thread-based server. You are given the
following code to accept client connections in the main function:
while (1) {
clientlen = sizeof(struct sockaddr_storage);
connfdp = malloc(sizeof(int));
if ((*connfdp = accept(listenfd, (SA *)&clientaddr, &clientlen)) < 0) {
perror("accept");
}
/* ... */
}
And the following thread routine:
/* Thread routine */
void *thread(void *vargp) {
int connfd = *((int *)vargp);
pthread_detach(pthread_self());
/* TODO: finish this function! */
return NULL;
}
The pthread_detach function is used to indicate to the implementation that
storage for the thread thread can be reclaimed when the thread terminates.
This means that the thread does not explicitly need to have it’s resources
cleaned up by calling pthread_join.
Why would we not want our main thread to call pthread_join on the threads we
create in the main function?
Your task is to complete the code given.
In echoservert.c, you will need to:
- Modify
mainto callpthread_createwith the correct arguments such that a new thread runningthreadis created withconnfdppassed in as its argument. - Fill in the rest of the
threadfunction so that it handles the entire echo client connection, including freeing the memory used byconnfdpwhen it is no longer needed.
Test your implementation by compiling echoservert, and attempt to connect to
it with multiple clients at once.
Exercise 3: Improving the Thread-Based Echo Server#
The remaining exercises for this lab are included with very little explanation of how to attempt them. In this particular case, the idea behind creating a thread-pool for a concurrent echo server was covered in the Week 9 Lectures.
It isn’t very efficient to create a new thread for every new client connection.
A better approach is to create a set number of threads once during program startup, each of which wait for a main thread to assign them a client connection to handle.
Modify echoservert.c to use a thread pool for managing client connections.
You will want to use the shared buffer code you wrote in the two extension tasks
for exercise 3 of the
previous lab
to distribute file descriptors of connected clients.
Exercise 4: Event Based Server (Extension Task)#
The third approach to writing concurrent web servers is to use a programming
pattern called I/O multiplexing. Specifically, it uses the select() system
call for I/O multiplexing. You can read more about the select() system call
here.
Select Demo#
You are provided with a select.c demo program. 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.
Let’s answer a few questions to check your understanding of select.c.
- Is the
select.cprogram an example of concurrent application? Explain the sources of concurrency inselect.c. - Notice that we assign
read_settoready_setat the start of the iterative while loop. Why is that? - If
select.cis 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+1in the call toselect?
Select-Based Echo Server#
Your final task is to first look at and understand the select-based server
provided in echoservers.c, then heed the following advice from the man page
for select which says:
WARNING: select() can monitor only file descriptors numbers that
are less than FD_SETSIZE (1024)—an unreasonably low limit for
many modern applications—and this limitation will not change.
All modern applications should instead use poll(2) or epoll(7),
which do not suffer this limitation.
Implement an event-based concurrent web server using epoll rather than select.
Conclusion#
If you’ve reached this point, you have now completed all the lab content for COMP2310/6310. Excellent work ✨🎉! You may now want to spend the remainder of your time either working on your second assignment or completing some of the practice exams available to you.