Linux(程序设计):65---同步HTTP请求异步HTTP请求

欠伸展肢体,吟咏心自愉。这篇文章主要讲述Linux(程序设计):65---同步HTTP请求异步HTTP请求相关的知识,希望能为你提供帮助。

  • 五大IO模型可以参阅:javascript:void(0)
一、同步HTTP请求
  • 以下图为例,同步HTTP请求的概念为:
    • 客户端调用某一线程向服务端发送数据,发送完数据之后套接字阻塞,阻塞等待服务端给自己返回数据,因此线程也就阻塞
    • 当服务端处理完请求,然后给客户端回送数据之后,客户端接收到数据阻塞返回,一次通信结束
    • 如果服务端没有给客户端回送数据,客户端一直阻塞等待。属于一问一答模式
Linux(程序设计):65---同步HTTP请求异步HTTP请求

文章图片

编码实现
  • 源码链接:https://github.com/dongyusheng/csdn-code/blob/master/syncHttp_and_asyncHttp/sync_http.c
  • 下面我们自己构造HTTP报文,并且向api.seniverse.com网页发送GET请求,该网页是一个天气API接口,可以向其获取天气数据
#include < stdio.h> #include < netdb.h> #include < string.h> #include < strings.h> #include < unistd.h> #include < fcntl.h> #include < sys/socket.h> #include < sys/types.h> #include < netinet/in.h> #include < arpa/inet.h> #include < stdlib.h> #include < time.h> #define HTTP_VERSION"HTTP/1.1" #define ENCODE_TYPE"Accept: text/html,application/xhtml+xml,application/xml; q=0.9,*/*; q=0.8" #define CONNECTION_TYPE "Connection: close"#define MAX_BUFFER_SIZE 1024struct http_request { const char* hostname; const char* resource; }; struct http_request res[] = { {"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" }*/ }; /***************************************************************************** 函 数 名: host_to_ip 功能描述: 根据主机名返回对应的ip 输入参数: const char *hostname -> 要解析的主机名 输出参数: 无 返 回 值: const char * --> 主机名对应的ip地址 调用函数: 被调函数: 修改历史: 1.日期: 2020年5月26日 作者: 董雨生 修改内容: 新生成函数*****************************************************************************/ const char *host_to_ip(const char *hostname); /***************************************************************************** 函 数 名: http_create_socket 功能描述: 在内部创建socket,并连接到服务端 输入参数: const char* ip --> 服务端ip 输出参数: 无 返 回 值: int --> 客户端的socket 调用函数: 被调函数: 修改历史: 1.日期: 2020年5月26日 作者: 董雨生 修改内容: 新生成函数*****************************************************************************/ int http_create_socket(const char* ip); /***************************************************************************** 函 数 名: http_client_commit 功能描述: 执行一个http请求 输入参数: const char *hostname --> 主机名 const char *resource --> http请求资源名称 输出参数: 无 返 回 值: void 调用函数: 被调函数: 修改历史: 1.日期: 2020年5月26日 作者: 董雨生 修改内容: 新生成函数*****************************************************************************/ void http_client_commit(const char *hostname, const char *resource); /***************************************************************************** 函 数 名: http_send_request 功能描述: 向服务端发送请求,并且将服务端的结果作为返回值返回 输入参数: int sockfd --> 客户端套接字 const char *hostname--> 服务端主机名 const char *resource--> 请求的资源名 输出参数: 无 返 回 值: char * --> 返回服务端回送的数据 调用函数: 被调函数: 修改历史: 1.日期: 2020年5月26日 作者: 董雨生 修改内容: 新生成函数*****************************************************************************/ char *http_send_request(int sockfd, const char *hostname, const char *resource); int main() { int size = sizeof(res) / sizeof(res[0]); for(int i = 0; i < size; i++) http_client_commit(res[i].hostname, res[i].resource); return 0; }const char *host_to_ip(const char *hostname) { struct hostent *host_entry = (struct hostent*)malloc(sizeof(struct hostent)); host_entry = NULL; //根据主机名返回对应的struct hostent结构 host_entry = gethostbyname(hostname); if(host_entry == NULL){ fprintf(stderr, "gethostbyname error: %s\\n", hstrerror(h_errno)); return NULL; } else{ //将网络字节序的ip地址转换为本地字节序的ip地址 return inet_ntoa(*(struct in_addr*)(*host_entry-> h_addr_list)); } }int http_create_socket(const char* ip) { int sockfd = -1; //创建socket sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1){ perror("socket"); exit(EXIT_FAILURE); }//服务端地址 struct sockaddr_in servaddr; bzero(& servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(ip); servaddr.sin_port = htons(80); //连接服务端 if(connect(sockfd, (struct sockaddr*)& servaddr, sizeof(servaddr)) == -1){ perror("connect"); exit(EXIT_FAILURE); }//将套接字设置为非阻塞的 fcntl(sockfd, F_SETFL, O_NONBLOCK); return sockfd; }void http_client_commit(const char *hostname, const char *resource) { const char *ip = host_to_ip(hostname); if(ip == NULL) return; //创建socket并且连接到服务端 int sockfd = http_create_socket(ip); //发送一条数据 char *result = http_send_request(sockfd, hostname, resource); printf("%s\\n", result); free(result); close(sockfd); }char *http_send_request(int sockfd, const char *hostname, const char *resource) { //数据操作缓冲区 char buffer[MAX_BUFFER_SIZE]; bzero(buffer, sizeof(buffer)); //构造HTTP报文 sprintf(buffer, "GET %s %s\\r\\n\\ Host:%s\\r\\n\\ %s\\r\\n\\ \\r\\n", resource, HTTP_VERSION, hostname, CONNECTION_TYPE); printf("%s\\n", buffer); //将HTTP报文发送出去 if(send(sockfd, buffer, strlen(buffer), 0) == -1){ perror("send"); exit(EXIT_FAILURE); }//将客户端套接字加入可读字符集中 fd_set fdread; FD_ZERO(& fdread); FD_SET(sockfd, & fdread); struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; //用来保存服务端返回的所有结果 char *result = (char*)malloc(sizeof(int)); bzero(result, sizeof(result)); //循环监听数据 while(1) { int selection = select(sockfd+1, & fdread, NULL, NULL, & tv); if(selection == -1){ perror("select"); exit(EXIT_FAILURE); } if(selection == 0) continue; //如果有数据返回回来,读取数据 if(FD_ISSET(sockfd, & fdread)) { bzero(buffer, sizeof(buffer)); //接收数据 int len = recv(sockfd, buffer, sizeof(buffer), 0); if(len == 0) break; //重新分配空间存放接收到的数据 result = realloc(result, strlen(result) + len + 1); strncat(result, buffer, len); } }return result; }

  • 效果如下所示:
