Multiplexed Chat Server – using select

This example presents an implementation of a client-server chat application working in the command line in an IRC style application with only one room. The server accepts multiple TCP clients and relays messages passed by each client to all other connected clients. The client needs to be able to:

·       read messages from standard input and pass them to the server;

·       read messages relayed from the server and display them to the user;

 

The motivation of using select lies here in the fact that there are multiple descriptors that need to be watched by both the client and the server. The server also needs a shared state approach where creating a new process to handle each client would only make things complicated.

 

Multiplexed chat SERVER

Chat CLIENT

/*
// Multiperson chat server using select
// Server part
*/
 
#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 <arpa/inet.h>
 
//#define PORT 9034   // port we're listening on
fd_set master;   // master file descriptor list
fd_set read_fds; // temp file descriptor list for select()
struct sockaddr_in myaddr;     // server address
struct sockaddr_in remoteaddr; // client address
int fdmax;        // maximum file descriptor number
int listener;     // listening socket descriptor
int newfd;        // newly accept()ed socket descriptor
char buf[256], tmpbuf[256];    // buffer for client data
int nbytes, ret;
int yes=1;        // for setsockopt() SO_REUSEADDR, below
int addrlen;
int i, j, crt, int_port,client_count=0;
 
 
struct sockaddr_in getSocketName(int s, bool local_or_remote) {
  struct sockaddr_in addr;
  int addrlen = sizeof(addr);
  int ret;
 
  memset(&addr, 0, sizeof(addr));
  ret = (local_or_remote==true?getsockname(s,(struct sockaddr *)&addr,(socklen_t*)&addrlen):
         getpeername(s,(struct sockaddr *)&addr, (socklen_t*)&addrlen) );
  if (ret < 0)
    perror("getsock(peer)name");
  return addr;
}
 
char * getIPAddress(int s, bool local_or_remote) {
  struct sockaddr_in addr;
  addr = getSocketName(s, local_or_remote);
  return inet_ntoa(addr.sin_addr);
}
 
int getPort(int s, bool local_or_remote) {
  struct sockaddr_in addr;
  addr = getSocketName(s, local_or_remote);
  return addr.sin_port;
}
 
// send to everyone
void sendToALL(char * buf, int nbytes) {
  int j, ret;
  for(j = 0; j <= fdmax; j++) {
    if (FD_ISSET(j, &master))
      // except the listener and ourselves
      if (j != listener && j != crt)
        if ( send(j, buf, nbytes, 0) == -1)
          perror("send");
  }
  return;
}
 
 
int main(int argc, char **argv)
{
   
    if (argc < 2 ) {
      printf("Usage:\n%s <portno>\n",argv[0]);
      exit(1);
    }
   
    int_port = atoi(argv[1]);
   
    FD_ZERO(&master);    // clear the master and temp sets
    FD_ZERO(&read_fds);
 
    // get the listener
    if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }
 
    // lose the pesky "address already in use" error message
    if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int) ) == -1) {
        perror("setsockopt:");
        exit(1);
    }
 
    // bind
    memset(&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    myaddr.sin_addr.s_addr = INADDR_ANY;
    myaddr.sin_port = htons(int_port);
    //    memset(&(myaddr.sin_zero), '\0', 8);
    if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) {
        perror("bind:");
        exit(1);
    }
    // listen
    if (listen(listener, 10) == -1) {
        perror("listen");
        exit(1);
    }
    // add the listener to the master set
    FD_SET(listener, &master);
    // keep track of the biggest file descriptor
    fdmax = listener; // so far, it's this one
    // main loop
    for(;;) {
        read_fds = master; // copy it
        if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
            perror("select");
            exit(1);
        }
        
        // run through the existing connections looking for data to read
        for(i = 0; i <= fdmax; i++) {
          if (FD_ISSET(i, &read_fds)) { // we got one!!
            crt = i;
            if (i == listener) {
              // handle new connections
              addrlen = sizeof(remoteaddr);
                 if ((newfd = accept(listener, (struct sockaddr *)&remoteaddr,(socklen_t*)&addrlen)) == -1){
                   perror("accept");
                 } else {
                   FD_SET(newfd, &master); // add to master set
                   if (newfd > fdmax) {    // keep track of the maximum
                     fdmax = newfd;
                   }
                   printf("selectserver: new connection from %s on "
                          "socket %d\n", getIPAddress(newfd, false),newfd);
                  
                   client_count++;
                   sprintf(buf,"Hi-you are client :[%d] (%s:%d) connected to server %s\nThere are %d clients connected\n",
                           newfd, getIPAddress(newfd,false), getPort(newfd, false),
                           getIPAddress(listener, true), client_count);
                   send(newfd,buf,strlen(buf)+1,0);
                 }
            } else {
              // handle data from a client
              if ((nbytes = recv(i, buf, sizeof(buf), 0)) <= 0) {
               // got error or connection closed by client
               if (nbytes == 0) {
                 // connection closed
                 printf("<selectserver>: client %d forcibly hung up\n", i);
               }
               else
                 perror("recv");
               client_count--;
               close(i); // bye!
               FD_CLR(i, &master); // remove from master set
              }
              else {
               // we got some data from a client
               // check for connection close request
               buf[nbytes]=0;
               //printf("%s\n",buf);
               if ( (strncasecmp("QUIT\n",buf,4) == 0)) {
                 sprintf(buf,"Request granted [%d] - %s. Disconnecting...\n",i,getIPAddress(i,false));
                 send(i,buf, strlen(buf)+1,0);
                 nbytes = sprintf(tmpbuf,"<%s - [%d]> disconnected\n",getIPAddress(i,false), i);
                 sendToALL(tmpbuf,nbytes);
                 client_count--;
                 close(i);
                 FD_CLR(i,&master);
               }
               else {
                 nbytes = sprintf(tmpbuf, "<%s - [%d]> %s",getIPAddress(crt, false),crt, buf);
                 sendToALL(tmpbuf, nbytes);
               }
              }
            }
          }
        }
    }
    return 0;
}

 

