getaddrinfo

In C programming, the functions getaddrinfo() and getnameinfo() convert domain names, hostnames, and IP addresses between human-readable text representations and structured binary formats for the operating system’s networking API. Both functions are contained in the POSIX standard application programming interface (API).

getaddrinfo and getnameinfo are inverse functions of each other. They are network protocol agnostic, and support both IPv4 and IPv6. It is the recommended interface for name resolution in building protocol independent applications and for transitioning legacy IPv4 code to the IPv6 Internet.

Internally, the functions perform resolutions using the Domain Name System (DNS) by calling other, lower level functions, such as gethostbyname().

int getaddrinfo(const char *node, const char *service,
                const struct addrinfo *hints,
                struct addrinfo **res);

void freeaddrinfo(struct addrinfo *res);

const char *gai_strerror(int errcode);

The addrinfo structure used by getaddrinfo() contains the following fields:

struct addrinfo {
    int              ai_flags;
    int              ai_family;
    int              ai_socktype;
    int              ai_protocol;
    socklen_t        ai_addrlen;
    struct sockaddr *ai_addr;
    char            *ai_canonname;
    struct addrinfo *ai_next;
};

The getaddrinfo() function allocates and initializes a linked list of addrinfo structures, one for each network address that matches node and service, subject to any restrictions imposed by hints, and returns a pointer to the start of the list in res. The items in the linked list are linked by the ai_next field.

There are several reasons why the linked list may have more than one addrinfo structure, including: the network host is multihomed, accessible over multiple protocols (e.g., both AF_INET and AF_INET6); or the same service is available from multiple socket types (one SOCK_STREAM address and another SOCK_DGRAM address, for example). Normally, the application should try using the addresses in the order in which they are returned. The sorting function used within getaddrinfo() is defined in RFC 3484; the order can be tweaked for a particular system by editing /etc/gai.conf (available since glibc 2.5).

getaddrinfo overhead

The getaddrinfo function call alone causes over 100 system calls! But getaddrinfo does a lot before these system calls, and it does quite a bit after them, too. See more: What does getaddrinfo do?

getaddrinfo doesn’t know anything about files, DNS, or any other way to find the address for a host. Instead, getaddrinfo gets a list of these “sources” at runtime from another file, /etc/nsswitch.conf, the “Name Service Switch”.

$cat /etc/nsswitch.conf
#
# /etc/nsswitch.conf
#
# An example Name Service Switch config file. This file should be
# sorted with the most-used services at the beginning.
#
# The entry '[NOTFOUND=return]' means that the search for an
# entry should stop if the search in the previous entry turned
# up nothing. Note that if the search failed due to some other reason
# (like no NIS server responding) then the search continues with the
# next entry.
#
# Valid entries include:
#
#       nisplus                 Use NIS+ (NIS version 3)
#       nis                     Use NIS (NIS version 2), also called YP
#       dns                     Use DNS (Domain Name Service)
#       files                   Use the local files
#       db                      Use the local database (.db) files
#       compat                  Use NIS on compat mode
#       hesiod                  Use Hesiod for user lookups
#       [NOTFOUND=return]       Stop searching if not found so far
#

# To use db, put the "db" in front of "files" for entries you want to be
# looked up first in the databases
#
# Example:
#passwd:    db files nisplus nis
#shadow:    db files nisplus nis
#group:     db files nisplus nis

#initgroups: files sss

#hosts:     db files nisplus nis dns
hosts:      files dns

...

Notice the line hosts: files dns. This says, “to find a host, first ask the library libnss_files.so. If that fails, ask the library libnss_dns.so.” The C standard library interpolates files, dns and so on into the pattern libnss_%s.so to find the libraries. As such, you could write a new library libnss_imfeelinglucky, and change your nsswitch.conf to hosts: imfeelinglucky. Enjoy the chaos.

You might think we’re done. Not yet! Before getaddrinfo does any of this, we have these system calls:

socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(3)                                = 0

What is /var/run/nscd/socket ..? Linux tells us, with ENOENT, that I don’t have that file! What is this supposed to be? As Google will tell you, this is a socket to talk to the Northern School of Contemporary Dance. But before your process can go to class, you have to install the daemon:

$ sudo apt-get install nscd
...
Setting up nscd (2.19-0ubuntu6.14) ...
 * Starting Name Service Cache Daemon

Sorry, nscd is actually the “name service cache daemon”, “a daemon that provides a cache for the most common name service requests”. After installing it, the daemon starts, and your process can dance:

As everyone on the internet will tell you, nscd is “shit”, “unstable”, and “badly designed”. But no worries: as part of the C standard library, nscd is not this program but a standard protocol, part of the standard UNIX specification, with many alternative implementations!

One last thing. You might think that getaddrinfo caches answers, so subsequent calls aren’t so expensive. It does not! It does this entire procedure every time!

Example

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

