Chinaunix首页 | 论坛 | 博客
  • 博客访问: 819585
  • 博文数量: 73
  • 博客积分: 2689
  • 博客等级: 少校
  • 技术积分: 897
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-07 19:39
个人简介

一个有目标,为自己的未来努力奋斗的人

文章分类
文章存档

2015年(9)

2014年(2)

2013年(6)

2012年(11)

2011年(33)

2010年(12)

分类: LINUX

2012-05-16 10:29:55

Asynchronous network address and service translation

    提到名字和地址转换,从事网络开发的朋友们估计都不会陌生。但是对于异步地址转换,你又了解多少呢?在这篇文章中,我将带你走进异步域名解析的世界之中...

    开始之前,还是让我们先回顾一下《UNP》中讲解的几种域名解析技术吧^_^

  • gethostbyname()函数

    1. 原型:#include <netdb.h>
    2.       struct hostent *gethostbyname(const char *hostname);
        该函数是查找主机名最基本的函数。如果调用成功了,他就返回一个指向hostent结构的指针,该结构中含有所查找主机的所有IPv4地址。这个函数的局限是只能返回IPv4地址。POSIX、规范预警可能会在将来的某个版本中撤销gethostbyname函数。该函数在执行时可能会阻塞,也就是说他是以同步的方式工作的。

  • gethostbyaddr函数

    gethostbyaddr函数试图由一个二进制的IP地址找到响应的主机名,与gethostbyname的行为正好相反。其原型如下:

    1. #include <netdb.h>
    2. struct hostent* gethostbyaddr(const char *addr, socklen_t len, 
                                    int family);
        本函数返回一个同样指向hostent结构的指针。addr参数实际上不是char *类型,而是一个指向存放IPv4地址的某个in_addr结构的指针;len参数是这个结构的大小:对于IPv4地址为4。family参数是AF_INET。 该函数在执行时可能会阻塞,也就是说他是以同步的方式工作的。

  • getaddrinfo函数

        前面两个函数仅仅支持IPv4,为了解决IPv6的情形,POSIX增加了getaddrinfo函数。getinfoaddr函数能够处理名字到地址及服务到端口这两种转换,返回的是一个sockaddr结构的链表而不是一个地址清单。这些sockaddr结构随后可由套接口直接使用。如此一来,getaddrinfo函数把协议相关性完全隐藏在这个库内部。应用程序只需要处理由getaddrinfo填写的套接口地址结构。该函数在POSIX规范中定义。其原型如下:

    1. #include <netdb.h>
    2. int getaddrinfo(const char *hostname, const char *service, 
                      const struct addrinfo *hints, 
                      struct addrinfo **result);
        其中hostname参数是一个主机名或地址串(IPv4的点分十进制数串或IPv6的十六进制数串)。service参数是一个服务名或十进制端口号数串。hints参数可以是一个空指针,也可以是一个指向某个addrinfo结构的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说,如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM,是的返回的仅仅是适用于数据包套结构的信息。如果hints参数是一个空指针,本函数就假设addrinfo结构中的ai_flag,ai_socktype和ai_protocol的值均为0,ai_family的值为AF_UNSPEC。

        如果函数返回成功(0),那么由result参数指向的变量已被填入一个指针,它指向的是由其中的ai_next成员串接起来的addrinfo结构链表。同样, 该函数在执行时可能会阻塞,也就是说他是以同步的方式工作的。 可导致返回多个addrinfo结构的情形有以下两个:

    • 如果与hostname参数关联的地址有多个,那么适用于所请求地址族(可通过hints结构的ai_family成员设置)的每个地址都返回一个对应的结构。

    • 如果service参数指定的服务支持多个套接口类型,那么每个套接口类型都可能返回一个对应的结构,具体取决于hints结构的ai_socktype成员。(注意:getaddrinfo的多数实现认为只能按照由ai_socktype成员请求的套接口类型端口号数串到端口的转换,如果没有指定这个成员,就返回一个错误。)
    可以看出,以上的函数都是以同步的方式完成域名解析的。那么,若是将其使用在对性能要求比较高的网络服务器程序中的话,可能会因为它们的阻塞执行行为而导致程序的服务能力大打折扣。那么,在这种情况下我们迫切需要一种能够异步执行域名解析的技术,来解决同步方式的性能问题!

    下面我们先简要介绍几种异步域名解析技术:
  1. 程序用采用多线程的方式进行域名解析,每一个域名解析事件由一个独立线程负责处理。这种方式实现简单,易于管理,但唯一的不足是程序的开销太大。

  2. 阅读DNS RFC文档,依据其中的原理和通信协议说明,自己实现一个异步的域名解析方案。这种技术,在实现时比较复杂且困难,但是在使用时较为灵活。一般人的话,不建议使用。

  3. 利用libevent高性能网络开发程序库中的API进行域名解析,编译时需要加上-levent选项。libevent就是利用2中说所的技术来解决异步的域名解析问题的,有兴趣的同学可以研读一下libevent.

  4. 利用linux平台下的api:getaddrinfo_a()函数进行异步域名解析。下面将重点讲解该技术。
   