Linux(程序设计):65---同步HTTP请求异步HTTP请求

文章图片

二、异步HTTP请求
  • 以下图为例,异步HTTP请求的概念为:
    • 客户端可以连接多个服务端,并且针对每一个服务端创建对应的套接字
    • 套接字创建完成之后,客户端可以向各个客户端发送请求,发送完请求完之后不阻塞,直接返回
    • 并且将套接字使用select或epoll进行轮询,当有哪个套接字有数据返回时就处理该部分数据
Linux(程序设计):65---同步HTTP请求异步HTTP请求

文章图片

  • 异步的返回会调用回调函数
  • 同步与异步的区别:
    • 同步:客户端发出请求,然后等待服务端返回数据
    • 异步:发送请求,不等待响应(1.创建一个线程,callbakc,统一做接收;2.使用io多路复用,等待哪个IO有数据来)
异步如何设计与实现
  • 可以用4步来实现
  • 1.初始化异步操作的上下文(context)
    • a.创建线程pthread_create
    • b.epoll_create
  • 2.销毁上下文
    • a.销毁线程pthread_cancel
    • b.close
  • 3.提交数据commit
    • a.准备socket
    • b.connect
    • c.protocol
    • d.send
    • e.把socket加入到epoll中,epoll_ctl
  • 4.callback()
    • a.检测socket是否有数据到来,epoll_wait
    • b.recv
编码实现
  • 源码链接:https://github.com/dongyusheng/csdn-code/blob/master/syncHttp_and_asyncHttp/async_http.c
