Escolar Documentos
Profissional Documentos
Cultura Documentos
stdin
stdout
fgets
fputs
TCP
Client
writen
readline
readline
writen
TCP
Server
In accept function call, where does the cliaddr variable gets the clients address
from?
From clients SYN segment
After fork, Child closes the listening socket and parent closes the connected
socket.
$ netstat a
Proto Recv-Q Send-Q Local Address Foreign Address
(state)
Tcp
0
0
localhost.9877 localhost.1052
ESTABLISHED
Tcp
0
0
localhost.1052 localhost.9877
ESTABLISHED
Tcp
0
0
*.9877
*.*
LISTEN
The first row above is for the server-child.
Second row is for the client.
Third row is for server-parent.
Netstat command displays active TCP connections.
Netstat -a : Displays all active TCP connections and the TCP and UDP ports on
which the computer is listening.
Normal Termination
When the client types EOF(Contl-D), fgets returns a NULL pointer and the
function str_cli returns.
Str_cli function returns to the main() function and main calls exit().
When the client socket is closed by the kernel, FIN is sent to the server, to which
the TCP server responds with an ACK. This is the first half of the TCP connection
termination sequence.
At this point, the server socket is in the CLOSE_WAIT state and client socket is
FIN_WAIT_1 state.
When the server received the FIN from the client, the server child was blocked in
call to readline. And now readline returns 0. This causes str_echo function to
return to the server child main.
And the server child main terminates itself by calling exit.
As a final step, FIN from the server is sent to the client, and an ACK is received
from the client. At this point the connection is completely terminated. The client
socket enters the TIME_WAIT state.
Another part of the process termination is the SIGCHLD signal to be sent to the
parent when the server child terminates. Ideally this signal should be caught and
the process details should be removed from the process table.
But in our program, we are not catching the signal, thus by default the signal will
be ignored and the child enters the zombie state.
$ ps
PID
19130
21130
21132
21167
TT
p1
p1
p1
p1
STAT
Ss
I
Z
R+
TIME
05:08
07:08
07:28
08:00
COMMAND
ksh
./tcpserv
(tcpserv)
ps
Status Z means zombie coz the server child has terminated but its signal
was not handled by the server parent.
A process goes in zombie state, when it has terminated but its information is
not removed from the process table by its parent proces.
Signal Handling
A signal is a notification to a process that an event has occurred. Signals are also
called software interrupts.
Signals are asynchronous, i.e. the process doesnt know ahead of time exactly
when a signal will occur.
Signals can be sent :
By one process to another process (or to itself)
By the kernel to the process.
SIGCHLD signal is the one that is send by the kernel to parent of the
terminating process.
Every signal has a disposition, which is also called the action
associated with the signal. We have three choices for the disposition:
1. We can provide a function, called the signal handler and this action is called
catching the signal.
2. We can ignore a signal by setting its disposition to SIG_IGN.
3. We can set the default disposition for a signal by setting its disposition to
SIG_DFL. The default is normally to terminate a process on the receipt of a
signal.
For setting the disposition for a signal, sigaction() function has to be called, but
this function needs a structure to be initialized and passed as argument, so a
wrapper function signal() is used.
The first argument to the signal() function is the signal number and the second
argument is the pointer to the signal handler function or constant SIG_IGN or
SIG_DFL.
If the signal that is caught is SIG_ALRM, we want to run the signal handler, but
along with that we want to interrupt the current running process.
So we set the sa_flags to SA_INTERRUPT
SIG_ALRM signal is used to notify a timer time-out. etc
If the signal is not SIG_ALRM, i.e. any other signal that is caught, then we must
make sure that after the signal handler function is executed, the current running
process restarts itself.
So for that sa_flags is set to SA_RESTART.
Signal handling:
Once a signal handler is installed, it remains installed.
While a signal handler is executing, the signal being delivered is blocked.
For blocking any other signals, sa_mask is used.
If a signal is generated one or more times while it is blocked, it is delivered only
once after the signal I unblocked. i.e by default, unix signals are NOT queued.
Whichever process calls fork(), should also make sure to clean up the child
process before terminating itself.
Zombies should not be left around, coz otherwise we may run out of space for
any new processes in the system.
To handle zombies, the parent process must catch the SIGCHLD signal and
within the handler, call wait().
#include<sys/wait.h>
pid_t wait (int *statloc);
Returns the process id of the child that has terminated.
Assume that the TCP echoserver and client are currently running.
Also SIGCHLD signal has been assigned the signal handler sig_chld( )
$ ./client
Hi
client types Hi and sends to the server
Hi
echo
Hello
Hello
echo
^D
client wants to end the connection
child 12245 terminated
printf instruction of the signal handler
accept error : Interrupted system call
The parent is delivered the signal SIG_CHLD when it was blocked in accept( )
Sig_chld() function executes, but though the parent is not handling the signal, and
its accept() function blocking state was being interrupted, that is why the error
accept error : Interrupted system call
3.
4.
We terminate the client by typing EOF (^D). The client sends a FIN to the
server child and the server child responds with an ACK.
The server child was blocked in readline() function when the client had send
FIN. That FIN in turns delivers EOF to the server childs pending readline().
And the server child terminates.
The server parent is blocked in call to accept() when the SIGCHLD signal is
delivered. The sig_chld signal handler executes, wait( ) fetches the childs PID
and termination status. The signal handler then returns.
The parent is delivered the signal SIG_CHLD when it was blocked in accept( )
Sig_chld() function executes, but though the parent is not handling the signal,
and its accept() function blocking state was being interrupted, that is why the
error
accept error : Interrupted system call
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid (pid_t pid, int *statloc, int options);
Both functions return process id of the terminated child if OK, -1 on error.
Both functions return 2 values:
Process ID of the terminated child process.
Termination status of the child is returned through the statloc pointer.
There are three termination status :
Child terminated normally
Was killed by a signal
Job-control stopped
If there are no terminated children for the process calling wait(), but the process
has one or more children still executing, wait() blocks until the first of the existing
children terminate.
Waitpid() gives us more control over which process to wait for. First argument to
waitpid() is pid, that lets us specify the process for which we want to wait for.
-1 in place of pid says to wait for the first of our children to terminate.
Options argument lets us specify additional options.
Difference between wait() and waitpid() when used to clean up terminated children
Suppose there is a client that has made a connect() call to the same server 5
times.
The server will call fork() 5 times and thus there will be one server parent and 5
server children.
When the client terminates, all 5 connections will send FIN at about the same
time to their respective server child, i.e. 5 FIN are sent, one on each connection.
This causes 5 SIGCHLD signals to be delivered to the parent at about the same
time.
The SIGCHLD signal that reaches the first calls the signal handler and the rest 4
SIGCHLD signals are not queued up and thus after the execution of signal
handler, only 1 SIGCHLD is again sent.
As a result, out of the 5 server child processes, 2 SIGCHLD signals are caught by
the server parent and handled. And the rest of the 3 server child processes
become zombies.
The correct way for the above kind of situation would be to all waitpid() instead of
wait()
We will call waitpid() with WNOHANG option, this tells waitpid not to block if
there exist running children that have not yet terminated.
We can not call wait() in a loop coz we cannot prevent wait from blocking.
SYN
SYN_RCVD
ack
SYN,
ack
Connect returns
ESTABLISHED
RST
accept returns
client
Server
When we type again, str_cli calls write( ) and the client TCP send the data to
the server. This is allowed by TCP coz the connection is in half-close state, i.e.
SIGPIPE Signal
Suppose there are two write( ) instructions in str_cli one after the other, i.e. the
client has to perform 2 write( ) before reading anything back.
The rule is : when a process writes to a socket that has received an RST, the
SIGPIPE signal is sent to the process. The default action of this signal is to
terminate the process, so the process must catch the signal to avoid being
terminated.
Void str_cli(FILE *fp, int sockfd)
{
char sendline[MAXLINE], recv[MAXLINE];
while( fgets(sendline, MAXLINE, fp) != NULL) {
writen(sockfd, sendline,1); //writing one byte of data
sleep(1);
writen(sockfd,sendline+1 ,strlen(sendline)-1); //writing rest of the data
readline(sockfd, recv, MAXLINE);
fputs(recv, stdout);
}
}
We write one byte of data first, and the rest after a pause of 1 second, the
intent is to write first to a closed socket and thus receive a RST. And then write
to the same socket again to generate SIGPIPE.
$ ./tcpcli
Hi
we type this line
Hi
echoed back by the server
we kill the server process
Hello
we type this and send to the server
$
no error is shown, just the command prompt comes
$ echo $?
To see the return value of the last command
269
256 + 13
1.
2.
3.
1.
2.
3.
4.
5.