Networks101
Networks101

Reputation: 1

Why does select() function always return 0 in my UDP server implementation?

I am trying to implement a unicast UDP server which services multiple clients when they request for the service. The message being sent is an updated counter value. I want the server to be able to receive incoming requests whenever there is one and when there are no requests, continue sending data to the list of clients one after the other. I tried to implement this using select() but it always returns 0. What am I doing wrong?

Server side - implementing select():

while(1)
{ 
    // >>> Step #3 <<<
    // Wait to receive a message from client
    sleep(10);     // Unix sleep for 1 second
    printf(".\n");
    printf("Waiting for recvfrom() to complete... \n");

    FD_ZERO(&readhandle); 
    FD_SET(server_s1, &readhandle);
    FD_SET(server_s2, &readhandle); 

    timeout_interval.tv_sec = 10;
    timeout_interval.tv_usec = 500000;

    int retval = select(max_servers+1, &readhandle, NULL, NULL, &timeout_interval);

    if (retval == -1)
    {
        printf("Select error\n");
    }
    else if (retval == 0)
    {
        printf("timeout\n");
    }
    else
    {
        if (FD_ISSET(server_s1, &readhandle))
        {
            addr_len = sizeof(client_addr);
            errno = 0;
            retcode = recvfrom(server_s1, in_buf, sizeof(in_buf), 0, (struct sockaddr *)&client_addr, &addr_len);

            if (retcode > 0)
            {
                // Copy the four-byte client IP address into an IP address structure
                memcpy(&client_ip_addr, &client_addr.sin_addr.s_addr, 4);

                // Print an informational message of IP address and port of the client
                printf("IP address of client = %s  port = %d) \n", inet_ntoa(client_ip_addr),ntohs(client_addr.sin_port));

                // Output the received message
                printf("Received from client: %s \n", in_buf);
                client_port = ntohs(client_addr.sin_port);
                insert_at_end(client_port, client_addr);
                printf("Client added :\n");
                display();
            }
            // >>> Step #4 <<<
            // Send to the client using the server socket
            sprintf(out_buf, "Sending update from SERVER to CLIENT %d",counter++);
            struct node *tmp;
            tmp=head;
            while(tmp!=NULL)
            {
                retcode = sendto(server_s1, out_buf, (strlen(out_buf) + 1), 0,(struct sockaddr *)&(tmp -> client_addr), sizeof(tmp -> client_addr));
                printf("IP address of client = %s  port = %d) \n", inet_ntoa(tmp -> client_addr.sin_addr),ntohs(tmp -> port_num));
                if (retcode < 0)
                {
                    printf("*** ERROR - sendto() failed \n");
                    exit(-1);
                }
                tmp=tmp->next;
            }
        }
        if(FD_ISSET(server_s2, &readhandle))
        {
            addr_len = sizeof(client_addr);
            errno = 0;
            retcode = recvfrom(server_s2, in_buf, sizeof(in_buf), 0, (struct sockaddr *)&client_addr, &addr_len);

            if (retcode > 0)
            {
                // Copy the four-byte client IP address into an IP address structure
                memcpy(&client_ip_addr, &client_addr.sin_addr.s_addr, 4);

                // Print an informational message of IP address and port of the client
                printf("IP address of client = %s  port = %d) \n",  inet_ntoa(client_ip_addr),ntohs(client_addr.sin_port));

                // Output the received message
                printf("Received acknowledgement from the client: %s \n", in_buf);
                client_port = ntohs(client_addr.sin_port);

                retcode = sendto(server_s2, out_buf, (strlen(out_buf) + 1), 0,(struct sockaddr *)&(client_addr), sizeof(client_addr));

                if (retcode < 0)
                {
                    printf("*** ERROR - sendto() failed \n");
                    exit(-1);
                }
            }
        }
    }
}

Upvotes: 0

Views: 1363

Answers (2)

user3793679
user3793679

Reputation:

The first argument for select() is the nfds, the number of fds... not the number of the last fd -- you probably want server_s + 1, here.


Added later for completeness -- collecting other comments etc and expanding on same...

...the other arguments for select() are (or may be) written to -- so you need to set them up before each call. Hence:

  • as @JeremyFriesner points out, you need to recreate all the fd_set before passing same to select() -- since when select() returns, only fd's which are read-ready or write-ready (or have exceptions) will be present in their respective fd_set.

    The obvious way to achieve that is to have a separate fd_set for all the things you are currently waiting for, and copy same to a 'working' version just before passing that to select(). When you get round to using 'write-ready' you will find that in general you will set 'read-ready' once and leave it (unless your inbound buffers fill), but you will set 'write-ready' only when you have something pending to be written, and will clear it once you have emptied your outbound buffers.

  • as @rici points out, the time-out may need to be refreshed.

    POSIX is wonderfully oblique on the matter. It does say:

    • Upon successful completion, the select() function may modify the object pointed to by the timeout argument.

    but I note the things it does not say include:

    • how select() may modify the timeout.

    • what happens on error(s)... in particular EINTR (!)

    • that pselect() may not modify the timeout -- though that is pretty clear from the fact that it takes a const struct timespec*.

    Anyway, pselect() is better standardized, and the handling of the signal mask is worth understanding -- against the day when you find you cannot live without it.

Upvotes: 6

rici
rici

Reputation: 241931

One possibility is the fact that you are not reinitializing tv before every call to select. Some OSs (including Linux) update the value of that parameter to indicate the amount of time left to wait. It is best practice to reinitialize the timeout value before every call.

From the Linux manpage for select(2):

On Linux, select() modifies timeout to reflect the amount of time not slept; most other implementations do not do this. (POSIX.1-2001 permits either behavior.) This causes problems both when Linux code which reads timeout is ported to other operating systems, and when code is ported to Linux that reuses a struct timeval for multiple select()s in a loop without reinitializing it. Consider timeout to be undefined after select() returns.

Upvotes: 0

Related Questions