0% found this document useful (0 votes)
152 views19 pages

Indian Institute of Space Science and Technology AV 341: Computer Networks Lab

The document describes an experiment to implement and compare iterative, multi-process concurrent, and multi-threaded concurrent TCP server implementations. Students are asked to create each type of server for a string reversal service and measure client response times with 1, 2, or 4 simultaneous clients. Key steps include creating an iterative server with delay, a multi-process server using fork, and a multi-threaded server using pthreads and mutexes to protect shared data from race conditions.

Uploaded by

shikhamaharana
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
152 views19 pages

Indian Institute of Space Science and Technology AV 341: Computer Networks Lab

The document describes an experiment to implement and compare iterative, multi-process concurrent, and multi-threaded concurrent TCP server implementations. Students are asked to create each type of server for a string reversal service and measure client response times with 1, 2, or 4 simultaneous clients. Key steps include creating an iterative server with delay, a multi-process server using fork, and a multi-threaded server using pthreads and mutexes to protect shared data from race conditions.

Uploaded by

shikhamaharana
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 19

Indian institute of Space Science and Technology AV 341: Computer Networks Lab

Date: 1/04/2013 and 5/04/2013 Experiment No: 5 Objectives: To implement and study iterative and concurrent servers implementations for communication over TCP sockets. Study multi-process and multi-threaded concurrent servers. Observe the performance of the servers by measuring the round trip time for each of the servers. Documents Required: You may use the code fractions provided in this document. Additionally, you may use the past Lab documents that were distributed through the course websites in the last weeks. Use the lab computer to write the code and experiment the outcome. References: Linux Manual pages on Socket APIs. Unix Network Programming by Richard Stevens et. al. Introduction: There exist two methods to implement TCP or UDP servers in network applications: (i) iterative server and (ii) concurrent server. An iterative server serves the client connections in an iterative manner (one function such as send/receive at a time in a sequence or one socket after another in a sequence). On the other hand, a concurrent server serves the client connections almost in parallel without any particular sequence. For very lightly loaded servers, there will be no performance difference between iterative and concurrent servers. However, for moderate to high load servers, or servers with moderate to larger numbers of clients, iterative servers may not work satisfactorily.

There exist several ways to implement concurrent servers. Two important approaches are: (a) using multiple processes where a child process serves a client and (b) using multiple threads where each thread serves a clients connection. A process is an instance of a computer program that is being executed in a computer. The Operating System (OS) creates a process by running the program code, maintaining the state variables, and allocating resources such as program memory, data memory, and processor cycles. An OS typically runs several processes by switching the execution between them at such a fast rate that they appear as concurrently running. Example depictions of processes within an OS is shown in Figure 1 .

Operating System
Process-1 (Data, Program, Cycles) Process-2 (Data, Program, Cycles) Process-N (Data, Program, Cycles)

Figure 1. Computing Processes.

Operating System
Process-1 (Data, Program, Cycles) Thread-1 Thread-2 Process-N (Data, Program, Cycles)

Figure 2. Threads of execution within Processes.

Compared to a process, a thread is a lightweight process or program execution sequence that shares most of the resources of the parent process such as program memory, data memory, etc. However, the stack pointer and other very important data, necessary for the execution of the program, will be separate for each thread within a process. Simply speaking, threads are light processes that share the data variables in its process. Therefore, it becomes necessary to safeguard the data when multiple treads manipulate them. Figure 2 shows a depiction of threads within a process.

Procedure:
Step 1: An iterative TCP server
Create an iterative TCP server application for string reversal service in another machine for serving multiple client programs implemented in your lab machine as shown in the figure below.

To simulate a computing intensive processing at the server, add a delay of 2 seconds in the string reversal server. Measure the performance such as delay of service when 1, 2, or 4 clients connect to the server and seek string reversal service. Make a chart of the delay performance as shown below. Number of simultaneous clients Average time taken for iterative and concurrent servers. Averaged from atleast 4 measurements) Iterative server Multi-process Multi-threaded concurrent server concurrent server

1 2 3

Appendix A of this document shows an example implementation of Iterative server. Changes required at the server: You may want to implement the string reversal service at the server using the past lab documents. That is, you may have to add a sleep(2) function in the connection handling function in the server process. Changes required at the client: Time measurement for measuring the service time between making a request to process the string and its response. You may want to use information from past lab documents.

