小节提纲
1.异步请求处理流程
2.四元组,create,commit,callback,destroy
3.异步请求框来封装
4.应用协议redis/dns/http请求封装
当业务服务器访问需要等待的服务时,业务访问线程需要等待挂起直到该服务给出反馈,例如对mysql,redis,http,dns等服务的请求,这些请求的返回需要等待一段时间,大大加深了业务服务器的承载压力,也会影响其总体性能,异步请求池就是为解决这个问题应运而生的。
本文通过epoll实现了一个简单的dns请求
DNS实际上是由一个分层的DNS服务器实现的分布式数据库和一个让主机能够查询分布式数据库的应用层协议组成。因此,要了解DNS的工作原理,需要从以上两个方便入手。
参考两个博客
:
DNS(域名解析协议)详解
自己动手实现DNS协议
DNS协议是用来将域名转换为IP地址(也可以将IP地址转换为相应的域名地址)
另外DNS通常还提供主机名到以下几项的转换服务
- 主机命名(host aloasing) 。有着复杂规范主机名(canonical hostname)的主机可能有一个或多个别名,通常规范主机名较复杂,而别名让人更容易记忆。应用程序可以调用DNS来获得主机别名对应的规范主机名,以及主机的ip地址。
- 邮件服务器别名(mail server aliasing) 。DNS也能完成邮件服务器别名到其规范主机名以及ip地址的转换。
- 负载均衡(load distribution)。DNS可用于冗余的服务器之间进行负载均衡。一个繁忙的站点,如abc.com,可能被冗余部署在多台具有不同ip的服务器上。在该情况下,在DNS数据库中,该主机名可能对应着一个ip集合,但应用程序调用DNS来获取该主机名对应的ip时,DNS通过某种算法从该主机名对应的ip集合中,挑选出某一ip进行响应。
DNS服务器根据域名命名空间(domian name space)组织成如下图所示的 树形结构(当然,只给出部分DNS服务器,只为显示出DNS服务器的层次结构):
在图中,根节点代表的是根DNS服务器,因特网上共有13台,编号从A到M;根DNS服务器之下的一层被称为顶级DNS服务器;再往下一层被称为权威DNS服务器。
- 当一个应用要通过DNS来查询某个主机名,比如www.google.com的ip时,粗略地说,查询过程是这样的:它先与根服务器之一联系,根服务器根据顶级域名com,会响应命名空间为com的顶级域服务器的ip;
- 于是该应用接着向com顶级域服务器发出请求,com顶级域服务器会响应命名空间为google.com的权威DNS服务器的ip地址;
- 最后该应用将请求命名空间为google.com的权威DNS服务器,该权威DNS服务器会响应主机名为www.google.com的ip。
实际上,除了上图层次结构中所展示的DNS外,还有一类与我们接触更为密切的DNS服务器,它们是 本地DNS服务器,我们经常在电脑上配置的DNS服务器通常就是此类。它们一般由某公司,某大学,或某居民区提供,比如Google提供的DNS服务器 8.8.8.8;比如常被人诟病的114.114.114.114 等。
加入了本地DNS的查询过程跟之前的查询过程基本上是一致的,查询流程如下图所示:
在实际工作中,DNS服务器是带缓存的。即DNS服务器在每次收到DNS请求时,都会先查询自身数据库包括缓存中有无要查询的主机名的ip,若有且没有过期,则直接响应该ip,否则才会按上图流程进行查询;而服务器在每次收到响应信息后,都会将响应信息缓存起来;
在介绍DNS层协议之前,先了解一下DNS服务器存储的资源记录(Resource Records,RRs),一条资源记录(RR)记载着一个映射关系。每条RR通常包含如下表所示的一些信息:
字段 | 含义 |
---|---|
NAME | 名字 |
TYPE | 类型 |
CLASS | 类 |
TTL | 生存时间 |
RDLENGTH | RDATA所占的字节数 |
RDATA | 数据 |
NAME和RDATA表示的含义根据TYPE的取值不同而不同,常见的:
- 若TYPE=A,则name是主机名,value是其对应的ip;
- 若TYPE=NS,则name是一个域,value是一个权威DNS服务器的主机名。该记录表示name域的域名解析将由value主机名对应的DNS服务器来做;
- 若TYPE=CNAME,则value是别名为name的主机对应的规范主机名;
- 若TYPE=MX,则value是别名为name的邮件服务器的规范主机名;
……
TYPE实际上还有其他类型,所有可能的type及其约定的数值表示如下
TYPE | value | meaning |
---|---|---|
A | 1 | a host address |
NS | 2 | an authoritative name server |
MD | 3 | a mail destination (Obsolete - use MX) |
MF | 4 | a mail forwarder (Obsolete - use MX) |
CNAME | 5 | the canonical name for an alias |
SOA | 6 | marks the start of a zone of authority |
MB | 7 | a mailbox domain name (EXPERIMENTAL) |
MG | 8 | a mail group member (EXPERIMENTAL) |
MR | 9 | a mail rename domain name (EXPERIMENTAL) |
NULL | 10 | a null RR (EXPERIMENTAL) |
WKS | 11 | a well known service description |
PTR | 12 | a domain name pointer |
HINFO | 13 | host information |
MINFO | 14 | mailbox or mail list information |
MX | 15 | mail exchange |
TXT | 16 | text strings |
DNS请求与响应的格式是一致的,其整体分为Header、Question、Answer、Authority、Additional5部分,如下图所示:
Header部分是一定有的,长度固定为12个字节;其余4部分可能有也可能没有,并且长度也不一定,这个在Header部分中有指明。Header的结构如下:
0:No error condition,没有错误条件;
1:Format error,请求格式有误,服务器无法解析请求;
2:Server failure,服务器出错。
3:Name Error,只在权威DNS服务器的响应中有意义,表示请求中的域名不存在。
4:Not Implemented,服务器不支持该请求类型。
5:Refused,服务器拒绝执行请求操作。
6~15:保留备用。
Question部分的每一个实体的格式如下图所示:
Answer、Authority、Additional部分格式一致,每部分都由若干实体组成,每个实体即为一条RR,之前有过介绍,格式如下图所示
1. Init 实现异步
a. epoll_create
b. pthread_create
2. commit
a. socket
b. connnect_server
c. encode-->redis/mysql/dns
d. send
e. fd加入到epoll中-->epoll_ctl
3. callback
while(1)
epoll_wait()
recv()
parse()
fd-->epoll_ delete
4. destroy
close(epfd);
pthread_cancel(thid)
#define DNS_SVR "114.114.114.114"
#define DNS_HOST 0x01
#define DNS_CNAME 0x05
#define ASYNC_CLIENT_NUM 1024
struct dns_header
{
unsigned short id;
unsigned short flags;
unsigned short qdcount;
unsigned short ancount;
unsigned short nscount;
unsigned short arcount;
};
struct dns_question
{
int length;
unsigned short qtype;
unsigned short qclass;
char *qname;
};
struct dns_item
{
char *domain;
char *ip;
};
// 异步请求上下文
struct async_context
{
int epfd; // epoll的fd
};
// epoll使用的参数
struct ep_arg
{
int sockfd; // socketfd
async_result_cb cb; //处理返回结果用的回调函数
};
// 创造dns协议中的header
int dns_create_header(struct dns_header *header)
{
if (header == NULL)
return -1;
memset(header, 0, sizeof(struct dns_header));
srandom(time(NULL));
header->id = random();
header->flags |= htons(0x0100);
header->qdcount = htons(1);
return 0;
}
// 创造dns协议中的question
int dns_create_question(struct dns_question *question, const char *hostname)
{
if (question == NULL)
return -1;
memset(question, 0, sizeof(struct dns_question));
question->qname = (char *)malloc(strlen(hostname) + 2);
if (question->qname == NULL)
return -2;
question->length = strlen(hostname) + 2;
question->qtype = htons(1);
question->qclass = htons(1);
const char delim[2] = ".";
char *hostname_dup = strdup(hostname);
char *token = strtok(hostname_dup, delim);
char *qname_p = question->qname;
while (token != NULL)
{
size_t len = strlen(token);
*qname_p = len;
qname_p++;
strncpy(qname_p, token, len + 1);
qname_p += len;
token = strtok(NULL, delim);
}
free(hostname_dup);
return 0;
}
// 创造dns请求
int dns_build_request(struct dns_header *header, struct dns_question *question, char *request)
{
int header_s = sizeof(struct dns_header);
int question_s = question->length + sizeof(question->qtype) + sizeof(question->qclass);
int length = question_s + header_s;
int offset = 0;
memcpy(request + offset, header, sizeof(struct dns_header));
offset += sizeof(struct dns_header);
memcpy(request + offset, question->qname, question->length);
offset += question->length;
memcpy(request + offset, &question->qtype, sizeof(question->qtype));
offset += sizeof(question->qtype);
memcpy(request + offset, &question->qclass, sizeof(question->qclass));
return length;
}
static int is_pointer(int in)
{
return ((in & 0xC0) == 0xC0);
}
// fcntl系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性
// 开启非阻塞
static int set_block(int fd, int block)
{
// 查询fd状态
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
return flags;
// 开启非阻塞
if (block)
flags &= ~O_NONBLOCK;
else
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
return -1;
return 0;
}
// dns解析名字
static void dns_parse_name(unsigned char *chunk, unsigned char *ptr, char *out, int *len)
{
int flag = 0, n = 0, alen = 0;
char *pos = out + (*len);
while (1)
{
flag = (int)ptr[0];
if (flag == 0)
break;
if (is_pointer(flag))
{
n = (int)ptr[1];
ptr = chunk + n;
dns_parse_name(chunk, ptr, out, len);
break;
}
else
{
ptr++;
memcpy(pos, ptr, flag);
pos += flag;
ptr += flag;
*len += flag;
if ((int)ptr[0] != 0)
{
memcpy(pos, ".", 1);
pos += 1;
(*len) += 1;
}
}
}
}
fcntl函数的用法总结
// dns解析请求
static int dns_parse_response(char *buffer, struct dns_item **domains)
{
int i = 0;
unsigned char *ptr = buffer;
ptr += 4;
int querys = ntohs(*(unsigned short *)ptr);
ptr += 2;
int answers = ntohs(*(unsigned short *)ptr);
ptr += 6;
for (i = 0; i < querys; i++)
{
while (1)
{
int flag = (int)ptr[0];
ptr += (flag + 1);
if (flag == 0)
break;
}
ptr += 4;
}
char cname[128], aname[128], ip[20], netip[4];
int len, type, ttl, datalen;
int cnt = 0;
struct dns_item *list = (struct dns_item *)calloc(answers, sizeof(struct dns_item));
if (list == NULL)
{
return -1;
}
for (i = 0; i < answers; i++)
{
bzero(aname, sizeof(aname));
len = 0;
dns_parse_name(buffer, ptr, aname, &len);
ptr += 2;
type = htons(*(unsigned short *)ptr);
ptr += 4;
ttl = htons(*(unsigned short *)ptr);
ptr += 4;
datalen = ntohs(*(unsigned short *)ptr);
ptr += 2;
if (type == DNS_CNAME)
{
bzero(cname, sizeof(cname));
len = 0;
dns_parse_name(buffer, ptr, cname, &len);
ptr += datalen;
}
else if (type == DNS_HOST)
{
bzero(ip, sizeof(ip));
if (datalen == 4)
{
memcpy(netip, ptr, datalen);
inet_ntop(AF_INET, netip, ip, sizeof(struct sockaddr));
printf("%s has address %s\n", aname, ip);
printf("\tTime to live: %d minutes , %d seconds\n", ttl / 60, ttl % 60);
list[cnt].domain = (char *)calloc(strlen(aname) + 1, 1);
memcpy(list[cnt].domain, aname, strlen(aname));
list[cnt].ip = (char *)calloc(strlen(ip) + 1, 1);
memcpy(list[cnt].ip, ip, strlen(ip));
cnt++;
}
ptr += datalen;
}
}
*domains = list;
ptr += 2;
return cnt;
}
// 1. 异步请求池初始化Init
// a. epoll_create
// b. pthread_create
// 返回包含epoll生成fd的async_context
struct async_context *dns_async_client_init(void)
{
int epfd = epoll_create(1);
if (epfd < 0)
return NULL;
// C 库函数 void *calloc(size_t nitems, size_t size) 分配所需的内存空间,并返回一个指向它的指针。malloc 和 calloc 之间的不同点是,
// malloc 不会设置内存为零,而 calloc 会设置分配的内存为零。
struct async_context *ctx = calloc(1, sizeof(struct async_context));
if (ctx == NULL)
{
close(epfd);
return NULL;
}
ctx->epfd = epfd;
pthread_t thread_id;
int ret = pthread_create(&thread_id, NULL, dns_async_client_proc, ctx);
if (ret)
{
perror("pthread_create");
return NULL;
}
usleep(1); // child go first
return ctx;
}
static void dns_async_client_result_callback(struct dns_item *list, int count)
{
int i = 0;
for (i = 0; i < count; i++)
{
printf("name:%s, ip:%s\n", list[i].domain, list[i].ip);
}
}
// 2. commit
// a. socket
// b. connnect_server
// c. encode-->redis/mysql/dns
// d. send
// e. fd加入到epoll中-->epoll_ctl
int dns_async_client_commit(struct async_context *ctx, const char *domain, async_result_cb cb)
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("create socket failed\n");
exit(-1);
}
printf("url:%s\n", domain);
set_block(sockfd, 0); // nonblock
struct sockaddr_in dest;
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(53);
dest.sin_addr.s_addr = inet_addr(DNS_SVR);
int ret = connect(sockfd, (struct sockaddr *)&dest, sizeof(dest));
// printf("connect :%d\n", ret);
struct dns_header header = {0};
dns_create_header(&header);
struct dns_question question = {0};
dns_create_question(&question, domain);
char request[1024] = {0};
int req_len = dns_build_request(&header, &question, request);
int slen = sendto(sockfd, request, req_len, 0, (struct sockaddr *)&dest, sizeof(struct sockaddr));
struct ep_arg *eparg = (struct ep_arg *)calloc(1, sizeof(struct ep_arg));
if (eparg == NULL)
return -1;
eparg->sockfd = sockfd;
eparg->cb = cb;
struct epoll_event ev;
ev.data.ptr = eparg;
ev.events = EPOLLIN;
ret = epoll_ctl(ctx->epfd, EPOLL_CTL_ADD, sockfd, &ev);
return ret;
}
// 3. callback
// while(1)
// epoll_wait()
// recv()
// parse()
// fd-->epoll_ delete
static void *dns_async_client_proc(void *arg)
{
struct async_context *ctx = (struct async_context *)arg;
int epfd = ctx->epfd;
while (1)
{
struct epoll_event events[ASYNC_CLIENT_NUM] = {0};
int nready = epoll_wait(epfd, events, ASYNC_CLIENT_NUM, -1);
if (nready < 0)
{
if (errno == EINTR || errno == EAGAIN)
{
continue;
}
else
{
break;
}
}
else if (nready == 0)
{
continue;
}
printf("nready %d \n", nready);
int i = 0;
for (i = 0; i < nready; i++)
{
struct ep_arg *data = (struct ep_arg *)events[i].data.ptr;
int sockfd = data->sockfd;
char buffer[1024] = {0};
struct sockaddr_in addr;
size_t addr_len = sizeof(struct sockaddr_in);
int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&addr, (socklen_t *)&addr_len);
struct dns_item *domain_list = NULL;
int count = dns_parse_response(buffer, &domain_list);
// 回调函数
data->cb(domain_list, count);
int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
close(sockfd);
dns_async_client_free_domains(domain_list, count);
free(data);
}
}
}
// 4. destroy
// close(epfd);
// pthread_cancel(thid)
void dns_async_client_free_domains(struct dns_item *list, int count)
{
int i = 0;
for (i = 0; i < count; i++)
{
free(list[i].domain);
free(list[i].ip);
}
free(list);
}
int main(int argc, char *argv[])
{
struct async_context *ctx = dns_async_client_init();
if (ctx == NULL)
return -2;
int count = sizeof(domain) / sizeof(domain[0]);
int i = 0;
for (i = 0; i < count; i++)
{
dns_async_client_commit(ctx, domain[i], dns_async_client_result_callback);
}
getchar();
}
异步请求池代码下载