如何使用 getaddrinfo_a 与 glibc 进行异步解析

发布于 2024-07-04 08:09:41 字数 36 浏览 12 评论 0原文

一个经常被忽视的功能,不需要外部库,但基本上没有任何文档。

An often overlooked function that requires no external library, but basically has no documentation whatsoever.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

蓝色星空 2024-07-11 08:09:41

更新 (2010-10-11):Linux 手册页现在有 getaddrinfo_a 的文档,您可以在这里找到它:http://www.kernel.org/doc/man-pages/online/pages/man3/getaddrinfo_a.3.html

作为免责声明,我应该补充一点,我对 C 很陌生,但不完全是新手,因此可能存在错误或不好的编码实践,请纠正我(我的语法也很糟糕)。

我个人并不知道它,直到我发现 Adam Langley 的这篇文章,我将给出一些代码片段来说明它的用法,并澄清一些第一次使用时可能不太清楚的事情。 使用此功能的好处是,您可以轻松获取可在 socket()listen() 和其他函数中使用的数据,如果做得正确,您将不必担心关于 ipv4/v6 也可以。
因此,首先从基础知识开始,如上面的链接所示(您需要链接 libanl (-lanl)):
下面是函数原型:

int getaddrinfo_a(int mode, struct gaicb *list[], int ent, 
                  struct sigevent *);
  1. mode 要么是 GAI_WAIT(这可能不是您想要的),要么是用于异步查找的 GAI_NOWAIT
  2. gaicb 参数接受要查找的主机数组ent 参数指定数组有多少个元素
  3. sigevent 将负责告诉函数如何通知我们,稍后将详细

介绍 gaicb 结构体像这样:

struct gaicb {
    const char *ar_name;
    const char *ar_service;
    const struct addrinfo *ar_request;
    struct addrinfo *ar_result;
};

如果您熟悉 getaddrinfo,那么这些字段与它们的对应关系如下:

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

节点是 ar_name 字段,service 是端口,hints 参数对应于 ar_request 成员,结果存储在其余部分中。< br>
现在,您可以指定如何通过 sigevent 结构获得通知:

struct sigevent {
    sigval_t sigev_value;
    int sigev_signo;
    int sigev_notify;
    void (*sigev_notify_function) (sigval_t);
    pthread_addr_t *sigev_notify_attributes;
};
  1. 您可以通过将 _sigev_notify_ 设置为 SIGEV_NONE 来忽略通知
  2. 您可以通过将 sigev_notify 设置为 SIGEV_SIGNAL 并将 sigev_signo 设置为所需信号来触发信号。 请注意,使用实时信号时(SIGRTMIN-SIGRTMAX,始终通过宏和加法 SIGRTMIN+2 等来使用它)可以在 sigev_value.sival_ptr 或 sigev_value.sival_int 成员中传递指针或值
  3. 您可以通过将 sigev_notify 设置为 SIGEV_NONE 在新线程中请求回调

所以基本上,如果您想查找主机名,请将 ar_name 设置为主机并设置其他一切都为NULL,如果您想连接到主机,请设置 ar_name 和 ar_service ,如果您想创建服务器,请指定 ar_service 和 ar_result 字段。 您当然可以根据自己的喜好自定义 ar_request 成员,请查看 man getaddrinfo 了解更多信息。

如果您有一个带有 select/poll/epoll/kqueue 的事件循环,您可能需要使用 signalfd 为方便起见。 Signalfd 创建一个文件描述符,您可以在其中使用通常的事件轮询机制,如下所示:

#define _GNU_SOURCE //yes this will not be so standardish
#include <netdb.h>
#include <signal.h>
#include <sys/signalfd.h>

void signalfd_setup(void) {
    int sfd;
    sigset_t mask;

    sigemptyset(&mask);
    sigaddset(&mask, SIGRTMIN);
    sigprocmask(SIG_BLOCK, &mask, NULL); //we block the signal
    sfd = signalfd(-1, &mask, 0);
    //add it to the event queue
}
void signalfd_read(int fd) {
    ssize_t s;
    struct signalfd_siginfo fdsi;
    struct gaicb *host;

    while((s = read(fd, &fdsi, sizeof(struct signalfd_siginfo))) > 0){
    if (s != sizeof(struct signalfd_siginfo)) return; //thats bad
    host = fdsi.ssi_ptr; //the pointer passed to the sigevent structure
            //the result is in the host->ar_result member
            create_server(host);
    }
}
void create_server(struct gaicb *host) {
    struct addrinfo *rp, *result;
    int fd;

    result = host->ar_result;
    for(rp = result; rp != NULL; rp = rp->ai_next) {
        fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        bind(fd, rp->ai_addr, rp->ai_addrlen);
        listen(fd, SOMAXCONN);
        //error checks are missing!

        freeaddrinfo(host->ar_request);
        freeaddrinfo(result);
        //you should free everything you put into the gaicb
    }
}
int main(int argc, char *argv[]) {
    struct gaicb *host;
    struct addrinfo *hints;
    struct sigevent sig;

    host = calloc(1, sizeof(struct gaicb));
    hints = calloc(1, sizeof(struct addrinfo));

    hints->ai_family = AF_UNSPEC; //we dont care if its v4 or v6
    hints->ai_socktype = SOCK_STREAM;
    hints->ai_flags = AI_PASSIVE;
    //every other field is NULL-d by calloc

    host->ar_service = "8888"; //the port we will listen on
    host->ar_request = hints;

    sig.sigev_notify = SIGEV_SIGNAL;
    sig.sigev_value.sival_ptr = host;
    sig.sigev_signo = SIGRTMIN;

    getaddrinfo_a(GAI_NOWAIT, &host, 1, &sig);

    signalfd_setup();

    //start your event loop
    return 0;
}