Step 2: Create a Multi-process concurrent server where a separate process serves each client connection.
The lab-1 experiment you carried out is an example of multi-process concurrent server. In the server code that we used in Lab 1, the function fork() is used for creating multiple processes where each new connection is served by a separate process. The changes required in the Step 1 to effect the String reversal service as well as addition of delay must be done to the Server code. Time measurement at the client code is essential. Fill the delay observations made in the table mentioned in Step 1. Appendix B of this document shows an example implementation of Iterative server. You may need to modify the code for (i) time measurement at the client side and (ii) string reversal service at the server side.

Step 3: Create a multi-threaded concurrent server to server each client request.


Create a multi-threaded concurrent server by using the posix thread library. In order to compile the C program with multithreaded code, you may use the lpthread option with gcc. That is to compile, you need to use the following command line statement:

$ gcc filename.c o filename.out lpthread For the multithreaded server implementation the following new declarations are required: #include <pthread.h>
static void* str_echo(void *arg); //declaring the function

The following new functions to be used for creating threads: int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg) //to create a new thread 1. pthread_t tid: thread identifier (unsigned int) 2. pthread_attr_t *attr: attributes such as thread priority, initial stack size, or should be run as deamon or not. Can be NULL to use default variables. 3. void*(*func)(void*): a function to be executed for the thread. The function should take a void pointer and returns a void pointer. The thread begins execution by running the function. The thread exits either by returning from the function or by explicitly calling ptherad_exit() function. 4. void * args: pointer to pass data to the function executed by the thread. It returns 0 if success and nonzero for an error.

int pthread_exit(void *status) //to exit a new thread.

Example code changes in the server program : main() { #include <pthread.h>

pthread_t

tid;

int *tempsocket; . .
listen(sockfd, 5); for ( ; ; ) { /* * Wait for a connection from a client process. * This is an example of a concurrent threaded server. */ clilen = sizeof(cli_addr); newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); if (newsockfd < 0) perror("server: accept error");
tempsocket=malloc(sizeof(int)); *tempsocket = clientsock; pthread_create(&tid, NULL, &str_echo, (void *)tempsocket); fprintf(stdout, "Client connected: %s\n", inet_ntoa(cli_addr.sin_addr));

In addition, it is essential to modify the str_echo(..) function properties. For example, modify the str_echo() function as follows:
static void* str_echo(void *arg) {

int n; char line[MAXLINE];


int sockfd = *((int *) arg); free(arg); for(; ; )

{ n = recv(sockfd, line, MAXLINE, 0); if (n == 0)

return; /* connection terminated */ else if (n < 0) perror("str_echo: readline error"); if (send(sockfd, line, n,0) != n) perror("str_echo: writen error"); } close(sockfd); }

Now, the most important point is to make sure the global data variables, that each of the multiple threads manipulate, are protected. For example if thread 1 and thread 2 are modifying a global variable then it may behave very inconsistent. You need to protect the critical data variables that multiple threads may manipulate. The two functions that can be used to safeguard threads operating on the data are: int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); The above two functions are meant for Mutually Exclusive (MUTEX) operations on variables so that two threads may not manipulate some important global variables simultaneously. An example of how to use the mutex functions is below. #include <pthread.h>

static void* str_echo(void *arg) {

int n; char line[MAXLINE];


int sockfd = *((int *) arg); free(arg); pthread_mutex_t buffer_mutex= PTHREAD_MUTEX_INITIALIZER; for(; ; )

{ n = recv(sockfd, line, MAXLINE, 0); if (n == 0) return; /* connection terminated */ else if (n < 0) perror("str_echo: readline error");

pthread_mutex_lock(&buffer_mutex); // Locks the mutex variable

// code fraction protected from other threads. // suppose if you use a global variable for string // reversal, you need to use protection/sync //mechanisms
pthread_mutex_unlock(&buffer_mutex); //unlocks the mutex variable

if (send(sockfd, line, n,0) != n) perror("str_echo: writen error"); } close(sockfd); }

Other changes required in the Step 1 to effect the delay in string reversal and time measurements are required in Step 3 also. Fill the delay observations made in the table mentioned in Step 1.

Homework/Documentation: Prepare a lab notebook documenting the code and outcome for the experiment.

Appendix A: Iterative server/client implementation TCP Iterative server code


