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

0 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);
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

小智 6

第一个参数select()是fds nfds数量 ...而不是最后一个 fd 的数量- 你可能想要的server_s + 1,这里.


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

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

  • 正如@JeremyFriesner所指出的那样,你需要fd_set在传递之前重新创建所有的select()- 因为当select()返回时,只有fd的是读取就绪或写入就绪(或有异常)将出现在它们各自的中fd_set.

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

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

    POSIX非常倾向于此事.它确实说:

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

    但我注意到它没有说的内容包括:

    • 如何select()修改超时.

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

    • pselect()可能不会修改超时 - 尽管这很明显,因为它需要一个const struct timespec*.

    无论如何,pselect()更好的标准化,信号掩码的处理是值得理解的 - 相对于你发现你不能没有它的那一天.