【问题标题】:Why does `select` continue to think I need to `accept`?为什么`select`继续认为我需要`accept`?
【发布时间】:2017-04-03 19:12:25
【问题描述】:

我正在尝试编写一个简单的套接字服务器,就本示例而言,它是一个回显服务器。

这是 socket_server.c(注意关于卡在哪里的注释)

int fdmax, i, socket_descriptor;
fd_set master, read_fds;
struct timeval tv = {.tv_sec = 0, .tv_usec = 1000};

void socket_server_init(void)
{
    FD_ZERO(&master);
    FD_ZERO(&read_fds);
}

socket_server_socket socket_server_start(char *socket_path)
{
    struct sockaddr_un local;
    int len;
    int socket_descriptor = socket(AF_UNIX, SOCK_STREAM, 0);

    if (socket_descriptor == -1)
        return (socket_server_socket) {.status = SOCKET_SERVER_START_SOCKET_FAILURE, .descriptor = -1};

    local.sun_family = AF_UNIX;
    strcpy(local.sun_path, socket_path);
    unlink(local.sun_path);
    len = strlen(local.sun_path) + sizeof(local.sun_family);

    if (bind(socket_descriptor, (struct sockaddr *)&local, len) == -1)
        return (socket_server_socket) {.status = SOCKET_SERVER_START_BIND_FAILURE, .descriptor = -1};

    if (listen(socket_descriptor, 5) == -1)
        return (socket_server_socket) {.status = SOCKET_SERVER_START_LISTEN_FAILURE, .descriptor = -1};

    FD_SET(socket_descriptor, &master);
    fdmax = socket_descriptor;
    return (socket_server_socket) {.status = 0, .descriptor = socket_descriptor};
}

socket_server_socket socket_server_wait_for_connection(socket_server_socket server)
{
    read_fds = master;
    select(fdmax+1, &read_fds, NULL, NULL, &tv);

    for (i = 0; i <= fdmax; i++)
    {
        if (FD_ISSET(i, &read_fds))
        {
            if (i == server.descriptor)
            {
                // It's getting stuck here.
                socket_descriptor = accept(server.descriptor, (struct sockaddr *) NULL, NULL);

                if (socket_descriptor > fdmax)
                    fdmax = socket_descriptor;

                FD_SET(socket_descriptor, &master);
                return (socket_server_socket) {.status = -1, .descriptor = socket_descriptor};
            } else {
                return (socket_server_socket) {.status = 0, .descriptor = i};
            }
        }
    }

    return (socket_server_socket) {.status = -1, .descriptor = -1};
}

int socket_server_update(socket_server_socket client)
{
    char buffer[256];
    int n = recv(client.descriptor, buffer, 256, 0);
    if (n < 0)
        return SOCKET_SERVER_UPDATE_RECV_FAILURE;

    if (send(client.descriptor, buffer, n, 0) < 0)
        return SOCKET_SERVER_UPDATE_SEND_FAILURE;

    close(client.descriptor);
    return 0;
}

然后在我的主程序中:

    socket_server_init();
    socket_server_socket server = socket_server_start(SOCKET_PATH);
    while (1) {
        printf("wait for conn\n");
        socket_server_socket client = socket_server_wait_for_connection(server);

        if (client.status == 0)
        {
            socket_server_update(client);
        }

        sleep(1);
        printf("%d: Log !\n", (int)time(NULL));
    }

当我运行程序时,我观察到:

  1. “记录!”在控制台输出
  2. 我通过socket连接到服务器
  3. 我继续看到“日志!”在控制台上
  4. 我从客户端发送了一些数据
  5. 客户端看到数据回显
  6. “记录!”不再显示在控制台上
  7. 来自客户端的任何后续数据都不会回显

我希望循环继续并“记录!”继续输出,但看起来我的程序在 accept 调用时卡住了,但只是第二次。

据我了解,select 仅应在需要被接受或接收时将描述符添加到read_fds。所以似乎发生的是:

  1. select 将服务器的描述符添加到 read_fds
  2. 它被接受了
  3. select 再次将服务器描述符添加到 read_fds
  4. 没有什么可以等待accepted 所以accept 挂起

我已经验证,当调用accept 时,描述符是相同的。所以我很困惑。

我做错了什么?我确定它不应该为同一个连接第二次点击accept

【问题讨论】:

    标签: c sockets unix-socket


    【解决方案1】:

    你使用了一个超时,如果这个超时过去了,select(2) 会返回 0,你的程序不关心它的返回值,把它当作一个成功的调用(“我们有东西要读”),但是 @987654323 @ 没有更新。因此,它仍然保持之前的值(“我们有一个新客户”),然后,您调用accept 并卡住。

    解决方法很简单:检查select(2)的返回值:

     int result = select(fdmax+1, &read_fds, NULL, NULL, &tv);
     if (result < 0) {
         // Handle error.
     } else if (result == 0) {
         // Handle timeout.
     }
    

    要了解更多关于 select 和 unix 中常见套接字的信息,我强烈推荐阅读 Beej's Guide to Network Programming

    【讨论】:

    • 这似乎很接近......但我无法遵循您的建议,因为这会导致 select 在等待连接时阻塞。我正在使用超时,以便我的程序的其他部分可以继续工作。我确实尝试过if (select(fdmax+1, &amp;read_fds, NULL, NULL, &amp;tv) &lt; 0) return;,但后来我遇到了select由于某种原因一直返回-1的情况。
    • 请记住,您应该 FD_CLR 来自您的 fd_set 的无效(关闭)套接字。 .请尝试按照 beej 的示例进行简单聊天并将其与您的程序进行比较。要查看为什么 select 返回 -1,请尝试使用 perror 打印最后一个错误。
    • @CameronBall 如果选择返回 0,您将超时。如果它返回 error 并且你需要检查 errno 和/或使用 perror 或其他任何东西来查看错误是什么。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-21
    • 1970-01-01
    • 2010-10-13
    • 2011-06-17
    • 2012-05-21
    • 2019-06-09
    相关资源
    最近更新 更多