#include #include #include #include #include #include #include #define #define #define <stdio.h> <stdlib.h> <string.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> SERV_TCP_PORT MAXLINE CONN_LIMIT 9877 512 4

char *pname; /* * Example of server using TCP protocol. */ main(argc, argv) int argc; char *argv[]; { int sockfd, newsockfd, clilen, childpid; struct sockaddr_in cli_addr, serv_addr; int connCount; int socketArray[CONN_LIMIT]; pname = argv[0]; /* * Open a TCP socket (an Internet stream socket). */ if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) perror("server: can't open stream socket"); /* * Bind our local address so that the client can send to us. */ bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

serv_addr.sin_port

= htons(SERV_TCP_PORT);

if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) perror("server: can't bind local address"); listen(sockfd, 5); for (connCount=0; connCount < CONN_LIMIT; connCount++) { /* * Wait for connections from client processes. * This is an example of iterative server. */ clilen = sizeof(cli_addr); socketArray[connCount]=accept(sockfd,(struct sockaddr *) &cli_addr, &clilen); if (socketArray[connCount] < 0) perror("server: accept error"); } for ( ; ; ) { for (connCount=0; connCount < CONN_LIMIT; connCount++) { str_echo(socketArray[connCount]); } } // // } /* * Read a stream socket one line at a time, and write each line back * to the sender. * * Return when the connection is terminated. */ str_echo(sockfd) int sockfd; { close(sockfd); for (int connCount=0; connCount < CONN_LIMIT; connCount++) //{close(socketArray[connCount]);}

int n; char line[MAXLINE]; n = recv(sockfd, line, MAXLINE, 0); if (n == 0) return; /* connection terminated */ else if (n < 0) perror("str_echo: readline error"); if (send(sockfd, line, n,0) != n) perror("str_echo: writen error"); }

TCP Iterative client code:

#include #include #include #include #include #include #include

<stdio.h> <stdlib.h> <string.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> /* host addr for server

#define SERV_TCP_PORT 9877 #define SERV_HOST_ADDR "127.0.0.1" replace it with the servers IP address*/ #define MAXLINE 512

char *pname; /* * Example of client using TCP protocol. */ main(argc, argv) int argc; char *argv[]; { int sockfd; struct sockaddr_in serv_addr; // char SERVER_IP_ADDR[25]; pname = argv[0]; /*

* Fill in the structure "serv_addr" with the address of the * server that we want to connect with. */ // strcpy(SERVER_IP_ADDR,"localhost"); bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR); serv_addr.sin_port = htons(SERV_TCP_PORT); /* * Open a TCP socket (an Internet stream socket). */ if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) perror("client: can't open stream socket"); /* * Connect to the server. */ if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) perror("client: can't connect to server"); str_cli(stdin, sockfd); close(sockfd); exit(0); } /* * Read the contents of the FILE *fp, write each line to the * stream socket (to the server process), then read a line back from * the socket and write it to the standard output. * * Return to caller when an EOF is encountered on the input file. */ str_cli(fp, sockfd) register FILE *fp; register int sockfd; { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; printf(Enter the string to be sent to the server\n); while (fgets(sendline, MAXLINE, fp) != NULL) { n = strlen(sendline); /* do it all */

printf(Sending %d bytes to server at %s\n,n,SERV_HOST_ADDR); if (send(sockfd, sendline, n,0) != n) perror("str_cli: writen error on socket"); /* * Now read a line from the socket and write it to * our standard output. */ n = recv(sockfd, recvline, MAXLINE,0); if (n < 0) perror("str_cli: readline error"); recvline[n] = 0; /* null terminate */ printf("Received From Server: "); fputs(recvline, stdout); } if (ferror(fp)) perror("str_cli: error reading file"); }

Modification required for TCP Iterative client/Server codes: Client requires round trip time (RTT) measurement code that you may have implemented in Lab 2. Server code requires code addition, between receiving a packet from client and sending it back to the client, for (i) string reversal code and (ii) a sleep(2) function call.

Appendix B: Multi-Process server/client implementation


Server Code #include #include #include #include #include <stdio.h> <stdlib.h> <string.h> <sys/types.h> <sys/socket.h>

#include #include #define #define

<netinet/in.h> <arpa/inet.h> SERV_TCP_PORT MAXLINE 512 9877

char *pname; /* * Example of server using TCP protocol. */

