Piotr Mielecki Ph. D. http://mielecki.ristel.pl/ piotr.mielecki@pwr.edu.pl pmielecki@gmail.com
Building blocks of client-server applications: Client, Server, Middleware. Simple client-server application: Version 1.0 non-multitasking server, client s requests queued and serviced according to FIFO strategy. Version 2.0 multitasking server, client s requests are serviced parallel. Version 2.5 multitasking server which can be installed as UNIX / Linux system service (daemon).
Client: runs the client side of the application, runs on the OS that provides an user interface and that can access distributed services, wherever they may be, the client can also run a component of the Distributed System Management (DSM) element: DSM agents run on every node in the client/server network, managing workstation collects information from all its agents on the network and displays it, the managing workstation can also instruct its agents to perform actions on its behalf.
Server: runs the server side of the application, the server application typically runs on top of some shrink-wrapped server software package, the five contending server platforms for creating client/server applications are: SQL database severs, Transaction Processing Monitors to ensure that the transaction processes completely or, if an error occurs, to take appropriate actions (in most of cases integrated with database servers), groupware servers, object servers see Component Object Model (COM) for example, web servers, the server side depends on the OS to interface with the middleware building block, it may be a simple agent or a shared object database etc.
Middleware: runs on both the client and server sides of an application, middleware is the nervous system of the client/server infrastructure, this can also have the Distributed System Management (DSM) component.
Client Middleware Server User interface Web browser DSM OS Service specific HTTP ODBC Mail TxRPC ORB Distributed System Management (DSM) SNMP CMIP Tivoli / ORB Network Operating System (NOS) Directory Services Security Distributed File System RPC Messaging Peer-to-Peer Transport stack NetBIOS TCP/IP IPX/SPX SNA SQL DBMS TP Monitor Groupware Objects Web server DSM OS
Server waits for message from client and sends back acknowledgement after receiving. Client sends message to server and waits for acknowledgement. Both use UNIX TCP/IP sockets as middleware. When server receives SHUTDOWN message quits (server recognizes a command from client we can call it Application level protocol ). Message Acknowledgement Sends a text message to server Waits for message from client
Server program (v. 1.0): Create socket Listen on socket NO Connection requested YES Receive message Send acknowledgement Accept connection Provide service Close connection Close socket YES SHUTDOWN command NO Quit
Server v. 1.0 code, part 1: #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define BUFF_LEN 256 // Length of the message buffer int main(int argc, char *argv[]) int sockfd, consockfd; // Socket file descriptors struct sockaddr_in serv_addr, cli_addr; socklen_t clilen; char cli_addr_str[inet_addrstrlen]; char buffer[buff_len]; const char *ack_message = "Message acknowledged."; int n; if (argc < 3) // Server s IP and port given in command line fprintf(stderr,"command: %s <address> <port>\n", argv[0]); clilen = sizeof(cli_addr); // Set length of client address
Server v. 1.0 code, part 2: // Step 1. Create the Internet socket (SOCK_STREAM and 0 means TCP): sockfd = socket( AF_INET, SOCK_STREAM, 0 ); if (sockfd < 0) fprintf(stderr,"server ERROR: socket opening failed.\n"); // Initialize address of the server in serv_addr: bzero( (char *) &serv_addr, sizeof(serv_addr) ); serv_addr.sin_family = AF_INET; // Function inet_pton() gets IP address from C character string and // stores it in sin_addr field of sockaddr_in type structure. // Use this function rather instead obsolete inet_addr() which doesn't // support IPv6. In this example AF_INET means IPv4. inet_pton( AF_INET, argv[1], &(serv_addr.sin_addr) ); // Function htons() converts port number in host byte order // (depends on host computer architecture) to a port number // in network byte order (standardized). serv_addr.sin_port = htons( atoi(argv[2]) );
Server v. 1.0 code, part 3: // Step 2. Bind the socket to address - sets the value of file // descriptor (handle). // Typecasting is required to fit sockaddr_in type structure // to general sockaddr type. if ( bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0 ) fprintf(stderr,"server ERROR: binding failed, the port is in use.\n"); // Step 3. Listen on socket pointed by its handle. // The 2nd parameter is size of the backlog queue - the number // of connections that can be waiting while the process is handling // a particular connection. Typicaly 5 is the maximum value. listen( sockfd, 5 );
Server v. 1.0 code, part 4: // Step 4. Accept connections from clients and perform operations. do // Function accept() blocks process till any connection from // client comes. consockfd = accept( sockfd, (struct sockaddr *) &cli_addr, &clilen ); // The returned new handle to socket represents this particular // connection and should be used for servicing this connection. if (consockfd < 0) fprintf(stderr,"server ERROR: accept failed.\n"); // Function inet_ntop() gets address from sin_addr field of sockaddr_in // type structure and stores it to C character string. inet_ntop(af_inet, &(cli_addr.sin_addr), cli_addr_str, INET_ADDRSTRLEN); printf("[server] Connection accepted with client %s port %d\n", cli_addr_str, ntohs(cli_addr.sin_port));
Server v. 1.0 code, part 5: bzero( buffer, BUFF_LEN ); n = read( consockfd, buffer, BUFF_LEN ); // Read from the socket. if (n < 0) fprintf(stderr,"server ERROR: reading from socket failed.\n"); printf("[server] Message from client: Length = %d Content = %s\n", strlen(buffer), buffer); // Send acknowledgement message write to socket: n = write( consockfd, ack_message, strlen(ack_message) ); if (n < 0) fprintf(stderr,"server ERROR: writing to socket failed.\n"); close( consockfd ); // Close socket descriptor used by connection. while ( strcmp( buffer, "SHUTDOWN" )!= 0 ); // Until SHUTDOWN command
Server v. 1.0 code, part 6: // Exiting the server program: close( sockfd ); // Close socket descriptor used for listening. printf("[server] - Shutting down.\n"); return 0;
Client program (v. 1.0): Create socket Connect to server Send message Receive acknowledgement Close socket Quit
Client v. 1.0 code, part 1: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define BUFF_LEN 256 // Length of the message buffer int main(int argc, char *argv[]) int sockfd, n; struct sockaddr_in serv_addr; struct hostent *server; char buffer[buff_len]; if (argc < 3) // Server s IP address and port given in command line. fprintf(stderr,"command: %s <address> <port>\n", argv[0]);
Client v. 1.0 code, part 2: // Step 1. Get host address for IP or URL given in argv[1]: server = gethostbyname( argv[1] ); if (server == NULL) fprintf(stderr,"error: server host not found.\n"); // Step 2. Create server address from gethostbyname() result: bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; bcopy( (char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length ); serv_addr.sin_port = htons( atoi(argv[2]) ); // Step 3. Create the Internet socket (SOCK_STREAM and 0 means TCP): sockfd = socket( AF_INET, SOCK_STREAM, 0 ); if (sockfd < 0) fprintf(stderr,"error: socket opening failed.\n");
Client v. 1.0 code, part 3: // Step 4. Connect to server - operation compatible with accept() // on server side: if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) fprintf(stderr,"error: connecting to server failed.\n"); // Input message text to the buffer: bzero( buffer, BUFF_LEN ); printf("message FOR server: "); fgets( buffer, BUFF_LEN, stdin ); // Replace end of line character(s) with NULL terminator: for( n = 0; n < strlen(buffer); n++ ) if ( buffer[n] == '\n' buffer[n] == '\r' ) buffer[n] = '\0';
Client v. 1.0 code, part 4: // Step 5. Send the message to server via socket: n = write( sockfd, buffer, strlen(buffer) ); if (n < 0) fprintf(stderr,"error: writing to socket failed.\n"); // Step 6. Read acknowledge message from server via socket: bzero( buffer, BUFF_LEN ); n = read( sockfd, buffer, BUFF_LEN ); if (n < 0) fprintf(stderr,"error: reading from socket failed.\n"); printf( "Message FROM server: %s\n", buffer ); // Step 7. Close Internet socket and connection: close( sockfd ); return 0;
Client program (v. 1.5): Create socket Connect to server Send message Receive acknowledgement Close socket YES QUIT command NO Quit YES SHUTDOWN command NO
Client v. 1.5 code, part 1: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define BUFF_LEN 256 // Length of the message buffer int main(int argc, char *argv[]) int sockfd, n; struct sockaddr_in serv_addr; struct hostent *server; char buffer[buff_len]; if (argc < 3) // Server s IP address and port given in command line. fprintf(stderr,"command: %s <address> <port>\n", argv[0]);
Client v. 1.5 code, part 2: // Step 1. Get host address for IP or URL given in argv[1]: server = gethostbyname( argv[1] ); if (server == NULL) fprintf(stderr,"error: server host not found.\n"); // Step 2. Create server address from gethostbyname() result: bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; bcopy( (char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length ); serv_addr.sin_port = htons( atoi(argv[2]) ); do // Client loop begining (doesn t exist in v. 1.0). // Step 3. Create the Internet socket: sockfd = socket( AF_INET, SOCK_STREAM, 0 ); if (sockfd < 0) fprintf(stderr,"error: socket opening failed.\n");
Client v. 1.5 code, part 3: // Step 4. Connect to server - operation compatible with accept() // on server side: if (connect(sockfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) fprintf(stderr,"error: connecting to server failed.\n"); // Input message text to the buffer: bzero( buffer, BUFF_LEN ); printf("message FOR server: "); fgets( buffer, BUFF_LEN, stdin ); // Replace end of line character(s) with NULL terminator: for( n = 0; n < strlen(buffer); n++ ) if ( buffer[n] == '\n' buffer[n] == '\r' ) buffer[n] = '\0'; // Check the exit conditions (doesn t exist in v. 1.0): if ( strcmp(buffer, "QUIT") == 0 strcmp(buffer, "SHUTDOWN") == 0 ) next_msg = 0; else next_msg = 1;
Client v. 1.5 code, part 4: // Step 5. Send the message to server via socket: n = write( sockfd, buffer, strlen(buffer) ); if (n < 0) fprintf(stderr,"error: writing to socket failed.\n"); // Step 6. Read acknowledge message from server via socket: bzero( buffer, BUFF_LEN ); n = read( sockfd, buffer, BUFF_LEN ); if (n < 0) fprintf(stderr,"error: reading from socket failed.\n"); printf( "Message FROM server: %s\n", buffer ); // Step 7. Close Internet socket and connection: close( sockfd ); while ( next_msg!= 0 ); // Client loop end. printf( "Quiting.\n" ); return 0;
Server program (v. 2.0): Create socket NO Listen on socket Connection requested PARENT fork() YES CHILD Accept connection Close connection Perform service 1 Exit child process NO QUIT command YES Close socket Close connection Quit parent process YES SHUTDOWN command NO 1 Exit child process
Server v. 2.0 code, part 1: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/wait.h> #include <signal.h> #define BUFF_LEN 256 // Length of the message buffer. int sockfd; // Parent socket handle declared as public // to be available for SIGQUIT handler.
Server v. 2.0 code, part 2: // Handler for SIGCHLD signal raised up by each child process on exit. // Parent process reads all information about exiting child to avoid // zombie creation. void sigchld_handler( int signo ) pid_t PID; int status; do PID = waitpid( -1, &status, WNOHANG ); while ( PID!= -1 ); signal( SIGCHLD, sigchld_handler ); // Handler for SIGQUIT signal used to quit parent process. void sigquit_handler( int signo ) close( sockfd ); printf("[server] Shutting down.\n"); exit( 0 );
Server v. 2.0 code, part 3: #define BUFF_LEN 256 // Length of the message buffer int main(int argc, char *argv[]) int consockfd; struct sockaddr_in serv_addr, cli_addr; socklen_t clilen; char cli_addr_str[inet_addrstrlen]; unsigned short cli_port; char buffer[buff_len]; const char *ack_message = "Message acknowledged."; pid_t PID, PPID; int n; if (argc < 3) fprintf(stderr,"command: %s <address> <port>\n", argv[0]); signal( SIGCHLD, sigchld_handler ); clilen = sizeof(cli_addr); // Set SIGCHLD signal handler.
Server v. 2.0 code, part 4: // Step 1. Create the Internet socket (SOCK_STREAM and 0 means TCP): sockfd = socket( AF_INET, SOCK_STREAM, 0 ); if (sockfd < 0) fprintf(stderr,"server ERROR: socket opening failed.\n"); // Initialize address of the server in serv_addr: bzero( (char *) &serv_addr, sizeof(serv_addr) ); serv_addr.sin_family = AF_INET; // Function inet_pton() gets IP address from C character string and // stores it in sin_addr field of sockaddr_in type structure. // Use this function rather instead obsolete inet_addr() which doesn't // support IPv6. In this example AF_INET means IPv4. inet_pton( AF_INET, argv[1], &(serv_addr.sin_addr) ); // Function htons() converts port number in host byte order // (depends on host computer architecture) to a port number // in network byte order (standardized). serv_addr.sin_port = htons( atoi(argv[2]) );
Server v. 2.0 code, part 5: // Step 2. Bind the socket to address - sets the value of file // descriptor (handle). // Typecasting is required to fit sockaddr_in type structure // to general sockaddr type. if ( bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0 ) fprintf(stderr,"server ERROR: binding failed, the port is in use.\n"); // Step 3. Listen on socket pointed by its handle. // The 2nd parameter is size of the backlog queue - the number // of connections that can be waiting while the process is handling // a particular connection. Typicaly 5 is the maximum value. listen( sockfd, 5 );
Server v. 2.0 code, part 6: // Step 4. Accept connections from clients and perform operations. do // Server main loop begining. consockfd = accept( sockfd, (struct sockaddr *) &cli_addr, &clilen ); if (consockfd < 0) fprintf(stderr,"server ERROR: accept failed.\n"); inet_ntop(af_inet, &(cli_addr.sin_addr), cli_addr_str, INET_ADDRSTRLEN); printf("[server] Connection accepted with client %s port %d\n", cli_addr_str, ntohs(cli_addr.sin_port)); // Create child process for servicing client s request: if( (PID = fork()) == -1 ) // fork() error. close( consockfd ); fprintf(stderr,"server ERROR: Creating new process failed.\n"); continue; else if( PID > 0 ) // Parent process. close( consockfd ); // Close the parent's copy of handle. signal( SIGQUIT, sigquit_handler ); // Set SIGQUIT signal handler. continue;
Server v. 2.0 code, part 7: // Child process starts here. PPID = getppid(); do // Session loop session with client. bzero( buffer, BUFF_LEN ); n = read( consockfd, buffer, BUFF_LEN ); // Read message from client. if (n < 0) fprintf(stderr,"server ERROR: reading from socket failed.\n"); // Replace end of line character(s) with terminator: for( n = 0; n < strlen(buffer); n++ ) if ( buffer[n] == '\n' buffer[n] == '\r' ) buffer[n] = '\0'; printf("[server] Message from client [%s %d]: %s\n", cli_addr_str, cli_port, buffer); n = write( consockfd, ack_message, strlen(ack_message) ); if (n < 0) fprintf(stderr,"server ERROR: writing to socket failed.\n");
Server v. 2.0 code, part 8: if ( strcmp(buffer, "SHUTDOWN") == 0 ) printf("[server] Shutting down.\n"); kill( PPID, SIGQUIT ); // Send SIGQUIT signal to parent process. break; while ( strcmp( buffer, "QUIT" )!= 0 ); // Session loop end. printf("[server] Session with client [%s %d] quits.\n", cli_addr_str, cli_port); close( consockfd ); exit( 0 ); // Exit child process (session with client). while ( 1 ); // Server main loop end.
Client program (v. 2.0): Create socket Connect to server Send message Receive acknowledgement YES QUIT command NO Close socket YES SHUTDOWN command NO Quit
Client v. 2.0 code, part 1: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define BUFF_LEN 256 // Length of the message buffer int main(int argc, char *argv[]) int sockfd, n, next_msg; struct sockaddr_in serv_addr; struct hostent *server; char buffer[buff_len]; if (argc < 3) fprintf(stderr,"command: %s <address> <port>\n", argv[0]);
Client v. 2.0 code, part 2: // Step 1. Get host address for IP or URL given in argv[1]: server = gethostbyname( argv[1] ); if (server == NULL) fprintf(stderr,"error: server host not found.\n"); // Step 2. Create server address from gethostbyname() result: bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; bcopy( (char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length ); serv_addr.sin_port = htons( atoi(argv[2]) ); // Step 3. Create the Internet socket: sockfd = socket( AF_INET, SOCK_STREAM, 0 ); if (sockfd < 0) fprintf(stderr,"error: socket opening failed.\n");
Client v. 2.0 code, part 3: // Step 4. Connect to server - operation compatible with accept() // on server side: if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) fprintf(stderr,"error: connecting to server failed.\n"); do // Client main loop session with active TCP connection. // Input message text to the buffer. bzero( buffer, BUFF_LEN ); printf("message FOR server: "); fgets( buffer, BUFF_LEN, stdin ); // Replace end of line character(s) with terminator: for( n = 0; n < strlen(buffer); n++ ) if ( buffer[n] == '\n' buffer[n] == '\r' ) buffer[n] = '\0'; // Check the exit conditions: if ( strcmp(buffer, "QUIT") == 0 strcmp(buffer, "SHUTDOWN") == 0 ) next_msg = 0; else next_msg = 1;
Client v. 2.0 code, part 4: // Step 5. Send the message to server via socket: n = write( sockfd, buffer, strlen(buffer) ); if (n < 0) fprintf(stderr,"error: writing to socket failed.\n"); // Step 6. Read acknowledge message from server via socket: bzero( buffer, BUFF_LEN ); n = read( sockfd, buffer, BUFF_LEN ); if (n < 0) fprintf(stderr,"error: reading from socket failed.\n"); printf( "Message FROM server: %s\n", buffer ); while ( next_msg!= 0 ); printf( "Quiting.\n" ); // Step 7. Close Internet socket and connection (session): close( sockfd ); return 0;
Server v. 2.5 (daemon) code, part 1: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/wait.h> #include <signal.h> #define BUFF_LEN 256 // Length of the message buffer. int sockfd; // Parent socket handle declared as public // to be available for SIGQUIT handler.
Server v. 2.5 code, part 2: // Handler for SIGCHLD signal raised up by each child process on exit. // Parent process reads all information about exiting child to avoid // zombie creation. void sigchld_handler( int signo ) pid_t PID; int status; do PID = waitpid( -1, &status, WNOHANG ); while ( PID!= -1 ); signal( SIGCHLD, sigchld_handler ); // Handler for SIGQUIT signal used to quit parent process. void sigquit_handler( int signo ) close( sockfd ); printf("[server] Shutting down.\n"); exit( 0 );
Server v. 2.5 code, part 3: #define BUFF_LEN 256 // Length of the message buffer int main(int argc, char *argv[]) int consockfd; struct sockaddr_in serv_addr, cli_addr; socklen_t clilen; char cli_addr_str[inet_addrstrlen]; unsigned short cli_port; char buffer[buff_len]; const char *ack_message = "Message acknowledged."; pid_t PID, PPID; int n; if (argc < 3) fprintf(stderr,"command: %s <address> <port>\n", argv[0]);
Server v. 2.5 code, part 4: // Function daemon() "cuts-off" the process from its parent, // that means process becomes intentionaly created orphan // and can run without user's session. // In fact it's a kind of fork() which kills parent process. // // Prototype: int daemon(int nochdir, int noclose); // nochdir - if set to 0 process changes its working // directory to / // noclose - if set to 0 process redirects standard input, // standard output and standard error to /dev/null daemon( 0, 1 ); signal( SIGCHLD, sigchld_handler ); // Set SIGCHLD signal handler. clilen = sizeof(cli_addr);
Server v. 2.5 code, part 5: // Step 1. Create the Internet socket (SOCK_STREAM and 0 means TCP): sockfd = socket( AF_INET, SOCK_STREAM, 0 ); if (sockfd < 0) fprintf(stderr,"server ERROR: socket opening failed.\n"); // Initialize address of the server in serv_addr: bzero( (char *) &serv_addr, sizeof(serv_addr) ); serv_addr.sin_family = AF_INET; // Function inet_pton() gets IP address from C character string and // stores it in sin_addr field of sockaddr_in type structure. // Use this function rather instead obsolete inet_addr() which doesn't // support IPv6. In this example AF_INET means IPv4. inet_pton( AF_INET, argv[1], &(serv_addr.sin_addr) ); // Function htons() converts port number in host byte order // (depends on host computer architecture) to a port number // in network byte order (standardized). serv_addr.sin_port = htons( atoi(argv[2]) );
Server v. 2.5 code, part 6: // Step 2. Bind the socket to address - sets the value of file // descriptor (handle). // Typecasting is required to fit sockaddr_in type structure // to general sockaddr type. if ( bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0 ) fprintf(stderr,"server ERROR: binding failed, the port is in use.\n"); // Step 3. Listen on socket pointed by its handle. // The 2nd parameter is size of the backlog queue - the number // of connections that can be waiting while the process is handling // a particular connection. Typicaly 5 is the maximum value. listen( sockfd, 5 );
Server v. 2.5 code, part 7: // Step 4. Accept connections from clients and perform operations. do // Server main loop begining. consockfd = accept( sockfd, (struct sockaddr *) &cli_addr, &clilen ); if (consockfd < 0) fprintf(stderr,"server ERROR: accept failed.\n"); inet_ntop(af_inet, &(cli_addr.sin_addr), cli_addr_str, INET_ADDRSTRLEN); printf("[server] Connection accepted with client %s port %d\n", cli_addr_str, ntohs(cli_addr.sin_port)); // Create child process for servicing client s request: if( (PID = fork()) == -1 ) // fork() error. close( consockfd ); fprintf(stderr,"server ERROR: Creating new process failed.\n"); continue; else if( PID > 0 ) // Parent process. close( consockfd ); // Close the parent's copy of handle. signal( SIGQUIT, sigquit_handler ); // Set SIGQUIT signal handler. continue;
Server v. 2.5 code, part 8: // Child process starts here. PPID = getppid(); do // Session loop session with client. bzero( buffer, BUFF_LEN ); n = read( consockfd, buffer, BUFF_LEN ); // Read message from client. if (n < 0) fprintf(stderr,"server ERROR: reading from socket failed.\n"); // Replace end of line character(s) with terminator: for( n = 0; n < strlen(buffer); n++ ) if ( buffer[n] == '\n' buffer[n] == '\r' ) buffer[n] = '\0'; printf("[server] Message from client [%s %d]: %s\n", cli_addr_str, cli_port, buffer); n = write( consockfd, ack_message, strlen(ack_message) ); if (n < 0) fprintf(stderr,"server ERROR: writing to socket failed.\n");
Server v. 2.5 code, part 9: if ( strcmp(buffer, "SHUTDOWN") == 0 ) printf("[server] Shutting down.\n"); kill( PPID, SIGQUIT ); // Send SIGQUIT signal to parent process. break; while ( strcmp( buffer, "QUIT" )!= 0 ); // Session loop end. printf("[server] Session with client [%s %d] quits.\n", cli_addr_str, cli_port); close( consockfd ); exit( 0 ); // Exit child process (session with client). while ( 1 ); // Server main loop end.