Open In App

Socket Programming in C/C++: Handling multiple clients on server without multi threading

Last Updated : 15 Jul, 2024
Comments
Improve
Suggest changes
42 Likes
Like
Report

This tutorial assumes you have a basic knowledge of socket programming, i.e you are familiar with basic server and client model. In the basic model, server handles only one client at a time, which is a big assumption if you want to develop any scalable server model. The simple way to handle multiple clients would be to spawn new thread for every new client connected to the server. This method is strongly not recommended because of various disadvantages, namely:

  • Threads are difficult to code, debug and sometimes they have unpredictable results.
  • Overhead switching of context
  • Not scalable for large number of clients
  • Deadlocks can occur

Select()

A better way to handle multiple clients is by using

select()

linux command.

  • Select command allows to monitor multiple file descriptors, waiting until one of the file descriptors become active.
  • For example, if there is some data to be read on one of the sockets select will provide that information.
  • Select works like an interrupt handler, which gets activated as soon as any file descriptor sends any data.

Data structure used for select:

fd_set It contains the list of file descriptors to monitor for some activity. There are four functions associated with fd_set:

fd_set readfds;

// Clear an fd_set
FD_ZERO(&readfds);

// Add a descriptor to an fd_set
FD_SET(master_sock, &readfds);

// Remove a descriptor from an fd_set
FD_CLR(master_sock, &readfds);

//If something happened on the master socket , then its an incoming connection
FD_ISSET(master_sock, &readfds);

Activating select:

Please read the man page for select to check all the arguments for select command.

activity = select( max_fd + 1 , &readfds , NULL , NULL , NULL);

Implementation:

C++
#include <iostream>  // for cout/cerr
#include <arpa/inet.h>  // for ip inet_pton()
#include <netinet/in.h> // for address
#include <sys/select.h> // for io multiplexing (select)
#include <sys/socket.h> // for socket
#include <unistd.h>  // for close()
#include <vector> // for storing client
/*
 consider adding thread if handling multiple client 
 simultaneously  for sending and reciving data at the same time

/*
 structure to encapsulate data of client this make easy to
 passing the argument to new thread;
*/
struct clientDetails{
      int32_t clientfd;  // client file descriptor
      int32_t serverfd;  // server file descriptor
      std::vector<int> clientList; // for storing all the client fd
      clientDetails(void){ // initializing the variable
            this->clientfd=-1;
            this->serverfd=-1;
      }
};

const int port=4277;
const char ip[]="127.0.0.1"; // for local host
//const ip[]="0.0.0.0"; // for allowing all incomming connection from internet
const int backlog=5; // maximum number of connection allowed



int main() {
auto client= new clientDetails();

      client->serverfd= socket(AF_INET, SOCK_STREAM,0); // for tcp connection
      // error handling
      if (client->serverfd<=0){
            std::cerr<<"socket creation error\n";
         delete client;
            exit(1);
      }else{
            std::cout<<"socket created\n";
      }
       // setting serverFd to allow multiple connection
       int opt=1;
      if (setsockopt(client->serverfd,SOL_SOCKET,SO_REUSEADDR, (char*)&opt, sizeof opt)<0){
            std::cerr<<"setSocketopt error\n";
        delete client;
            exit(2);
      }

        // setting the server address
        struct sockaddr_in serverAddr;
        serverAddr.sin_family=AF_INET;
        serverAddr.sin_port=htons(port);
        inet_pton(AF_INET, ip, &serverAddr.sin_addr);
        // binding the server address
        if (bind(client->serverfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr))<0){
            std::cerr<<"bind error\n";
            delete client;
            exit(3);
        }else{
              std::cout<<"server binded\n";
        }
        // listening to the port
        if (listen(client->serverfd, backlog)<0){
            std::cerr<<"listen error\n";
            delete client;
            exit(4);
        }else{
                std::cout<<"server is listening\n";
        }

        fd_set readfds;
        size_t  valread;
        int maxfd;
        int sd=0;
        int activity;
        while (true){
           std::cout<<"waiting for activity\n";
           FD_ZERO(&readfds);
           FD_SET(client->serverfd, &readfds);
           maxfd=client->serverfd;
           // copying the client list to readfds
           // so that we can listen to all the client
           for(auto sd:client->clientList){
                 FD_SET(sd, &readfds);
                 if (sd>maxfd){
                       maxfd=sd;
                 }
           }
           //
           if (sd>maxfd){
                 maxfd=sd;
           }
           /* using select for listen to multiple client
              select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
              fd_set *restrict errorfds, struct timeval *restrict timeout);
           */

           // for more information about select type 'man select' in terminal
           activity=select(maxfd+1, &readfds, NULL, NULL, NULL);
           if (activity<0){
                 std::cerr<<"select error\n";
                 continue;
           }
           /*
            * if something happen on client->serverfd then it means its
            * new connection request
            */
           if (FD_ISSET(client->serverfd, &readfds)) {
                 client->clientfd = accept(client->serverfd, (struct sockaddr *) NULL, NULL);
                 if (client->clientfd < 0) {
                       std::cerr << "accept error\n";
                       continue;
                 }
                 // adding client to list
                 client->clientList.push_back(client->clientfd);
                 std::cout << "new client connected\n";
                 std::cout << "new connection, socket fd is " << client->clientfd << ", ip is: "
                           << inet_ntoa(serverAddr.sin_addr) << ", port: " << ntohs(serverAddr.sin_port) << "\n";

                 /*
                  * std::thread t1(handleConnection, client);
                  * t1.detach();
                  *handle the new connection in new thread
                  */
           }
           /*
            * else some io operation on some socket
            */

           // for storing the recive message
           char message[1024];
           for(int i=0;i<client->clientList.size();++i){
                 sd=client->clientList[i];
                 if (FD_ISSET(sd, &readfds)){
                       valread=read(sd, message, 1024);
                       //check if client disconnected
                       if (valread==0){
                             std::cout<<"client disconnected\n";

                             getpeername(sd, (struct sockaddr*)&serverAddr, (socklen_t*)&serverAddr);
                             // getpeername name return the address of the client (sd)
                      
                             std::cout<<"host disconnected, ip: "<<inet_ntoa(serverAddr.sin_addr)<<", port: "<<ntohs(serverAddr.sin_port)<<"\n";
                             close(sd);
                             /* remove the client from the list */
                             client->clientList.erase(client->clientList.begin()+i);
                       }else{
                             std::cout<<"message from client: "<<message<<"\n";
                             /*
                              * handle the message in new thread
                              * so that we can listen to other client
                              * in the main thread
                              * std::thread t1(handleMessage, client, message);
                              * // detach the thread so that it can run independently
                              * t1.detach();
                              */
                       }
                 }
           }
      }
        delete client;
        return 0;
}
C

Compile the file and run the server. Use telnet to connect the server as a client. Try running on different machines using following command:

 telnet localhost 8888

Code Explanation:

  • We have created a fd_set variable readfds, which will monitor all the active file descriptors of the clients plus that of the main server listening socket.
  • Whenever a new client will connect, master_socket will be activated and a new fd will be open for that client. We will store its fd in our client_list and in the next iteration we will add it to the readfds to monitor for activity from this client.
  • Similarly, if an old client sends some data, readfds will be activated and we will check from the list of existing client to see which client has send the data.
  • Alternatives:
  • There are other functions that can perform tasks similar to select. pselect , poll , ppoll

Article Tags :
Practice Tags :

Similar Reads