如何使用getaddrinfo选择服务器套接字地址?

时间:2016-08-18 19:34:19

标签: sockets unix network-programming posix getaddrinfo

我想创建一个TCP服务器应用程序,它允许用户选择绑定调用中使用的本地地址。用户可以提供主机名或IP地址的文本表示,因此我想使用getaddrinfo函数将文本表示转换为一个或多个sockaddr结构(必要时执行名称查找)。

现在我的问题是:getaddrinfo函数似乎不适合我的需要,因为它需要在提示结构中设置AI_PASSIVE标志才能获得可以在绑定调用中使用的套接字地址。但是如果我使用AI_PASSIVE,我就不能再使用nodename参数,这会让用户选择本地地址的目的就失败了。如果我不提供AI_PASSIVE,getaddrinfo将仅返回可用于connect,sendto和sendmsg调用的那些地址,但可能存在可用于绑定但不用于connect,sendto或sendmsg调用的地址,这些地址将被省略。请参阅有关getaddrinfo/freeaddrinfo函数的POSIX规范。

为了澄清我的需求,这里是我试图创建的应用程序草图:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>

int main(int argc, char **argv)
{
    if (argc != 3) {
        fprintf(stderr, "Usage: %s address port\n", argv[0]);
        return EXIT_FAILURE;
    }

    struct addrinfo hints = {0};
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    struct addrinfo *result;
    /* The next line won't work as intended! It will behave as if the
    AI_PASSIVE flag was not set. */
    if (getaddrinfo(argv[1], argv[2], &hints, &result) != 0) {
        perror("getaddrinfo");
        return EXIT_FAILURE;
    }

    /* Iterate result list */
    int sfd;
    struct addrinfo *rp = result;
    do {
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1)
            continue;

        if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
            break; /* Success */

        close(sfd);
        rp = rp->ai_next;
    } while (rp != NULL);

    freeaddrinfo(result);

    if (rp == NULL) { /* No address succeeded */
        fprintf(stderr, "Could not bind\n");
        return EXIT_FAILURE;
    }

    /* ... use socket bound to sfd ... */

    close(sfd);

    return EXIT_SUCCESS;
}

我实际上有几个关于这个主题的问题。首先,为什么是使用AI_PASSIVE标志时禁止的nodename参数?意图是什么?这对我没有任何意义。是否有任何(最好是符合POSIX的)方法来查找我可以绑定的本地地址,并且对应于文本表示中的给定主机名或IP地址?如果给定的节点名对应于本地地址且AI_PASSIVE 设置,那么getaddrinfo会返回任何不能用于绑定的地址吗?或者更糟糕的是,是否有任何地址仅适用于绑定,在这种情况下不会返回?

相关(并没有令人满意地回答):getaddrinfo: in what way is AI_PASSIVE ignored if the nodename is specified?

1 个答案:

答案 0 :(得分:3)

我同意参考问题没有得到满意的答复,IMO的整个功能都没有明确规定。

我认为AI_PASSIVE标志名称错误。应该称之为AI_ANY_ADDRESS。它只是意味着&#34;给我一个令牌,我可以用来绑定到这个系统上的任何节点地址(在地址族等内)&#34;这样您就不必对INADDR_ANYIN6ADDR_ANY_INIT进行硬编码。因此,如果您提供节点名称参数,则可以避免AI_PASSIVE的观点。你在说&#34;这里是我要绑定的地址&#34;。

在实践中,这一切都很好,因为bindconnect接受的地址结构在所有情况下基本相同(除了你不能connect到特殊的&#34;任何&#34;地址)。这就是为什么 - 当您指定节点名称时 - 是否指定AI_PASSIVE并不重要。您将获得适用于指定名称/地址的bindconnect调用的参数。

获取&#34;地址信息&#34;并不意味着bindconnect会成功。您可以成功获取无法bind的地址 - 因为它不是本地计算机的地址 - 或者出于其他原因。 (显然,connect也可能由于多种原因而失败 - 可能没有机器在该地址应答或没有服务器在端口上侦听等等。)

如果您提供的节点名称不是IP地址,并且名称解析提供了不同系列中的多个IP地址(本地计算机上都可用),您应该可以bind到所有这些地址地址。

因此,底线:如果您想允许用户指定地址,请将其提供给getaddrinfo。您获得的地址(如果有)应该可以在bind电话中使用。