Handle multiple socket connections with fd_set and select on Linux

By | July 31, 2020

Handle multiple socket connections

When writing server programs using sockets , it becomes necessary to handle multiple connections at a time , since a server needs to serve multiple clients.

There are many ways to do so. On linux this can be done in various ways like forking , threading , select method etc.

In this tutorial we shall use the select method approach. The select function allows the program to monitor multiple sockets for a certain "activity" to occur. For example if there is some data to be read on one of the sockets select will provide that information.

fd_set

An fd_set is a set of sockets to "monitor" for some activity. There are four useful macros : FD_CLR, FD_ISSET, FD_SET, FD_ZERO for dealing with an fd_set.

FD_ZERO - Clear an fd_set
FD_ISSET - Check if a descriptor is in an fd_set
FD_SET - Add a descriptor to an fd_set
FD_CLR - Remove a descriptor from an fd_set
//set of socket descriptors
fd_set readfds;

//socket to set
FD_SET( s , &readfds);

select function

The select method takes a list of socket for monitoring them. Here is how :

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

The select function blocks , till an activity occurs. For example when a socket is ready to be read , select will return and readfs will have those sockets which are ready to be read.

Code

/**
	Handle multiple socket connections with select and fd_set on Linux
*/
 
#include <stdio.h>
#include <string.h>   //strlen
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>   //close
#include <arpa/inet.h>    //close
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h> //FD_SET, FD_ISSET, FD_ZERO macros
 
#define TRUE   1
#define FALSE  0
#define PORT 8888