getaddrinfo_a 异步网络地址和服务转换函数
 
    getaddrinfo_a函数的功能类似于getaddrinfo,但是它允许异步地解析多个域名。函数原型如下:

  1. #define _GNU_SOURCE
  2. #include <netdb.h>

  3. int getaddrinfo_a(int mode, struct gaicb *list[], int nitems, 
                      struct sigevent *sevp);

  4. int gai_suspend(struct gaicb *list[], int nitems, 
                    struct timespec *timeout);

  5. Link with -lanl
    其中,mode参数可能的赋值如下所示:
  • GAI_WAIT
    执行同步解析,函数调用阻塞到解析完成时为止。

  • GAI_NOWAIT
    执行异步解析,函数调用会立即返回,但是操作在后台将继续执行。详见下文中对sevp参数的讨论。
    list数组中包含了待处理的请求信息。nitem参数指定了list数组中请求的数目。在执行时,这些操作将被并行的处理。若list数组的某成员值为NULL,那么该请求将被忽略。

    struct gaich结构定义如下:

  1. struct gaicb{
  2.     const char *ar_name;
  3.     const char *ar_service;
  4.     const struct addrinfo *ar_request;
  5.     struct addrinfo *ar_result;
  6. };
    该结构体中的成员和getaddrinfo函数的参数是对应的。ar_name对应于node参数并且ar_service对应于service参数,用于标识一个网络主机和一个服务。ar_request中的元素对应于hints参数,用于规范返回的用于套接口地址结构的数据。最后,ar_result对应于res参数,该成员不必执行初始化操作,当请求被成功处理后会自动的被设置。addrinfo结构的详细描述详见Linux Manual getaddrinfo(3)

    当mode被指定为GAI_NOWAIT并且请求被成功处理之后,系统将会按照定义在sevp参数指定的sigevent结构中的成员的约定执行后续操作。

struct sigevent结构定义如下:

  1. struct sigevent {
  2.     int   sigev_notify;        /* Notification method */
  3.     int   sigev_signo        /* Notification signal */
  4.     union sigval sigev_value;  /* Data passed with notification */
  5.     void  (*sigev_notify_function) (union sigval);
  6.                  /* Function used for thread notification
  7.                     (SIGEV_THREAS) */
  8.     void  *sigev_notify_attributes;
  9.                  /* Attributes for notification thread 
  10.                     (SIGEV_THREAD) */
  11.     pid_t sigev_notify_thread_id;
  12.                  /* ID of thread to signal (SIGEV_THREAD_ID) */
  13. };

  14. union sigval {       /* Data passed with notification */
  15.     int  sival_int;  /* Integer value */
  16.     void *sival_ptr; /* Pointer value */
  17. };
