Asynchronous network address and service translation
提到名字和地址转换,从事网络开发的朋友们估计都不会陌生。但是对于异步地址转换,你又了解多少呢?在这篇文章中,我将带你走进异步域名解析的世界之中...
开始之前,还是让我们先回顾一下《UNP》中讲解的几种域名解析技术吧^_^
- gethostbyname()函数
- 原型:#include <netdb.h>
- struct hostent *gethostbyname(const char *hostname);
该函数是查找主机名最基本的函数。如果调用成功了,他就返回一个指向hostent结构的指针,该结构中含有所查找主机的所有IPv4地址。这个函数的局限是只能返回IPv4地址。POSIX、规范预警可能会在将来的某个版本中撤销gethostbyname函数。该函数在执行时可能会阻塞,也就是说他是以同步的方式工作的。
- gethostbyaddr函数
gethostbyaddr函数试图由一个二进制的IP地址找到响应的主机名,与gethostbyname的行为正好相反。其原型如下:
- #include <netdb.h>
- 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规范中定义。其原型如下:
- #include <netdb.h>
- 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成员请求的套接口类型端口号数串到端口的转换,如果没有指定这个成员,就返回一个错误。)
可以看出,以上的函数都是以同步的方式完成域名解析的。那么,若是将其使用在对性能要求比较高的网络服务器程序中的话,可能会因为它们的阻塞执行行为而导致程序的服务能力大打折扣。那么,在这种情况下我们迫切需要一种能够异步执行域名解析的技术,来解决同步方式的性能问题!
下面我们先简要介绍几种异步域名解析技术:- 程序用采用多线程的方式进行域名解析,每一个域名解析事件由一个独立线程负责处理。这种方式实现简单,易于管理,但唯一的不足是程序的开销太大。
- 阅读DNS RFC文档,依据其中的原理和通信协议说明,自己实现一个异步的域名解析方案。这种技术,在实现时比较复杂且困难,但是在使用时较为灵活。一般人的话,不建议使用。
- 利用libevent高性能网络开发程序库中的API进行域名解析,编译时需要加上-levent选项。libevent就是利用2中说所的技术来解决异步的域名解析问题的,有兴趣的同学可以研读一下libevent.
- 利用linux平台下的api:getaddrinfo_a()函数进行异步域名解析。下面将重点讲解该技术。
getaddrinfo_a 异步网络地址和服务转换函数
getaddrinfo_a函数的功能类似于getaddrinfo,但是它允许异步地解析多个域名。函数原型如下:
- #define _GNU_SOURCE
- #include <netdb.h>
- int getaddrinfo_a(int mode, struct gaicb *list[], int nitems,
struct sigevent *sevp);
- int gai_suspend(struct gaicb *list[], int nitems,
struct timespec *timeout);
- Link with -lanl
其中,mode参数可能的赋值如下所示:- GAI_WAIT
执行同步解析,函数调用阻塞到解析完成时为止。
- GAI_NOWAIT
执行异步解析,函数调用会立即返回,但是操作在后台将继续执行。详见下文中对sevp参数的讨论。
list数组中包含了待处理的请求信息。nitem参数指定了list数组中请求的数目。在执行时,这些操作将被并行的处理。若list数组的某成员值为NULL,那么该请求将被忽略。 struct gaich结构定义如下:
- struct gaicb{
- const char *ar_name;
- const char *ar_service;
- const struct addrinfo *ar_request;
- struct addrinfo *ar_result;
- };
该结构体中的成员和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结构定义如下:
- struct sigevent {
- int sigev_notify; /* Notification method */
- int sigev_signo; /* Notification signal */
- union sigval sigev_value; /* Data passed with notification */
- void (*sigev_notify_function) (union sigval);
- /* Function used for thread notification
- (SIGEV_THREAS) */
- void *sigev_notify_attributes;
- /* Attributes for notification thread
- (SIGEV_THREAD) */
- pid_t sigev_notify_thread_id;
- /* ID of thread to signal (SIGEV_THREAD_ID) */
- };
- union sigval { /* Data passed with notification */
- int sival_int; /* Integer value */
- void *sival_ptr; /* Pointer value */
- };
其中,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,其中前一个是同步执行时的事例,后一个是异步执行时的事例。
- Synchronous Example
- 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:
- $ ./a.out ftp.us.kernel.org enoent.linuxfoundation.org gnu.cz
- ftp.us.kernel.org: 128.30.2.36
- enoent.linuxfoundation.org: Name or service not known
- gnu.cz: 87.236.197.13
- Here is the program source code
- #define _GNU_SOURCE
- #include <netdb.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- int
- main(int argc, char *argv[])
- {
- int i, ret;
- struct gaicb *reqs[argc - 1];
- char host[NI_MAXHOST];
- struct addrinfo *res;
- if (argc < 2) {
- fprintf(stderr, "Usage: %s HOST...\n", argv[0]);
- exit(EXIT_FAILURE);
- }
- for (i = 0; i < argc - 1; i++) {
- reqs[i] = malloc(sizeof(*reqs[0]));
- if (reqs[i] == NULL) {
- perror("malloc");
- exit(EXIT_FAILURE);
- }
- memset(reqs[i], 0, sizeof(*reqs[0]));
- reqs[i]->ar_name = argv[i + 1];
- }
- ret = getaddrinfo_a(GAI_WAIT, reqs, argc - 1, NULL);
- if (ret != 0) {
- fprintf(stderr, "getaddrinfo_a() failed: %s\n",
- gai_strerror(ret));
- exit(EXIT_FAILURE);
- }
- for (i = 0; i < argc - 1; i++) {
- printf("%s: ", reqs[i]->ar_name);
- ret = gai_error(reqs[i]);
- if (ret == 0) {
- res = reqs[i]->ar_result;
- ret = getnameinfo(res->ai_addr, res->ai_addrlen,
- host, sizeof(host),
- NULL, 0, NI_NUMERICHOST);
- if (ret != 0) {
- fprintf(stderr, "getnameinfo() failed: %s\n",
- gai_strerror(ret));
- exit(EXIT_FAILURE);
- }
- puts(host);
- } else {
- puts(gai_strerror(ret));
- }
- }
- exit(EXIT_SUCCESS);
- }
下面的一个事例是异步方式的getaddrinfo_a()函数的工作demo:
- Asynchronous Example
- This example shows a simple interactive getaddrinfo_a() front-end. The notification facility is not demonstrated.
- An example session might look like like this:
- $ ./a.out
- > a ftp.us.kernel.org enoent.linuxfoundation.org gnu.cz
- > c 2
- [2] gnu.cz: Request not canceled
- > w 0 1
- [00] ftp.us.kernel.org: Finished
- > l
- [00] ftp.us.kernel.org: 216.165.129.139
- [01] enoent.linuxfoundation.org: Processing request in progress
- [02] gnu.cz: 87.236.197.13
- > l
- [00] ftp.us.kernel.org: 216.165.129.139
- [01] enoent.linuxfoundation.org: Name or service not known
- [02] gnu.cz: 87.236.197.13
- The program source goes as follows:
- #define _GNU_SOURCE
- #include <netdb.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- static struct gaicb **reqs = NULL;
- static int nreqs = 0;
- static char *
- getcmd(void)
- {
- static char buf[256];
- fputs("> ", stdout); fflush(stdout);
- if (fgets(buf, sizeof(buf), stdin) == NULL)
- return NULL;
- if (buf[strlen(buf) - 1] == '\n')
- buf[strlen(buf) - 1] = 0;
- return buf;
- }
- /* Add requests for specified hostnames */
- static void
- add_requests(void)
- {
- int nreqs_base = nreqs;
- char *host;
- int ret;
- while ((host = strtok(NULL, " "))) {
- nreqs++;
- reqs = realloc(reqs, nreqs * sizeof(reqs[0]));
- reqs[nreqs - 1] = calloc(1, sizeof(*reqs[0]));
- reqs[nreqs - 1]->ar_name = strdup(host);
- }
- /* Queue nreqs_base..nreqs requests. */
- ret = getaddrinfo_a(GAI_NOWAIT, &reqs[nreqs_base],
- nreqs - nreqs_base, NULL);
- if (ret) {
- fprintf(stderr, "getaddrinfo_a() failed: %s\n",
- gai_strerror(ret));
- exit(EXIT_FAILURE);
- }
- }
- /* Wait until at least one of specified requests completes */
- static void
- wait_requests(void)
- {
- char *id;
- int i, ret, n;
- struct gaicb const **wait_reqs = calloc(nreqs, sizeof(*wait_reqs));
- /* NULL elements are ignored by gai_suspend(). */
- while ((id = strtok(NULL, " ")) != NULL) {
- n = atoi(id);
- if (n >= nreqs) {
- printf("Bad request number: %s\n", id);
- return;
- }
- wait_reqs[n] = reqs[n];
- }
- ret = gai_suspend(wait_reqs, nreqs, NULL);
- if (ret) {
- printf("gai_suspend(): %s\n", gai_strerror(ret));
- return;
- }
- for (i = 0; i < nreqs; i++) {
- if (wait_reqs[i] == NULL)
- continue;
- ret = gai_error(reqs[i]);
- if (ret == EAI_INPROGRESS)
- continue;
- printf("[%02d] %s: %s\n", i, reqs[i]->ar_name,
- ret == 0 ? "Finished" : gai_strerror(ret));
- }
- }
- /* Cancel specified requests */
- static void
- cancel_requests(void)
- {
- char *id;
- int ret, n;
- while ((id = strtok(NULL, " ")) != NULL) {
- n = atoi(id);
- if (n >= nreqs) {
- printf("Bad request number: %s\n", id);
- return;
- }
- ret = gai_cancel(reqs[n]);
- printf("[%s] %s: %s\n", id, reqs[atoi(id)]->ar_name,
- gai_strerror(ret));
- }
- }
- /* List all requests */
- static void
- list_requests(void)
- {
- int i, ret;
- char host[NI_MAXHOST];
- struct addrinfo *res;
- for (i = 0; i < nreqs; i++) {
- printf("[%02d] %s: ", i, reqs[i]->ar_name);
- ret = gai_error(reqs[i]);
- if (!ret) {
- res = reqs[i]->ar_result;
- ret = getnameinfo(res->ai_addr, res->ai_addrlen,
- host, sizeof(host),
- NULL, 0, NI_NUMERICHOST);
- if (ret) {
- fprintf(stderr, "getnameinfo() failed: %s\n",
- gai_strerror(ret));
- exit(EXIT_FAILURE);
- }
- puts(host);
- } else {
- puts(gai_strerror(ret));
- }
- }
- }
- int
- main(int argc, char *argv[])
- {
- char *cmdline;
- char *cmd;
- while ((cmdline = getcmd()) != NULL) {
- cmd = strtok(cmdline, " ");
- if (cmd == NULL) {
- list_requests();
- } else {
- switch (cmd[0]) {
- case 'a':
- add_requests();
- break;
- case 'w':
- wait_requests();
- break;
- case 'c':
- cancel_requests();
- break;
- case 'l':
- list_requests();
- break;
- default:
- fprintf(stderr, "Bad command: %c\n", cmd[0]);
- break;
- }
- }
- }
- exit(EXIT_SUCCESS);
- }
祝,好运!
阅读(8657) | 评论(0) | 转发(0) |