int main(int argc , char *argv[])
{
    int opt = TRUE;
    int master_socket , addrlen , new_socket , client_socket[30] , max_clients = 30 , activity, i , valread , sd;
	int max_sd;
    struct sockaddr_in address;
     
    char buffer[1025];  //data buffer of 1K
     
    //set of socket descriptors
    fd_set readfds;
     
    //a message
    char *message = "ECHO Daemon v1.0 \r\n";
 
    //initialise all client_socket[] to 0 so not checked
    for (i = 0; i < max_clients; i++) 
    {
        client_socket[i] = 0;
    }
     
    //create a master socket
    if( (master_socket = socket(AF_INET , SOCK_STREAM , 0)) == 0) 
    {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
 
    //set master socket to allow multiple connections , this is just a good habit, it will work without this
    if( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 )
    {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
 
    //type of socket created
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons( PORT );
     
    //bind the socket to localhost port 8888
    if (bind(master_socket, (struct sockaddr *)&address, sizeof(address))<0) 
    {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
	printf("Listener on port %d \n", PORT);
	
    //try to specify maximum of 3 pending connections for the master socket
    if (listen(master_socket, 3) < 0)
    {
        perror("listen");
        exit(EXIT_FAILURE);
    }
     
    //accept the incoming connection
    addrlen = sizeof(address);
    puts("Waiting for connections ...");
    
	while(TRUE) 
    {
        //clear the socket set
        FD_ZERO(&readfds);
 
        //add master socket to set
        FD_SET(master_socket, &readfds);
        max_sd = master_socket;
		
        //add child sockets to set
        for ( i = 0 ; i < max_clients ; i++) 
        {
            //socket descriptor
			sd = client_socket[i];
            
			//if valid socket descriptor then add to read list
			if(sd > 0)
				FD_SET( sd , &readfds);
            
            //highest file descriptor number, need it for the select function
            if(sd > max_sd)
				max_sd = sd;
        }
 
        //wait for an activity on one of the sockets , timeout is NULL , so wait indefinitely
        activity = select( max_sd + 1 , &readfds , NULL , NULL , NULL);
   
        if ((activity < 0) && (errno!=EINTR)) 
        {
            printf("select error");
        }
         
        //If something happened on the master socket , then its an incoming connection
        if (FD_ISSET(master_socket, &readfds)) 
        {
            if ((new_socket = accept(master_socket, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0)
            {
                perror("accept");
                exit(EXIT_FAILURE);
            }
         
            //inform user of socket number - used in send and receive commands
            printf("New connection , socket fd is %d , ip is : %s , port : %d \n" , new_socket , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));
       
            //send new connection greeting message
            if( send(new_socket, message, strlen(message), 0) != strlen(message) ) 
            {
                perror("send");
            }
             
            puts("Welcome message sent successfully");
             
            //add new socket to array of sockets
            for (i = 0; i < max_clients; i++) 
            {
                //if position is empty
				if( client_socket[i] == 0 )
                {
                    client_socket[i] = new_socket;
                    printf("Adding to list of sockets as %d\n" , i);
					
					break;
                }
            }
        }
         
        //else its some IO operation on some other socket :)
        for (i = 0; i < max_clients; i++) 
        {
            sd = client_socket[i];
             
            if (FD_ISSET( sd , &readfds)) 
            {
                //Check if it was for closing , and also read the incoming message
                if ((valread = read( sd , buffer, 1024)) == 0)
                {
                    //Somebody disconnected , get his details and print
                    getpeername(sd , (struct sockaddr*)&address , (socklen_t*)&addrlen);
                    printf("Host disconnected , ip %s , port %d \n" , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));
                     
                    //Close the socket and mark as 0 in list for reuse
                    close( sd );
                    client_socket[i] = 0;
                }
                 
                //Echo back the message that came in
                else
                {
                    //set the string terminating NULL byte on the end of the data read
                    buffer[valread] = '
/**
	Handle multiple socket connections with select and fd_set on Linux
*/
 
#include <stdio.h>
#include <string.h>   //strlen
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>   //close
#include <arpa/inet.h>    //close
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h> //FD_SET, FD_ISSET, FD_ZERO macros
 
#define TRUE   1
#define FALSE  0
#define PORT 8888

int main(int argc , char *argv[])
{
    int opt = TRUE;
    int master_socket , addrlen , new_socket , client_socket[30] , max_clients = 30 , activity, i , valread , sd;
	int max_sd;
    struct sockaddr_in address;
     
    char buffer[1025];  //data buffer of 1K
     
    //set of socket descriptors
    fd_set readfds;
     
    //a message
    char *message = "ECHO Daemon v1.0 \r\n";
 
    //initialise all client_socket[] to 0 so not checked
    for (i = 0; i < max_clients; i++) 
    {
        client_socket[i] = 0;
    }
     
    //create a master socket
    if( (master_socket = socket(AF_INET , SOCK_STREAM , 0)) == 0) 
    {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
 
    //set master socket to allow multiple connections , this is just a good habit, it will work without this
    if( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 )
    {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
 
    //type of socket created
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons( PORT );
     
    //bind the socket to localhost port 8888
    if (bind(master_socket, (struct sockaddr *)&address, sizeof(address))<0) 
    {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
	printf("Listener on port %d \n", PORT);
	
    //try to specify maximum of 3 pending connections for the master socket
    if (listen(master_socket, 3) < 0)
    {
        perror("listen");
        exit(EXIT_FAILURE);
    }
     
    //accept the incoming connection
    addrlen = sizeof(address);
    puts("Waiting for connections ...");
    
	while(TRUE) 
    {
        //clear the socket set
        FD_ZERO(&readfds);
 
        //add master socket to set
        FD_SET(master_socket, &readfds);
        max_sd = master_socket;
		
        //add child sockets to set
        for ( i = 0 ; i < max_clients ; i++) 
        {
            //socket descriptor
			sd = client_socket[i];
            
			//if valid socket descriptor then add to read list
			if(sd > 0)
				FD_SET( sd , &readfds);
            
            //highest file descriptor number, need it for the select function
            if(sd > max_sd)
				max_sd = sd;
        }
 
        //wait for an activity on one of the sockets , timeout is NULL , so wait indefinitely
        activity = select( max_sd + 1 , &readfds , NULL , NULL , NULL);
   
        if ((activity < 0) && (errno!=EINTR)) 
        {
            printf("select error");
        }
         
        //If something happened on the master socket , then its an incoming connection
        if (FD_ISSET(master_socket, &readfds)) 
        {
            if ((new_socket = accept(master_socket, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0)
            {
                perror("accept");
                exit(EXIT_FAILURE);
            }
         
            //inform user of socket number - used in send and receive commands
            printf("New connection , socket fd is %d , ip is : %s , port : %d \n" , new_socket , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));
       
            //send new connection greeting message
            if( send(new_socket, message, strlen(message), 0) != strlen(message) ) 
            {
                perror("send");
            }
             
            puts("Welcome message sent successfully");
             
            //add new socket to array of sockets
            for (i = 0; i < max_clients; i++) 
            {
                //if position is empty
				if( client_socket[i] == 0 )
                {
                    client_socket[i] = new_socket;
                    printf("Adding to list of sockets as %d\n" , i);
					
					break;
                }
            }
        }
         
        //else its some IO operation on some other socket :)
        for (i = 0; i < max_clients; i++) 
        {
            sd = client_socket[i];
             
            if (FD_ISSET( sd , &readfds)) 
            {
                //Check if it was for closing , and also read the incoming message
                if ((valread = read( sd , buffer, 1024)) == 0)
                {
                    //Somebody disconnected , get his details and print
                    getpeername(sd , (struct sockaddr*)&address , (socklen_t*)&addrlen);
                    printf("Host disconnected , ip %s , port %d \n" , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));
                     
                    //Close the socket and mark as 0 in list for reuse
                    close( sd );
                    client_socket[i] = 0;
                }
                 
                //Echo back the message that came in
                else
                {
                    //set the string terminating NULL byte on the end of the data read
                    buffer[valread] = '\0';
                    send(sd , buffer , strlen(buffer) , 0 );
                }
            }
        }
    }
     
    return 0;
}
'; send(sd , buffer , strlen(buffer) , 0 ); } } } } return 0; }

The source code has been put up on the following url
https://gist.github.com/silv3rm00n/5604330

Compile and run the above program. Then connect to it using telnet from 3 different terminals.

$ telnet localhost 8888

Now whatever you type and send to server will be send back as it is, or echoed.

The server terminal would show details of connections like this :

Waiting for connections...
New connection , socket fd is 4 , ip is : 127.0.0.1 , port : 57831 
Welcome message sent successfully
Adding to list of sockets as 0
New connection , socket fd is 5 , ip is : 127.0.0.1 , port : 57832 
Welcome message sent successfully
Adding to list of sockets as 1
New connection , socket fd is 6 , ip is : 127.0.0.1 , port : 57833 
Welcome message sent successfully
Adding to list of sockets as 2
New connection , socket fd is 7 , ip is : 127.0.0.1 , port : 57834 
Welcome message sent successfully

The client terminal can be like this

$ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
ECHO Daemon v1.0 
ccc
ccc
ddd
ddd
fff
fff

There are other functions that can perform tasks similar to select. pselect , poll , ppoll

Resources

1. http://pubs.opengroup.org/onlinepubs/7908799/xsh/select.html
2. http://linux.die.net/man/2/select

About Silver Moon

A Tech Enthusiast, Blogger, Linux Fan and a Software Developer. Writes about Computer hardware, Linux and Open Source software and coding in Python, Php and Javascript. He can be reached at [email protected].

31 Comments

Handle multiple socket connections with fd_set and select on Linux
  1. Vandana

    Thanks for the sample cod, it works very well. I tried to modify it for udt, but now no incoming connections happen. Pls advise

  2. jutt

    Sir there is no other way to handle the multi client by telnet. I want to connect my client with this server code how i attach this

  3. stowoda

    Code is commented very well so a beginner can understand it quickly.
    Though there are some drawbacks which users pointed out, this how-to helped me a lot.
    Thanks!

  4. Jesus

    With the system call ‘select’ you can not handle multiple connections at a time. To do that, you need to use threads or processes.

    Let’s suppose that a client request takes a long time to be processed. With ‘select’, during that time you can not attend to another client request until the first client request has finished

    1. Bingo

      What is your argument exactly? This tutorial is about handling multiple clients, using select function, which it does and it is correct.

  5. Usman

    if server also write something time to time and want to broadcast the some messsage to the connected client then how it possible,,,please replay.
    sir tanks in advace

  6. VS

    I can multi clients connect but when I send data from client 1 only client 1 receive response from server. How to server response to all client connected?

  7. Stephan Berger

    Thanx a lot for sharing!

    Just one hint: If the client doesn’t close the socket-connection properly, the prog will exit when sending to this connection (line 171). “Program received signal SIGPIPE, Broken pipe.”
    To avoid this You should use send() with the “MSG_NOSIGNAL” option:
    send(sd , buffer , strlen(buffer) , MSG_NOSIGNAL );

    Cheers, Stephan

  8. nmmm

    it is bit unclear what will happen when client_socket array is full?
    clients will be accepted using accept() but then they will appear nowhere?

  9. MhenryDpai

    I get the success on connect message multiple times from different telnet sessions – however I do not get anything echoed back to telnet client

    1. Silver Moon

      is the client able to connect to server in the first place ?
      does the server indicate that it received a new connection ?

      if yes, then whatever message the client sends to server, it should get back the same and print it.

  10. Jyx

    Nice article, but isn’t it a problem in the part “//add new socket to array of sockets” where you assign max_clients? I.e. you set “i = max_clients;” what if a client with socket number less than max_clients has disconnected earlier. Then in the loop you will find it in the client_socket[i] which is equal to zero. Then you will add the new socket in that location and assign max_clients to that socket. However there could be sockets with higher id still available.

    1. Silver Moon

      i is just a temporary loop variable.
      i = max_clients is used just to quit the loop, putting break instead of that would do the same thing.

      and empty position in client_sockets array is searched everytime a new socket connection comes in and the loop starts i from 0.

  11. Nishant

    I go to this link while googling for “handling multiple socket connections”. This is exactly what I was looking for. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *