Three Ways to Web Server Concurrency

A Multithreading Server

The multithreading_server shown in Listing 3 avoids the context-switch downside
of the forking_server but faces challenges of its own. Each process has
at least one thread of execution. A single multithreaded process has
multiple threads. The threading_server is multithreaded.

The threading_server mimics the division-of-labor strategy in the
forking_server, but the client handlers are now threads within a single
process instead of forked child processes. This difference is huge. Thanks
to COW, separate processes effectively have separate address spaces,
but separate threads within a process share one address space.

When a client connects, the threading_server delegates the handling to
a new thread:

The thread gets a unique identifier and executes a thread routine—in
this case, handle_client. The threading_server passes the client socket
to the thread routine, which reads from and writes to the client.

How could the WordGame be ported to the forking_server? This server must
ensure one WordGame instance per client. The single WordGame:

WordGame game; /* one instance */

could become an array of these:

WordGame games[BACKLOG]; /* BACKLOG == max clients */

When a client connects, the threading_server could search for an available
game instance and pass this to the client-handling thread:

int game_index = get_open_game(); /* called in main so thread safe */

In the function main, the threading_server would invoke get_open_game, and
each client-handling thread then would have access to its own WordGame
instance:

This server executes as one thread in one process and so must support
concurrency by jumping quickly from one task (for example, accepting connections)
to another (for example, reading requests). These nimble jumps are among
nonblocking I/O operations, in particular calls to accept (connections)
and recv (requests).

The polling_server's call to accept returns immediately:

If there are no clients waiting to connect, the server moves on to check
whether there are requests to read.

If there are waiting clients, the polling_server accepts them in a
loop.

The polling_server uses the epoll system library, declaring a single
epoll structure and an array of these:

The server uses the single structure to register interest in connections
on the listening socket and in requests on the client sockets. The epoll
library uses the array of epoll structures to record such events. The
division of labor is:

The polling_server registers events of interest with epoll.

The epoll library records detected events in epoll_event structures.

The polling_server handles epoll-detected events.

The polling_server is interested in incoming (EPOLLIN) events and
in edge-triggered (EPOLLET) rather than level-triggered events. The
distinction comes from digital logic design but examples abound. A
red traffic light is a level-triggered event signaling that a vehicle
should remain stopped, whereas the transition from green to red is an
edge-triggered event signaling that a vehicle should come to a stop. The
polling_server is interested in connecting and requesting events when
these first occur.

A for loop iterates through detected events. Above the loop, the statement:

int n = epoll_wait(epollfd, event_buffers, MAX_EVENTS, -1);

gets an event count, where the events are either connections or requests.

My polling_server takes a shortcut. When the server reads a request,
it reads only the bytes then available. Yet the server might require
several reads to get the full request; hence, the server should buffer the
partials until the request is complete. I leave this fix as an exercise
for the reader.

How could the WordGame be ported to the polling_server? This server,
like the threading_server, must ensure one WordGame instance per client
and must coordinate a client's access to its WordGame. On the upside,
the polling_server is single-threaded and thereby threadsafe. Unlike
the forking_server, the polling_server does not incur the cost of context
switches among forked children.

Conclusions

Which is the best way to client concurrency? A reasoned answer must
consider traditional multiprocessing and multithreading, together with
hybrids of these. The evented I/O way that epoll exemplifies also
deserves study. In the end, the selected method must meet the challenges
of supporting concurrency across real-world Web applications under
real-world conditions.

Resources

Martin Kalin is a professor at the College of Computing and Digital Media at DePaul University,
Chicago, Illinois. He earned his PhD at Northwestern University.
Martin has co-authored various books on C and C++, authored a book on Java and,
most recent

The primary function of a web server is to deliver web pages on the request to clients using the Hypertext Transfer Protocol (HTTP). This means delivery of HTML documents and any additional content that may be included by a document, such as images, style sheets and scripts.

A user agent, commonly a web browser or web crawler, initiates communication by making a request for a specific resource using HTTP and the server responds with the content of that resource or an error message if unable to do so. The resource is typically a real file on the server's secondary memory, but this is not necessarily the case and depends on how the web server is implemented.Aménagement Sur Mesure

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.