C++ 中的客户端,使用 gethostbyname 或 getaddrinfo

Client in C++, use gethostbyname or getaddrinfo

提问人:Foobar 提问时间:10/10/2018 最后编辑:Foobar 更新时间:10/16/2019 访问量:39446

问:

我找到了以下代码来打开C语言中的连接:

int OpenConnection(const char *hostname, int port)
{
    int sd;
    struct hostent *host;
    struct sockaddr_in addr = {0};
    if ((host = gethostbyname(hostname)) == NULL)
    {
        perror(hostname);
        abort();
    }

    sd = socket(PF_INET, SOCK_STREAM, 0);

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = *(long *)(host->h_addr_list[0]);
    if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
    {
        close(sd);
        perror(hostname);
        abort();
    }
    return sd;
}

当我在 C++ 年重写这段代码时,我发现我应该使用而不是 ,根据这篇文章中的评论:C - *(long *)(host->h_addr); 做什么?.getaddrinfogethostbyname

我正在查看 Beej 的套接字编程指南,并发现了以下示例:

int status;
struct addrinfo hints;
struct addrinfo *servinfo;  // will point to the results

memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC;     // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
    fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
    exit(1);
}

// servinfo now points to a linked list of 1 or more struct addrinfos

// ... do everything until you don't need servinfo anymore ....

freeaddrinfo(servinfo); // free the linked-list

但是,这个例子适用于创建服务器,这让我很纳闷

A] 应该用于客户端getaddrinfo

B] 我将如何修改提示结构和函数参数,以使此代码适合创建客户端?

目前,我有以下代码,但我

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

struct addrinfo *client_info;

const int status = getaddrinfo(hostname, port, &hints, &client_info); //don't know if its correct

更新

对于任何可能觉得它有用的人,这是我阅读接受的答案后的最终代码:

int OpenConnection(const char *hostname, const char *port)
{
    struct hostent *host;
    if ((host = gethostbyname(hostname)) == nullptr)
    {
        //More descriptive error message?
        perror(hostname);
        exit(EXIT_FAILURE);
    }

    struct addrinfo hints = {0}, *addrs;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    const int status = getaddrinfo(hostname, port, &hints, &addrs);
    if (status != 0)
    {
        fprintf(stderr, "%s: %s\n", hostname, gai_strerror(status));
        exit(EXIT_FAILURE);
    }

    int sfd, err;
    for (struct addrinfo *addr = addrs; addr != nullptr; addr = addr->ai_next)
    {
        sfd = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
        if (sfd == ERROR_STATUS)
        {
            err = errno;
            continue;
        }

        if (connect(sfd, addr->ai_addr, addr->ai_addrlen) == 0)
        {
            break;
        }

        err = errno;
        sfd = ERROR_STATUS;
        close(sfd);
    }

    freeaddrinfo(addrs);

    if (sfd == ERROR_STATUS)
    {
        fprintf(stderr, "%s: %s\n", hostname, strerror(err));
        exit(EXIT_FAILURE);
    }

    return sfd;
}
C++ 套接字

评论

1赞 user4581301 10/10/2018
A) 适合在客户端中使用。B)现在必须是一个数组,因为它足够聪明,可以吃掉像“ftp”这样的输入并将其转换为适当的数字。休息看起来不错。getaddrinfoportchargetaddrinfo
2赞 RbMm 10/10/2018
什么是“客户端”或“服务器”? 将主机名转换为地址。这与你的代码接下来要做什么完全无关getaddrinfo

答:

7赞 paulsm4 10/10/2018 #1

自“永远”以来,我一直使用 gethostbyname()。它总是有效,它继续有效,而且它“更简单”。

getaddrinfo() 是较新的函数:

http://man7.org/linux/man-pages/man3/getaddrinfo.3.html

getaddrinfo() 函数结合了 getHostByName(3) 和 getServByName(3) 提供的功能 功能到单个界面中,但与后者的功能不同, getaddrinfo() 是可重入的,并允许程序消除 IPv4 与 IPv6 的依赖关系。

我知道 getaddrinfo() 更强大、更高效、更安全:无论如何你都不应该使用 gethostbyname()

补遗:

回答您的具体问题:

A] 优先用于查找主机名的 IP 地址;“客户端”或“服务器”。getaddrinfo()gethostbyname()

B] Q: 如何修改提示结构和函数参数?

答:“提示”看起来不错,但我可能会将端口修改为 NULL。

下面是一个完整的示例:

https://www.kutukupret.com/2009/09/28/gethostbyname-vs-getaddrinfo/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    struct addrinfo hints, *res, *p;
    int status;
    char ipstr[INET6_ADDRSTRLEN];

    if (argc != 2) {
       fprintf(stderr, "Usage: %s hostname\n", argv[0]);
       return 1;
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
    hints.ai_socktype = SOCK_STREAM;

    if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
        return 2;
    }

    for(p = res;p != NULL; p = p->ai_next) {
        void *addr;
        if (p->ai_family == AF_INET) {
            return 1;  
        } else {
            struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
            addr = &(ipv6->sin6_addr);

            /* convert the IP to a string and print it: */
            inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);             
            printf("Hostname: %s\n", argv[1]);
            printf("IP Address: %s\n", ipstr);
        }
    }

    freeaddrinfo(res); // free the linked list     
    return 0;
}

评论

