Typical socket programming in C/C++ uses the system calls like
write to communicate with through the socket. As in other cases, these sockets use file descriptors (FDs) to indicate the communication link, which represent an index for an entry in a kernel-resident array data structure containing the details of open files, either physical files, network sockets and other IO streams.
These system calls are called “blocking” calls. It means that, whenever your program calls one of those functions, your program will be blocked until it gets the intended interrupt. For instance, if you use
read() against a network socket, it will cause the program to hang/block until it gets some data from that socket. This causes some drastic problems when the server needs to handle multiple clients at once.
Then the idea of “non-blocking” calls comes into place. The idea is to keep the program executing (sometimes after waiting for a small amount of time) even though there is no interrupt to satisfy the call. This can be achieved by several methods, while
select() is one of the widely used.
Spoiler Alert : The source code can be found here.
#include <sys/time.h> #include <sys/types.h> #include <unistd.h>
int select( int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
In here, we can provide a set of FDs and get notified if either of them are ready for communication.
select() returns the number of FDs in the three descriptor sets (
We can call
write() etc. if and only if there is an activity on
select() which indicated the ready-to-use FDs.
In order to use select(), we must define the FD sets.
Then we can use following macros to manipulate them.
- void FD_ZERO(fd_set* set);
- Clears the given FD set
- void FD_SET( int fd, fd_set *set);
- Adds an FD to a FD set
- int FD_CLR( int fd, fd_set *set);
- Removes an FD from a FD set
- int FD_ISSET( int fd, fd_set *set);
- Checks if an FD is set in a FD set
To illustrate this concept, following program is designed.
- Initialize the socket and bind it to a port
- Listen with a timeout
- Clear the read FD set
- Add the socket FD to a read FD set.
- Call select on the read FD set.
- If it has an activity,
- That means there is an incoming connection.
- Then accept the connection and add it’s read/write FD to an accepted list
- For each FD in the accepted list
- Add the FD to read FD set.
- Call select on the read FD set
- If it has an activity,
- That means there is incoming data on an accepted socket.
- For each FD in the read FD set
- Check if the FD is set in read FD set
- If an FD is set, call read() and/or write on that FD.
However, when using
select(), the first argument has to be current max FD + 1. That means, we have to keep track of the
maximum FD index when we’re adding/removing FDs. We can use C-5 step to update a global variable to keep track of the maximum FD index.
And also we can use
timeval stuct to tell select, how much time it has to wait for an activity.
fd_set s_FDReadSet; int i_MaxFD; timeval t_Delay; t_Delay.tv_sec = 1; // seconds t_Delay.tv_usec = 0; // micro seconds select(i_MaxFD + 1, &s_FDReadSet, NULL, NULL, &t_Delay);
This will wait one second to check for any activity on the given FD set and return with the number of usable FDs. Then it’s easy to avoid blocking calls to hang when there is no activity is involved.
To be more precise, we can make a socket action non-blocking by setting its parameters. We can use
method for that task.
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ );
In this program, we make the sockets non-blocking as below.
fcntl(i_SockFD, F_SETFL, fcntl(i_SockFD,F_GETFL) | O_NONBLOCK);
This is used to let the system know that these sockets are used for non-blocking calls.
I implemented a simple client-server application using this concept and the source code is available in GitHub.