其中,sigev_notify成员可以有如下一些的合法值:
  • SIGEV_NONE
    没有提供任何通知机制,也就是当请求完成的时候将不采取任何后续措施。

  • SIGEV_SIGNAL
    当一个解析任务完成的时候,系统将为相应的进程产生一个sigev_signo信号。那么,此时siginfo_t结构中的si_code成员的值将被设置成SI_ASYNCNL.

  • SIGEV_THREAD
    当一个解析任务完成的时候,系统将通过sigev_notify_function指定的函数启动一个线程处理后续的任务。
    技术细节提示:当sigev_notify的取值为SIGEV_SIGNAL和SIGEV_THREAD的时候,最好将sevp->sigev_value.sival_ptr指向list,因为系统在后续的某个时刻可能会使用。

    在使用getaddrinfo_a函数的时候,需要结合gai_suspend()函数才能完成异步解析任务。该函数将暂停调用线程直到有一个或多个请求被完成时为止。

    如果getaddrinfo_a函数成功处理了加入到队列中的所有请求,那么返回值是0。否则,将返回如EAI_AGAIN,EAI_MEMORY,EAI_SYSTEM的错误代码。

    如果至少有一个请求被成功的处理了,gai_suspend函就返回0;否则,将返回诸如EAI_AGAIN,EAI_ALLDONE,EAI_INTR的错误代码。

    下面是两个使用getaddrinfo_a函数进行域名解析的demo,其中前一个是同步执行时的事例,后一个是异步执行时的事例。

  1. Synchronous Example
  2.        The program below simply resolves several hostnames in parallel, giving a speed-up compared to resolving the hostnames sequentially using getaddrinfo(3). The program might be used like this:

  3.            $ ./a.out ftp.us.kernel.org enoent.linuxfoundation.org gnu.cz
  4.            ftp.us.kernel.org: 128.30.2.36
  5.            enoent.linuxfoundation.org: Name or service not known
  6.            gnu.cz: 87.236.197.13

  7.        Here is the program source code

  8.        #define _GNU_SOURCE
  9.        #include <netdb.h>
  10.        #include <stdio.h>
  11.        #include <stdlib.h>
  12.        #include <string.h>

  13.        int
  14.        main(int argc, char *argv[])
  15.        {
  16.            int i, ret;
  17.            struct gaicb *reqs[argc - 1];
  18.            char host[NI_MAXHOST];
  19.            struct addrinfo *res;

  20.            if (argc < 2) {
  21.                fprintf(stderr, "Usage: %s HOST...\n", argv[0]);
  22.                exit(EXIT_FAILURE);
  23.            }

  24.            for (i = 0; i < argc - 1; i++) {
  25.                reqs[i] = malloc(sizeof(*reqs[0]));
  26.                if (reqs[i] == NULL) {
  27.                    perror("malloc");
  28.                    exit(EXIT_FAILURE);
  29.                }
  30.                memset(reqs[i], 0, sizeof(*reqs[0]));
  31.                reqs[i]->ar_name = argv[i + 1];
  32.            }

  33.            ret = getaddrinfo_a(GAI_WAIT, reqs, argc - 1, NULL);
  34.            if (ret != 0) {
  35.                fprintf(stderr, "getaddrinfo_a() failed: %s\n",
  36.                        gai_strerror(ret));
  37.                exit(EXIT_FAILURE);
  38.            }

  39.            for (i = 0; i < argc - 1; i++) {
  40.                printf("%s: ", reqs[i]->ar_name);
  41.                ret = gai_error(reqs[i]);
  42.                if (ret == 0) {
  43.                    res = reqs[i]->ar_result;

  44.                    ret = getnameinfo(res->ai_addr, res->ai_addrlen,
  45.                            host, sizeof(host),
  46.                            NULL, 0, NI_NUMERICHOST);
  47.                    if (ret != 0) {
  48.                        fprintf(stderr, "getnameinfo() failed: %s\n",
  49.                                gai_strerror(ret));
  50.                        exit(EXIT_FAILURE);
  51.                    }
  52.                    puts(host);

  53.                } else {
  54.                    puts(gai_strerror(ret));
  55.                }
  56.            }
  57.            exit(EXIT_SUCCESS);
  58.        }
    下面的一个事例是异步方式的getaddrinfo_a()函数的工作demo:

  1. Asynchronous Example
  2.        This example shows a simple interactive getaddrinfo_a() front-end. The notification facility is not demonstrated.

  3.        An example session might look like like this:

  4.            $ ./a.out
  5.            > a ftp.us.kernel.org enoent.linuxfoundation.org gnu.cz
  6.            > c 2
  7.            [2] gnu.cz: Request not canceled
  8.            > w 0 1
  9.            [00] ftp.us.kernel.org: Finished
  10.            > l
  11.            [00] ftp.us.kernel.org: 216.165.129.139
  12.            [01] enoent.linuxfoundation.org: Processing request in progress
  13.            [02] gnu.cz: 87.236.197.13
  14.            > l
  15.            [00] ftp.us.kernel.org: 216.165.129.139
  16.            [01] enoent.linuxfoundation.org: Name or service not known
  17.            [02] gnu.cz: 87.236.197.13

  18.        The program source goes as follows:


  19.        #define _GNU_SOURCE
  20.        #include <netdb.h>
  21.        #include <stdio.h>
  22.        #include <stdlib.h>
  23.        #include <string.h>

  24.        static struct gaicb **reqs = NULL;
  25.        static int nreqs = 0;

  26.        static char *
  27.        getcmd(void)
  28.        {
  29.            static char buf[256];

  30.            fputs("> ", stdout); fflush(stdout);
  31.            if (fgets(buf, sizeof(buf), stdin) == NULL)
  32.                return NULL;

  33.            if (buf[strlen(buf) - 1] == '\n')
  34.                buf[strlen(buf) - 1] = 0;

  35.            return buf;
  36.        }

  37.        /* Add requests for specified hostnames */
  38.        static void
  39.        add_requests(void)
  40.        {
  41.            int nreqs_base = nreqs;
  42.            char *host;
  43.            int ret;

  44.            while ((host = strtok(NULL, " "))) {
  45.                nreqs++;
  46.                reqs = realloc(reqs, nreqs * sizeof(reqs[0]));

  47.                reqs[nreqs - 1] = calloc(1, sizeof(*reqs[0]));
  48.                reqs[nreqs - 1]->ar_name = strdup(host);
  49.            }

  50.            /* Queue nreqs_base..nreqs requests. */

  51.            ret = getaddrinfo_a(GAI_NOWAIT, &reqs[nreqs_base],
  52.                                nreqs - nreqs_base, NULL);
  53.            if (ret) {
  54.                fprintf(stderr, "getaddrinfo_a() failed: %s\n",
  55.                        gai_strerror(ret));
  56.                exit(EXIT_FAILURE);
  57.            }
  58.        }

  59.        /* Wait until at least one of specified requests completes */
  60.        static void
  61.        wait_requests(void)
  62.        {
  63.            char *id;
  64.            int i, ret, n;
  65.            struct gaicb const **wait_reqs = calloc(nreqs, sizeof(*wait_reqs));
  66.                        /* NULL elements are ignored by gai_suspend(). */

  67.            while ((id = strtok(NULL, " ")) != NULL) {
  68.                n = atoi(id);

  69.                if (n >= nreqs) {
  70.                    printf("Bad request number: %s\n", id);
  71.                    return;
  72.                }

  73.                wait_reqs[n] = reqs[n];
  74.            }

  75.            ret = gai_suspend(wait_reqs, nreqs, NULL);
  76.            if (ret) {
  77.                printf("gai_suspend(): %s\n", gai_strerror(ret));
  78.                return;
  79.            }

  80.            for (i = 0; i < nreqs; i++) {
  81.                if (wait_reqs[i] == NULL)
  82.                    continue;

  83.                ret = gai_error(reqs[i]);
  84.                if (ret == EAI_INPROGRESS)
  85.                    continue;

  86.                printf("[%02d] %s: %s\n", i, reqs[i]->ar_name,
  87.                       ret == 0 ? "Finished" : gai_strerror(ret));
  88.            }
  89.        }

  90.        /* Cancel specified requests */
  91.        static void
  92.        cancel_requests(void)
  93.        {
  94.            char *id;
  95.            int ret, n;

  96.            while ((id = strtok(NULL, " ")) != NULL) {
  97.                n = atoi(id);

  98.                if (n >= nreqs) {
  99.                    printf("Bad request number: %s\n", id);
  100.                    return;
  101.                }

  102.                ret = gai_cancel(reqs[n]);
  103.                printf("[%s] %s: %s\n", id, reqs[atoi(id)]->ar_name,
  104.                       gai_strerror(ret));
  105.            }
  106.        }

  107.        /* List all requests */
  108.        static void
  109.        list_requests(void)
  110.        {
  111.            int i, ret;
  112.            char host[NI_MAXHOST];
  113.            struct addrinfo *res;

  114.            for (i = 0; i < nreqs; i++) {
  115.                printf("[%02d] %s: ", i, reqs[i]->ar_name);
  116.                ret = gai_error(reqs[i]);

  117.                if (!ret) {
  118.                    res = reqs[i]->ar_result;

  119.                    ret = getnameinfo(res->ai_addr, res->ai_addrlen,
  120.                                      host, sizeof(host),
  121.                                      NULL, 0, NI_NUMERICHOST);
  122.                    if (ret) {
  123.                        fprintf(stderr, "getnameinfo() failed: %s\n",
  124.                                gai_strerror(ret));
  125.                        exit(EXIT_FAILURE);
  126.                    }
  127.                    puts(host);
  128.                } else {
  129.                    puts(gai_strerror(ret));
  130.                }
  131.            }
  132.        }

  133.        int
  134.        main(int argc, char *argv[])
  135.        {
  136.            char *cmdline;
  137.            char *cmd;

  138.            while ((cmdline = getcmd()) != NULL) {
  139.                cmd = strtok(cmdline, " ");

  140.                if (cmd == NULL) {
  141.                    list_requests();
  142.                } else {
  143.                    switch (cmd[0]) {
  144.                    case 'a':
  145.                        add_requests();
  146.                        break;
  147.                    case 'w':
  148.                        wait_requests();
  149.                        break;
  150.                    case 'c':
  151.                        cancel_requests();
  152.                        break;
  153.                    case 'l':
  154.                        list_requests();
  155.                        break;
  156.                    default:
  157.                        fprintf(stderr, "Bad command: %c\n", cmd[0]);
  158.                        break;
  159.                    }
  160.                }
  161.            }
  162.            exit(EXIT_SUCCESS);
  163.        }
祝,好运!
阅读(7653) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~