从C语言异步爬虫小例子-想想异步操作

异步处理

异步这个词,好像听起来简单,但真的理解起来就挺复杂的。

从一个生活的例子谈起:早上有这么几件事,刷牙洗脸,上厕所,烧开水,泡面,那么现在怎么做。

方案一:

顺序做:假设刷牙洗脸10分钟,上厕所5分钟,烧开水5分钟,泡面3分钟,那么这样就是23分钟。

方案二:

先烧开水,然后上厕所,然后刷牙洗脸,然后泡面,这样就是18分钟。

方案一就是程序中的同步操作,方案二就是程序中的异步思想,大概能知道到底什么是异步了吧。

异步就是:有多个任务要完成,任务都是耗时的,不用去等待任务的执行结果,就去继续做另一个任务,这样就可以更加高效的利用时间了,对于程序来说就是高性能的。

为什么需要异步呢?

刚才其实已经回答过这一个问题了,就是为了性能,异步的性能是要比同步操作高的,所以需要异步。

程序中如何异步

在计算机世界中,有很多这样的应用,异步消息队列、异步I/O,异步请求…,总之我的理解异步就是同一个时间里做着很多件事,也就是说这些事情或者说任务完成起来就会很快,性能就会很高。

这里给出一个异步请求的解决方案:比如redis异步请求,http异步请求,mysql异步请求,都可以,只要你频繁进行了请求,又需要性能,那么就可以考录异步实现。应该怎么做。

请求以后,服务端会执行,然后把结果返回回来,然后再去处理,这里怎么办呢,就是找那个耗时的操作,当然是服务端返回结果的耗时啦,我们想着在这个时间里再去干点啥,比如继续发请求,而不是傻傻的等在那里,所以我们这时候就应该这么干。

  1. 开启一个线程专门去处理响应的结果;
  2. 主线程不用阻塞等待处理结果,发完请求以后就继续发下一条请求。

完事,听起来很简单吧,是的,确实就是这么简单,我们通过一个具体的http爬虫小例子来感受一下异步http请求应该怎么办。

这里访问的是一个天气网站,通过C语言实现。

http异步爬虫小例子思路:
  1. 初始化操作,我们需要一个上下文对象,来传递主线程和处理结果线程的一些公共参数,开启工作线程,工作线程中通过epoll来监听连接的sockefd,执行处理结果。
  2. 发起请求,需要指定这个请求对应响应的回调函数,这就是异步的思想,我不知道啥时候响应,所以响应来了,我应该要干点啥,所以这个干点啥的操作我得提前告诉你,也就是通过回调函数的思想实现,在请求的时候就指定回调函数。
  3. 继续发请求就可以了,因为,响应有另外一个线程在处理。

代码实现:
通过: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);


}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值