池式结构(4) 请求池

本文主要讲异步请求池的实现和原理。

什么是异步?和阻塞、非阻塞有什么区别?

同步:

一请求就等返回结果,没有等到就一直等

异步:

只管发送请求,进程不需要一直等下去, 而是继续执行下面的操作
阻塞和非阻塞是针对fd的状态,有数据就返回,没有数据就挂起,这是阻塞状态;非阻塞就压根不管,直接返回。
阻塞【挂起】和非阻塞是线程的一种状态,同步和异步是指的是线程执行方法的一种方式,当然同步执行时,一般都伴随着线程的阻塞。

背景:

客户端与mysql服务器连接后,发送完一个请求没有等到返回结果,要是再要发送请求,只能新建一个与mysql的fd连接。
我们现在要让请求和回复解耦合,而且要避免fd连接会阻塞其他的连接。那么fd要用epoll管理。

如何设计?

1、commit

(1)建立网络连接
(2)建立连接后,组织好对应的协议数据
(3)send发送数据到对应的服务器
(4)监控fd是否可读,并将fd加入epoll监控

2、init

其工作主要内容是:初始化异步操作的上下文,上下文指的是epollfd和线程id;pthread_create开启一个线程,处理所有fd的response;

3、callback函数

其为线程的入口函数,处理所有fd的response。 epoll_wait会获取到可读的fd,通过recv(fd)读出所有的协议数据,将其解析并进行相应的操作;

4、destroy

close(fd)
pthread_cancel;

注意:以往的epoll都是再服务器端实现的,本次是epoll作为客户端来做。

代码实现:

相关结构体:

//上下文
struct async_context {
	int epfd; //epollfd
	pthread_t threadid; //线程id
};

//存储fd和相关会用到的参数
struct ep_arg {
	int sockfd;
	async_result_cb cb;
};

init初始化:

// 1 . context 
// 2 . return context;
// 初始化 做法两种:(1)将上下文当作参数传入;(2)初始化函数返回上下文
//一般当作参数传比较好
struct async_context* dns_async_client_init(void) {

	int epfd = epoll_create(1);
	if (epfd < 0) return NULL;

	//分配一个上下文,上下文初始化
	struct async_context* ctx = calloc(1, sizeof(struct async_context));
	if (ctx == NULL) return NULL;

	ctx->epfd = epfd;
	
	//response的回调函数,线程初始化
	int ret = pthread_create(&ctx->threadid, NULL, dns_async_callback, ctx);
	if (ret) {
		close(epfd);
		free(ctx);
		return NULL;
	}

	return ctx;
}

response的回调函数:

#define ASYNC_EVENTS		128
//response的回调函数
void *dns_async_callback(void *arg) {

	//上下文
	struct async_context* ctx = (struct async_context*)arg;

	//不断的去接收
	while (1) {
		struct epoll_event events[ASYNC_EVENTS] = {0};
		
		int nready = epoll_wait(ctx->epfd, events, ASYNC_EVENTS, -1);
		if (nready < 0) {
			continue;
		}

		int i = 0;
		for (i = 0;i < nready;i ++) {

			struct ep_arg *ptr = events[i].data.ptr;
			int sockfd = ptr->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);
				
			printf("recvfrom n : %d\n", n);
			struct dns_item *domains = NULL;
			//回调函数里进行解析,已经保证数据接收完了
			int count = dns_parse_response(buffer, &domains);

			ptr->cb(domains, count);

			// sockfd 关闭fd
			close (sockfd);

			free(ptr);
			// epollout --> 
			
			//epoll_ctl(ctx->epfd, EPOLL_CTL_MOD, sockfd, NULL);
			
			//
			

		}

	}

}

commit:

/ 提交代码,可以理解为请求
//传入上下文,回调函数
int dns_async_client_commit(struct async_context *ctx, async_result_cb cb) {

	//1、建立好socket,
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0) {
		perror("create socket failed\n");
		exit(-1);
	}

	printf("url:%s\n", domain);

	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);
	
	//2、建立好连接
	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);
	//3、准备好协议
	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 *ptr = calloc(1, sizeof(struct ep_arg));
	if (ptr == NULL) return -1;
	ptr->sockfd = sockfd;
	ptr->cb = cb;
	
	//4、将IO加入到epoll中,关注其是否可读
	struct epoll_event ev;
	ev.data.ptr = ptr; //可传
	ev.events = EPOLLIN;
	epoll_ctl(ctx->epfd, EPOLL_CTL_ADD, sockfd, &ev);

	return 0;
}

destroy:

//销毁函数
int dns_async_client_destroy(struct async_context* ctx) {

	close(ctx->epfd);
	pthread_cancel(ctx->threadid);
	
} 

思考:

问题1:
response处理完收到的数据之后,fd是否要关闭?
1、关闭可以
close(fd);
将fd从epoll中移除掉:epoll_ctl(ctx->epfd, EPOLL_CTL_DEL,sockfd,NULL);
2、不关闭如何处理?
内部有些资源可重用,不关闭,哪里可以重用,如何用?
a.fd要存储,存储在epoll中,可以改成EPOLL_CTL_MOD。改成可写,sendto会使用。这个fd会一直触发可写,不能 这样操作。应该改成边缘触发。sendcb();改成边缘触发来做
b. sockfd长时间没有用,设置超时,要有时间的管理,超时就关闭

问题2:
若send的数据丢失了,response一直没有返回(epoll没有触发),fd要如何处理?
1、加入fd定时器,超时就重发;

面试题:

异步的mysql、redis怎么做?
回答四元组,异步请求池。
1、commit函数;
2、init;
3、callback;
4、destroy
然后根据业务场景,讲协议组织起来,将其发送出去;收到response的地方,处理协议即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值