关于getaddrinfo()函数阻塞时间过长的问题

 1. getaddrinfo()函数阻塞时间过长

        getaddrinfo()函数是一个用于网络编程的系统调用函数,主要功能是将主机名或服务名解析为一组网络地址。在使用该函数时,如果网络环境较差或者DNS服务器响应缓慢,可能会出现阻塞时间过长的情况。

        为了解决getaddrinfo()阻塞时间过长的问题,可以采取以下措施:

1. 设置超时时间:在调用 getaddrinfo() 函数时可以设置一个超时时间,如果在指定时间内没有得到结果,就会返回错误。可以使用 `c-ares` 库实现异步DNS查询,从而更好的控制超时时间。

2. 多线程异步查询:通过使用多线程异步方式查询 DNS 服务器,可以避免一个线程被阻塞而影响整个应用程序的性能。可以使用 `libcurl` 或其他支持异步DNS查询的库来实现多线程异步查询。

3. DNS缓存:如果你的应用程序需要频繁的调用getaddrinfo() 函数,可以考虑使用一个 DNS 缓存来避免重复的网络访问和查询,从而提高应用程序的性能。

        总之,避免在主线程中直接调用 getaddrinfo() 函数,而是通过异步方式或者多线程方式进行查询,从而避免阻塞主程序。

2. select()函数对getaddrinfo()函数进行封装

        使用select()函数对getaddrinfo()函数进行封装,可以在等待DNS服务器响应时设置超时时间,从而避免阻塞。具体实现方法如下:```
 

#include 
#include 
#include 
#include 

int getaddrinfo_with_timeout(const char *node, const char *service,
                             const struct addrinfo *hints,
                             struct addrinfo **res, int timeout_sec)
{
    fd_set fds;
    int sockfd, n;
    struct timeval tv;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket error");
        return -1;
    }

    // 设置套接字为非阻塞模式
    int flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

    // 调用getaddrinfo()函数
    int ret = getaddrinfo(node, service, hints, res);
    if (ret != 0) {
        close(sockfd);
        return -1;
    }

    // 等待DNS服务器响应
    FD_ZERO(&fds);
    FD_SET(sockfd, &fds);
    tv.tv_sec = timeout_sec;
    tv.tv_usec = 0;
    n = select(sockfd + 1, NULL, &fds, NULL, &tv);
    if (n == 0) {  // 超时
        close(sockfd);
        freeaddrinfo(*res);
        return -1;
    } else if (n < 0) {  // 出错
        close(sockfd);
        freeaddrinfo(*res);
        return -1;
    }

    // 恢复套接字阻塞模式
    fcntl(sockfd, F_SETFL, flags);

    return sockfd;
}

        上述代码中,getaddrinfo_with_timeout()函数调用了select()函数来等待DNS服务器响应。如果超时,则关闭套接字和释放addrinfo结构体;如果成功,则返回套接字文件描述符。

        需要注意的是,在使用该函数时,必须在完成DNS解析后调用freeaddrinfo()函数来释放addrinfo结构体。此外,为了规避select()函数出现“文件描述符过多”的问题,可以采用epoll()等高级I/O模型进行优化。

3. `c-ares` 库实现异步DNS查询

        要使用 `c-ares` 库实现异步DNS查询,你需要进行以下步骤:

1. 安装 `c-ares` 库:可以通过包管理器或者源码的方式安装 `c-ares` 库。

2. 初始化 `c-ares` 库:在使用 `c-ares` 库前,需要通过 `ares_library_init()` 函数来初始化 `c-ares` 库。

3. 创建查询请求:可以使用 `ares_gethostbyname()` 函数异步地发送 DNS 查询请求。

4. 处理查询结果:在 DNS 查询结果返回后,`c-ares` 库会调用回调函数来处理结果。可以通过 `ares_gethostbyname()` 函数的最后一个参数来指定回调函数。

        下面是使用 `c-ares` 库实现异步DNS查询的代码示例:

#include 
#include 
#include 
#include 

static void callback(void *arg, int status, int timeouts, struct hostent *host) {
    if (status == ARES_SUCCESS) {
        printf("DNS lookup succeeded:\n");
        printf("name: %s\n", host->h_name);
        printf("ip: %s\n", inet_ntoa(*(struct in_addr *)host->h_addr_list[0]));
    } else {
        printf("DNS lookup failed with error code: %d\n", status);
    }
}

int main(int argc, char **argv) {
    ares_channel channel;
    int status;
    struct ares_options options;
    memset(&options, 0, sizeof(options));
    options.timeout = 2000; // 设置超时时间为 2 秒
    options.tries = 2; // 设置最大重试次数为 2

    status = ares_library_init(ARES_LIB_INIT_ALL);
    if (status != ARES_SUCCESS) {
        printf("ares_library_init() failed: %s\n", ares_strerror(status));
        return EXIT_FAILURE;
    }

    status = ares_init_options(&channel, &options, ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES);
    if (status != ARES_SUCCESS) {
        printf("ares_init_options() failed: %s\n", ares_strerror(status));
        return EXIT_FAILURE;
    }

    ares_gethostbyname(channel, "example.com", AF_INET, callback, NULL);

    ares_destroy(channel);
    ares_library_cleanup();

    return EXIT_SUCCESS;
}

        在上面的示例中,我们创建了一个 `ares_channel` 对象,并设置了一些选项,然后异步地发送了一个 DNS 查询请求,并指定了回调函数。当 DNS 查询结果返回时,`c-ares` 库会调用回调函数 `callback()` 来处理查询结果。在回调函数中,我们可以通过 `struct hostent` 结构体来获取 DNS 查询结果。

        需要注意的是,callback() 函数的第一个参数是一个 void 指针,可以用来传递任何额外的数据。在上面的例子中,我们将其设置为 NULL。

你可能感兴趣的:(c,编程实践)