select statement

At some point you will run into a need for more flexibility than a simple read or write operation on a socket. The problem with read and write is that these 2 calls block until the operation is done. Especially the read operation may wait for a while. The solution to this is the unique and flexible select command.

select allows you to make one call that simulataneously wait for:

select steps:

  1. For each Read that you want to wait on, set a bit in an fd_set structure. For example, if you have 2 file descriptors with numbers of 3 and 8, then you set bits 3 and 8 in an fd_set structure. Note that a socket can be used as a file descriptor.
  2. In the following code below, fdCheck is an fd_set array of bits which can be completely cleared out with a call to a macro:
    FD_ZERO (&fdCheck);
  3. For each file descriptor fd, that I want to wait for we need to make a call like:
    FD_SET(fd);
  4. We need to fill in a timeval structure to setup a timeout, so that if we don't get any response in some specified time our select call returns. If we don't want a timeout, just fill in a NULL for the timeval structure. This will be done in subsequent examples
  5. The select calls wants to receive the maximum file descriptor +1 as its first parameter. The rest of the call looks like:
    iNumInputs = select (iMaxFd, &fdCheck, 0, 0, &stv);
    &stv contains a pointer to our timeout value.
    iNumInputs contains the number of reads available to you. If iNumInputs comes back with a zero, then you have a timeout. A negative return code indicates a failure.
  6. To determine if file descriptor fd received input, you use a call to
    FD_ISSET(fd , &fdCheck)
  7. If a file descriptor has received input, then you can safely read without worrying about blocking.
  8. If the number of bytes read is 0 then you are at the end of your input. For socket programming, you might as well close your socket.
  9. A negative return from a read is an error condition in which you would close a socket.
  10. The file descriptor can come from a file open, a socket, STDIN, or any input.
  11. The 2 zeros in the select below are sometimes used. The first zero is for possible blocking write calls. Normally writes don't block so you can call write without too many problems with blocking I/O. However, when you use some I/O mechanisms like Pipes for example there are cases where writes can block. You do the same thing as we did for reads to use the write capability in a select statement. This is not as commonly used as reads. The second zero is for I/O exceptions and I have never used this feature so I can't offer much help on this. The main use for selects is for dealing with blocking reads.

timeout.c

#include <sys/socket.h> 

#include <stdio.h> 
#include <unistd.h> 
#include <string.h> 
main (int argc, char **argv) 
{ 
    struct timeval stv;    
    int nread, iMaxFd, iNumInputs, nContinue = 1; 
    fd_set fdCheck; 
    char buffer[10000];
   
    for(;nContinue;) 
    { 
        // creating a 2 and 1/2 second timeout
        stv.tv_sec = 2; 
        stv.tv_usec = 500000; //  1/2 second (units = microseconds) 
        FD_ZERO(&fdCheck); 
        iMaxFd = STDIN_FILENO+1; 
        FD_SET(STDIN_FILENO , &fdCheck); 
        
        // This select will return when the 2 1/2 second timeout expires
        // or STDIN returns some input.
        
        iNumInputs = select(iMaxFd, &fdCheck, 0, 0, &stv); 
        if (iNumInputs == -1) 
        { 
            /* Error*/ 
            printf("\n Bad return from select ==> die\n"); 
            nContinue = 0; 
        } 
        else if (iNumInputs == 0) 
        { 
            /* Timeout */ 
            printf("\n Timeout from select\n"); 
        } 
        else if (FD_ISSET(STDIN_FILENO  , &fdCheck)) 
        { 
            // This read won't be blocking
            nread = read(STDIN_FILENO, buffer, sizeof(buffer));
            if (nread == 0) 
            {
                // Probably Control-D on your Keyboard
                printf("keyboard done\n");
                exit(0);
            }
            if (nread > 0) 
            {
                buffer[nread] = 0;
                printf("Received from keyboard: %s\n", buffer);
            }
            
        } 
    } 
} 


cchat.c

