These are some notes that I took while working through the excellent blog post here : https://unixism.net/2019/04/linux-applications-performance-introduction/
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()
(andpthread_mutex_unlock()
)to get a lock and thus a block on theallow()
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]