异步处理
异步这个词,好像听起来简单,但真的理解起来就挺复杂的。
从一个生活的例子谈起:早上有这么几件事,刷牙洗脸,上厕所,烧开水,泡面,那么现在怎么做。
方案一:
顺序做:假设刷牙洗脸10分钟,上厕所5分钟,烧开水5分钟,泡面3分钟,那么这样就是23分钟。
方案二:
先烧开水,然后上厕所,然后刷牙洗脸,然后泡面,这样就是18分钟。
方案一就是程序中的同步操作,方案二就是程序中的异步思想,大概能知道到底什么是异步了吧。
异步就是:有多个任务要完成,任务都是耗时的,不用去等待任务的执行结果,就去继续做另一个任务,这样就可以更加高效的利用时间了,对于程序来说就是高性能的。
为什么需要异步呢?
刚才其实已经回答过这一个问题了,就是为了性能,异步的性能是要比同步操作高的,所以需要异步。
程序中如何异步
在计算机世界中,有很多这样的应用,异步消息队列、异步I/O,异步请求…,总之我的理解异步就是同一个时间里做着很多件事,也就是说这些事情或者说任务完成起来就会很快,性能就会很高。
这里给出一个异步请求的解决方案:比如redis异步请求,http异步请求,mysql异步请求,都可以,只要你频繁进行了请求,又需要性能,那么就可以考录异步实现。应该怎么做。
请求以后,服务端会执行,然后把结果返回回来,然后再去处理,这里怎么办呢,就是找那个耗时的操作,当然是服务端返回结果的耗时啦,我们想着在这个时间里再去干点啥,比如继续发请求,而不是傻傻的等在那里,所以我们这时候就应该这么干。
- 开启一个线程专门去处理响应的结果;
- 主线程不用阻塞等待处理结果,发完请求以后就继续发下一条请求。
完事,听起来很简单吧,是的,确实就是这么简单,我们通过一个具体的http爬虫小例子来感受一下异步http请求应该怎么办。
这里访问的是一个天气网站,通过C语言实现。
http异步爬虫小例子思路:
- 初始化操作,我们需要一个上下文对象,来传递主线程和处理结果线程的一些公共参数,开启工作线程,工作线程中通过epoll来监听连接的sockefd,执行处理结果。
- 发起请求,需要指定这个请求对应响应的回调函数,这就是异步的思想,我不知道啥时候响应,所以响应来了,我应该要干点啥,所以这个干点啥的操作我得提前告诉你,也就是通过回调函数的思想实现,在请求的时候就指定回调函数。
- 继续发请求就可以了,因为,响应有另外一个线程在处理。
代码实现:
通过:gcc -o req xxx.c -lptread 编译完成即可执行
/**************************************************
*一个异步处理的框架简单实现
*封装完了可以适用于各种异步请求
*yc 2020-1-15
*用一个简单的异步http模拟一下
*
*
***************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> /* close */
#include <netdb.h>
#include<pthread.h>
#include <sys/epoll.h>
#define ASYNC_CLIENT_NUM 1024
#define HOSTNAME_LENGTH 128
#define HTTP_VERSION "HTTP/1.1"
#define USER_AGENT "User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2\r\n"
#define ENCODE_TYPE "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
#define CONNECTION_TYPE "Connection: close\r\n"
#define BUFFER_SIZE 1024
#define HOSTNAME_SIZE 255
#define EPOLL_EVENT_SIZE 1024
//回调函数
typedef void (*async_result_callback)(const char* hostname,char* result);
//上下文
struct async_context
{
int epfd;
pthread_t thread_id;
};
//事件传递参数的结构体
struct ev_arg{
int sockfd;
char hostname[HOSTNAME_SIZE];
async_result_callback cb;
};
//域名解析函数
static char* host_to_ip(const char* hostname)
{
//注意这个函数不可重入,也就是说只能在单线程中使用
struct hostent* hostip = gethostbyname(hostname);
if(hostip)
{ //将int型ip转换成192.168.13.11得形式
return inet_ntoa(*(struct in_addr*)*hostip->h_addr_list);
}
else{
return NULL;
}
}
int http_async_client_callback(void* context)
{
struct async_context* ctx = (struct async_context*) context;
//线程循环异步去处理请求结果
while(1)
{
//创建event数组,接收返回的event
struct epoll_event events[EPOLL_EVENT_SIZE] = {0};
int count = epoll_wait(ctx->epfd,events,EPOLL_EVENT_SIZE,-1);
if(count < 0)
{
if(errno == EINTR || errno == EAGAIN)
{
continue;
}else
{
break;
}
}else if(count == 0)
{
continue;
}
//处理接收的结果
for(int i = 0;i < count; i++)
{
struct ev_arg* arg = (struct ev_arg*)events[i].data.ptr;
char buffer[BUFFER_SIZE] = {0};
int len = recv(arg->sockfd,buffer,BUFFER_SIZE,0);
//回调结果的处理函数
arg->cb(arg->hostname,buffer);
//由于短连接所以释放
int ret = epoll_ctl(ctx->epfd,EPOLL_CTL_DEL,arg->sockfd,NULL);
close(arg->sockfd);
free(arg);//记得释放
}
}
}
//发起请求
int http_async_client_request(struct async_context* ctx,const char* hostname,const char* resource,async_result_callback cb)
{
char* ip = host_to_ip(hostname);
if(NULL == ip) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in sin = {0};
sin.sin_addr.s_addr = inet_addr(ip);
sin.sin_port = htons(80);
sin.sin_family = AF_INET;
//连接
if(-1 == connect(sockfd,(struct sockaddr*)&sin,sizeof(struct sockaddr_in)))
{
perror("connect!");
return -1;
}
//将socket设置成非阻塞的
fcntl(sockfd,F_SETFL,O_NONBLOCK);
//构建http请求
char buffer[BUFFER_SIZE] = {0};
int len = sprintf(buffer,
"GET %s %s\r\n\
Host: %s\r\n\
%s\r\n\
\r\n",
resource, HTTP_VERSION,
hostname,
CONNECTION_TYPE
);
//发送请求
int slen = send(sockfd,buffer,strlen(buffer),0);
//将请求的sockfd添加到epoll上
//构建一个结构体传递所有的参数
struct ev_arg* evarg = (struct ev_arg*) calloc(1,sizeof(struct ev_arg));
if(NULL == evarg) return -1;
evarg->sockfd = sockfd;
strcpy(evarg->hostname,hostname);
evarg->cb = cb;
struct epoll_event ev = {0};
ev.data.ptr = evarg;
//读事件
ev.events = EPOLLIN;
int ret = epoll_ctl(ctx->epfd,EPOLL_CTL_ADD,sockfd,&ev);
return ret;
}
static struct async_context* http_async_client_init(void)
{
struct async_context* ctx = (struct async_context*)calloc(1,sizeof(struct async_context));
if(NULL == ctx) return NULL;
int epfd = epoll_create(1);
if(epfd < 0)
{
return NULL;
}
ctx->epfd = epfd;
int ret = pthread_create(&ctx->thread_id,NULL,(void*)http_async_client_callback,(void*)ctx);
if(ret)
{
perror("pthread_create!");
close(epfd);
free(ctx);
return NULL;
}
return ctx;
}
static void http_async_client_uninit(struct async_context* ctx)
{
close(ctx->epfd);
pthread_cancel(ctx->thread_id);
free(ctx);
}
//结果处理函数
static void result_callback(const char* hostname,char* result)
{
printf("hostname:%s,result:%s\n",hostname,result);
}
struct http_request {
char *hostname;
char *resource;
};
struct http_request reqs[] = {
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=beijing&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=changsha&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=shenzhen&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=shanghai&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=tianjin&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=wuhan&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=hefei&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=hangzhou&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=nanjing&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=jinan&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=taiyuan&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=wuxi&language=zh-Hans&unit=c" },
{"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=suzhou&language=zh-Hans&unit=c" },
};
int main(int argc,char* argv[])
{
struct async_context* ctx = http_async_client_init();
if(ctx == NULL) return -1;
int count = sizeof(reqs)/sizeof(reqs[0]);
for(int i = 0;i < count;i++)
{
http_async_client_request(ctx,reqs[i].hostname,reqs[i].resource,result_callback);
}
//不加的话会直接结束的
getchar();
http_async_client_uninit(ctx);
}