简介
c-ares是一个C语言实现的异步请求DNS的实现。很多知名 软件(curl、seastar、gevent、Nodejs等等)都使用了该软件。但是该软件相关的中文资料很少,就 此以几个实例来简要说明该库的使用教程。
使用c-ares
时通常只需要引用ares.h
头文件即可,该库的相关头文件都包含在内。
以下先介绍几个该库初始化相关的函数
init
ares_library_init(ARES_LIB_INIT_ALL)
在实例初始化时,应该先调用该函数对该库相关内部模块(其实该初始化函数只在win32平台上有实际动作)。
ares_library_cleanup()
在实例销毁时调用该函数进行反初始化。与ares_library_init(ARES_LIB_INIT_ALL)
向对应。
ares_library_init_mem
初始化时指定该库内部内存分配的malloc
/free
/realloc
函数(如不指定则默认使用glibc提供的)。
然后调用ares_library_init
。
ares_channel
c-ares
以ares_channel
作为整个异步请求的上下文(context),与请求相关的函数用ares_channel
贯穿起来调用。因此发起一个请求时,应该先初始化ares_channel
。
ares_channel
需要通过ares_init_options
或者ares_init
进行初始化,区别是ares_init
使用默认
参数,ares_init_options
支持自定义参数,自定义参数为struct ares_options
和int optmask
。optmask
用于决定使用struct ares_options
中的那些字段。optmask
的定义对照struct ares_options
中的字段对照
一看就能明白,其值有(也可以参看文档:
#define ARES_OPT_FLAGS (1 << 0)
#define ARES_OPT_TIMEOUT (1 << 1)
#define ARES_OPT_TRIES (1 << 2)
#define ARES_OPT_NDOTS (1 << 3)
#define ARES_OPT_UDP_PORT (1 << 4)
#define ARES_OPT_TCP_PORT (1 << 5)
#define ARES_OPT_SERVERS (1 << 6)
#define ARES_OPT_DOMAINS (1 << 7)
#define ARES_OPT_LOOKUPS (1 << 8)
#define ARES_OPT_SOCK_STATE_CB (1 << 9)
#define ARES_OPT_SORTLIST (1 << 10)
#define ARES_OPT_SOCK_SNDBUF (1 << 11)
#define ARES_OPT_SOCK_RCVBUF (1 << 12)
#define ARES_OPT_TIMEOUTMS (1 << 13)
#define ARES_OPT_ROTATE (1 << 14)
#define ARES_OPT_EDNSPSZ (1 << 15)
#define ARES_OPT_NOROTATE (1 << 16)
struct ares_options {
int flags; // 参见下段描述
int timeout; /* in seconds or milliseconds, depending on options */ // 请求超时的时间、默认是5s
int tries; // 默认跟每个nameserver重试请求的次数
int ndots;
unsigned short udp_port; // nameserver udp port
unsigned short tcp_port; // nameserver tcp port
int socket_send_buffer_size;
int socket_receive_buffer_size;
struct in_addr *servers; // nameserver list
int nservers; // servers count
char **domains;// 强制进行dns查询的域,不按照hosts、resolve.conf内指定
int ndomains; // domains count
char *lookups; // 默认查找的范围 'b' 代表走DNS查询、'f'代表查询`hosts`文件,默认可以填'fb'
ares_sock_state_cb sock_state_cb; // socket 状态变更时的回调
void *sock_state_cb_data; // 回调的私有数据
struct apattern *sortlist;
int nsort;
int ednspsz;
};
ares_options中的flags的有效字段:
#define ARES_FLAG_USEVC (1 << 0) // 总是使用tcp进行dns查询
#define ARES_FLAG_PRIMARY (1 << 1)
#define ARES_FLAG_IGNTC (1 << 2)
#define ARES_FLAG_NORECURSE (1 << 3)
#define ARES_FLAG_STAYOPEN (1 << 4)
#define ARES_FLAG_NOSEARCH (1 << 5)
#define ARES_FLAG_NOALIASES (1 << 6)
#define ARES_FLAG_NOCHECKRESP (1 << 7)
#define ARES_FLAG_EDNS (1 << 8)
ares_channel
创建并初始化后,可以对其再进行一些设置。常见的设置函数有:
// 设置请求时的本地ip
void ares_set_local_ip4(ares_channel channel, unsigned int local_ip);
void ares_set_local_ip6(ares_channel channel, const unsigned char* local_ip6);
// 设置请求时跟哪个网卡绑定在一起,当有多个网卡都对外可达时,可以用此函数选取绑定一个网卡
void ares_set_local_dev(ares_channel channel, const char* local_dev_name);
// 注册一个回调,当c-ares socket被创建后,配置完成一些c-ares默认配置后,会触发该回调。当需要再自定义
// 一些设置时,可以在此实现。
void ares_set_socket_configure_callback(ares_channel channel, ares_sock_config_callback callback, void *user_data);
// 注册一个回调,当c-ares socket调用connect后,会触发该回调。如果需要在创建连接后做一些自定义设置时,
// 可以在此实现,例如tcp的keepalive等
void ares_set_socket_callback(ares_channel channel, ares_sock_create_callback callback, void *user_data);
// 当用户需要hook/hack 网卡IO时,用户可以注册socket相关的回调。回调中的几个函数分别对应
// socket、close、connect、recvfrom、sendv几个系统回调。当用户注册后,c-ares的跟网络IO有关
// 系的操作会调用该回调中的方法,而不会调用默认的系统调用了。
struct ares_socket_functions {
ares_socket_t(*asocket)(int, int, int, void *);
int(*aclose)(ares_socket_t, void *);
int(*aconnect)(ares_socket_t, const struct sockaddr *, socklen_t, void *);
ssize_t(*arecvfrom)(ares_socket_t, void *, size_t, int, struct sockaddr *, socklen_t *, void *);
ssize_t(*asendv)(ares_socket_t, const struct iovec *, int, void *);
};
void ares_set_socket_functions(ares_channel channel, const struct ares_socket_functions *funcs, void *user_data);
process
c-ares
整个请求的异步逻辑都封装在该库内部,但是内部并不驱动这些逻辑运转,c-ares
将逻辑驱动暴露给用户调用,
让用户驱动整个逻辑的运转,方便将其嵌入各种事件框架中去。
相关的函数有:
int ares_fds(ares_channel channel, fd_set *read_fds, fd_set *write_fds);
int ares_getsock(ares_channel channel, ares_socket_t *socks, int numsocks);
struct timeval *ares_timeout(ares_channel channel, struct timeval *maxtv, struct timeval *tv);
void ares_process(ares_channel channel, fd_set *read_fds, fd_set *write_fds);
void ares_process_fd(ares_channel channel, ares_socket_t read_fd, ares_socket_t write_fd);
整个请求的处理过程将在例子中给出。
error
当函数返回错误时,可以调用const char *ares_strerror(int code)
获取错误码对应的错误原因。
util
c-ares
还提供一些util函数,通常是一些封装,提供跨平台的能力。这些函数名同linux中glibc提供的api
命名相似,功能也相似
const char *ares_inet_ntop(int af, const void *src, char *dst, ares_socklen_t size);
int ares_inet_pton(int af, const char *src, void *dst);
example
select
下面这个例子来自于stackoverflow.com, 该例子通过ip地址反查nameserver的域名:
#include <sys/select.h>
#include <ares.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <iostream>
void dns_callback(void* arg, int status, int timeouts, struct hostent* host) {
if (status == ARES_SUCCESS)
std::cout << host->h_name << std::endl;
else
std::cout << "lookup failed: " << ares_strerror(status) << std::endl;
}
void main_loop(ares_channel& channel) {
int nfds, count;
fd_set readers, writers;
timeval tv, *tvp;
while (1) {
FD_ZERO(&readers);
FD_ZERO(&writers);
// 获取c-ares内部需要处理的fd
nfds = ares_fds(channel, &readers, &writers);
if (nfds == 0) break;
// 获取超时时间
tvp = ares_timeout(channel, NULL, &tv);
// 调用select来等待fd达到条件
count = select(nfds, &readers, &writers, NULL, tvp);
// 将fd的新状态交于c-ares内部处理
ares_process(channel, &readers, &writers);
}
}
int main(int argc, char** argv) {
struct in_addr ip;
int res;
if (argc < 2) {
std::cout << "usage: " << argv[0] << " ip.address\n";
return 1;
}
// 读取输入的ip地址
inet_aton(argv[1], &ip);
ares_library_init(ARES_LIB_INIT_ALL);
// 初始化channel
ares_channel channel;
if ((res = ares_init(&channel)) != ARES_SUCCESS) {
std::cout << "ares feiled: " << res << '\n';
return 1;
}
// 调用查询api,当main_loop中驱动时间运转时,查询有结果时会触发回调dns_callback
ares_gethostbyaddr(channel, &ip, sizeof ip, AF_INET, dns_callback, NULL);
main_loop(channel);
ares_library_cleanup();
return 0;
}
libevent
下面的例子展示c-ares
如何跟libevent
的事件驱动框架相结合:
// g++ example.cc -lcares -levent --std=c++11
#include <ares.h>
#include <event.h>
#include <stdio.h>
#include <sys/socket.h>
#include <iostream>
#include <string>
#include <unordered_map>
std::string AddressToString(void *vaddr, int len) {
auto addr = reinterpret_cast<unsigned char *>(vaddr);
std::string addv;
if (len == 4) {
char buff[4 * 4 + 3 + 1];
sprintf(buff, "%u.%u.%u.%u", addr[0], addr[1], addr[2], addr[3]);
return addv.assign(buff);
} else if (len == 16) {
for (int ii = 0; ii < 16; ii += 2) {
if (ii > 0) addv.append(":");
char buff[4 + 1];
sprintf(buff, "%02x%02x", addr[ii], addr[ii + 1]);
addv.append(buff);
}
}
return addv;
}
struct context {
struct event_base *base;
struct event timeout_evt;
ares_channel channel;
ares_options ares_opts;
std::unordered_map<int, struct event> events;
};
int main(int argc, char **argv) {
context ctx = {};
char lookups[] = "fb";
int res;
struct timeval tv, *tvp;
if (argc < 2) {
std::cout << "usage: " << argv[0] << "hostname" << std::endl;
return -1;
}
ares_library_init(ARES_LIB_INIT_ALL);
ctx.base = event_init();
ctx.ares_opts.lookups = lookups;
ctx.ares_opts.timeout = 3000;
ctx.ares_opts.tries = 1;
ctx.ares_opts.sock_state_cb_data = &ctx;
ctx.ares_opts.sock_state_cb = [](void *arg, int fd, int readable,
int writable) {
auto ctx = reinterpret_cast<context *>(arg);
short events = 0;
auto &event = ctx->events[fd];
if (readable) events |= EV_READ;
if (writable) events |= EV_WRITE;
if (events == 0) {
event_del(&event);
ctx->events.erase(fd);
return;
}
if (event_assign(&event, ctx->base, fd, events,
[](int fd, short events, void *arg) {
int w = ARES_SOCKET_BAD, r = ARES_SOCKET_BAD;
auto ctx = reinterpret_cast<context *>(arg);
if (events & EV_READ) r = fd;
if (events & EV_WRITE) w = fd;
ares_process_fd(ctx->channel, r, w);
},
ctx) != 0) {
std::cout << "event_assign failed" << std::endl;
return;
}
if (event_add(&event, nullptr) != 0) {
std::cout << "event_add failed" << std::endl;
}
};
if ((res = ares_init_options(&ctx.channel, &ctx.ares_opts,
ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES |
ARES_OPT_SOCK_STATE_CB |
ARES_OPT_LOOKUPS)) != ARES_SUCCESS) {
std::cout << "ares feiled: " << ares_strerror(res) << std::endl;
return res;
}
event_assign(&ctx.timeout_evt, ctx.base, -1, EV_TIMEOUT,
[](int fd, short events, void *arg) {
(void)fd;
(void)events;
auto ctx = reinterpret_cast<context *>(arg);
ares_process_fd(ctx->channel, ARES_SOCKET_BAD,
ARES_SOCKET_BAD);
},
&ctx);
ares_gethostbyname(
ctx.channel, argv[1], AF_INET,
[](void *data, int status, int timeouts, struct hostent *hostent) {
auto ctx = reinterpret_cast<context *>(data);
if (timeouts > 0) {
std::cout << "loopkup timeout" << std::endl;
return;
} else {
event_del(&ctx->timeout_evt);
}
if (status != ARES_SUCCESS) {
std::cout << "lookup failed: " << ares_strerror(status) << std::endl;
return;
}
if (hostent->h_addr_list) {
char **paddr = hostent->h_addr_list;
while (*paddr != nullptr) {
std::cout << "ip: " << AddressToString(*paddr, hostent->h_length)
<< std::endl;
paddr++;
}
}
},
&ctx);
tvp = ares_timeout(ctx.channel, NULL, &tv);
event_add(&ctx.timeout_evt, tvp);
if ((res = event_base_loop(ctx.base, 0)) < 0) {
std::cout << "event base loop failed: " << res << std::endl;
return res;
}
ares_destroy(ctx.channel);
ares_library_cleanup();
return 0;
}