1. Internetworking with TCP/IP Volume-III
by
Douglas E. Comer & David L. Stevens
2. Manuals socket(2), ip(7),
tcp(7) and fork(2)
Square brackets are used to separate keywords, such as filenames,
programs names etc etc from lines of text. They have no other meaning.
Create directory [socket2] in your home directory.
$ mkdir $HOME/socket2
Change into [socket2] directory
$ cd $HOME/socket2
Bring the file [ socket2-681.txt ] in this directory using NFS or FTP.
Creation of a new process:
A new process can be created with [fork()]
system call. Read manual of fork.
$ man 2 fork
After the [fork()] call, the newly created process is called [ child ]
and the main program is called [parent] process. The codes that follow
the [fork()] call is executed by the [ parent ] and the [ child ]. Who
executes first is not predictable.
Save the following code as [f1.c].
--------------------------------------------------
// file-name f1.c
// example of fork()
#include
#include
int main( void)
{
pid_t x;
// a new process is created
x = fork();
// following lines are executed by two processes
write( STDOUT_FILENO,"My name is Sumit\n", 17 );
return 0;
}
--------------------------------------------------
Compile and execute the program.
$ gcc -Wall ./f1.c -o ./one
$ ./one
Was the line "My name is Sumit" printed twice ? ( Y / n )
The two processes can be made to execute separate blocks of code. The
[ fork() ] call returns two PID values. The variable [ x ] gets two
values. The [child] gets x=0 and the parent gets x=some-other-integer.
Zero is not the PID of the [ child ] process, but this can be used to
confine the [child] process to a given block of code. The rest of the
codes are executed by the parent process.
Save the following as [f2.c]
----------------------------------------------------
// file-name f2.c
// example of fork()
#include
#include
#include
int main( void)
{
pid_t x;
x = fork();
// two values of x are printed.
printf("x = %u\n", x );
// both the processes sleep for a second. This is
// used only to impart some clarity in the output.
sleep(1);
if( x == 0 ) // child process
{
printf("x = %u; I am child\n", x );
exit(0);
} // end of child's block
// rest is parent process
printf("x = %u; I am parent\n", x );
return 0;
}
----------------------------------------------------
Compile and run the program.
$ gcc -Wall ./f2.c -o ./two
$ ./two
Did the child execute it's block of code ? ( Y / n )
Run the program a number of times.
Was the parent running first ? ( y / n / unpredictable )
In a concurrent server using process, a new [child] process is created
for each client's request. While a client's request is being processed
by the [ child ] process, the parent waits for the next request from
another client. If the [child] exits but the parent continues and does
not read the exit status of the child; [ zombie ] process is created.
The next program creates ten [zombie] processes.
Save the following program as [f3.c]
---------------------------------
// file-name f3.c
// creation of zombie process
#include
#include
#include
#include
int main( void)
{
pid_t x;
int n = 10;
while( n > 0 )
{
x = fork();
if( x == 0 ) // child process
{
exit(0);
} // end of child's block
n--;
//wait(NULL);
}
while(1) { sleep(1); }
}
---------------------------------
Compile and run the executable.
$ gcc -Wall ./f3.c -o ./three
$ ./three
In another terminal check processes
$ ps ax
Were zombies created ? ( Y / n )
When the parent exits, the operating system removes the zombies.
Terminate [three] with CTRL-c.
Were zombies removed ? ( Y / n )
The [zombie] processes, if not reaped (removed), use system resources.
If the [parent] reads the exit status of a [child], the [ zombie ] is
removed. Parent can use [ wait() ] system call to read the exit status
of a child. You may read the manual of [wait] and [waitpid] calls.
$ man 2 wait
Uncomment the line
//wait(NULL);
in [f3.c], compile and run the executable.
$ gcc -Wall ./f3.c -o ./three
$ ./three
In another terminal check the processes
$ ps ax
Was any zombie created ? ( y / N )
Terminate [three] with CTRL-c.
Whenever a [ child ] exits or stops, the operating system delivers a
SIGCHLD signal to the [parent] process. In [f4.c], the parent installs
a signal handler for SIGCHLD. The [parent] reads the exit status of a
child within the signal handler, whenever a [ child ] stops or exits.
The SIGCHLD signal might interrupt whatever the parent was executing.
The program [f4.c] is a concurrent TCP ECHO server. This program uses
a SIGCHLD handler to reap zombies.
------------------------------------------------------------
// file-name f4.c
// TCP ECHO server( concurrent - using process )
// Using SIGCHLD handler to reap zombie
// usage: program-name
#include
#include
#include
#include
#include
#include
#include
#include
#define LOCAL_PORT 65432
// signal handler prototype
void reap_zombie( int signum );
// a global variable
jmp_buf yy;
int main ( void )
{
int serv_sd; // socket descriptor
int temp_sd; // socket descriptor
int n;
struct sockaddr_in serv_addr; // IPv4 address
struct sigaction act; // signal action
sigset_t ss; // signal set
pid_t pid;
// members of [serv_addr] are initialised
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons( LOCAL_PORT );
serv_addr.sin_addr.s_addr = htonl( INADDR_ANY );
// TCP ( IPv4 stream ) socket is created
serv_sd = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
if( serv_sd == -1 ) { perror("socket-call"); exit(1); }
// TCP socket is bound to a local address
n = bind( serv_sd,
( struct sockaddr *) &serv_addr,
sizeof(serv_addr) );
if( n == -1 ) { perror("bind-call"); exit(1); }
// TCP socket is put in listening state
n = listen( serv_sd, 5 );
if( n == -1 ) { perror("listen-call"); exit(1); }
// main installs signal handler for SIGCHLD
act.sa_handler = reap_zombie;
act.sa_mask = ss;
act.sa_flags = 0;
n = sigaction( SIGCHLD, &act, NULL );
if ( n == -1 ) { perror("sigaction"); exit(1); }
// server accepts client's request in an endless loop
while( 1 )
{
printf( "accepting request on TCP port %u\n",
LOCAL_PORT );
// If [accept()] call is interrupted by SIGCHLD, return
// here from signal handler.
n = sigsetjmp(yy,1); // second argument is non-zero
if( n == 0 ) printf("normal flow\n");
if( n == 66 ) printf("returning from interrupt\n");
temp_sd = accept( serv_sd, NULL, 0 );
if( temp_sd == -1 ) perror("accept-call");
pid = fork();
if( pid == -1 ) perror("fork");
if( pid == 0 ) // child process handles client's request
{
#define BUFFERSIZE 1024
char buffer[BUFFERSIZE];
ssize_t i, j ;
// clear buffer
memset( buffer, '\0', BUFFERSIZE );
// read clients request string in the buffer
i = read( temp_sd, buffer, BUFFERSIZE );
printf("child: %u bytes received from client\n", i );
// write the contents of the buffer in the [temp_sd]
j = write( temp_sd, buffer, i );
if( j == -1 ) { perror("write-call"); }
shutdown( temp_sd, SHUT_RDWR );
close( temp_sd );
exit(0);
} // end of child's block
// rest is parent's code
close( temp_sd );
} //end of endless loop
return 0;
} //end of main
// signal handler
void reap_zombie( int signum )
{
pid_t pid;
printf("reap-zombie: received signal %u\n", signum );
pid = waitpid( 0, NULL, WNOHANG );
if( pid == -1 ) perror("waitpid");
siglongjmp(yy,66);
}
------------------------------------------------------------
Compile and run the server.
$ gcc -Wall ./f4.c -o ./four
$ ./four
Request your friends to use your ECHO server.
friend $ tcp-echo-client your-ip-address your-port-number
From another terminal use your ECHO server repeatedly.
$ tcp-echo-client 127.0.0.1 65432
Check processes
$ ps ax
Was there any zombie process ? ( y / N )
If reaping of [ zombie ] is not done with SIGCHLD handler, the above
program can be simplified. In [f5.c] SIGCHLD handler is not used.
-------------------------------------------------------------------
// file-name f5.c
// TCP ECHO server(concurrent - using fork) without SIGCHLD handler
// usage: program-name
#include
#include
#include
#include
#include
#include
#define LOCAL_PORT 65432
int main ( void )
{
int serv_sd; // socket descriptor
int temp_sd; // socket descriptor
int n;
struct sockaddr_in serv_addr; // IPv4 address
pid_t pid,x;
// members of [serv_addr] are initialised
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons( LOCAL_PORT );
serv_addr.sin_addr.s_addr = htonl( INADDR_ANY );
// TCP ( IPv4 stream ) socket is created
serv_sd = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
if( serv_sd == -1 ) { perror("socket-call"); exit(1); }
// TCP socket is bound to a local address
n = bind( serv_sd,
( struct sockaddr *) &serv_addr,
sizeof(serv_addr) );
if( n == -1 ) { perror("bind-call"); exit(1); }
// TCP socket is put in listening state
n = listen( serv_sd, 5 );
if( n == -1 ) { perror("listen-call"); exit(1); }
// server accepts client's request in an endless loop
while( 1 )
{
printf( "server: accepting request on TCP port %u\n",
LOCAL_PORT );
temp_sd = accept( serv_sd, NULL, 0 );
if( temp_sd == -1 ) perror("accept-call");
// create a process
pid = fork();
if( pid == -1 ) { perror("fork"); exit(1); }
if( pid == 0 ) // child process handles client's request
{
#define BUFFERSIZE 1024
char buffer[BUFFERSIZE];
ssize_t i, j ;
// clear buffer
memset( buffer, '\0', BUFFERSIZE );
// read clients request string in the buffer
i = read( temp_sd, buffer, BUFFERSIZE );
printf("server: %u bytes received from client\n", i );
// write the contents of the buffer in the [temp_sd]
j = write( temp_sd, buffer, i );
if( j == -1 ) { perror("write-call"); }
shutdown( temp_sd, SHUT_RDWR );
close( temp_sd );
exit(0);
} // end of child's block
// rest is parent's code
close( temp_sd );
x = waitpid( 0, NULL, WNOHANG );
if( x == -1 ) perror("waitpid");
} //end of endless loop
return 0;
} //end of main
-------------------------------------------------------------------
Compile and run the server
$ gcc -Wall ./f5.c -o ./ser-con-p
$ ./ser-con-p
Use the ECHO server repeatedly
$ tcp-echo-client 127.0.0.1 65432
Was some zombie remained in system ? ( Y / n )
In the following program [f6.c], main program creates a [ thread ] to
process a client's request, after a successful [accept()] system call.
When a [ thread ] is created, it starts to execute it's start routine,
with the supplied argument. Main program creates the [thread] with the
newly created socket, as the argument.
----------------------------------------------------------------------
// file-name f6.c
// TCP echo server (concurrent - using threads)
// usage: program-name
#include
#include
#include
#include
#include
#define PORT 65432
void func( int arg );
int main(void)
{
int ser_sd,tempsd;
int n;
struct sockaddr_in ser_addr;
pthread_t x; // a thread item
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons( PORT );
ser_addr.sin_addr.s_addr = htonl( INADDR_ANY );
ser_sd = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if( ser_sd == -1 ) { perror("socket"); exit(1); }
n = bind( ser_sd,(struct sockaddr *) &ser_addr,
sizeof(ser_addr) );
if( n == -1) { perror("bind"); exit(1); }
n = listen( ser_sd, 5 );
if( n == -1 ) { perror("listen"); exit(1); }
while ( 1 )
{
printf("server is ready on TCP port %u \n", PORT );
// wait for a client's request
tempsd = accept( ser_sd, NULL, 0 );
if( tempsd == -1 ) perror("accept");
else
{
printf("creating a thread to process client's request\n");
n = pthread_create( &x, NULL,( void *) func, (void *) tempsd );
if( n != 0 ) perror("pthread_create");
}
} // endless loop
return 0;
}
// client's request is processes by this thread
void func( int arg )
{
#define BUFFERSIZE 1024
char buffer[BUFFERSIZE];
ssize_t i,j;
int n;
memset(buffer, '\0', BUFFERSIZE);
i = read( arg, buffer, BUFFERSIZE);
if( i == -1 ) { perror("read"); exit(1); }
printf("thread: read %u bytes from server\n", i );
// write [i] bytes to client
j = write( arg, buffer, i );
if(j == -1 ) { perror("write"); exit(1); }
n = shutdown( arg, SHUT_RDWR );
if( n == -1 ) { perror("shutdown"); }
close(arg);
pthread_exit(NULL);
}
----------------------------------------------------------------------
Compile and run the executable.
$ gcc -Wall -D_RENTRANT -lpthread ./f6.c -o ./ser-con-t
$ ./ser-con-t
Test the ECHO server from localhost and from other hosts.
Did the ECHO server process multiple client requests ? ( Y / n )
----------------------------
Optional assignment-1:
Trace system calls and signals when the program
[four] executes and client connects to it.
$ strace ./four
Optional assignment-2:
Capture packets generated by client and server.
Start ECHO server
$ ./ser-con-t
In another terminal, capture TCP packets, to and from your host. Save
the captured packets in a file [kk] in the current directory.
# tcpdump -i eth0 host your-ip-address and tcp -w ./kk
The data in file [ kk ] is not text. Convert the captured data into
text and save it in file [jj]. in the current directory.
# tethereal -V -r ./kk > ./jj
Examine the text file.
# less ./jj
-------------------
