http异步请求方式

http异步请求方式问题来了 上一篇文章讲解了 http 的同步请求 如果需要在主线程中做多个 http 同步请求 必定阻塞耗费大量的时间 严重影响用户体验 那么问题来了 该如何解决呢 解决方案 此时我们可以将 http 同步请求改进为 http 异步请求 如下图所示 该如何实现 下面的任务就是该如何实现 http 的异步请求 这里有如下几个步骤 1 将 send 与 recv 分离在两个线程 那么 send 就不

问题来了:

上一篇文章讲解了http的同步请求,如果需要在主线程中做多个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异步请求实现完毕,接下来看看运行效果:

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

http异步请求方式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

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

(0)
上一篇 2026年3月19日 上午10:08
下一篇 2026年3月19日 上午10:08


相关推荐

  • HBase面试题总结1「建议收藏」

    HBase面试题总结1「建议收藏」hbase的特点是什么??1)hbase是一个分布式的基于列式存储的数据库,基于Hadoop的hdfs存储,zookeeper管理。2)hbase适合存储半结构化和非结构化数据,对于结构化数据字段不够确定或者杂乱无章很难按一个概念去抽取数据;3)hbase为空的纪录不会被存储;4)基于的表包含rowkey,时间戳,列族,新写入数据时,时间戳更新,同时可以查询到以前的版本;5)hbase是…

    2022年5月8日
    59
  • 史上最全ASCII码对照表0-255(%d)

    史上最全ASCII码对照表0-255(%d)十进制代码 十六进制代码 MCS字符或缩写 DEC多国字符名 ASCII控制字符1 0 0 NUL 空字符 1 1 SOH 标…

    2022年6月24日
    58
  • linux下ant安装和使用教程,ant安装与简单应用

    linux下ant安装和使用教程,ant安装与简单应用ant安装与简单应用1、下载ant软件包,本次下载的是apache-ant-1.9.6-bin.tar.gz2、传到Linux服务器上,我传到/usr/local/下3、解压缩,并创建软连接[root@localhostlocal]#tarxfapache-ant-1.9.6-bin.tar.gz[root@localhostlocal]#ln-svapache-ant-1.9.6…

    2022年7月24日
    8
  • BoundsChecker

    BoundsChecker

    2021年8月12日
    61
  • hibernate二级缓存策略

    hibernate二级缓存策略ibernate二级缓存策略2008-08-0111:00相关文章:关于HibernateCache数据库对象的缓存策略Spring+Hibernate缓存不起作用推荐圈子:JBPM@net更多相关推荐很多人对二级缓存都不太了解,或者是有错误的认识,我一直想写一篇文章介绍一下hibernate的二级缓存的,今天终于忍不住了。我的经验主要来自hi

    2022年5月23日
    34
  • Numpy数字类型 dtype「建议收藏」

    Numpy数字类型 dtype「建议收藏」dtype

    2022年6月12日
    39

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号