为什么select()函数总是在我的UDP服务器实现中返回0?

时间:2014-09-21 22:38:06

标签: c sockets udp

我正在尝试实现一个单播UDP服务器,它在请求服务时为多个客户端提供服务。正在发送的消息是更新的计数器值。我希望服务器能够在没有请求的情况下接收传入请求,并且在没有请求的情况下,继续将数据一个接一个地发送到客户端列表。我尝试使用select()来实现它,但它总是返回0.我做错了什么?

服务器端 - 实施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);
                }
            }
        }
    }
}

2 个答案:

答案 0 :(得分:6)

select()的第一个参数是nfds,fds的数字 ...而不是 last fd的编号 - 你可能想要server_s + 1,这里。


稍后添加完整性 - 收集其他评论等并扩展相同...

... select()的其他参数是(或可能)写入 - 所以你需要在每次调用之前设置它们。因此:

  • 正如@JeremyFriesner所指出的那样,在将fd_set传递给select()之前,您需要重新创建所有select() - 因为当fd_set返回时,只有fd是可读的或者准备就绪(或有例外)将出现在各自的fd_set

    实现这一目标的显而易见的方法是为您当前正在等待的所有内容单独select(),并在将其传递给select()之前将其复制到“工作”版本。当你开始使用'write-ready'时,你会发现一般情况下你会设置'read-ready'一次并离开它(除非你的入站缓冲区填满),但你只有当你拥有时才设置'write-ready'待写的东西,一旦你清空出境缓冲区就会清除它。

  • 正如@rici指出的那样,可能需要刷新超时。

    POSIX在此事上非常倾斜。它确实说:

      

    •成功完成后,select()函数可能会修改超时参数指向的对象。

    但是我注意到它所做的事情说包括:

    • EINTR如何修改超时。

    • 错误会发生什么......特别是pselect()(!)

    • const struct timespec*可能修改超时 - 尽管这一点非常清楚,因为它需要pselect()

    无论如何,{{1}}标准化程度更高,信号掩码的处理值得理解 - 相比之下,当你发现没有它时就无法生存。

答案 1 :(得分:0)

一种可能性是,您在每次致电tv之前都没有重新初始化select。某些操作系统(包括Linux)更新该参数的值以指示剩余的等待时间。最佳做法是在每次调用之前重新初始化超时值。

来自select(2)

的Linux联机帮助页
  

在Linux上,select()修改超时以反映未睡眠的时间;大多数其他实现不会这样做。 (POSIX.1-2001允许任何一种行为。)当读取超时的Linux代码移植到其他操作系统时,以及当代码移植到Linux时,在循环中为多个select()s重用struct timeval时,这会导致问题重新初始化它。在select()返回后,请考虑超时未定义。