【问题标题】:Why does select() function always return 0 in my UDP server implementation?为什么在我的 UDP 服务器实现中 select() 函数总是返回 0?
【发布时间】:2024-01-09 22:50:01
【问题描述】:

我正在尝试实现一个单播 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);
                }
            }
        }
    }
}

【问题讨论】:

    标签: c sockets udp


    【解决方案1】:

    select() 的第一个参数是 nfds,fd 的 number...不是 last fd 的编号 -- 你可能想要 @ 987654323@,这里。


    为了完整起见,稍后添加 - 收集其他 cmets 等并扩展相同...

    ... 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() 的标准化程度更高,信号掩码的处理也值得了解——以防有一天你会发现没有它就活不下去。

    【讨论】:

    • 我进行了更改,但似乎仍然存在问题。 Select 在 10 秒超时后连续返回 0。已经用我所做的更改编辑了代码.. 有什么建议吗?
    • 正在扫描更新的代码...我看不到 max_servers 的初始化位置,但我认为它与 server_s1server_s2 一起。我没有看到 select()... 的问题,所以我会在别处寻找问题。如果你在select() 返回超时时戳server_s1server_s2,你应该会发现它们没问题,没有什么可读的!在这种情况下,您需要四处挖掘并找出他们没有收到任何东西的原因。
    【解决方案2】:

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

    来自select(2) 的 Linux 手册页:

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

    【讨论】:

    • 我尝试重新初始化它.. 仍然存在同样的问题.. select 函数现在每 2 秒返回 0。
    • 在 2 秒后返回 0 正是 select() 在传入 2 秒 timeval 结构时所期望的。如果您不希望它在指定的超时后返回,请为 timeval 参数传入 NULL。 (至于为什么当 server_s 有可供读取的数据时它不返回,请参阅@gmch 的答案——还请注意,您应该在循环的每次迭代中调用 FD_CLR() 和 FD_SET ,而不仅仅是在启动时调用一次)
    最近更新 更多