main(argc, argv) int argc; char *argv[]; { int sockfd, newsockfd, clilen, childpid; struct sockaddr_in cli_addr, serv_addr; pname = argv[0]; /* * Open a TCP socket (an Internet stream socket). */ if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) perror("server: can't open stream socket"); /* * Bind our local address so that the client can send to us. */ bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(SERV_TCP_PORT); if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) perror("server: can't bind local address"); listen(sockfd, 5); for ( ; ; ) { /* * Wait for a connection from a client process. * This is an example of a concurrent server.

*/ clilen = sizeof(cli_addr); newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); if (newsockfd < 0) perror("server: accept error"); if ( (childpid = fork()) < 0) perror("server: fork error"); else if (childpid == 0) { close(sockfd); str_echo(newsockfd); exit(0); } close(newsockfd); } } /* * Read a stream socket one line at a time, and write each line back * to the sender. * * Return when the connection is terminated. */ str_echo(sockfd) int sockfd; { int n; char line[MAXLINE]; for ( ; ; ) { n = recv(sockfd, line, MAXLINE, 0); if (n == 0) return; /* connection terminated */ else if (n < 0) perror("str_echo: readline error"); if (send(sockfd, line, n,0) != n) perror("str_echo: writen error"); } } /* child process */ /* close original socket */ /* process the request */

/* parent process */

Client code:
#include #include #include #include #include #include #include #define #define #define <stdio.h> <stdlib.h> <string.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> SERV_TCP_PORT SERV_HOST_ADDR MAXLINE 512 9877 "127.0.0.1" /* host addr for server */

char *pname; /* * Example of client using TCP protocol. */ main(argc, argv) int argc; char *argv[]; { int sockfd; struct sockaddr_in serv_addr; // char SERVER_IP_ADDR[25]; pname = argv[0]; /* * Fill in the structure "serv_addr" with the address of the * server that we want to connect with. */ // strcpy(SERVER_IP_ADDR,"localhost"); bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR); serv_addr.sin_port = htons(SERV_TCP_PORT); /* * Open a TCP socket (an Internet stream socket). */ if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) perror("client: can't open stream socket");

/* * Connect to the server. */ if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) perror("client: can't connect to server"); str_cli(stdin, sockfd); close(sockfd); exit(0); } /* * Read the contents of the FILE *fp, write each line to the * stream socket (to the server process), then read a line back from * the socket and write it to the standard output. * * Return to caller when an EOF is encountered on the input file. */ str_cli(fp, sockfd) register FILE *fp; register int sockfd; { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; printf(Enter the string to be sent to the server\n); while (fgets(sendline, MAXLINE, fp) != NULL) { n = strlen(sendline); printf("Sending %d bytes to server at %s\n",n,SERV_HOST_ADDR); if (send(sockfd, sendline, n,0) != n) perror("str_cli: writen error on socket"); /* * Now read a line from the socket and write it to * our standard output. */ n = recv(sockfd, recvline, MAXLINE,0); if (n < 0) perror("str_cli: readline error"); recvline[n] = 0; /* null terminate */ printf("Received From Server: "); /* do it all */

fputs(recvline, stdout); } if (ferror(fp)) perror("str_cli: error reading file"); }

Appendix C: Multi-threaded server/client implementation


You may modify the TCP Server Code and TCP client code provided in Appendix B for implementing a Multi-threaded server.

Appendix D: Code fraction for round trip time measurement


The following code fraction can be used for measuring the time taken between two points (time1 and time2) in the code. Note that the struct timeval has two member variables, tv_sec and tv_usec for the seconds and microseconds time measurements, respectively. For accurate estimation of elapsed time, you may need to combine both the seconds and microseconds measurements.
struct timeval time1, time2; // structures that can take time in seconds and micro seconds. gettimeofday(&time1,NULL); // time measurement at time instant t1 sleep(1);//one second delay added as an example gettimeofday(&time2,NULL); // time measurement at time instant t2 printf("Time difference in %f ms \n", ((double)(time2.tv_sectime1.tv_sec)*1000000+(double)(time2.tv_usec-time1.tv_usec))/1000); //print the elapsed time in milli seconds.

Appendix E: String reversal code/logic


The following code fraction may be used for reversing the received string at the server.
//input is the buffer where the string is stored // size is the length of the string //c is a character variable for (i=0;i<(size/2);i++) { c= input[i]; input[i]=input[size-i-1]; input[size-i-1]=c; }

You might also like