int main(void)
{
  char strIP[INET_ADDRSTRLEN] = {0};

  struct addrinfo* addr;
  struct addrinfo* res;

  int result = getaddrinfo("google.com", NULL, NULL, &addr);
  if (result != 0) {
    printf("Error from getaddrinfo: %s\n", gai_strerror(result));
    return 1;
  }

  //struct sockaddr_in* internet_addr = (struct sockaddr_in*) addr->ai_addr;
  //printf("google.com is at: %s\n", inet_ntoa(internet_addr->sin_addr));

  for (res = addr; res != NULL; res = res->ai_next) { 
    struct sockaddr_in* psa = (struct sockaddr_in*) res->ai_addr;
    inet_ntop(AF_INET, &(psa->sin_addr), strIP, INET_ADDRSTRLEN);
    printf("%s\n", strIP);
  }

  freeaddrinfo(addr); 
  return 0;
}
$./a.out 
142.251.42.238
142.251.42.238
142.251.42.238
int SelectIP(const std::string& strHostName, std::string& strIP)
{
    char szIP[INET_ADDRSTRLEN] = {0};

    // block operation
    struct addrinfo* addr;
    struct addrinfo* res;
    int result = getaddrinfo(strHostName.c_str(), nullptr, nullptr, &addr);
    if (0 != result)
    {
        LOG_ERROR("getaddrinfo host(%s) err(%s)\n", strHostName.c_str(), gai_strerror(result));
        return 1;
    }
    
    for (res = addr; res != NULL; res = res->ai_next)
    {
        struct sockaddr_in* psa = (struct sockaddr_in*)res->ai_addr;
        inet_ntop(AF_INET, &(psa->sin_addr), szIP, INET_ADDRSTRLEN);
        strIP = szIP;
        break; // get first IP address
    }

    freeaddrinfo(addr);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>

#ifndef   NI_MAXHOST
#define   NI_MAXHOST 1025
#endif

int main(void)
{
    struct addrinfo* result;
    struct addrinfo* res;
    int error;

    /* resolve the domain name into a list of addresses */
    error = getaddrinfo("www.example.com", NULL, NULL, &result);
    if (error != 0) {   
        if (error == EAI_SYSTEM) {
            perror("getaddrinfo");
        } else {
            fprintf(stderr, "error in getaddrinfo: %s\n", gai_strerror(error));
        }   
        exit(EXIT_FAILURE);
    }   

    /* loop over all returned results and do inverse lookup */
    for (res = result; res != NULL; res = res->ai_next) {   
        char hostname[NI_MAXHOST];
        error = getnameinfo(res->ai_addr, res->ai_addrlen, hostname, NI_MAXHOST, NULL, 0, 0); 
        if (error != 0) {
            fprintf(stderr, "error in getnameinfo: %s\n", gai_strerror(error));
            continue;
        }
        if (*hostname != '\0')
            printf("hostname: %s\n", hostname);
    }   

    freeaddrinfo(result);
    return 0;
}

C-ares

This is c-ares, an asynchronous resolver library. It is intended for applications which need to perform DNS queries without blocking, or need to perform multiple DNS queries in parallel.

c-ares is a C library for asynchronous DNS requests (including name resolves). C89 compatibility, MIT licensed, builds for and runs on POSIX, Windows, Netware, Android and many more operating systems.

CMake builds

Current releases of c-ares introduce a CMake v3+ build system that has been tested on most platforms including Windows, Linux, FreeBSD, MacOS, AIX and Solaris.

In the most basic form, building with CMake might look like:

cd ~/tools/c-ares
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/tools/c-ares/c-ares-1.18.1-install ../c-ares-1.18.1
make
make install

Options to CMake are passed on the command line using “-D${OPTION}=${VALUE}”. The values defined are all boolean and take values like On, Off, True, False.

  • CARES_STATIC - Build the static library (off by default)
  • CARES_SHARED - Build the shared library (on by default)
  • CARES_INSTALL - Hook in installation, useful to disable if chain building
  • CARES_STATIC_PIC - Build the static library as position-independent (off by default)

Example

ares_gethostbyaddr

#include <time.h>
#include <iostream>
#include <netdb.h>
#include <arpa/inet.h>
#include <ares.h>

void dns_callback (void* arg, int status, int timeouts, struct hostent* host)
{
        if (status == ARES_SUCCESS)
            std::cout << host->h_name << "\n";
        else
            std::cout << "lookup failed: " << status << '\n';
}
void main_loop(ares_channel &channel)
{
        int nfds, count;
        fd_set readers, writers;
        timeval tv, *tvp;
        while (1) {
                FD_ZERO(&readers);
                FD_ZERO(&writers);
                nfds = ares_fds(channel, &readers, &writers);
                if (nfds == 0) {
                    break;
                
                tvp = ares_timeout(channel, NULL, &tv);
                count = select(nfds, &readers, &writers, NULL, tvp);
                ares_process(channel, &readers, &writers);
        }

}
int main(int argc, char **argv)
{
        struct in_addr ip;
        int res;
        if(argc < 2 ) {
                std::cout << "usage: " << argv[0] << " ip.address\n";
                return 1;
        }
        inet_aton(argv[1], &ip);
        ares_channel channel;
        if((res = ares_init(&channel)) != ARES_SUCCESS) {
                std::cout << "ares feiled: " << res << '\n';
                return 1;
        }
        ares_gethostbyaddr(channel, &ip, sizeof ip, AF_INET, dns_callback, NULL);
        main_loop(channel);
        return 0;
}

g++ demo.cc -I deps/c-ares-1.18.1/include deps/c-ares-1.18.1/lib/libcares.a

$./a.out 8.8.8.8
dns.google

ares_gethostbyname

#include <time.h>
#include <iostream>
#include <netdb.h>
#include <arpa/inet.h>
#include <ares.h>
#include <cstdio>

void dns_callback (void* arg, int status, int timeouts, struct hostent* host)
{
        if (status == ARES_SUCCESS) {
            puts(host->h_name);
            printf("%u.%u.%u.%u\n", host->h_addr_list[0][0],
                            host->h_addr_list[0][1],
                            host->h_addr_list[0][2],
                            host->h_addr_list[0][3]);
        }
        else
            std::cout << "lookup failed: " << status << '\n';
}

void main_loop(ares_channel &channel)
{
        int nfds, count;
        fd_set readers, writers;
        timeval tv, *tvp;
        while (1) {
                FD_ZERO(&readers);
                FD_ZERO(&writers);
                nfds = ares_fds(channel, &readers, &writers);
                if (nfds == 0) {
                    break;
                
                tvp = ares_timeout(channel, NULL, &tv);
                count = select(nfds, &readers, &writers, NULL, tvp);
                ares_process(channel, &readers, &writers);
        }

}

int main(int argc, char **argv)
{
        int res;
        if(argc < 2 ) {
                std::cout << "usage: " << argv[0] << " domain_name\n";
                return 1;
        }

        ares_channel channel;
        if((res = ares_init(&channel)) != ARES_SUCCESS) {
                std::cout << "ares feiled: " << res << '\n';
                return 1;
        }
        ares_gethostbyname(channel, argv[1], AF_INET, dns_callback, NULL);
        main_loop(channel);
        return 0;
}

g++ ares_gethostbyname.cc -o ares_gethostbyname -I deps/c-ares-1.18.1/include deps/c-ares-1.18.1/lib/libcares.a

$./ares_gethostbyname google.cn
google.cn
58.63.4294967273.98
$./ares_gethostbyname baidu.com
baidu.com
4294967260.4294967221.38.4294967291
s$./ares_gethostbyname gerryyang.com
gerryyang.com
119.28.41.102

ares_process_fd + poll

#include <sys/time.h>
#include <sys/poll.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <ares.h>
#include <pthread.h>
#include <deque>
#include <netdb.h>
#include <string>
#include <cstring>
#include <cstdio>

class dns_resolver_t
{
public:
    dns_resolver_t() : channel_(NULL)
    {
        int ret = ares_init(&channel_);
        if (ret != ARES_SUCCESS)
        {
            err_info_ = ares_strerror(ret);
        }
    }

    ~dns_resolver_t()
    {
        if (channel_)
        {
            ares_destroy(channel_);
        }
    }

    int resolve(int af, const std::string& domain, int& timeout/*ms*/, void* addr, size_t addr_len)
    {
        dns_res_t res = {NULL, addr, addr_len};
        ares_gethostbyname(channel_, domain.c_str(), af, dns_callback, &res);

        struct timeval last, now;
        gettimeofday(&last, NULL);
        int nfds = 1;
        while (nfds)
        {
            struct timeval *tvp, tv, store = {timeout / 1000, (timeout % 1000) * 1000};

            // return maximum time to wait
            tvp = ares_timeout(channel_, &store, &tv);
            int timeout_ms = tvp->tv_sec * 1000 + tvp->tv_usec / 1000;

            printf("timeout_ms(%d)\n", timeout_ms);
            nfds = dns_wait_resolve(channel_, timeout_ms);
            printf("dns_wait_resolve nfds(%d)\n", nfds);

            gettimeofday(&now, NULL);
            timeout -= (now.tv_sec - last.tv_sec) * 1000 + (now.tv_usec - last.tv_usec) / 1000;
            last = now;
        }

        if (res.error_info)
        {
            err_info_ = res.error_info;
            printf("resolve err(%s)\n", res.error_info);
            return -1;
        }
        return 0;
    }

    operator bool() const
    {
        return channel_;
    }

    const std::string& error_info() const
    {
        return err_info_;
    }

private:
    dns_resolver_t(const dns_resolver_t&);
    dns_resolver_t& operator=(const dns_resolver_t&);

    struct dns_res_t
    {
        const char* error_info;
        void* address;
        size_t len;
    };

    static void dns_callback(void* arg, int status, int timeouts, struct hostent* hptr)
    {
        // TODO: get the first address

        printf("dns_callback status(%d) timeouts(%d)\n", status, timeouts);

        dns_res_t& res = *(dns_res_t*)arg;
        if (status != ARES_SUCCESS)
        {
            res.error_info = ares_strerror(status);
            printf("dns_callback err(%s)\n", res.error_info);
            return;
        }

        if (AF_INET == hptr->h_addrtype)
        {
            char** pptr = hptr->h_addr_list;
            if (*pptr)
            {
                memcpy(res.address, *pptr, res.len);
                return;
            }

            res.error_info = "no invalid address get";
            printf("dns_callback err(%s)\n", res.error_info);
        }
        else
        {
            res.error_info = "addrtype not supported";
            printf("addrtype(%d) not supported\n", hptr->h_addrtype);
        }
    }

    static int dns_wait_resolve(ares_channel channel_, int timeout_ms)
    {
        if (timeout_ms < 0)
        {
            ares_process_fd(channel_, ARES_SOCKET_BAD, ARES_SOCKET_BAD);

            // TODO: 这里若不执行 ares_cancel,channel_ 析构会出现 coredump
            ares_cancel(channel_);
            return 0;
        }
        int nfds;
        int bitmask;
        ares_socket_t socks[ARES_GETSOCK_MAXNUM];
        struct pollfd pfd[ARES_GETSOCK_MAXNUM];
        int i;
        int num = 0;

        bitmask = ares_getsock(channel_, socks, ARES_GETSOCK_MAXNUM);

        for (i = 0; i < ARES_GETSOCK_MAXNUM; i++)
        {
            pfd[i].events = 0;
            pfd[i].revents = 0;
            if (ARES_GETSOCK_READABLE(bitmask, i))
            {
                pfd[i].fd = socks[i];
                pfd[i].events |= POLLRDNORM | POLLIN;
            }
            if (ARES_GETSOCK_WRITABLE(bitmask, i))
            {
                pfd[i].fd = socks[i];
                pfd[i].events |= POLLWRNORM | POLLOUT;
            }
            if (pfd[i].events != 0)
            {
                num++;
            }
            else
            {
                break;
            }
        }

        if (num)
        {
            nfds = poll(pfd, num, timeout_ms/*milliseconds */);
        }
        else
        {
            nfds = 0;
        }

        if (!nfds)
        {
            ares_process_fd(channel_, ARES_SOCKET_BAD, ARES_SOCKET_BAD);

            // TODO: 这里若不执行 ares_cancel,在超时错误时,channel_ 析构会出现 coredump
            ares_cancel(channel_);
        }
        else
        {
            for (i = 0; i < num; i++)
            {
                ares_process_fd(channel_,
                                (pfd[i].revents & (POLLRDNORM | POLLIN)) ? pfd[i].fd : ARES_SOCKET_BAD,
                                (pfd[i].revents & (POLLWRNORM | POLLOUT)) ? pfd[i].fd : ARES_SOCKET_BAD);
            }
        }
        return nfds;
    }

    ares_channel channel_;
    std::string err_info_;
};

int main()
{
    struct sockaddr_in sa = {};
    std::string domain = "gerryyang.com";
    int timeout_ms = 1000;
    dns_resolver_t dr;
    if (dr)
    {
        int ret = dr.resolve(AF_INET, domain, timeout_ms, &sa.sin_addr.s_addr, sizeof(sa.sin_addr.s_addr));
        if (0 != ret)
        {
            printf("dr.resolve ret(%d) err(%s)\n", ret, dr.error_info().c_str());
            return;
        }
        char strIP[INET_ADDRSTRLEN] = {0};
        inet_ntop(AF_INET, &(sa.sin_addr), strIP, INET_ADDRSTRLEN);
        printf("%s\n", strIP);
    }
    else
    {
        printf("dns_resolver_t init err(%s)\n", dr.error_info().c_str());
        return;
    }
}

g++ ares_gethostbyname2.cc -o ares_gethostbyname2 -I deps/c-ares-1.18.1/include deps/c-ares-1.18.1/lib/libcares.a

$./ares_gethostbyname2
119.28.41.102

Interface

More: c-ares documentation

ares_init

#include <ares.h>
 
int ares_init(ares_channel *channelptr)

The ares_init function initializes a communications channel for name service lookups. If it returns successfully, ares_init will set the variable pointed to by channelptr to a handle used to identify the name service channel. The caller should invoke ares_destroy on the handle when the channel is no longer needed.

The ares_init_options function is provide to offer more init alternatives.

NOTES

When initializing from /etc/resolv.conf, ares_init(3) reads the domain and search directives to allow lookups of short names relative to the domains specified. The domain and search directives override one another. If more that one instance of either domain or search directives is specified, the last occurrence wins. For more information, please see the resolv.conf(5) manual page.

  • https://c-ares.org/ares_init.html

ares_gethostbyaddr

#include <ares.h>

typedef void (*ares_host_callback)(void *arg, int status, int timeouts, struct hostent *hostent)
void ares_gethostbyaddr(ares_channel channel, const void *addr, int addrlen, int family, ares_host_callback callback, void *arg)

The ares_gethostbyaddr function initiates a host query by address on the name service channel identified by channel. The parameters addr and addrlen give the address as a series of bytes, and family gives the type of address. When the query is complete or has failed, the ares library will invoke callback. Completion or failure of the query may happen immediately, or may happen during a later call to ares_process(3), ares_destroy(3) or ares_cancel(3).

The callback argument arg is copied from the ares_gethostbyaddr argument arg. The callback argument status indicates whether the query succeeded and, if not, how it failed.

The callback argument timeouts reports how many times a query timed out during the execution of the given request.

On successful completion of the query, the callback argument hostent points to a struct hostent containing the name of the host returned by the query. The callback need not and should not attempt to free the memory pointed to by hostent; the ares library will free it when the callback returns. If the query did not complete successfully, hostent will be NULL.

  • https://linux.die.net/man/3/ares_gethostbyaddr

ares_gethostbyname

#include <ares.h>

typedef void (*ares_host_callback)(void *arg, int status, int timeouts, struct hostent *hostent)
void ares_gethostbyname(ares_channel channel, const char *name, int family, ares_host_callback callback, void *arg)

The ares_gethostbyname function initiates a host query by name on the name service channel identified by channel. The parameter name gives the hostname as a NUL-terminated C string, and family gives the desired type of address for the resulting host entry. When the query is complete or has failed, the ares library will invoke callback. Completion or failure of the query may happen immediately, or may happen during a later call to ares_process(3), ares_destroy(3) or ares_cancel(3).

The callback argument arg is copied from the ares_gethostbyname argument arg. The callback argument status indicates whether the query succeeded and, if not, how it failed.

The callback argument timeouts reports how many times a query timed out during the execution of the given request.

On successful completion of the query, the callback argument hostent points to a struct hostent containing the name of the host returned by the query. The callback need not and should not attempt to free the memory pointed to by hostent; the ares library will free it when the callback returns. If the query did not complete successfully, hostent will be NULL.

  • https://linux.die.net/man/3/ares_gethostbyname

ares_process

#include <ares.h>

void ares_process(ares_channel channel, fd_set *read_fds, fd_set *write_fds)
void ares_process_fd(ares_channel channel, ares_socket_t read_fd, ares_socket_t write_fd)

The ares_process(3) function handles input/output events and timeouts associated with queries pending on the name service channel identified by channel. The file descriptor sets pointed to by read_fds and write_fds should have file descriptors set in them according to whether the file descriptors specified by ares_fds(3) are ready for reading and writing. (The easiest way to determine this information is to invoke select with a timeout no greater than the timeout given by ares_timeout(3) ).

The ares_process function will invoke callbacks for pending queries if they complete successfully or fail.

ares_process_fd(3) works the same way but acts and operates only on the specific file descriptors (sockets) you pass in to the function. Use ARES_SOCKET_BAD for “no action”. This function is of course provided to allow users of c-ares to void select() in their applications and within c-ares.

The following code fragment waits for all pending queries on a channel to complete:

int nfds, count;
fd_set readers, writers;
struct timeval tv, *tvp;

while (1)
{
    FD_ZERO(&readers);
    FD_ZERO(&writers);

    nfds = ares_fds(channel, &readers, &writers);
    if (nfds == 0)
    {
        break;
    }
        
    tvp = ares_timeout(channel, NULL, &tv);
    count = select(nfds, &readers, &writers, NULL, tvp);
    ares_process(channel, &readers, &writers);
}
  • https://linux.die.net/man/3/ares_process

ares_fds

#include <ares.h>
int ares_fds(ares_channel channel, fd_set *read_fds, fd_set *write_fds)

The ares_fds function retrieves the set of file descriptors which the calling application should select on for reading and writing for the processing of name service queries pending on the name service channel identified by channel. File descriptors will be set in the file descriptor sets pointed to by read_fds and write_fds as appropriate. File descriptors already set in read_fds and write_fds will remain set; initialization of the file descriptor sets (using FD_ZERO) is the responsibility of the caller.

ares_fds returns one greater than the number of the highest socket set in either read_fds or write_fds. If no queries are active, ares_fds will return 0.

  • https://linux.die.net/man/3/ares_fds

ares_timeout

#include <ares.h>

struct timeval *ares_timeout(ares_channel channel, struct timeval *maxtv, struct timeval *tv)

The ares_timeout function determines the maximum time for which the caller should wait before invoking ares_process(3) to process timeouts. The parameter maxtv specifies a existing maximum timeout, or NULL if the caller does not wish to apply a maximum timeout. The parameter tv must point to a writable buffer of type struct timeval. It is valid for maxtv and tv to have the same value.

If no queries have timeouts pending sooner than the given maximum timeout, ares_timeout returns the value of maxtv; otherwise ares_timeout stores the appropriate timeout value into the buffer pointed to by tv and returns the value of tv.

  • https://linux.die.net/man/3/ares_timeout

Performance compare

Result

编译:g++ performance_compare.cc -o performance_compare -I deps/c-ares-1.18.1/include deps/c-ares-1.18.1/lib/libcares.a -std=c++11

Loop: 10000

Resolve Time (ns) Speedup
getaddrinfo 10606284252 1x
c-ares 9253562550 1.145x

Loop: 100000

Resolve Time (ns) Speedup
getaddrinfo 105582573144 1x
c-ares 93443656627 1.13x

加上 -O2 编译优化:

Loop: 10000

Resolve Time (ns) Speedup
getaddrinfo 10878913658 1x
c-ares 9644415228 1.128x

Loop: 100000

Resolve Time (ns) Speedup
getaddrinfo 105851071727 1x
c-ares 93014894039 1.138x

c-ares 和文件相关的系统调用

$strace -s1024 -tt -e trace=file ./performance_compare
11:28:34.980050 execve("./performance_compare", ["./performance_compare"], [/* 788 vars */]) = 0
11:28:34.981232 access("/etc/ld.so.preload", R_OK) = 0
11:28:34.981347 open("/etc/ld.so.preload", O_RDONLY|O_CLOEXEC) = 3
11:28:34.981678 readlink("/proc/self/exe", "/data/home/gerryyang/github/mac-utils/programing/cpp/http/c-ares/performance_compare", 4096) = 84
11:28:34.981808 open("/lib64/libonion.so", O_RDONLY|O_CLOEXEC) = 3
11:28:34.982477 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
11:28:34.982810 open("/lib64/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3
11:28:34.983499 open("/lib64/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
11:28:34.984061 open("/lib64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
11:28:34.984603 open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
11:28:34.985269 open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
11:28:34.987771 open("/etc/resolv.conf", O_RDONLY) = 3
11:28:34.988396 open("/etc/nsswitch.conf", O_RDONLY) = 3
11:28:34.988933 open("/dev/urandom", O_RDONLY) = 3
11:28:34.989195 open("/etc/hosts", O_RDONLY) = 3
resolve1 : 4248534 ns
11:28:34.992087 +++ exited with 0 +++

c-ares 和网络相关的调用情况

$strace -s1024 -tt -e trace=network  ./performance_compare 
11:24:19.110979 socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
11:24:19.111332 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, 16) = 0
11:24:19.111470 sendto(3, "s\224\1\0\0\1\0\0\0\0\0\0\tgerryyang\3com\7default\3svc\7cluster\5local\0\0\1\0\1", 57, MSG_NOSIGNAL, NULL, 0) = 57
11:24:19.111864 recvfrom(3, "s\224\205\3\0\1\0\0\0\1\0\0\tgerryyang\3com\7default\3svc\7cluster\5local\0\0\1\0\1\7cluster\5local\0\0\6\0\1\0\0\0\21\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0b}\3177\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 4097, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, [16]) = 150
11:24:19.111962 sendto(3, "0h\1\0\0\1\0\0\0\0\0\0\tgerryyang\3com\3svc\7cluster\5local\0\0\1\0\1", 49, MSG_NOSIGNAL, NULL, 0) = 49
11:24:19.112057 recvfrom(3, 0x7ffe20c14bb0, 4097, 0, 0x7ffe20c14b90, 0x7ffe20c14b8c) = -1 EAGAIN (Resource temporarily unavailable)
11:24:19.112196 recvfrom(3, "0h\205\3\0\1\0\0\0\1\0\0\tgerryyang\3com\3svc\7cluster\5local\0\0\1\0\1\7cluster\5local\0\0\6\0\1\0\0\0\21\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0b}\3177\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 4097, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, [16]) = 142
11:24:19.112274 sendto(3, ">\277\1\0\0\1\0\0\0\0\0\0\tgerryyang\3com\7cluster\5local\0\0\1\0\1", 45, MSG_NOSIGNAL, NULL, 0) = 45
11:24:19.112356 recvfrom(3, 0x7ffe20c14bb0, 4097, 0, 0x7ffe20c14b90, 0x7ffe20c14b8c) = -1 EAGAIN (Resource temporarily unavailable)
11:24:19.112498 recvfrom(3, ">\277\205\3\0\1\0\0\0\1\0\0\tgerryyang\3com\7cluster\5local\0\0\1\0\1\7cluster\5local\0\0\6\0\1\0\0\0\21\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0b}\3177\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 4097, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, [16]) = 138
11:24:19.112577 sendto(3, "9\213\1\0\0\1\0\0\0\0\0\0\tgerryyang\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
11:24:19.112668 recvfrom(3, 0x7ffe20c14bb0, 4097, 0, 0x7ffe20c14b90, 0x7ffe20c14b8c) = -1 EAGAIN (Resource temporarily unavailable)
11:24:19.112811 recvfrom(3, "9\213\205\200\0\1\0\1\0\0\0\0\tgerryyang\3com\0\0\1\0\1\tgerryyang\3com\0\0\1\0\1\0\0\0\22\0\4w\34)f", 4097, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, [16]) = 60
11:24:19.112894 socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) = 4
11:24:19.112966 connect(4, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("119.28.41.102")}, 16) = 0
11:24:19.113039 getsockname(4, {sa_family=AF_INET, sin_port=htons(59710), sin_addr=inet_addr("9.135.18.186")}, [16]) = 0
resolve1 : 3858319 ns
11:24:19.113629 +++ exited with 0 +++

getaddrinfo 和文件相关的系统调用

$strace -s1024 -tt -e trace=file ./performance_compare
11:30:30.549208 execve("./performance_compare", ["./performance_compare"], [/* 788 vars */]) = 0
11:30:30.550343 access("/etc/ld.so.preload", R_OK) = 0
11:30:30.550469 open("/etc/ld.so.preload", O_RDONLY|O_CLOEXEC) = 3
11:30:30.550707 readlink("/proc/self/exe", "/data/home/gerryyang/github/mac-utils/programing/cpp/http/c-ares/performance_compare", 4096) = 84
11:30:30.550800 open("/lib64/libonion.so", O_RDONLY|O_CLOEXEC) = 3
11:30:30.551300 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
11:30:30.551562 open("/lib64/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3
11:30:30.552006 open("/lib64/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
11:30:30.552352 open("/lib64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
11:30:30.552682 open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
11:30:30.553106 open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
11:30:30.555178 stat("/etc/resolv.conf", {st_mode=S_IFREG|0644, st_size=105, ...}) = 0
11:30:30.555292 open("/etc/host.conf", O_RDONLY|O_CLOEXEC) = 3
11:30:30.555623 open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 3
11:30:30.556216 open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
11:30:30.556563 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
11:30:30.556770 open("/lib64/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3
11:30:30.557258 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
11:30:30.557588 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
11:30:30.557798 open("/lib64/libnss_dns.so.2", O_RDONLY|O_CLOEXEC) = 3
11:30:30.558124 open("/lib64/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 3
resolve2 : 13396723 ns
11:30:30.568509 +++ exited with 0 +++

getaddrinfo 和网络相关的调用情况

$strace -s1024 -tt -e trace=network  ./performance_compare 
11:32:46.239270 socket(PF_NETLINK, SOCK_RAW, 0) = 3
11:32:46.239459 bind(3, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 0
11:32:46.239733 getsockname(3, {sa_family=AF_NETLINK, pid=3627407, groups=00000000}, [12]) = 0
11:32:46.239809 sendto(3, "\24\0\0\0\26\0\1\3^\321}b\0\0\0\0\0\0\0\0", 20, 0, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 20
11:32:46.239899 recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"D\0\0\0\24\0\2\0^\321}b\217Y7\0\2\10\200\376\1\0\0\0\10\0\1\0\177\0\0\1\10\0\2\0\177\0\0\1\7\0\3\0lo\0\0\24\0\6\0\377\377\377\377\377\377\377\377\34\335\371@\34\335\371@P\0\0\0\24\0\2\0^\321}b\217Y7\0\2 \200\0\3\0\0\0\10\0\1\0\t\207\22\272\10\0\2\0\t\207\22\272\10\0\4\0\t\207\22\272\t\0\3\0eth0\0\0\0\0\24\0\6\0\377\377\377\377\377\377\377\377<\335\371@<\335\371@", 4096}], msg_controllen=0, msg_flags=0}, 0) = 148
11:32:46.239986 recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"\24\0\0\0\3\0\2\0^\321}b\217Y7\0\0\0\0\0", 4096}], msg_controllen=0, msg_flags=0}, 0) = 20
11:32:46.241100 socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
11:32:46.241204 connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
11:32:46.241355 socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
11:32:46.241426 connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
11:32:46.244024 socket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
11:32:46.244110 setsockopt(3, SOL_IP, IP_RECVERR, [1], 4) = 0
11:32:46.244187 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, 16) = 0
11:32:46.244315 sendto(3, "\356\361\1\0\0\1\0\0\0\0\0\0\tgerryyang\3com\7default\3svc\7cluster\5local\0\0\1\0\1", 57, MSG_NOSIGNAL, NULL, 0) = 57
11:32:46.244792 recvfrom(3, "\356\361\205\3\0\1\0\0\0\1\0\0\tgerryyang\3com\7default\3svc\7cluster\5local\0\0\1\0\1\7cluster\5local\0\0\6\0\1\0\0\0\32\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0b}\321>\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, [16]) = 150
11:32:46.244974 socket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
11:32:46.245053 setsockopt(3, SOL_IP, IP_RECVERR, [1], 4) = 0
11:32:46.245130 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, 16) = 0
11:32:46.245262 sendto(3, "\242P\1\0\0\1\0\0\0\0\0\0\tgerryyang\3com\3svc\7cluster\5local\0\0\1\0\1", 49, MSG_NOSIGNAL, NULL, 0) = 49
11:32:46.245717 recvfrom(3, "\242P\205\3\0\1\0\0\0\1\0\0\tgerryyang\3com\3svc\7cluster\5local\0\0\1\0\1\7cluster\5local\0\0\6\0\1\0\0\0\32\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0b}\321>\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, [16]) = 142
11:32:46.245921 socket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
11:32:46.246015 setsockopt(3, SOL_IP, IP_RECVERR, [1], 4) = 0
11:32:46.246111 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, 16) = 0
11:32:46.246284 sendto(3, "\262$\1\0\0\1\0\0\0\0\0\0\tgerryyang\3com\7cluster\5local\0\0\1\0\1", 45, MSG_NOSIGNAL, NULL, 0) = 45
11:32:46.246819 recvfrom(3, "\262$\205\3\0\1\0\0\0\1\0\0\tgerryyang\3com\7cluster\5local\0\0\1\0\1\7cluster\5local\0\0\6\0\1\0\0\0\32\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0b}\321>\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, [16]) = 138
11:32:46.247011 socket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
11:32:46.247120 setsockopt(3, SOL_IP, IP_RECVERR, [1], 4) = 0
11:32:46.247255 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, 16) = 0
11:32:46.247386 sendto(3, "n\225\1\0\0\1\0\0\0\0\0\0\tgerryyang\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
11:32:46.247832 recvfrom(3, "n\225\205\200\0\1\0\1\0\0\0\0\tgerryyang\3com\0\0\1\0\1\tgerryyang\3com\0\0\1\0\1\0\0\0\32\0\4w\34)f", 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("9.166.31.254")}, [16]) = 60
resolve2 : 8803496 ns
11:32:46.248537 +++ exited with 0 +++

测试代码

#include <sys/time.h>
#include <sys/poll.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <ares.h>
#include <pthread.h>
#include <deque>
#include <netdb.h>
#include <string>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <chrono>

const int MAXCNT = 10000;

class ScopedTimer 
{
	public:
		ScopedTimer(const char* name): m_name(name), m_beg(std::chrono::high_resolution_clock::now()) { }
		~ScopedTimer() {
			auto end = std::chrono::high_resolution_clock::now();
			auto dur = std::chrono::duration_cast<std::chrono::nanoseconds>(end - m_beg);
			std::cout << m_name << " : " << dur.count() << " ns\n";
		}
	private:
		const char* m_name;
		std::chrono::time_point<std::chrono::high_resolution_clock> m_beg;
};

class dns_resolver_t
{
public:
    dns_resolver_t() : channel_(NULL)
    {
        int ret = ares_init(&channel_);
        if (ret != ARES_SUCCESS)
        {
            err_info_ = ares_strerror(ret);
        }
    }

    ~dns_resolver_t()
    {
        if (channel_)
        {
            ares_destroy(channel_);
        }
    }

    int resolve(int af, const std::string& domain, int& timeout, void* addr, size_t addr_len)
    {
        dns_res_t res = {NULL, addr, addr_len};
        ares_gethostbyname(channel_, domain.c_str(), af, dns_callback, &res);

        struct timeval last, now;
        gettimeofday(&last, NULL);
        int nfds = 1;
        while (nfds)
        {
            struct timeval *tvp, tv, store = {timeout / 1000, (timeout % 1000) * 1000};
            tvp = ares_timeout(channel_, &store, &tv);
            int timeout_ms = tvp->tv_sec * 1000 + tvp->tv_usec / 1000;

            nfds = dns_wait_resolve(channel_, timeout_ms);
            gettimeofday(&now, NULL);
            timeout -= (now.tv_sec - last.tv_sec) * 1000 + (now.tv_usec - last.tv_usec) / 1000;
            last = now;
        }

        if (res.error_info)
        {
            err_info_ = res.error_info;
            return -1;
        }
        return 0;
    }

    operator bool() const
    {
        return channel_;
    }

    const std::string& error_info() const
    {
        return err_info_;
    }

private:
    dns_resolver_t(const dns_resolver_t&);
    dns_resolver_t& operator=(const dns_resolver_t&);

    struct dns_res_t
    {
        const char* error_info;
        void* address;
        size_t len;
    };

    static void dns_callback(void* arg, int status, int timeouts, struct hostent* hptr)
    {
        //printf("dns_callback status(%d) timeouts(%d)\n", status, timeouts);

        dns_res_t& res = *(dns_res_t*)arg;
        if (status != ARES_SUCCESS)
        {
            res.error_info = ares_strerror(status);
            printf("dns_callback err(%s)\n", res.error_info);
            return;
        }

        if (AF_INET == hptr->h_addrtype)
        {
            char** pptr = hptr->h_addr_list;
            if (*pptr)
            {
                memcpy(res.address, *pptr, res.len);
                return;
            }

            res.error_info = "no invalid address get";
            printf("dns_callback err(%s)\n", res.error_info);
        }
        else
        {
            res.error_info = "addrtype not supported";
            printf("addrtype(%d) not supported\n", hptr->h_addrtype);
        }
    }

    static int dns_wait_resolve(ares_channel channel_, int timeout_ms)
    {
        if (timeout_ms < 0)
        {
            ares_process_fd(channel_, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
            ares_cancel(channel_);
            return 0;
        }
        int nfds;
        int bitmask;
        ares_socket_t socks[ARES_GETSOCK_MAXNUM];
        struct pollfd pfd[ARES_GETSOCK_MAXNUM];
        int i;
        int num = 0;

        bitmask = ares_getsock(channel_, socks, ARES_GETSOCK_MAXNUM);

        for (i = 0; i < ARES_GETSOCK_MAXNUM; i++)
        {
            pfd[i].events = 0;
            pfd[i].revents = 0;
            if (ARES_GETSOCK_READABLE(bitmask, i))
            {
                pfd[i].fd = socks[i];
                pfd[i].events |= POLLRDNORM | POLLIN;
            }
            if (ARES_GETSOCK_WRITABLE(bitmask, i))
            {
                pfd[i].fd = socks[i];
                pfd[i].events |= POLLWRNORM | POLLOUT;
            }
            if (pfd[i].events != 0)
            {
                num++;
            }
            else
            {
                break;
            }
        }

        if (num)
        {
            nfds = poll(pfd, num, timeout_ms);
        }
        else
        {
            nfds = 0;
        }

        if (!nfds)
        {
            ares_process_fd(channel_, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
            ares_cancel(channel_);
        }
        else
        {
            for (i = 0; i < num; i++)
            {
                ares_process_fd(channel_,
                                (pfd[i].revents & (POLLRDNORM | POLLIN)) ? pfd[i].fd : ARES_SOCKET_BAD,
                                (pfd[i].revents & (POLLWRNORM | POLLOUT)) ? pfd[i].fd : ARES_SOCKET_BAD);
            }
        }
        return nfds;
    }

    ares_channel channel_;
    std::string err_info_;
};

void resolve1()
{
    ScopedTimer timer("resolve1");

    char strIP[INET_ADDRSTRLEN] = {0};
    for (auto i = 0; i < MAXCNT; ++i)
    {
        struct sockaddr_in sa = {};
        std::string domain = "gerryyang.com";
        int timeout_ms = 1000;
        dns_resolver_t dr;
        if (dr)
        {
            dr.resolve(AF_INET, domain, timeout_ms, &sa.sin_addr.s_addr, sizeof(sa.sin_addr.s_addr));
            if (0 != ret)
            {
                printf("resolve ret(%d) err(%s)\n", ret, dr.error_info().c_str());
                return;
            }
            inet_ntop(AF_INET, &(sa.sin_addr), strIP, INET_ADDRSTRLEN);
            //printf("%s\n", strIP);
        }
        else
        {
            printf("dns_resolver_t init err(%s)\n", dr.error_info().c_str());
            return;
        }
    }
}

void resolve2()
{
    ScopedTimer timer("resolve2");

    char strIP[INET_ADDRSTRLEN] = {0};
    for (auto i = 0; i < MAXCNT; ++i)
    {
        struct addrinfo* addr;
        int result = getaddrinfo("gerryyang.com", NULL, NULL, &addr);
        if (result != 0)
        {
            printf("Error from getaddrinfo: %s\n", gai_strerror(result));
            return;
        }
        struct sockaddr_in* psa = (struct sockaddr_in*)addr->ai_addr;
        inet_ntop(AF_INET, &(psa->sin_addr), strIP, INET_ADDRSTRLEN);
        //printf("%s\n", strIP);

        freeaddrinfo(addr);
    }
}

int main()
{
    resolve1();
    resolve2();
}

Refer