These are some notes that I took while working through the excellent blog post here :

Iterative Servers

How to access server running in VM?

  • port forwarding on host and access via localhost

setsocketop to reuse address : else prevents quick restarts, as the port will still be in a bind() state. It goes into TIME_WAIT state  checked using netstat

INADDR_ANY : Any address available

listen(sock, <queue_length>)

server socket accepts the connection and delegates work to the client socket through which any communication takes place.

\r : carriage return

\n : line feed

Done sending headers when an empty line with the above two.

Learn about read() , write() operations in user/kernal space

sendfile() works in the kernel space without the need to switch to user space by copying directly from one file descriptor to the next.

In iterative server the parent process does not block on the accept call till the request is fully processed and sent to the client. Thus the next request is queued and not processed till then.

Forking servers

  • fork returns 0 in the child process and non zero in the parent
  • exit(0) to end the child process
  • After a child process dies it stays around in a zombie state (still has the process entry in the process table). The wait() family of system calls is used by the parent process to read children : get their return status, do system accounting and remove from process table.
  • We create a signal handler to run in the background with the SA_RESTART flag so that it automatically gets blocked on accept() . Whenever a child is reaped by signal handler it sends the parent process a -1 and sets the global errno to EINTR signaling an interuption.
  • Ignoring a signal signal(SIGINT, SIG_IGN) : SIGINT = ctrl + c
  • If we don’t handle SIGCHLD which is the signal sent when the child dies, linux reaps the child but it is not portable across other operating systems.
  • Big improvement in handling concurrent users compared to iterative servers.

Pre - Forking servers

  • We create child processes (workers) before we start accepting requests. Each of them is basically an iterative server
  • Server loop i.e. accept() is called from within the child process.
  • The parent process is paused.
  • Thundering herd : Not all process can bind() to the same port so the CPU scheduler has to decide fairly which process to allow. This wastes cycles. In linux(and in most networking servers) just one process is woken instead of all so that no time is wasted on deciding.
  • Up to 5 times more efficient than forking servers.

Threaded servers

  • pthread_create to create a thread.
  • pthread+detatch(pthread_self()) to detach from the main thread

Pre-threaded servers

  • pthread_mutex_lock() (and pthread_mutex_unlock() )to get a lock and thus a block on the allow() call.
  • The above avoids the thundering herd problem.
  • Handles concurrency better than pre-forked server as the overhead for creating a thread is less than the overhead for creating a process.

Poll-based servers

  • Self explanatory in the blog post.

Tests :
Iterative : 1440, 201.86 [#/sec]
Forking : 2252, 214.68 [#/sec]
Pre-forking : 3334, 335.27 [#/sec]
Threaded : 4209, 422.87 [#/sec]
PreThreaded : 4402, 326.70 [#/sec]
Poll : 2853, 432.05 [#/sec]
Epoll : 3493, 506.22 [#/sec]