/* 
// Multiperson chat using select
// Client part
*/
#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 <arpa/inet.h>
#include <netdb.h>
 
fd_set read_fds,master; // temp file descriptor list for select()
int sock;        //socket
struct sockaddr_in servaddr; 
char buf[256];  // buffer for client data
int nbytes, ret, int_port;
 
int main(int argc, char **argv)
{
    
    if (argc < 3 ) {
      printf("Usage:\n%s <hostname or IP address> <portno>\n",argv[0]);
      exit(1);
    }
    
    
    int_port = atoi(argv[2]);
    
    int ipaddr = inet_addr(argv[1]);
    // check if address is a hostname
    
    printf("%s => %d ip address\n",argv[1],ipaddr);
    if (ipaddr == -1 ) {
      struct in_addr inaddr;
      struct hostent * host = gethostbyname( argv[1] );
      if (host == NULL ) {
               printf("Error getting the host address\n");
               exit(1);
      }
      memcpy(&inaddr.s_addr, host->h_addr_list[0],sizeof(inaddr));
      printf("Connecting to %s ...\n",inet_ntoa( inaddr) );
      memcpy(&ipaddr, host->h_addr_list[0],sizeof(unsigned long int)) ;
    }
 
    // get the socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }
    
    memset(&servaddr,0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = ipaddr;
    servaddr.sin_port = htons( int_port );
    // connect to server
    if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0 ) {
      perror("connect");
      exit(1);
    }
  
    // add the listener to the master set
    FD_ZERO(&read_fds);    // clear the set
    FD_ZERO(&master);
    FD_SET(0, &master);
    FD_SET(sock, &master);
 
    for(;;) {
      read_fds = master;
      if (select(sock+1, &read_fds, NULL, NULL, NULL) == -1) {
                perror("select");
               exit(1);
      }
      
      // check if read from keyboard 
      if ( FD_ISSET(0, &read_fds) ) {
               nbytes = read(0, buf,sizeof(buf)-1);
               ret = send(sock, buf, nbytes,0);
               if (ret <= 0 ){
                       perror("send");
                       exit(1);
               }
        //else  printf("WARNING Not all data has been sent: %d bytes out of %d\n", ret, nbytes);
      }
      
      // check if read from server
      if ( FD_ISSET(sock, &read_fds) ) {
               nbytes = read(sock, buf, sizeof(buf)-1);
               if (nbytes <= 0) { 
                       printf("Server has closed connection... closing...\n");
                       exit(2);
               }
               write(1,buf, nbytes);
     }
    }
    
    return 0;
}