Como usar getaddrinfo_a para resolver assíncronas com glibc
-
09-06-2019 - |
Pergunta
Uma função frequentemente esquecida que não requer biblioteca externa, mas basicamente não possui qualquer documentação.
Solução
ATUALIZAÇÃO (11/10/2010):As páginas de manual do Linux agora possuem documentação do getaddrinfo_a, você pode encontrá-la aqui: http://www.kernel.org/doc/man-pages/online/pages/man3/getaddrinfo_a.3.html
Como isenção de responsabilidade, devo acrescentar que sou muito novo em C, mas não exatamente um novato, então pode haver bugs ou práticas de codificação inadequadas. Corrija-me (e minha gramática também é uma droga).
Eu pessoalmente não sabia disso até que descobri esta postagem por Adam Langley, darei alguns trechos de código para ilustrar seu uso e esclarecer algumas coisas que podem não ficar tão claras no primeiro uso.Os benefícios de usar isso é que você recupera dados prontamente utilizáveis em soquete(), ouvir() e outras funções e, se feito corretamente, você também não precisará se preocupar com ipv4/v6.
Então, para começar com o básico, conforme retirado do link acima (você precisará vincular ao libanl (-lanl)):
Aqui está o protótipo da função:
int getaddrinfo_a(int mode, struct gaicb *list[], int ent,
struct sigevent *);
- O modo é GAI_WAIT (que provavelmente não é o que você deseja) e GAI_NOWAIT para pesquisas assíncronas
- O gaicb argumento aceita uma matriz de hosts para pesquisar com o ent argumento especificando quantos elementos o array possui
- O evento sige será responsável por informar à função como seremos notificados, mais sobre isso em um momento
Uma estrutura gaicb se parece com isto:
struct gaicb {
const char *ar_name;
const char *ar_service;
const struct addrinfo *ar_request;
struct addrinfo *ar_result;
};
Se você estiver familiarizado com getaddrinfo, esses campos correspondem a eles da seguinte forma:
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);
O nó é o campo ar_name, o serviço é a porta, o argumento de dicas corresponde ao membro ar_request e o resultado é armazenado no restante.
Agora você especifica como deseja ser notificado através da estrutura 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;
};
- Você pode ignorar a notificação definindo _sigev_notify_ como SIGEV_NONE
- Você pode acionar um sinal configurando sigev_notify para SIGEV_SIGNAL e sigev_signo para o sinal desejado.Observe que ao usar um sinal em tempo real (SIGRTMIN-SIGRTMAX, sempre use-o através das macros e adição SIGRTMIN+2 etc.) você pode passar um ponteiro ou valor no membro sigev_value.sival_ptr ou sigev_value.sival_int, respectivamente
- Você pode solicitar um retorno de chamada em um novo tópico definindo sigev_notify como SIGEV_NONE
Então, basicamente, se você quiser procurar um nome de host, defina ar_name como host e defina todo o resto como NULO, se você deseja se conectar a um host, defina ar_name e ar_service e, se desejar criar um servidor, especifique ar_service e o campo ar_result.É claro que você pode personalizar o membro ar_request como desejar, veja homem getaddrinfo para mais informações.
Se você tiver um loop de eventos com select/poll/epoll/kqueue você pode querer usar sinalfd Por conveniência.Signalfd cria um descritor de arquivo no qual você pode usar os mecanismos usuais de pesquisa de eventos como este:
#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;
}
É claro que você também pode usar um manipulador de sinal simples para esse trabalho, veja homem sigaction para mais informações.