1 前言
请求池出现的运用场景,主要是为了解决同步处理带来的阻塞导致效率的大大降低,异步请求池将请求事件和接收事件结果解析过程放到不同线程,大大的减少了中间阻塞等待的时间,从而提升事件处理效率,在多业务处理情况下,具有极其重要的意义。
2 异步请求池原理
2.1 基本原理
异步请求池的原理,主要是利用两个线程将发送请求和接收服务器回应两个事件分开,这样可以减少中间耗时操作的等待。具体操作是发送完请求后将soketfd加入到epoll队列,接收线程在epoll_wait中等待epoll队列的事件,接收到fd事件后,读取响应数据分发到具体的业务处理函数处理。
2.2 结构体设计
上下文定义主要包括:发送对象,发送数据,epoll对象和线程id;发送数据包括,数据和长度;
接收数据包括,soketfd和响应回调函数。
//响应回调函数
typedef void (*result_callback)(void *data, int len);
//发送数据
typedef struct rp_send_data {
unsigned char *data;
int len;
}rp_send_data;
//接收数据
typedef struct rp_recv_data {
int sfd;
result_callback re_cb;
}rp_recv_data;
//上下文
typedef struct rp_context {
int epfd;
pthread_t thid;
struct sockaddr_in dst;
rp_send_data sdata;
}rp_context;
3 请求池实现
3.1 请求池创建
请求池创建,主要工作包括创建epoll对象和请求池的接收响应线程,并将数据句柄放到上下文中。
int rp_pool_create(rp_context *ctx)
{
if(ctx == NULL) return -1;
int epfd = epoll_create(1);
if(epfd < 0) return -2;
ctx->epfd = epfd;
pthread_t thread_id;
int ret = pthread_create(&thread_id, NULL, rp_pool_response, ctx);
if(ret)
{
close(epfd);
printf("phtead_create failed!\n");
ctx->thid = 0;
return -3;
}
ctx->thid = thread_id;
return 0;
}
3.2 请求池销毁
请求池释放,需要释放epoll对象和响应线程。
int rp_pool_destory(rp_context *ctx)
{
if(ctx->thid)
pthread_cancel(ctx->thid);
if(ctx->epfd)
close(ctx->epfd);
return 0;
}
3.3 请求响应函数
请求响应函数是响应线程的主函数,利用epoll_wait等待对方的请求响应事件,接收到数据后,利用回调函数进行事件的处理。
void *rp_pool_response(void *arg)
{
rp_context *ctx = (rp_context *)arg;
if(ctx == NULL) return NULL;
printf("epoll epfd:%d\n", ctx->epfd);
while(1)
{
struct epoll_event ev[EPOLL_EVENTS_MAX];
int nready = epoll_wait(ctx->epfd, ev, EPOLL_EVENTS_MAX, -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++)
{
if(ev[i].events == EPOLLIN)
{
rp_recv_data *arg = (rp_recv_data *)ev[i].data.ptr;
struct sockaddr_in src;
size_t sock_len = sizeof(struct sockaddr_in);
int sockfd = 0;
unsigned char buf[RECV_BUFFER_LEN] = {0};
int len = 0;
sockfd = arg->sfd;
len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&src, (socklen_t*)&sock_len);
if(len < 0)
{
perror("recvfrom:");
continue;
}
// printf("recv len:%d\n", len);
arg->re_cb(buf, len);
int ret = epoll_ctl(ctx->epfd, EPOLL_CTL_DEL, sockfd, NULL);
//printf("epoll_ctl DEL --> sockfd:%d\n", sockfd);
close(sockfd);
free(arg);
}
}
}
return NULL;
}
3.3 请求函数
请求函数是需要根据不同的应用场景进行,可能发生在不同的业务中,主要功能是创建非阻塞的soket对象,并将事先处理好的数据发送到目标服务器上,需要注意的是发送后要将soketfd加入到epoll队列管理。
int rp_pool_commit(rp_context *ctx)
{
//socket create
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(socket_fd < 0)
{
perror("socket:");
return -1;
}
set_block(socket_fd, 0); //nonblock
int ret = connect(socket_fd, (struct sockaddr *)&ctx->dst, sizeof(struct sockaddr_in));
if(ret)
{
perror("connect:");
return -1;
}
#if 0
char ip[64] = {0};
inet_ntop(AF_INET , &ctx->dst.sin_addr , ip , sizeof(struct sockaddr));
printf("dst ip:%s\n", ip);
#endif
//send
// printf("sendto data:%p,len:%d\n", ctx->sdata.data, ctx->sdata.len);
ret = sendto(socket_fd, ctx->sdata.data, ctx->sdata.len, 0, (struct sockaddr *)&ctx->dst, sizeof(struct sockaddr_in));
if(ret < 0)
{
perror("sendto:");
return -2;
}
rp_recv_data *rd = (rp_recv_data *)malloc(sizeof(rp_recv_data));
if(rd == NULL) return -1;
rd->sfd = socket_fd;
rd->re_cb = dns_result_response;
//epoll add
struct epoll_event ev;
ev.data.ptr = rd;
ev.events = EPOLLIN;
ret = epoll_ctl(ctx->epfd, EPOLL_CTL_ADD, socket_fd, &ev);
// printf(" epoll_ctl ADD: sockfd->%d, ret:%d\n", socket_fd, ret);
if(ret) return -4;
return 0;
}
具体实现移步:myreqpool.c