问题来了:

上一篇文章讲解了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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/time.h>#include <fcntl.h>#include <pthread.h>
#include <sys/epoll.h>
#include <errno.h>#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 socketchar *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.255sin.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 httpchar 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 epollstruct 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_idif (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异步请求实现完毕,接下来看看运行效果:

很快不阻塞的请求完毕,然后在线程中处理响应的数据。

http异步请求方式相关推荐

  1. Ajax(异步请求)和传统(同步请求)区别

    在服务器端判断request来自Ajax请求(异步)还是传统请求(同步): 两种请求在请求的Header不同,Ajax 异步请求比传统的同步请求多了一个头参数 1.传统同步请求参数 accept  t ...

  2. httpservletrequest 设置请求头_大部分程序员不知道的 Servelt3 异步请求,原来这么简单?

    前言 当一个 HTTP 请求到达 Tomcat,Tomcat 将会从线程池中取出线程,然后按照如下流程处理请求: 将请求信息解析为 HttpServletRequest 分发到具体 Servlet 处 ...

  3. 详解Spring框架的异步请求

    文章目录 详解Spring框架的异步请求 1.导入响应的jar包(gson) 2.前端请求 3.后端逻辑处理并返回结果 详解Spring框架的异步请求 在开发过程中有异步请求和同步请求之分. 同步请求 ...

  4. AJAX怎么实现同步请求?Ajax同步和异步请求有什么区别以及使用场景有哪些?

    一.AJAX怎么实现同步请求? ajax请求我们分为同步请求和异步请求,但是默认的都是异步请求,那么,当我们想用ajax同步请求时,我们该如何去实现这个同步请求呢?接下来的这篇文章就来给大家介绍一下关 ...

  5. $.ajax同步请求,异步请求

    jquery中ajax方法有个属性async用于控制同步和异步,默认是true,即ajax请求默认是异步请求,有时项目中会用到AJAX同步.这个同步的意思是当JS代码加载到当前AJAX的时候会把页面里 ...

  6. AJAX异步请求解决跨域问题的三种方式

    一 什么是跨域 出于浏览器的同源策略限制.同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响.可以说We ...

  7. JSP同步请求和html+ajax异步请求的两种方式

    war包:包括所有的项目资源,只要从浏览器发起的都是属于请求,然后把资源响应给浏览器,解析显示出来. 方式一:HTML+ajax(跳转静态html也是属于请求响应,把整个页面响应给浏览器.) html ...

  8. ajax是一种异步的请求方式,ajax异步请求的三种方式

    Ajax能够在无需加载整个页面的情况下,能够更新部分网页内容,可以减小服务器的资源浪费. ajax大体上有四种实现方式,由于基于JS的实现方式太过于复杂,基本上用不到,所以就暂不贴出其实现代码了. 1 ...

  9. jQuery实现Ajax异步请求的三种方式

    jQuery实现Ajax jQuery框架对js原生的ajax进行了封装,封装后的ajax相比原生就变的更加简洁方便,而且功能更加丰富 常用的三种ajax实现的方法: get:$.get(url,[d ...

最新文章

  1. ec20 复位命令_《EC20 — AT指令》
  2. 19. 邮件提醒(发送邮件)
  3. Apache Beam发布第一个稳定版本
  4. ArcGIS Server发布WFS中文图层名称乱码问题解决方案
  5. vuecli启动的服务器位置,VUE启动流程vue-cli
  6. mac下使用自带的apache与php
  7. 12v小型电机型号大全_伊藤8KW静音柴油发电机YT8100T型号规格
  8. 多次面试美团后,我整理了这几个必备的技术栈!
  9. seo外链工具是什么?外链工具有用吗?
  10. 飞机大战-玩家飞机被击中
  11. linux v4l2-ctl,V4L2总结
  12. 多表连接查询与高级查询上(第三天)
  13. 对数似然比LLR公式的问题
  14. UEBA架构设计之路1
  15. 人脸实名认证实现方案(微信H5百度云篇)
  16. Git使用时无.ssh目录:/.ssh: No such file or directory
  17. window对象的方法
  18. oracle中使用online,batch(Oracle+shell)及Online(web即Java)使用场景区分(1)
  19. 绝望的主妇,四位时尚女皇
  20. oracle 孙帅_求个大佬指点一下,0基础想自学一下java,哪怕入个门,该如何开始?...

热门文章

  1. ae中用粒子系统做的特效怎么循环
  2. AutoCAD入门基本操作
  3. 搭建智能语音交互系统
  4. 手机钢琴键盘模拟器好用吗?
  5. PayPal 国际支付接口安全可行性平台(电子商务)集成解决方案
  6. B站韩顺平java学习笔记(八)-- 房屋出租系统(项目)章节
  7. 5天学会Linux(实操练手+最全教程) Day1 环境搭建
  8. 心理小测试c语言,基础心理学测试题(无答案)
  9. Osg文字的固定大小和固定朝向设置
  10. MySQL JSON 函数