Using non-blocking sockets and serving clients in a single process using the select() function.

An example of using the function.

#include

#include

#include

#include

#include

main(int argc, char *argv[])

{

int i;

struct host *host;

struct in_addr addr;

host = gethostbyname(argv[1]);

bzero(&addr,sizeof(addr));

if(host!=NULL)

{

printf(”Official name: %sn”, host->h_name);

for(i=0;host->h_aliases[i]!=0;i++)

{

printf(”alias[%d]: %sn”, i+1, host->h_aliases[i]);

};

printf(” Address type=%dn”, host->h_addrtype);

for(i=0; i< 2; i++)

{

bcopy(host->h_addr_list[i],&addr,host->h_length);

printf(”Addr[%d]: %sn ”, i+1, inet_ntoa(addr));

};

}else{

error(argv[1] );

};

};

return 0;

};

In the above example, only two addresses are defined for the host with the name given as an argument when starting the program.

Note that this function converts the hostname to an IP address in the byte order required for its use in the sockaddr_in structure.

When creating a server serving several clients at once, there are several ways to solve this problem, for example:

1. Using blocking sockets and maintaining interaction with each client by a separate process.

2. Using non-blocking sockets and serving clients in one process using the select() function.

Consider each of the proposed ways.

Using non-blocking sockets and serving clients in a single process using the select() function.

First, after creating a socket, you need to put it in non-blocking mode. This is done with the following system calls

#include
#include

…………………………………………. …………………………………………. …..



int s = socket(PF_INET, SOCK_STREAM, 0);
fcntl(s, F_SETFL, O_NONBLOCK); // set to non-blocking mode

The non-blocking mode of sockets is characterized by the fact that when a data read (write) operation is called on one end of the socket in the absence of a data write (read) operation call on the other end of the socket, the read (write) call will end with an execution code = -1 with an EWOULDBLOCK error in the errno variable .

Further actions for servicing several clients can be implemented as follows – the server will poll all created sockets in a loop (for example, with the recv () and send () functions), if the socket is not ready to receive or transmit data, then system calls will return the code =- 1 and errno value equal to EWOULDBLOCK, if the socket is ready to receive or transmit data, then system calls for writing or reading data to (from) the socket will be executed successfully and then polling all sockets will continue in a cycle.

However, there is a more elegant solution using the select() function. This function has the following syntax and description

#include
int select(int n , fd_set * readfds , fd_set * writefds , fd_set * exceptfds ,
struct timeval * timeout );

FD_SET(int fd , fd_set * set );

FD_CLR(int fd , fd_set * set );

FD_ISSET(int fd , fd_set * set );

FD_ZERO(fd_set * set );

The select() function gives us the ability to check multiple sockets at the same time to see if they have data waiting to be recv()ed or if you can send() data to the socket without blocking. This function operates in blocking mode until either socket read/write events occur or the timeout specified for this call expires. The arguments to the select() function have the following meaning:

fd_set * readfds, fd_set * writefds, fd_set * exceptfds are pointers to sets of socket descriptors for reads, writes, and exceptions. If the program, for example, only monitors sockets to be able to read from them (ie, use the recv() function), then NULL should be used instead of the remaining sets of socket descriptors.

int n – this number is defined as the maximum value of the descriptor found in the readfds, writefds and exceptfds sets plus 1. The struct timeval * timeout structure allows you to specify how long the select() call must wait for events in the descriptor sets to occur. The struct timeval structure consists of two fields: tv_sec is the number of seconds and tv_usec is the number of milliseconds ( 1000000 milliseconds = 1 second).

The select() function returns the number of descriptors in the set that are ready for read or write operations, 0 if the timeout has expired and no descriptors are ready, and -1 on error. In this case, the readfds, writefds, and exceptfds descriptor sets are modified to indicate which descriptors are ready for input or output operations.

The macros specified after the function definition above perform the following functions:

FD_SET(int fd , fd_set * set ); adds int fd descriptor to fd_set * set

FD_CLR(int fd , fd_set * set ); removes int fd descriptor from fd_set * set

FD_ISSET(int fd , fd_set * set ); returns true if an int fd in fd_set * set is ready to perform an input or output operation.

FD_ZERO(fd_set * set ); resets the contents of fd_set * set

Now let’s give an example of the simplest program using the select () function, which will look at the sockets only to see if they can read data from them.

int s1, s2, n, rv;
fd_set readfds;
struct timeval tv;
char buf1[256], buf2[256];

// assume we’ve contacted both servers at this point
//s1 = socket(…);
//s2 = socket(…);
//connect(s1, …)…
//connect(s2, …)…

// clear the set of descriptors
FD_ZERO(&readfds);

// add our descriptors to the set
FD_SET(s1, &readfds);
FD_SET(s2, &readfds);

//as we received the second descriptor s2 in the text of the program, then its value

// “greater than”, and we will use it to set the n parameter in select()
n = s2 + 1;

// set the timeout to 10.5 sec
tv.tv_sec = 10;
tv.tv_usec = 500000;
rv = select(n, &readfds, NULL, NULL, &tv);

if (rv == -1) {
error(“select”); // error when calling select()
} else if (rv == 0) {
printf(“Timeout occurred! No data after 10.5 seconds.n”);
} else {
// one or both descriptors have data to use the recv() function
if (FD_ISSET(s1, &readfds)) {
recv(s1, buf1, sizeof buf1, 0);
}
if (FD_ISSET(s2, &readfds)) {
recv(s1, buf2, sizeof buf2, 0);
}
}

Be First to Comment

Leave a Reply

Your email address will not be published.