当业务服务器访问需要等待的服务时,业务访问线程需要等待挂起直到该服务给出反馈,例如对mysql,redis,http,dns等服务的请求,这些请求的返回需要等待一段时间,大大加深了业务服务器的承载压力,也会影响其总体性能,异步请求池就是为解决这个问题应运而生的。
异步请求池需要考虑下列问题:
1.异步请求使用一个链接还是多个链接
2.链接的网络IO的管理问题
3.既然是多个链接,发完请求后响应没有接受之前,fd存储在哪里
4.请求和响应能否做在一个线程之中
为解决以上问题,特提出一下设计思想:
作为业务服务器对请求池commit请求
1.业务服务器与被请求方建立网络链接
2.组织好对应的协议,例如dns协议或其他协议
3.把请求发送到对应的服务器
4.把fd加入到epoll,关注 其是否可读
示例代码如下:
//dns_async_client_commit(ctx, domain)
//socket init
//dns_request
//sendto dns send
int dns_async_client_commit(struct async_context* ctx, const char *domain, async_result_cb cb) {
//fd创建
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;
//关注fd可写
ret = epoll_ctl(ctx->epfd, EPOLL_CTL_ADD, sockfd, &ev);
//printf(" epoll_ctl ADD: sockfd->%d, ret:%d\n", sockfd, ret);
return ret;
}
请求池初始化
1.请求池上下文初始化
2.epoll_create 创建
3.线程创建
示例代码如下:
//异步请求上下文
struct async_context {
int epfd;
};
//异步请求池初始化
struct async_context *dns_async_client_init(void) {
//epoll创建
int epfd = epoll_create(1); //
if (epfd < 0) return NULL;
//异步请求上下文初始化
struct async_context *ctx = calloc(1, sizeof(struct async_context));
if (ctx == NULL) {
close(epfd);
return NULL;
}
//上下文关注epfd
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;
}
请求响应线程回调
1.epoll_wait关注fd可读事件
2.触发可读时读取请求返回的数据
示例代码如下:
//dns_async_client_proc()
//epoll_wait
//result callback
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); //call cb
int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
//printf("epoll_ctl DEL --> sockfd:%d\n", sockfd);
close(sockfd); /
dns_async_client_free_domains(domain_list, count);
free(data);
}
}
}
最后就是回收操作
1.关闭epfd
2.线程就结束回收