#include < stdio.h> #include < string.h> #include < strings.h> #include < unistd.h> #include < pthread.h> #include < netdb.h> #include < errno.h> #include < fcntl.h> #include < stdlib.h> #include < arpa/inet.h> #include < netinet/in.h> #include < sys/socket.h> #include < sys/epoll.h> #include < sys/types.h> #define HTTP_VERSION "HTTP/1.1" #define CONNECTION_TYPE "Connection:close"#define HOSTNAME_LENGTH 1024 #define BUFFER_SIZE 1024 #define EPOLL_WAIT_NUM 10typedef void (*async_result_cb)(const char *hostname, char *result); struct async_context { int epoll_fd; pthread_t thread_id; }; struct ep_arg { int client_fd; char hostname[HOSTNAME_LENGTH]; async_result_cb cb; }; struct http_request { const char *hostname; const 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" } }; /***************************************************************************** 函 数 名: host_to_ip 功能描述: 根据主机名返回对应的ip 输入参数: const char *hostname -> 要解析的主机名 输出参数: 无 返 回 值: const char * --> 成功主机名对应的ip地址,失败返回NULL 调用函数: 被调函数: 修改历史: 1.日期: 2020年5月26日 作者: 董雨生 修改内容: 新生成函数*****************************************************************************/ const char *host_to_ip(const char *hostname); /***************************************************************************** 函 数 名: async_http_create_socket 功能描述: 在内部创建socket,并连接到服务端 输入参数: const char* ip --> 服务端ip 输出参数: 无 返 回 值: int --> 成功返回客户端socket,失败返回-1 调用函数: 被调函数: 修改历史: 1.日期: 2020年5月26日 作者: 董雨生 修改内容: 新生成函数*****************************************************************************/ int async_http_create_socket(const char* ip); /***************************************************************************** 函 数 名: async_http_init_context 功能描述: 初始化上下文 输入参数: 无 输出参数: 无 返 回 值: struct async_context * --> 成功创建的当前程序的上下文,失败返回NULL 调用函数: 被调函数: 修改历史: 1.日期: 2020年5月27日 作者: 董雨生 修改内容: 新生成函数*****************************************************************************/ struct async_context *async_http_init_context(); /***************************************************************************** 函 数 名: async_http_thread_func 功能描述: 线程执行函数 输入参数: void *arg 输出参数: 无 返 回 值: void * 调用函数: 被调函数: 修改历史: 1.日期: 2020年5月27日 作者: 董雨生 修改内容: 新生成函数*****************************************************************************/ void *async_http_thread_func(void *arg); /***************************************************************************** 函 数 名: async_http_commit 功能描述: 执行一次HTTP请求的操作 输入参数: const char *hostname --> 请求的服务端的主机名 const char* resource --> HTTP请求的资源 struct async_context *ctx --> 上下文 async_result_cb cb --> 客户端回调函数 输出参数: 无 返 回 值: int --> 成功返回0,失败返回-1 调用函数: 被调函数: 修改历史: 1.日期: 2020年5月27日 作者: 董雨生 修改内容: 新生成函数*****************************************************************************/ int async_http_commit(const char *hostname, const char* resource, struct async_context *ctx, async_result_cb cb); /***************************************************************************** 函 数 名: async_http_client_result_callback 功能描述: 客户端回调函数 输入参数: const char *hostname --> 客户端连接的服务端的主机名 char *result --> 服务端返回的数据集 输出参数: 无 返 回 值: void 调用函数: 被调函数: 修改历史: 1.日期: 2020年5月27日 作者: 董雨生 修改内容: 新生成函数*****************************************************************************/ void async_http_client_result_callback(const char *hostname, char *result); /***************************************************************************** 函 数 名: async_http_destory_context 功能描述: 关闭上下文,在其内部会关闭epoll套接字和取消线程 输入参数: struct async_context *ctx --> 上下文结构体 输出参数: 无 返 回 值: void 调用函数: 被调函数: 修改历史: 1.日期: 2020年5月27日 作者: 董雨生 修改内容: 新生成函数*****************************************************************************/ void async_http_destory_context(struct async_context *ctx); int main() { //初始化上下文 struct async_context *context = async_http_init_context(); if(context == NULL) exit(EXIT_FAILURE); //进行HTTP请求 int count = sizeof(reqs) / sizeof(reqs[0]); int i; for(i = 0; i < count; ++i) async_http_commit(reqs[i].hostname, reqs[i].resource, context, async_http_client_result_callback); //防止主线程先执行结束,从而导致子线程还未完成任务就终止了 getchar(); //销毁上下文 async_http_destory_context(context); return 0; }const char *host_to_ip(const char *hostname) { struct hostent *host_entry = (struct hostent*)malloc(sizeof(struct hostent)); host_entry = NULL; //根据主机名返回对应的struct hostent结构 host_entry = gethostbyname(hostname); if(host_entry == NULL){ fprintf(stderr, "gethostbyname error: %s\\n", hstrerror(h_errno)); return NULL; } else{ //将网络字节序的ip地址转换为本地字节序的ip地址 return inet_ntoa(*(struct in_addr*)(*host_entry-> h_addr_list)); } }int async_http_create_socket(const char* ip) { int sockfd = -1; //创建socket sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1){ perror("socket"); exit(EXIT_FAILURE); }//服务端地址 struct sockaddr_in servaddr; bzero(& servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(ip); servaddr.sin_port = htons(80); //连接服务端 if(connect(sockfd, (struct sockaddr*)& servaddr, sizeof(servaddr)) == -1){ perror("connect"); exit(EXIT_FAILURE); }//将套接字设置为非阻塞的 fcntl(sockfd, F_SETFL, O_NONBLOCK); return sockfd; }struct async_context *async_http_init_context() { struct async_context *context = (struct async_context*)malloc(sizeof(struct async_context)); if(context == NULL){ perror("malloc"); return NULL; }//创建epoll套接字 int epfd = epoll_create(5); if(epfd == -1){ free(context); perror("epoll_create"); return NULL; } context-> epoll_fd = epfd; //开启新的线程 int thread_ret = pthread_create(& context-> thread_id, NULL, async_http_thread_func, context); if(thread_ret == -1){ free(context); perror("pthread_create"); return NULL; }usleep(1); return context; }void *async_http_thread_func(void *arg) { struct async_context *context = (struct async_context *)arg; char buffer[BUFFER_SIZE]; //调用epoll_wait监听 while(1) { struct epoll_event events[EPOLL_WAIT_NUM]; bzero(& events, sizeof(events)); int epoll_ret = epoll_wait(context-> epoll_fd, events, EPOLL_WAIT_NUM, -1); if(epoll_ret == -1){ if(errno == EINTR || errno == EAGAIN) continue; else{ perror("epoll_wait"); break; } } else if(epoll_ret == 0) continue; for(int i = 0; i < epoll_ret; i++){ bzero(buffer, BUFFER_SIZE); struct ep_arg *data = https://www.songbingjia.com/android/(struct ep_arg*)events[i].data.ptr; int client_fd = data-> client_fd; if(recv(client_fd, buffer, BUFFER_SIZE, 0) == -1){ perror("recv"); close(client_fd); free(data); continue; } //调用客户端的回调函数 data-> cb(data-> hostname ,buffer); //处理完成之后记得将套接字移除 if(epoll_ctl(context-> epoll_fd, EPOLL_CTL_DEL, client_fd, NULL) == -1){ perror("epoll_ctl"); close(client_fd); free(data); continue; }//善后处理 close(client_fd); free(data); } } }int async_http_commit(const char *hostname, const char* resource, struct async_context *ctx, async_result_cb cb) { const char *ip = host_to_ip(hostname); //创建客户端套接字 int client_fd = async_http_create_socket(ip); //向服务端发送数据 char buffer[BUFFER_SIZE]; bzero(buffer, BUFFER_SIZE); sprintf(buffer, "GET %s %s\\r\\n\\ Host:%s\\r\\n\\ %s\\r\\n\\ \\r\\n", resource, HTTP_VERSION, hostname, CONNECTION_TYPE); printf("%s\\n", buffer); //发送数据 if(send(client_fd, buffer, strlen(buffer), 0) == -1) { perror("send"); return -1; }struct ep_arg *data = https://www.songbingjia.com/android/(struct ep_arg*)calloc(1, sizeof(struct ep_arg)); data-> client_fd = client_fd; bcopy(hostname, data-> hostname, strlen(hostname)); data-> cb = cb; //创建事件,加入到epoll池中 struct epoll_event ev; ev.data.ptr = data; ev.events = EPOLLIN; if(epoll_ctl(ctx-> epoll_fd, EPOLL_CTL_ADD, client_fd, & ev) == -1){ perror("epoll_ctl"); return -1; }return 0; }void async_http_client_result_callback(const char *hostname, char *result) { printf("%s\\n\\n\\n", result); flush(stdout); }void async_http_destory_context(struct async_context *ctx) { close(ctx-> epoll_fd); pthread_cancel(ctx-> thread_id); }

  • 演示效果如下:
Linux(程序设计):65---同步HTTP请求异步HTTP请求

文章图片

【Linux(程序设计):65---同步HTTP请求异步HTTP请求】

    推荐阅读