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. Theselect
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
1 | //set of socket descriptors |
2 | fd_set readfds; |
3 |
4 | //socket to set |
5 | FD_SET( s , &readfds); |
select
The select method takes a list of socket for monitoring them. Here is how :
1 | activity = select( max_clients , &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
1 | /* |
2 | * @brief |
3 | * manage multiple connections with FD_SET |
4 | * |
5 | * @author Silver Moon |
6 | * |
7 | * */ |
8 |
9 | #include <stdio.h> |
10 | #include <string.h> //strlen |
11 | #include <stdlib.h> |
12 | #include <errno.h> |
13 | #include <unistd.h> //close |
14 | #include <arpa/inet.h> //close |
15 | #include <sys/types.h> |
16 | #include <sys/socket.h> |
17 | #include <netinet/in.h> |
18 | #include <sys/time.h> //FD_SET, FD_ISSET, FD_ZERO macros |
19 |
20 | #define TRUE 1 |
21 | #define FALSE 0 |
22 |
23 | int main( int argc , char *argv[]) |
24 | { |
25 | int opt = TRUE; |
26 | int master_socket , addrlen , new_socket , client_socket[30] , max_clients = 30 , activity, i , valread , s; |
27 | struct sockaddr_in address; |
28 | |
29 | char buffer[1025]; //data buffer of 1K |
30 | |
31 | //set of socket descriptors |
32 | fd_set readfds; |
33 | |
34 | //a message |
35 | char *message = "ECHO Daemon v1.0 \r\n" ; |
36 |
37 | //initialise all client_socket[] to 0 so not checked |
38 | for (i = 0; i < max_clients; i++) |
39 | { |
40 | client_socket[i] = 0; |
41 | } |
42 | |
43 | //create a master socket |
44 | if ( (master_socket = socket(AF_INET , SOCK_STREAM , 0)) == 0) |
45 | { |
46 | perror ( "socket failed" ); |
47 | exit (EXIT_FAILURE); |
48 | } |
49 |
50 | //set master socket to allow multiple connections , this is just a good habit, it will work without this |
51 | if ( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, ( char *)&opt, sizeof (opt)) < 0 ) |
52 | { |
53 | perror ( "setsockopt" ); |
54 | exit (EXIT_FAILURE); |
55 | } |
56 |
57 | //type of socket created |
58 | address.sin_family = AF_INET; |
59 | address.sin_addr.s_addr = INADDR_ANY; |
60 | address.sin_port = htons( 8888 ); |
61 | |
62 | //bind the socket to localhost port 8888 |
63 | if (bind(master_socket, ( struct sockaddr *)&address, sizeof (address))<0) |
64 | { |
65 | perror ( "bind failed" ); |
66 | exit (EXIT_FAILURE); |
67 | } |
68 |
69 | //try to specify maximum of 3 pending connections for the master socket |
70 | if (listen(master_socket, 3) < 0) |
71 | { |
72 | perror ( "listen" ); |
73 | exit (EXIT_FAILURE); |
74 | } |
75 | |
76 | //accept the incoming connection |
77 | addrlen = sizeof (address); |
78 | puts ( "Waiting for connections..." ); |
79 | while (TRUE) |
80 | { |
81 | //clear the socket set |
82 | FD_ZERO(&readfds); |
83 |
84 | //add master socket to set |
85 | FD_SET(master_socket, &readfds); |
86 | |
87 | //add child sockets to set |
88 | for ( i = 0 ; i < max_clients ; i++) |
89 | { |
90 | s = client_socket[i]; |
91 | if (s > 0) |
92 | { |
93 | FD_SET( s , &readfds); |
94 | } |
95 | } |
96 |
97 | //wait for an activity on one of the sockets , timeout is NULL , so wait indefinitely |
98 | activity = select( max_clients + 3 , &readfds , NULL , NULL , NULL); |
99 | |
100 | if ((activity < 0) && ( errno !=EINTR)) |
101 | { |
102 | printf ( "select error" ); |
103 | } |
104 | |
105 | //If something happened on the master socket , then its an incoming connection |
106 | if (FD_ISSET(master_socket, &readfds)) |
107 | { |
108 | if ((new_socket = accept(master_socket, ( struct sockaddr *)&address, (socklen_t*)&addrlen))<0) |
109 | { |
110 | perror ( "accept" ); |
111 | exit (EXIT_FAILURE); |
112 | } |
113 | |
114 | //inform user of socket number - used in send and receive commands |
115 | printf ( "New connection , socket fd is %d , ip is : %s , port : %d \n" , new_socket , inet_ntoa(address.sin_addr) , ntohs(address.sin_port)); |
116 | |
117 | //send new connection greeting message |
118 | if ( send(new_socket, message, strlen (message), 0) != strlen (message) ) |
119 | { |
120 | perror ( "send" ); |
121 | } |
122 | |
123 | puts ( "Welcome message sent successfully" ); |
124 | |
125 | //add new socket to array of sockets |
126 | for (i = 0; i < max_clients; i++) |
127 | { |
128 | s = client_socket[i]; |
129 | if (s == 0) |
130 | { |
131 | client_socket[i] = new_socket; |
132 | printf ( "Adding to list of sockets as %d\n" , i); |
133 | i = max_clients; |
134 | } |
135 | } |
136 | } |
137 | |
138 | //else its some IO operation on some other socket :) |
139 | for (i = 0; i < max_clients; i++) |
140 | { |
141 | s = client_socket[i]; |
142 | |
143 | if (FD_ISSET( s , &readfds)) |
144 | { |
145 | //Check if it was for closing , and also read the incoming message |
146 | if ((valread = read( s , buffer, 1024)) == 0) |
147 | { |
148 | //Somebody disconnected , get his details and print |
149 | getpeername(s , ( struct sockaddr*)&address , (socklen_t*)&addrlen); |
150 | printf ( "Host disconnected , ip %s , port %d \n" , inet_ntoa(address.sin_addr) , ntohs(address.sin_port)); |
151 | |
152 | //Close the socket and mark as 0 in list for reuse |
153 | close( s ); |
154 | client_socket[i] = 0; |
155 | } |
156 | |
157 | //Echo back the message that came in |
158 | else |
159 | { |
160 | //set the terminating NULL byte on the end of the data read |
161 | buffer[valread] = '\0' ; |
162 | send( s , buffer , strlen (buffer) , 0 ); |
163 | } |
164 | } |
165 | } |
166 | } |
167 | |
168 | return 0; |
169 | } |
Compile and run the above program. Then connect to it using telnet from 3 different terminals.
1 | $ 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