The following program is going to be used as a client to a chat program. The program is close to what telnet functionally does for you. It doesn't quite work for telnet because it doesn't know how to negotiate the login sequence.

  1. cchat.c does a connect on a socket and waits for input from either the keyboard or the socket.
  2. If input comes from the keyboard, it is sent to the socket.
  3. If input comes from the socket, it is printed on the screen (printf).
  4. This program sets 2 bits in the fd_set structure. The first bit is the fd_chat socket, and the second bit is STDIN_FILENO which is actually 0.
  5. Because this program doesn't use a timeout, the last parameter is zero.
  6. When select returns, we need to check which file descriptors have input for us. We do this by issuing the commands
  7. Use the echo_server to test out cchat.c for now. The echo_server of course never sends anything without being sent to first. Later we will use cchat.c to talk to a simple chat server schat.c.
#include <sys/socket.h> 
#include <netdb.h> 
 
#include <stdio.h> 
#include <unistd.h> 
#include <string.h> 
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <stdlib.h>

#include <netinet/in.h>
#include <arpa/inet.h>

void chatUser(char *hostname, int aPort);
 
main (int argc, char *argv[]) 
{ 
    int i, aPort=7777; 
    char hostname[64];  
    
    printf("usage:%s [-p port] [-h hostname] \n" , argv[0]);
    gethostname(hostname, sizeof(hostname));

    for (i = 1; i < argc; i++)
    {
        if (strcmp(argv[i],"-h") == 0)
            if (i < argc-1)
                strcpy(hostname, argv[i+1]); 
            
        if (strcmp(argv[i],"-p") == 0)
            if (i < argc-1)
                aPort = atoi(argv[i+1]); 
    }    
    
    
    printf("\nStarting chat session: host(%s) port(%d)\n", 
        hostname, aPort);    
    
    chatUser(hostname, aPort);
    
    exit(0); 
} 

#define BUFF_SIZE 10000 

void chatUser(char *hostname, int aPort)
{  
    unsigned char buffer[BUFF_SIZE]; 
    struct sockaddr_in sin; 
    struct hostent *hp; 
    struct timeval stv; 
    int i, nread; 
    int fd_chat  = -1; 
    int iMaxFd, iNumInputs, nContinue = 1; 
    fd_set fdCheck; 
    
    // Now we need to create a socket connection with the chat server
    // 
 
// Get the IP address of the hostname

    hp = gethostbyname(hostname); 
    if (hp == NULL) 
    { 
        printf("\n%s: unknown host.\n", hostname); 
        nContinue = 0; 
    } 
    else 
    { 

// Create a socket and connect to the chat server

        if ((fd_chat  = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
        { 
            perror("client: socket()"); 
            nContinue = 0; 
        } 
        else 
        {    
            sin.sin_family = AF_INET; 
            sin.sin_port = htons(aPort); 
            memcpy(&sin.sin_addr, hp->h_addr, hp->h_length); 
            
            if (connect(fd_chat , (struct sockaddr *)&sin, sizeof(sin)) < 0) 
            { 
                perror("client: connect()"); 
                nContinue = 0; 
            } 
        } 
    } 
    
    // Now that we have connected to the other end, we will sit waiting for
    // input from either the fd_chat socket or from STDIN.  
    // When we read from fd_chat, we write the received buffer to STDOUT.
    // When we receive from the keyboard (STDIN), we send the contents to
    // the chat server.
    // The select statement is the key call in this routine. 
    
    for(;nContinue;) 
    { 
        stv.tv_sec = 0; 
        stv.tv_usec = 0; 
        FD_ZERO(&fdCheck); 
        iMaxFd = STDIN_FILENO; 
        if (fd_chat  > STDIN_FILENO  ) iMaxFd = fd_chat ;
        
        iMaxFd += 1; 

  // Set up mask to look for input from fd_chat or STDIN        
        FD_SET(STDIN_FILENO  , &fdCheck); 
        FD_SET(fd_chat , &fdCheck); 

        iNumInputs = select(iMaxFd, &fdCheck, 0, 0, NULL /* &stv*/); 
        if (iNumInputs == -1) 
        { 
            /* Error*/ 
            printf("\n Bad return from select ==> die\n"); 
            nContinue = 0; 
        } 
        else if (iNumInputs == 0) 
        { 
            /* Timeout */ 
            printf("\n Timeout from select\n"); 
            nContinue = 0;
        } 
        else if (FD_ISSET(fd_chat  , &fdCheck)) 
        { 
            // Can safely read without blocking
            nread = read(fd_chat, buffer, BUFF_SIZE);
            if (nread > 0)
                {
                // Received from chat server
                    buffer[nread] = 0;                   
                    write(STDOUT_FILENO, buffer, nread);
                }
            else
            {
                // chat server socket seems to have disconnected
                 
                if (nread == 0) nContinue = 0;
                if (nread < 0)
                    fprintf(stderr,"fd_chat err:%s\n", 
                        strerror(errno));
            }

        } 
        else if (FD_ISSET(STDIN_FILENO , &fdCheck)) 
        { 
            // can safely read without blocking
            nread = read(STDIN_FILENO, buffer, BUFF_SIZE);
            if (nread > 0)
            {
            // Keyboard input that needs to be sent to the chat server

                buffer[nread] = 0;
                write(fd_chat, buffer, nread);
            }
        } 
    } 
    if (fd_chat  >= 0) 
        close(fd_chat ); 
    exit(0); 

}


snoop.c

  1. The snoop program acts both like a server and a client. By putting snoop in between a conversation such as a browser and a web server, snoop can watch the conversation and print it out for your evaluation.
  2. The first parameter provides the port number for the server part of snoop. The second parameter provides the port number for which server snoop is supposed to connect to. If you only provide 2 parameters, then it is assumed the server is on the same machine as snoop is executing. If the server is realling on another machine, you can add the third parameter which specifies the host. For example:
    ./snoop 17xx1 17xx0 -h shrek.wccnet.org
  3. Because snoop can receive binary data, it normally stops printing when it encounters non-printable characters. However, if you want to see all of the gory details, snoop has one last parameter to request that all data is to be printed. Non-printable data is printed in hex format. For example,
    ./snoop 17xx1 17xx0 -all
  4. A snoop child is created for each connection. Snoop uses a select to wait for reads from it's client side and from its server side. When it receives a read, and issues a write to the other side and prints out the result.
  5. There are no new socket or select capabilities used in snoop that haven't already been covered.
#include <sys/wait.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <netdb.h> 
#include <signal.h> 

#include <stdio.h> 
#include <unistd.h> 
#include <string.h> 

#include <sys/time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>


int all_flag = 0;

void server(char *hostname, int aPort, int bPort);
void myChild(int fd_client  , int aPort, int bPort, char *hostname); 
int readWrite(int fdRead, int fdWrite, char * readId, char * writeId);
void dumpAsciiOnly(unsigned char * buffer, int num);

// Signal handler to process terminated children
void mysig(int nsig) 
{ 
    int nStatus, nPid; 
    
    nPid = waitpid(-1, &nStatus, WNOHANG); 
    printf("\nSignal(%d) received\n****************************\n\n\n\n\n\n\n", 
        nsig); 
    signal(SIGCHLD, mysig); 
} 
 
main (int argc, char **argv) 
{ 
    int i, aPort, bPort; 
    char hostname[64];  
    
    if (argc < 3) 
    { 
        fprintf(stderr, 
            "usage:%s porta  portb  [-h hostname] [-a]\n" , argv[0]); 
        exit(1); 
    } 
    aPort = atoi(argv[1]); 
    bPort = atoi(argv[2]); 
    gethostname(hostname, sizeof(hostname));

    for (i = 3; i < argc; i++)
    {
        if (strcmp(argv[i],"-h") == 0)
            if (i < argc-1)
                strcpy(hostname, argv[i+1]);              
    
        if (strcmp(argv[i],"-a") == 0)
            all_flag = 1;  
    }    
    
    
    printf("\nI will be an intermediary between ports %d and %d host(%s)\n", 
        aPort, bPort, hostname);    
    
    signal(SIGCHLD, mysig); // To clean up terminated children
    server(hostname, aPort, bPort);
    
    exit(0); 
} 

// Server Loop for processing Snoop Requests
// Any new connect to snoop results in spawning a child
// process to baby sit the connection

void server(char *hostname, int aPort, int bPort)
{  
    struct sockaddr_in sin, fsin; 
    int nChildCnt = 0;   
    int fd_connect, fd_accept;   
    int fromlen, nOpt =1; 
    long pid;

    // First of all snoop needs to present a Server interface.
    // Nothing new here
    if ((fd_accept = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    { 
        perror("server: socket()"); 
        exit(1); 
    } 
    setsockopt(fd_accept, SOL_SOCKET, SO_REUSEADDR, &nOpt, sizeof(int)); 
    
    memset(&sin, 0, sizeof(sin)); 
    sin.sin_family = AF_INET; 
    sin.sin_port = htons(aPort); 
    sin.sin_addr.s_addr = htonl(INADDR_ANY); 
    
    if (bind(fd_accept, (struct sockaddr *)&sin, sizeof(sin)) < 0) 
    { 
        perror("server: bind()"); 
        exit(1); 
    } 
    
    if (listen(fd_accept, 5) < 0) 
    { 
        perror("server: listen()"); 
        exit(1); 
    } 
    
    fromlen = sizeof(fsin); 
    
    for (;;) 
    { 
        if ((fd_connect = accept(fd_accept, (struct sockaddr *)&fsin, &fromlen)) < 0) 
        { 
            perror("server:accept()"); 
            continue; /* hope this is normally due to a child death*/ 
        } 
        nChildCnt += 1; 
        printf("\n#_#_#_#_#_#_#_#_#_#_#_#_\nCHILD(%d)\n", 
            nChildCnt); 
        fflush(stdout); 
        
        
        if ((pid = fork()) > 0) 
        { 
            /* Parent */ 
            close(fd_connect); 
            continue; 
        } 
        if (pid == 0) 
        { 
            /* Must be the child */ 
            myChild(fd_connect, aPort, bPort, hostname); /* Never returns */ 
        } 
        if (pid < 0)
            printf("**** Can't fork CHILD\n");
    } 
    
    close(fd_accept);  

}

// Child process to manager one connect requests reads/writes 
// When the connection seems to go away, the child terminates

void myChild(int fd_client  , int aPort, int bPort, char *hostname) 
{ 
    struct sockaddr_in sin; 
    struct hostent *hp; 
    struct timeval stv; 
    int i; 
    int fd_server  = -1; 
    int iMaxFd, iNumInputs, nContinue = 1; 
    fd_set fdCheck; 
    
    // Client has connected to snoop.  Now snoop needs to connect to the
    // desired server. 
    // 
 
    hp = gethostbyname(hostname); 
    if (hp == NULL) 
    { 
        printf("\n%s: unknown host.\n", hostname); 
        nContinue = 0; 
    } 
    else 
    { 
        if ((fd_server  = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
        { 
            perror("client: socket()"); 
            nContinue = 0; 
        } 
        else 
        {    
            sin.sin_family = AF_INET; 
            sin.sin_port = htons(bPort); 
            memcpy(&sin.sin_addr, hp->h_addr, hp->h_length); 
            
            if (connect(fd_server , (struct sockaddr *)&sin, sizeof(sin)) < 0) 
            { 
                perror("client: connect()"); 
                nContinue = 0; 
            } 
        } 
    } 
    
    // Now that we have connected to the other end, we will sit waiting for
    // input from either of the 2 sockets.  When we read from one socket,
    // we write to the other socket.  We also dump out the buffer read if its
    // ascii.  
    // The select statement is the key call in this routine. 
    
    for(;nContinue;) 
    { 
        stv.tv_sec = 0; 
        stv.tv_usec = 0; 
        FD_ZERO(&fdCheck); 
        iMaxFd = fd_client  ; 
        if (fd_server  > fd_client  ) iMaxFd = fd_server ; 
        else  iMaxFd = fd_client  ; 
        
        iMaxFd += 1; 
        
        FD_SET(fd_client  , &fdCheck); 
        FD_SET(fd_server , &fdCheck); 
        iNumInputs = select(iMaxFd, &fdCheck, 0, 0, NULL /* &stv*/); 
        if (iNumInputs == -1) 
        { 
            /* Error*/ 
            printf("\n Bad return from select ==> die\n"); 
            nContinue = 0; 
        } 
        else if (iNumInputs == 0) 
        { 
            /* Timeout */ 
            printf("\n Timeout from select\n"); 
            nContinue = 0;
        } 
        else if (FD_ISSET(fd_client  , &fdCheck)) 
        { 
            // read from fd_client   and write to fd_server .  If nContinue is returned
            // as a zero then one of our sockets has closed. 

            nContinue = readWrite(fd_client  , fd_server , "Client","Server");    
        } 
        else if (FD_ISSET(fd_server , &fdCheck)) 
        { 
            // read from fd_server  and write to fd_client  .  If nContinue is returned
            // as a zero then one of our sockets has closed.  
        
            nContinue = readWrite(fd_server , fd_client  , "Server", "Client");
        } 
    } 
    if (fd_server  >= 0) 
        close(fd_server ); 
    close(fd_client  ); 
    exit(0); 
}

// The readWrite routine reads from one file descriptor and 
// writes to another file descriptor.  The buffer read is then
// dumped to the screen 

#define BUFF_SIZE 10000 
int readWrite(int fdRead, int fdWrite, char * readId, char * writeId)
{
    int nBytes;
    int nCont =1;
    unsigned char buffer[2*(BUFF_SIZE+1)]; 
    
    nBytes = read(fdRead, buffer, BUFF_SIZE); 
    printf("\nRead %d bytes from %s\n", nBytes, readId); 
    if (nBytes > 0) 
    { 
        if (write(fdWrite, buffer, nBytes) <= 0) 
        { 
            printf("\nWrite Error on %s ==> " 
                "Broken Connection\n", writeId); 
            nCont = 0; 
        } 
        dumpAsciiOnly(buffer, nBytes); // print out buffer
    }    
    else 
    { 
        printf("\n%s read (%d) error ==> " 
            "Connection Broken\n",readId, nBytes); 
        nCont = 0; 
    }    
    fflush(stdout); 
    return nCont;
}

// The following routine carefully copies a read buffer to 
// the screen.  If a binary file is passed like a gif or jpeg
// then we stop trying to print to the screen once we see
// non-ascii characters.
  
void dumpAsciiOnly(unsigned char * buffer, int num)
{
    int i;
    for (i = 0; i < num; i++) 
    { 
        if (buffer[i] > 0x7f) 
        { 
            printf("%2x", buffer[i]); 
            if (!all_flag)break; // Non Ascii char found
        } 
        if (buffer[i] < ' ') 
        { 
            if ((buffer[i] != '\n') && (buffer[i] != '\r') && 
                (buffer[i] != '\t') && (buffer[i] != '\b')) 
            { 
                printf("%2x", buffer[i]); 
                if (!all_flag)break; // Non Ascii char found
            } 
        } 
        printf("%c", buffer[i]); // Good Ascii character
    } 
    
}



schat.c

  1. As promised here we have a simple chat server. The schat.c program is single threaded. Using the select statement, schat.c can listen to reads on many connections as well as new accepts.
  2. In a nutshell, when schat.c receives from one connection, it sends out the buffer with a little formatting to all of the other connections.
  3. We need to demonstrate the capability in class first.
  4. Using the cchat.c program, the user can issue a command like:
    -name=Clem
    and from that point on this connection is associated with the name Clem. Every message sent from the Clem connection will be labeled with Clem's name.
  5. On the initial connection a user receives a list of all of the other connected users.
  6. The schat.c program is single threaded and uses a select statement to listen for all of the possible reads from all of the users possible. It also uses the select statement to listen for new accept calls. Being single threaded facilitates the implementation of this code.
#include <sys/socket.h> 
#include <netinet/in.h> 

#include <stdio.h>
#include <unistd.h> 
#include <string.h> 
#include <errno.h>

#include <sys/time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>


#define NAME_SIZE 40
#define MAX_CHATS 40
#define BUFF_SIZE 10000 
// Message formats
#define chatFull  "Max Chats ... Sorry"
#define welcome_msg "Welcome to chatter:\n Enter your name with a command like:\n-name=Clem\n"
#define change_name "-name="

// Array to contain the sockets for all of the chat users
int fd_chats[MAX_CHATS];
struct name_rec
{
    char name[NAME_SIZE+1];
 };
typedef struct name_rec NAME_REC;
// Array to hold the names for all of the chat users
NAME_REC name_chats[MAX_CHATS];
int numChats = 0;

void server( int aPort);
void chatLoop(int fd_accept);
void processChat(int instance, char * buffer, int nread);
void addChatter(int newConnect);
void remove_chatter(int fd_toRemove);

main (int argc, char **argv) 
{ 
    int i, aPort=7777; 
    char hostname[64];  
    
    fprintf(stderr, "usage:%s [port] \n" , argv[0]); 
     
    if (argc > 1)
        aPort = atoi(argv[1]);
    gethostname(hostname, sizeof(hostname));    
    
    printf("\nI will be your chat server(%s) on port %d\n\n", 
        hostname, aPort);    
  
 // real work happens here
    server(aPort);
    
    exit(0); 
} 


void server(int aPort)
{  
    struct sockaddr_in sin, fsin; 
    int nChildCnt = 0;   
    int fd_connect, fd_accept;   
    int fromlen, nOpt =1; 
    long pid;

    if ((fd_accept = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    { 
        perror("server: socket()"); 
        exit(1); 
    } 
 // The following option allows a port to be reused after exitting this
 // server.  Otherwise, we end up waiting for a while 
    setsockopt(fd_accept, SOL_SOCKET, SO_REUSEADDR, &nOpt, sizeof(int)); 
    
    memset(&sin, 0, sizeof(sin)); 
    sin.sin_family = AF_INET; 
    sin.sin_port = htons(aPort); 
    sin.sin_addr.s_addr = htonl(INADDR_ANY); 
    
// Bind socket to port number
    if (bind(fd_accept, (struct sockaddr *)&sin, sizeof(sin)) < 0) 
    { 
        perror("server: bind()"); 
        exit(1); 
    } 
    
// Set up Listen queue of 5 simultaneous clients awaiting connect
    if (listen(fd_accept, 5) < 0) 
    { 
        perror("server: listen()"); 
        exit(1); 
    } 
// Notice we don't block on an accept call like all the other servers
// Accept loop in the following routine
    chatLoop(fd_accept);

}
 


void chatLoop(int fd_accept)
{ 
    unsigned char buffer[BUFF_SIZE];    
    struct sockaddr_in fsin; 
    int fromlen;
    struct timeval stv; 
    int i; 
    int fd_server  = -1; 
    int iMaxFd, iNumInputs, nContinue = 1; 
    int newConnect, nread;
    fd_set fdCheck; 
    
    // The select statement is the key call in this routine. 
    
    for(;nContinue;) 
    { 
        stv.tv_sec = 0; 
        stv.tv_usec = 0; 
        FD_ZERO(&fdCheck); 

// Select mask contains accept socket number
        FD_SET(fd_accept  , &fdCheck); 
        iMaxFd = fd_accept; 
   
// Select mask contains socket number of each chat user's socket
        for (i = 0; i < numChats; i++)
        {
            FD_SET(fd_chats[i], &fdCheck);
            if (fd_chats[i] > iMaxFd) iMaxFd = fd_chats[i];
        }
        iMaxFd += 1; 
        
// No timeout on the select
        iNumInputs = select(iMaxFd, &fdCheck, 0, 0, NULL /* &stv*/); 
        if (iNumInputs == -1) 
        { 
            /* Error*/ 
            printf("\n Bad return from select ==> die\n"); 
            nContinue = 0; 
        } 
        else if (iNumInputs == 0) 
        { 
            /* Timeout -- won't occur for us.*/ 
            printf("\n Timeout from select\n"); 
            nContinue = 0;
        } 
        else 
        {
// Pending connect request of new chat user 
            if (FD_ISSET(fd_accept  , &fdCheck)) 
            { 
                 // Now we can make an accept without blocking
                 fromlen = sizeof(fsin); 
                 newConnect = accept(fd_accept, (struct sockaddr *)&fsin, &fromlen);
                 printf("New Connect : %d\n", newConnect);
                 if (newConnect < 0) 
                    { 
                        fprintf(stderr,"accept err: %s\n", strerror(errno));
                        continue; 
                    }                  

                 if (numChats >= MAX_CHATS)
                 {
                // We are full message sent
                    printf("%s\n", chatFull);
                    write(newConnect, chatFull, strlen(chatFull));
                    close(newConnect);
                 }
                 else
                    addChatter(newConnect); // add new user to tables
              
            }  // end of fd_accept handling
           
   
            for (i =0; i < numChats; i++)
            {
                if (FD_ISSET(fd_chats[i], &fdCheck))
                {
            // Received message from one of the chat clients
                    nread = read(fd_chats[i], buffer, BUFF_SIZE); 
                    if (nread > 0)
                    {
                        if (buffer[nread-1] == '\n') 
                            buffer[nread-1] = 0;
                        else
                            buffer[nread] = 0;

                    // processChat sends out information to all
                    // chatters
                        processChat(i,  buffer, nread);
                        
                    }
                    else
                    {
                        printf("Time to close out chat file descriptor: %d\n",
                            fd_chats[i]);
                        if (nread < 0)
                             fprintf(stderr, "Read err:%s\n", strerror(errno));
                        
                       // close out terminated chat user and compress the
                       // chat tables
                        remove_chatter(i);
                    }
                }
            } // end of for loop on numChats
        } 
    } // End of the select for loop    
}

//************************************************
// processChat called to deal with each chat message
 
void processChat(int instance, char * buffer, int nread)
{
   unsigned char outbuff[BUFF_SIZE]; 
   int i, len;
  
   len = strlen(change_name);
   if (strncmp(change_name, buffer, len)== 0)
    {        
    // Special message of  chatter requesting a name something like:
    // -name=Clem
    // In this case we copy Clem into the name_chats table

        strcpy((char *)&name_chats[instance], &buffer[len]);
        return;
    }
    
   // Prefix buffer with name of user who sent it
   len = sprintf(outbuff, "%s:\n%s\n", 
       (char *)&name_chats[instance], buffer);
   for (i=0; i < numChats; i++)
    {
    // Send buffer to all chatters
        write(fd_chats[i], outbuff, len);
    }
    
}

// *******************************************************
 // addChatter called to deal with a new Chatter connect

void addChatter(int newConnect)
{
  unsigned char buffer[BUFF_SIZE];  
  int i,total = 0;  

  sprintf(buffer,"unknown:%d", numChats);
  strcpy((char *)&name_chats[numChats], buffer);

// Create welcome message along with a list of who is currently
// connected to the chat server
 
  total = sprintf(buffer,"%s\n", welcome_msg);
  if (numChats <= 0)
    total +=sprintf(&buffer[total], "You are the first chatter\n");
  else
    {
        total += sprintf(&buffer[total], " Current Chatters\n");
        for ( i=0; i < numChats; i++)
        {
          total += sprintf(&buffer[total], "%s\n", (char *) &name_chats[i]);  
        }
    }

// Send message back to new connecting chatter
  write(newConnect, buffer, total);

// Add Chatter to fd_chat table
  fd_chats[numChats++] = newConnect;
}


void remove_chatter(int fd_toRemove)
{
   int k, fd_save; 
   // close out terminated chat user and compress the
   // chat tables
   fd_save = fd_chats[fd_toRemove];
   for (k=fd_toRemove; k < numChats -1; k++)
     {
        fd_chats[k] = fd_chats[k+1];
        strcpy((char *)&name_chats[k], (char *)&name_chats[k+1]);
     }
   numChats -= 1;
   printf("Closing %d\n", fd_save);
   close(fd_save);
}


One last example

I don't want to spend much time on this, but I once had to create a method for issuing commands from host that executed on many other hosts. The following web page has a version of the code developed for this project:

cproxy_and_sproxy


Exercise

  1. Modify the chat program to have a command called:
    -private: Sam: Message
    This would send Message only to Sam.
  2. Modify the chat program to have a command called:
    -createRoom:
    and to enter the chat room, we need the command:
    -joinRoom: number
  3. Create a "Paper, Scissors, Rock " game. To keep it simple, lets say that the first 2 people connecting are put in the first game, the next 2 in the next game, etc. When on person in a game disconnects, the other person in the game is told that his partner disconnected and then his connection is broken. HINT:The server can spawn a child to handle the game once it has received 2 connectors. HINT2: probably good to have the player client more controlled than cchat.c --- more like send command, get back response, etc.