Rakesh
Rakesh

Reputation: 1

Issue in using 'writefds' parameter in select() function in C programming

This is query is related to socket programming and server is using select() function to find out the sockets that are ready to read.

As per the man pages of select() function. In short "The select function allows a program to monitor multiple network sockets, which are ready for reading and which fds are ready for writing" (leave exceptions for now). The below two programs one for server and one for client program.

I am asking this query after searching many articles in stackoverflow and other websites. I didn't find any solutions related.

I have used below code for readfds in select() and is working fine.

Server: Using readfds

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>

int main(void)
{
fd_set master;
fd_set read_fds;  // file descriptor list used for select()
int fdmax;
int listener;
int newfd;        // newly accepted socket descriptor
struct sockaddr_storage remoteaddr; // client address
socklen_t addrlen;
char remoteIP[INET6_ADDRSTRLEN], buf[20];
int yes=1;        // for setsockopt() SO_REUSEADDR, below
int i, rv, nbytes;
struct addrinfo hints, *ai, *p;

FD_ZERO(&master);
FD_ZERO(&read_fds);

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((rv = getaddrinfo(NULL, "6000", &hints, &ai)) != 0) {
    fprintf(stderr, "selectserver: %s\n", gai_strerror(rv));
    return 1;
}

for(p = ai; p != NULL; p = p->ai_next) {
    listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
    if (listener < 0) {
        continue;
    }

    if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
        perror("setsockopt");
        return 1;
    }

    if (bind(listener, p->ai_addr, p->ai_addrlen) < 0)
    {
        close(listener);
        continue;
    }

    break;
}

// if we got here, it means we didn't get bound
if (p == NULL) {
    printf("selectserver: failed to bind\n");
    return 1;
}

freeaddrinfo(ai); // all done with this

if (listen(listener, 5) == -1) {
    perror("listen");
    return 1;
}

FD_SET(listener, &master);

fdmax = listener; // remembering max fd

// main loop
for(;;)
{
    read_fds = master; // copying to read fds it
    if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
        perror("select");
        return 1;
    }

    // run through the existing connections looking for data to read
    for(i = fdmax; i >= 0; i--)
    {
        if (FD_ISSET(i, &read_fds))
        { // we got one!!
            if (i == listener)
            {
                // handle new connections
                addrlen = sizeof remoteaddr;
                newfd = accept(listener,
                    (struct sockaddr *)&remoteaddr,
                    &addrlen);

                printf("selectserver: new connection from %s on socket %d\n",
                        inet_ntop(remoteaddr.ss_family,
                        &(((struct sockaddr_in *)&remoteaddr)->sin_addr),
                        remoteIP, INET6_ADDRSTRLEN),
                        listener);

                //if (nbytes = send(newfd, "something", strlen("something"), 0) == -1)
                if ((nbytes = recv(newfd, buf, 20, 0)) <= 0)
                    printf("send failed\n");
                else
                {
                    buf[nbytes] = '\0';
                    printf("%s\n", buf);
                }
            } // END handle data from client
        } // END got new incoming connection
    } // END looping through file descriptors
    close(newfd);
}
close(listener);
return 0;
}

Client:

#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
int clientSocket;
struct sockaddr_in serverAddr;
socklen_t addr_size;
char buf[20];
int nbytes;
int rv;

struct addrinfo hints, *ai, *p;

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
//hints.ai_flags = AI_PASSIVE;

if ((rv = getaddrinfo(NULL, "6000", &hints, &ai)) != 0) {
  fprintf(stderr, "selectserver: %s\n", gai_strerror(rv));
  return 1;
}

for(p = ai; p != NULL; p = p->ai_next) {
    clientSocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
  if (clientSocket < 0) {
      printf("error");
      continue;
  }
  break;
}

addr_size = sizeof serverAddr;
if (connect(clientSocket, (struct sockaddr *) p->ai_addr, p->ai_addrlen) == -1)
{
    printf("Connect failed\n");
    return 1;
}

printf("connect success\n");
//if ((nbytes = recv(clientSocket, buf, 20, 0)) <= 0)
if ((nbytes = send(clientSocket, "something", strlen("something"), 0)) == -1)
{
    printf("failed, Number of bytes: %d\n", nbytes);
    return 1;
}

return 0;
}

I added comments where ever required.

In server, select() function is used for readfds (which will return when one of the readfds are set) and once returned i will read from the socket after accepting the socket. This program is working as expected, Now

In server program:

//if (nbytes = send(newfd, "something", strlen("something"), 0) == -1) <br>
if ((nbytes = recv(newfd, buf, 20, 0)) <= 0)

In client program:

//if ((nbytes = recv(clientSocket, buf, 20, 0)) <= 0)<br>
if ((nbytes = send(clientSocket, "something", strlen("something"), 0)) == -1)

---------------------------------------------------------------

I have UN-commented the send statement in server and recv statement in client. like below,

In server program:

if (nbytes = send(newfd, "something", strlen("something"), 0) == -1)<br>
//if ((nbytes = recv(newfd, buf, 20, 0)) <= 0)

In client program:

if ((nbytes = recv(clientSocket, buf, 20, 0)) <= 0)<br>
//if ((nbytes = send(clientSocket, "something", strlen("something"), 0)) == -1)

---------------------------------------------------------------

Now the programs are modified in such a way that, server is sending the data and client is receiving (remember that i am using 'readfds' in the select() function). The modified programs works perfectly alright. By this we can say that, the parameter 'readfds' in select() function is setting fds that are ready to write and also setting those fds are ready to write in the fd_set.

**Now, from the modified code, Do the below changes and keep the rest of the code as it is,

if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {<br>

Replace with below line,

if (select(fdmax+1, NULL, &read_fds, NULL, NULL) == -1) {<br>

The above change will make the select() function to return (from waiting state) when it found a socket which is ready to write. (as per the functionality). But, 'select()' is not returning at all returning and waiting for a socket to write data on to it. And is getting blocked here.. which is not as expected.**

Main requirement is, i have a server running on a machine and waiting for both the type of client sockets running on same machine which are ready to be read and ready to write. To achieve this, i have modified the select statement by specifying the readfds (2nd parameter in select()) and writefds (3rd parameter in select()). Which is not working (i.e. writefds are not getting set, problem is explained before). Main problem is that, i am unable to differentiate between sockets (ready to read and write fds), because select() function is catching read and write sock in readfds only.

This is my first post on stackoverflow. So, I tried to explain clearly to avoid data insufficiency. Please comment if any thing needs to be added/modified.

Upvotes: 0

Views: 1058

Answers (1)

Some programmer dude
Some programmer dude

Reputation: 409176

When you select on the writefds argument, the set doesn't contain any sockets ready to write. It doesn't contain any sockets ready to write because you never add the new accepted socket in the master set.

Technically, you don't check if the newly accepted connections are ready to read either.

The only socket in the master set is the listening socket, and it will never be writable.

Solution: Add sockets in the master set after you accept them.


On a side-note, you should not really loop over all possible file-descriptors while checking for the ready sockets. Just do e.g.

if (select(...) < 0)
{
    // Handle error
}

if (FD_ISSET(listener, &read_fds))
{
    // Handle new connection
}

And to know which of your connected clients have sent data, keep the connected clients in a list and loop over that list only.

Upvotes: 1

Related Questions