Title: Concurrent TCP connections
1Concurrent TCP connections
- A look at design-changes which permit a TCP
server to handle multiple clients without delays
2Recall design-paradigm
The server application
socket()
bind()
The client application
socket()
listen()
bind()
connect()
3-way handshake
accept()
read()
write()
data flow to server
write()
read()
data flow to client
close()
close()
4-way handshake
3Three sockets used
The servers listening socket is strictly for
one-way communication it can only receive
connection-requests from clients, but it does
not receive a clients data, nor is it able to
send any data back to a client
The server process
The client process
servers listening socket servers
connection socket
clients connection socket
The servers connected socket is for doing
two-way communication it can be used by the
server to receive data from its connected client,
and it can be used by the server to send data
to that connected client
4Fast service only
- The design-paradigm we just described is OK for
servers that reply very quickly to a single
client request (as with our original echo
application a short sentence is sent by the
client, the server capitalizes all its letters
and sends that sentence back, and then the
connection is immediately closed - But this design-paradigm is not well-suited for a
more general kind of TCP application
5Original echo example
Our server application
socket()
Our client application
bind()
Ask user to type in a short sentence and
read reply
listen()
socket()
connect()
3-way handshake
accept()
The duration of this connection is very
brief
read()
write()
data flow to server
write()
read()
data flow to client
close()
close()
4-way handshake
6Delayed-service problem
- To demonstrate the problem that arises with our
original iterative server design, we need to
make a small revision in our clients code to
prolong the duration of the connection of the
server to the client - If we cut-and-paste a few lines of code, we can
arrange for our client to connect to the server
before it reads the users input
7New echo example
Our server application
Ask user to type in a short sentence and
read reply
socket()
bind()
Our client application
listen()
cut and paste
socket()
accept()
connect()
3-way handshake
An indeterminate delay after connection
occurs while user types input
Ask user to type in a short sentence and
read reply
read()
write()
data flow to server
write()
read()
data flow to client
close()
close()
4-way handshake
8Demo tcpclient2.cpp
- Run our original tcpserver.cpp in one of the
windows on your graphical desktop - Then run our revised tcpclient2.cpp demo in
several other windows at the same time
./tcpserver Server is listening on port
54321
./tcpclient2 localhost Please type a
short sentence
./tcpclient2 localhost Please type a
short sentence
./tcpclient2 localhost Please type a
short sentence
9A concurrent server
- To avoid such service delays, most TCP servers
use a different design-paradigm, taking advantage
of UNIXs multitasking - Each connection-request that the server receives
will handled by a different task - The operating system will schedule these multiple
tasks to be executed concurrently, so delays by
one task will not affect others
10Servers code-outline
// the basic steps in an initial concurrent
server design int sock socket( AF_INET,
SOCK_DGRAM, IPPROTO_TCP ) bind( sock,
(sockaddr)serveraddr, salen ) listen( sock, 5
) for() int conn accept( sock,
(sockaddr)clientaddr, calen ) int pid
fork() if ( pid 0 ) // child-process
close( sock ) int rx read( conn, buf,
sizeof( buf ) ) if ( rx gt 0 ) write( conn,
buf, rx ) close( conn ) exit(0) //
parent-process close( conn ) continue
11No change in connection-setup
The server process
The client process
servers listening socket servers
connection socket
clients connection socket
The servers listening socket is strictly for
one-way communication it can only receive
connection-requests from clients, but it does
not receive a clients data, nor is it able to
send any data back to a client
12Connect, then fork
The server parent-process
parent closes connection-socket
servers listening socket servers
connection socket
The client process
clients connection socket
The server child-process
servers listening socket servers
connection socket
child closes listening-socket
The servers listening socket will not used by
the child-process, so it immediately gets
closed, and the servers connection socket will
not be used by the parent-process, so it
immediately gets closed
13Server continues listening
The server parent-process
Next client process
servers listening socket
clients connection socket
The client process
The server child-process
clients connection socket
servers connection socket
The servers listening socket can continue to
receive connection-requests from other clients
that are made to the servers parent-process,
while the earlier client is maintaining its
connection with the servers child-process
14Demo tcpserver2.cpp
- Execute our revised tcpserver2.cpp in one of
your windows, and again run our tcpclient2.cpp
demo in other windows - The service delay problem has vanished!
./tcpserver2 Server is listening on port
54321
./tcpclient2 localhost Please type a
short sentence
./tcpclient2 localhost Please type a
short sentence
./tcpclient2 localhost Please type a
short sentence
15New problem zombies
- When you use the ps command to look at the list
of all of your processes, you notice that our
revised servers child-processes are still
residing within the system -- even though they
have already terminated as so called zombie
processes, and they are using system resources
(e.g., memory) - ps -a
16Parent didnt wait
- When a child-process exits, its existence is
remembered within the Linux system until its
parent-process calls one of the wait()
functions, to find out that childs status -- and
to relinquish its resources - Failure to wait could eventually exhaust the
systems memory, preventing further useful work
from being done!
17But wait() blocks!
- If a parent calls the usual wait() function
before its child has terminated, the parent will
be put to sleep, and is awakened only when one of
its child-processes exits - But putting out server-process to sleep would
delay it from accepting any more
connection-requests from new clients - To avoid this we need a new mechanism
18The SIGCHLD signal
- Whenever a process exits, the operating system
will automatically notify its parent by
delivering a signal to the parent - We can arrange for our concurrent server to
catch any such signals, because then theres no
risk of sleeping if it calls wait() - That way, the resources owned by child- processes
will get released (no zombies!)
19Signal-handler
include ltsignal.hgt // for signal() include
ltwait.hgt // for wait() void sigchld_action(
int signo ) wait( NULL ) // release a
child-processs resources signal( SIGCHLD,
sigchld_action ) // reinstall handler int
main( int argc, char argv ) signal(
SIGCHLD, sigchld_action ) // install
handler
20In-class exercise
- Try running our tcpserver3.cpp example, which
invokes a signal-handler to wait() as soon as a
child-process calls exit() - Now run tcpclient2.cpp to satisfy yourself that
a zombie process is no longer being left in the
system to consume resources