问题来了:
上一篇文章讲解了http的同步请求,如果需要在主线程中做多个http同步请求,必定阻塞耗费大量的时间,严重影响用户体验。那么问题来了,该如何解决呢?
解决方案:
此时我们可以将http同步请求改进为http异步请求,如下图所示:

该如何实现?
下面的任务就是该如何实现http的异步请求,这里有如下几个步骤:
1、将send与recv分离在两个线程,那么send就不会出现同步方式那样的阻塞(当然异步模式下sockfd需要设置为非阻塞的)
2、在send线程中,send之后,立即将该sockfd添加到epoll中,并关注 EPOLLIN事件
3、创建一个线程,不停的去epoll_wait监听关注的事件,一旦有就绪的fd并且事件是关注的事件则开始处理
这样的话client这边就不用阻塞的等待上一个请求响应成功后再进行下一个请求,而仅仅只需要将请求sockfd用epoll管理,同时在创建的线程中不断epoll_wait处理连接即可。这么做的话效率会高很多。
这里将上述步骤抽象为如下 4 个函数:
- http_aysnc_client_init // 该函数用于异步http客户端初始化,里边会创建epoll,同时创建一个线程不停的epoll_wait;
- http_async_client_uninit // 该函数用于反初始化
- http_async_client_commit // 用于提交http请求,里边就是组织http请求属于,通过send发送
- http_async_client_callback // 这里就是上述线程的处理函数,里边会不停的epoll_wait,并处理
预定义的头文件以及宏定义:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFFER_SIZE 4096 #define HOSTNAME_LENGTH 128 #define HTTP_VERSION "HTTP/1.1" #define USER_AGENT "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36\r\n" #define CONNECTION_TYPE "Connection: close\r\n" #define ACCEPT_TYPE "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n" #define ACCEPT_ENCODE "Accept-Encoding: gzip, deflate, br\r\n" #define LOG printf
下面首先实现 http_async_client_init,做两件事:创建epfd,并且创建一个线程,在线程里边实现epoll_wait轮询clientfd是否就绪有数据可读。
struct async_context * http_async_client_init(void) { printf("http_async_client_init\n"); int epfd = epoll_create(1); // calloc在动态分配完内存后,自动初始化该内存空间为零 struct async_context *ctx = calloc(1, sizeof(struct async_context)); // async_context 包含两个成员:epfd 和 线程id(用来处理异步回调) if (ctx == NULL) { close(epfd); return NULL; } ctx->epfd = epfd; int ret = pthread_create(&ctx->thread_id, NULL, http_async_client_callback, ctx); if (ret) { perror("pthread_create"); free(ctx); return NULL; } return ctx; }
http_async_client_uninit反初始化做一些清理工作:
int http_async_client_uninit(struct async_context *ctx) { close(ctx->epfd); pthread_cancel(ctx->thread_id); }
下边是关键的http请求函数 http_async_client_commit ,用于发送http请求,这里实现的是Get方法,获取天气信息。发送数据后并不立即recv,而是添加到epoll管理,监听EPOLLIN事件,如果有有数据了再去读,相比同步recv阻塞等待性能高不少。
typedef void (*async_result_cb)(const char *hostname, const char *result); char * host_to_ip(const char *hostname) { // gethostbyname 可以将域名转换为ip // gethostbyname 不可以重入,只适合在单线程不适合多线程 // gethostbyname_r 线程安全,但是该函数对ipv6的支持不强 // getaddrinfo 解决ipv6的支持 struct hostent *host_entry = gethostbyname(hostname); if (host_entry) { return inet_ntoa(*(struct in_addr *)*host_entry->h_addr_list); // host_entry先与 "->" 结合后 再与 “*” 结合,如果是多网卡则这里需要一次遍历h_addr_list列表 }else { return NULL; } return NULL; } int http_async_client_commit(struct async_context *ctx, char *hostname, char *resource, async_result_cb cb) { / // init socket char *ip = host_to_ip(hostname); LOG("ip:%s\n", ip); int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in sin = {0}; sin.sin_addr.s_addr = inet_addr(ip); // inet_aton(ip, &sin.sin_addr); inet_addr不能识别255.255.255.255 sin.sin_port = htons(80); sin.sin_family = AF_INET; int iRet = connect(sockfd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)); if (iRet != 0) { return -1; } // 创建sockfd设置为非阻塞的 fcntl(sockfd, F_SETFL, O_NONBLOCK); // 同步方式下这里不设置,默认就是阻塞,异步方式下必须设置为非阻塞的。 // encode 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 ); // send send(sockfd, buffer, strlen(buffer), 0); struct ep_arg *eparg = (struct ep_arg*)calloc(1, sizeof(struct ep_arg)); if (eparg == NULL) return -1; eparg->sockfd = sockfd; eparg->cb = cb; // add epoll struct epoll_event ev; ev.events = EPOLLIN; ev.data.ptr = eparg; epoll_ctl(ctx->epfd, EPOLL_CTL_ADD, sockfd, &ev); return iRet; }
提交了请求后,将sockfd加入epoll对象管理起来,接下来就为了需要开启一个线程,不停的epoll_wait有哪些sockfd连接成功并收到了server的响应。实现http_async_client_callback函数:
struct async_context { int epfd; pthread_t thread_id; }; struct ep_arg { int sockfd; char hostname[HOSTNAME_LENGTH]; async_result_cb cb; }; static void *http_async_client_callback(void *arg) { struct async_context *ctx = (struct async_context *)arg; int epfd = ctx->epfd; while (1) { struct epoll_event events[1024]; int nready = epoll_wait(epfd, events, 1024, -1); printf("nready = %d\n", nready); if (nready < 0) { if (errno == EINTR || errno == EAGAIN) { continue; } else { break; } } else if (nready == 0) { continue; } int i = 0; for (i = 0; i < nready; i++) { struct ep_arg *data = (struct ep_arg*)events[i].data.ptr; int sockfd = data->sockfd; LOG("sockfd:%d\n", sockfd); char buffer[BUFFER_SIZE] = {0}; int n = recv(sockfd, buffer, BUFFER_SIZE, 0); if (n > 0) { LOG("response:%s\n", buffer); epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL); close(sockfd); free(data); } } } }
我们在main函数中调用方式
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" }, }; static void http_async_client_result_callback(const char *hostname, const char *result) { printf("hostname:%s, result:%s\n", hostname, result); } int main(int argc, const char *argv[]) { struct async_context *ctx = http_async_client_init(); // struct aysnc_context中包含 epfd和pthread_id if (ctx == nullptr) return -1; int count = sizeof(reqs) / sizeof(reqs[0]); for (int i = 0; i < count; i++) { http_async_client_commit(ctx, reqs[i].hostname, reqs[i].resouce, http_async_client_result_callback); } getchar(); }
OK,至此http异步请求实现完毕,接下来看看运行效果:
很快不阻塞的请求完毕,然后在线程中处理响应的数据。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/209057.html原文链接:https://javaforall.net