您当然也可以使用简单的信号处理程序来完成此工作,请查看 man sigaction 了解更多信息。

UPDATE (2010-10-11): The linux man-pages now have documentation of the getaddrinfo_a, you can find it here: http://www.kernel.org/doc/man-pages/online/pages/man3/getaddrinfo_a.3.html

As a disclaimer I should add that I'm quite new to C but not exactly a newbie, so there might be bugs, or bad coding practices, please do correct me (and my grammar sucks too).

I personally didn't know about it until I came upon this post by Adam Langley, I shall give a few code snippets to illustrate the usage of it and clarify some things that might not be that clear on first use. The benefits of using this is that you get back data readily usable in socket(), listen() and other functions, and if done right you won't have to worry about ipv4/v6 either.
So to start off with the basics, as taken from the link above (you will need to link against libanl (-lanl)) :
Here is the function prototype:

int getaddrinfo_a(int mode, struct gaicb *list[], int ent, 
                  struct sigevent *);
  1. The mode is either GAI_WAIT (which is probably not what you want) and GAI_NOWAIT for async lookups
  2. The gaicb argument accepts an array of hosts to lookup with the ent argument specifying how many elements the array has
  3. The sigevent will be responsible for telling the function how we are to be notified, more on this in a moment

A gaicb struct looks like this:

struct gaicb {
    const char *ar_name;
    const char *ar_service;
    const struct addrinfo *ar_request;
    struct addrinfo *ar_result;
};

If you're familiar with getaddrinfo, then these fields correspond to them like so:

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

The node is the ar_name field, service is the port, the hints argument corresponds to the ar_request member and the result is stored in the rest.
Now you specify how you want to be notified through the sigevent structure:

struct sigevent {
    sigval_t sigev_value;
    int sigev_signo;
    int sigev_notify;
    void (*sigev_notify_function) (sigval_t);
    pthread_addr_t *sigev_notify_attributes;
};
  1. You can ignore the notification via setting _sigev_notify_ to SIGEV_NONE
  2. You can trigger a signal via setting sigev_notify to SIGEV_SIGNAL and sigev_signo to the desired signal. Note that when using a real-time signal (SIGRTMIN-SIGRTMAX, always use it via the macros and addition SIGRTMIN+2 etc.) you can pass along a pointer or value in the sigev_value.sival_ptr or sigev_value.sival_int member respectivley
  3. You can request a callback in a new thread via setting sigev_notify to SIGEV_NONE

So basically if you want to look up a hostname you set ar_name to the host and set everything else to NULL, if you want to connect to a host you set ar_name and ar_service , and if you want to create a server you specify ar_service and the ar_result field. You can of course customize the ar_request member to your hearts content, look at man getaddrinfo for more info.

If you have an event loop with select/poll/epoll/kqueue you might want to use signalfd for convenience. Signalfd creates a file descriptor on which you can use the usuall event polling mechanisms like so:

#define _GNU_SOURCE //yes this will not be so standardish
#include <netdb.h>
#include <signal.h>
#include <sys/signalfd.h>

void signalfd_setup(void) {
    int sfd;
    sigset_t mask;

    sigemptyset(&mask);
    sigaddset(&mask, SIGRTMIN);
    sigprocmask(SIG_BLOCK, &mask, NULL); //we block the signal
    sfd = signalfd(-1, &mask, 0);
    //add it to the event queue
}
void signalfd_read(int fd) {
    ssize_t s;
    struct signalfd_siginfo fdsi;
    struct gaicb *host;

    while((s = read(fd, &fdsi, sizeof(struct signalfd_siginfo))) > 0){
    if (s != sizeof(struct signalfd_siginfo)) return; //thats bad
    host = fdsi.ssi_ptr; //the pointer passed to the sigevent structure
            //the result is in the host->ar_result member
            create_server(host);
    }
}
void create_server(struct gaicb *host) {
    struct addrinfo *rp, *result;
    int fd;

    result = host->ar_result;
    for(rp = result; rp != NULL; rp = rp->ai_next) {
        fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        bind(fd, rp->ai_addr, rp->ai_addrlen);
        listen(fd, SOMAXCONN);
        //error checks are missing!

        freeaddrinfo(host->ar_request);
        freeaddrinfo(result);
        //you should free everything you put into the gaicb
    }
}
int main(int argc, char *argv[]) {
    struct gaicb *host;
    struct addrinfo *hints;
    struct sigevent sig;

    host = calloc(1, sizeof(struct gaicb));
    hints = calloc(1, sizeof(struct addrinfo));

    hints->ai_family = AF_UNSPEC; //we dont care if its v4 or v6
    hints->ai_socktype = SOCK_STREAM;
    hints->ai_flags = AI_PASSIVE;
    //every other field is NULL-d by calloc

    host->ar_service = "8888"; //the port we will listen on
    host->ar_request = hints;

    sig.sigev_notify = SIGEV_SIGNAL;
    sig.sigev_value.sival_ptr = host;
    sig.sigev_signo = SIGRTMIN;

    getaddrinfo_a(GAI_NOWAIT, &host, 1, &sig);

    signalfd_setup();

    //start your event loop
    return 0;
}

You can of course use a simple signal handler for this job too, look at man sigaction for more info.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文