如何使用 getaddrinfo 选择服务器套接字地址?
How to choose a server socket address using getaddrinfo?
我想创建一个 TCP 服务器应用程序,让用户选择在绑定调用中使用的本地地址。用户可能会提供主机名或 IP 地址的文本表示,因此我想到使用 getaddrinfo 函数将文本表示转换为一个或多个 sockaddr 结构(必要时执行名称查找)。
现在我的问题是:getaddrinfo 函数似乎不适合我的需要,因为它需要在 hints 结构中设置 AI_PASSIVE 标志以获取可能在 bind 中使用的套接字地址电话。但是如果我使用 AI_PASSIVE,我就不能再使用 nodename 参数,这违背了让用户选择本地地址的整个目的。如果我不提供 AI_PASSIVE,getaddrinfo 只会 return 那些可以在 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-conform)方法来找到我可以绑定的本地地址,并且完全对应于文本表示中的给定主机名或 IP 地址?如果给定的节点名对应于本地地址并且 AI_PASSIVE 未设置 ,是否会有任何地址 returned 由 getaddrinfo 编辑,不能用于捆绑?或者更糟的是,在这种情况下是否会有任何地址只适合绑定并且不会 returned?
相关(未回答满意):getaddrinfo: in what way is AI_PASSIVE ignored if the nodename is specified?
我同意参考问题没有得到令人满意的回答,IMO 整个功能没有详细说明。
我认为 AI_PASSIVE
标志被错误命名。它应该被称为 AI_ANY_ADDRESS
。它只是表示 "give me a token I can use to bind to any node address on this system (within the address family, etc)",因此您不必硬编码 INADDR_ANY
和 IN6ADDR_ANY_INIT
。因此,如果您提供节点名称参数,您就避免了 AI_PASSIVE
的要点。你是说 "Here's the address(es) I want to bind to".
实际上,这一切都很好,因为 bind
和 connect
接受的地址结构在所有情况下基本相同(除了你不能 connect
到特殊的 "any" 地址)。这就是为什么 - 当您指定节点名称时 - 您是否指定 AI_PASSIVE
并不重要。您将获得适用于对指定 name/address 的 bind
或 connect
调用的参数。
获得"address info"并不意味着bind
或connect
会成功。您可以成功获得一个您无法 bind
的地址——因为它不是本地机器的地址——或者出于其他原因。 (显然,connect
也可能由于多种原因而失败——可能没有机器在该地址应答或没有服务器在端口上侦听,等等)
如果您提供的节点名称不是 IP 地址,并且名称解析提供了不同系列的多个 IP 地址(在本地计算机上都可用),您应该能够 bind
所有这些地址。
所以,底线:如果你想让用户指定地址,只需将其提供给 getaddrinfo
。您返回的地址(如果有的话)应该可以在 bind
通话中使用。
我想创建一个 TCP 服务器应用程序,让用户选择在绑定调用中使用的本地地址。用户可能会提供主机名或 IP 地址的文本表示,因此我想到使用 getaddrinfo 函数将文本表示转换为一个或多个 sockaddr 结构(必要时执行名称查找)。
现在我的问题是:getaddrinfo 函数似乎不适合我的需要,因为它需要在 hints 结构中设置 AI_PASSIVE 标志以获取可能在 bind 中使用的套接字地址电话。但是如果我使用 AI_PASSIVE,我就不能再使用 nodename 参数,这违背了让用户选择本地地址的整个目的。如果我不提供 AI_PASSIVE,getaddrinfo 只会 return 那些可以在 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-conform)方法来找到我可以绑定的本地地址,并且完全对应于文本表示中的给定主机名或 IP 地址?如果给定的节点名对应于本地地址并且 AI_PASSIVE 未设置 ,是否会有任何地址 returned 由 getaddrinfo 编辑,不能用于捆绑?或者更糟的是,在这种情况下是否会有任何地址只适合绑定并且不会 returned?
相关(未回答满意):getaddrinfo: in what way is AI_PASSIVE ignored if the nodename is specified?
我同意参考问题没有得到令人满意的回答,IMO 整个功能没有详细说明。
我认为 AI_PASSIVE
标志被错误命名。它应该被称为 AI_ANY_ADDRESS
。它只是表示 "give me a token I can use to bind to any node address on this system (within the address family, etc)",因此您不必硬编码 INADDR_ANY
和 IN6ADDR_ANY_INIT
。因此,如果您提供节点名称参数,您就避免了 AI_PASSIVE
的要点。你是说 "Here's the address(es) I want to bind to".
实际上,这一切都很好,因为 bind
和 connect
接受的地址结构在所有情况下基本相同(除了你不能 connect
到特殊的 "any" 地址)。这就是为什么 - 当您指定节点名称时 - 您是否指定 AI_PASSIVE
并不重要。您将获得适用于对指定 name/address 的 bind
或 connect
调用的参数。
获得"address info"并不意味着bind
或connect
会成功。您可以成功获得一个您无法 bind
的地址——因为它不是本地机器的地址——或者出于其他原因。 (显然,connect
也可能由于多种原因而失败——可能没有机器在该地址应答或没有服务器在端口上侦听,等等)
如果您提供的节点名称不是 IP 地址,并且名称解析提供了不同系列的多个 IP 地址(在本地计算机上都可用),您应该能够 bind
所有这些地址。
所以,底线:如果你想让用户指定地址,只需将其提供给 getaddrinfo
。您返回的地址(如果有的话)应该可以在 bind
通话中使用。