user1161604
user1161604

Reputation: 353

How to use select and FD_SET in socket programming?

I am new to socket programming, and I am having trouble understanding how select() and FD_SET() works.

I modify an example from Beej's tutorial in an attempt to figure it out. What I want to do in the for loop is at each iterations I wait for 4 seconds. If a read is available, I would print "A key was pressed" and if it timeout, then it would print "Timed out." Then I would clear the set and repeat the process 9 more times. But it seems that once file descriptor 0 is set, it never gets unset even after a call to FD_ZERO() and/or FD_CLR(). In other words after I press a key in the first iteration of the loop the file descriptor is set for the rest of the iterations and no more waiting is done. So there must be something I am missing, but I don't know what.

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

#define SERVERPORT 4950

int main(int argc, char *argv[]) {
    struct sockaddr_in their_addr; // connector's address information
    struct hostent *he;
    int numbytes;
    int broadcast = 1;

    if ((he=gethostbyname(argv[1])) == NULL) {  // get the host info
        perror("gethostbyname");
        exit(1);
    }

    // this call is what allows broadcast packets to be sent:
    if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast,
        sizeof broadcast) == -1) {
        perror("setsockopt (SO_BROADCAST)");
        exit(1);
    }
    their_addr.sin_family = AF_INET;     // host byte order
    their_addr.sin_port = htons(SERVERPORT); // short, network byte order
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);

    struct timeval tv;
    fd_set broadcastfds;

    int i;
    for(i=0; i < 10; i++) {
        tv.tv_sec = 4;
        tv.tv_usec = 500000;

        FD_ZERO(&broadcastfds);
        FD_CLR(0, &broadcastfds);
        FD_SET(0, &broadcastfds);
        if(select(0+1, &broadcastfds, NULL, NULL, &tv) == -1) perror("select");

        if (FD_ISSET(0, &broadcastfds)) printf("A key was pressed!\n");
        else printf("Timed out.\n");
        fflush(stdout); 
    }   
    close(sockfd);
    return 0;
}

Upvotes: 12

Views: 50211

Answers (2)

Celada
Celada

Reputation: 22261

You are using FD_SET correctly. You are asking select() to notify you when file descriptor 0 (standard input) is ready for reading. It does this. The problem is that you are not reading standard input to consume the input that is available. So when you loop back and call select() again, standard input is still ready for reading and it returns immediately.

The correct way to use select() (or poll(), which is usually a better option) is:

  • Set all of the file descriptors involved to nonblocking mode. For most use cases you want this because you want to do all your blocking inside select() (or poll()), not while servicing individual file descriptors.
  • Set up the arguments to select() or poll() to register which file descriptors you are interested in.
  • Call select() or poll().
  • React to the notifications it gives you by consuming input and writing output.
  • Loop back to step 2.

P.S.: What does your UDP socket sockfd have to do with anything? You open it but it doesn't get used for anything.

Upvotes: 8

Rcl
Rcl

Reputation: 89

The problem is that you never read the data from the file descriptor.

select() reports state, not events.

So after the first time select() returns, there is always data available for reading, so select() reports that immediately.

PS. Whereever you're getting that code from, it looks about 15 years old. poll() is generally more convenient than select(), and getaddrinfo() is more convenient than gethostbyname(). [And they work better, too].

Upvotes: 5

Related Questions