0赞 jiveturkey 10/10/2018
井。。。您复制/粘贴了我即将访问的同一部分,这里的诀窍是从服务中返回一个条目。这“暗示”此方法是用于服务器而不是客户端的服务。getservbyname
2赞 paulsm4 10/10/2018
不,我想传达的印象只是这是一种将主机名映射到 IP 的“更好”方法,而不是经典的。这两个函数都适用于客户端或服务器。getaddrinfo()gethostbyname()
3赞 Daniel Schepler 10/10/2018
@jiveturkey只是一个将符号“服务名称”(如端口号)映射到端口号的函数,但它与运行的是客户端还是服务器无关。它可能只是以这种方式命名,因为在 Unix 系统上,它会读取指定映射的读数。getservbyname()httpftp/etc/services
0赞 Remy Lebeau 10/10/2018
"自“永远”以来,我一直使用 gethostbyname()。它总是有效,它继续有效,而且它“更简单”。- 它也与IPv6不兼容,这是创建替换它的主要原因。getaddrinfo()
0赞 Foobar 10/10/2018
我很惊讶端口应该是 null - 毕竟,我正在创建一个客户端程序,该程序正在尝试连接到在特定端口上运行的服务器。
19赞 Remy Lebeau 10/10/2018 #2

和函数在大多数平台上已弃用,并且它们不实现对 IPv6 的支持。IPv4 已经达到了极限,世界转向 IPv6 已经有一段时间了。请分别使用 和。gethostbyname()gethostbyaddr()getaddrinfo()getnameinfo()

要回答您的问题,请执行以下操作:

一个。 并且可以用于客户端和服务器,就像可以一样。它们只是主机/地址解析函数,如何使用解析的值由调用应用决定。getaddrinfo()getnameinfo()gethostbyname()gethostbyaddr()

B. 客户端代码使用将如下所示:getaddrinfo()

int OpenConnection(const char *hostname, int port)
{
    int sd, err;
    struct addrinfo hints = {}, *addrs;
    char port_str[16] = {};

    hints.ai_family = AF_INET; // Since your original code was using sockaddr_in and
                               // PF_INET, I'm using AF_INET here to match.  Use
                               // AF_UNSPEC instead if you want to allow getaddrinfo()
                               // to find both IPv4 and IPv6 addresses for the hostname.
                               // Just make sure the rest of your code is equally family-
                               // agnostic when dealing with the IP addresses associated
                               // with this connection. For instance, make sure any uses
                               // of sockaddr_in are changed to sockaddr_storage,
                               // and pay attention to its ss_family field, etc...
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    sprintf(port_str, "%d", port);

    err = getaddrinfo(hostname, port_str, &hints, &addrs);
    if (err != 0)
    {
        fprintf(stderr, "%s: %s\n", hostname, gai_strerror(err));
        abort();
    }

    for(struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next)
    {
        sd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
        if (sd == -1)
        {
            err = errno;
            break; // if using AF_UNSPEC above instead of AF_INET/6 specifically,
                   // replace this 'break' with 'continue' instead, as the 'ai_family'
                   // may be different on the next iteration...
        }

        if (connect(sd, addr->ai_addr, addr->ai_addrlen) == 0)
            break;

        err = errno;

        close(sd);
        sd = -1;
    }

    freeaddrinfo(addrs);

    if (sd == -1)
    {
        fprintf(stderr, "%s: %s\n", hostname, strerror(err));
        abort();
    }

    return sd;
}

评论

0赞 Foobar 10/10/2018
感谢您的帮助!我有几个问题,但我知道它们可能超出了我最初问题的范围,所以请随时不回答。是“套接字描述符”的简写吗?另外,为什么接受指向 形式的指针的指针。根据我的理解,以指向链表中第一个节点的指针形式返回指针链表。但是,我不明白为什么创建指针链表需要知道链表中第一个节点的指针。sdgetaddrinfo&addrsgetaddrinfo
0赞 Remy Lebeau 10/10/2018
@Roymunson “sd 是”套接字描述符“的简写吗?最有可能。“Why does getaddrinfo 接受指向 &addrs 形式的指针的指针” 因为它将分配的内存地址返回给调用方。 传入自身的地址,以便可以将分配的内存地址分配给 。&addrsaddrsgetaddrinfo()addrs
0赞 Remy Lebeau 10/10/2018
@Roymunson “getaddrinfo 以指向链表中第一个节点的指针形式返回指针链表” 它是一个输出参数。C 为此使用指针。“我不明白为什么创建指针链表需要知道指向第一个节点指针的指针”您认为还能如何为您提供指向第一个节点的指针?它不能使用已用于返回错误代码的返回值。因此,使用输出参数,这需要传入指向接收第一个节点地址的指针变量的指针。getaddrinfo()
0赞 Foobar 10/10/2018
感谢您的快速回复。我的最后一个问题是,如果创建套接字失败,您的 for 循环将中止,但如果连接失败,它将继续到链表中的下一个节点。如果创建套接字失败,为什么它也不会继续?
1赞 Remy Lebeau 10/10/2018
@Roymunson 因为在我的示例中,使用 ,传递给的参数值不会在循环的每次迭代中更改,因此如果失败一次,它可能会在后续迭代中再次失败,因此循环在第一次失败时停止。如果改用,则继续尝试会更有意义,因为参数值可能不同,在 和 之间交替。AF_INEThintssocket()socket()AF_UNSPECAF_INETAF_INET6