• 五大IO模型可以参阅:https://blog.csdn.net/qq_41453285/article/details/89607010

一、同步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;//创建socketsockfd = 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;
}
  • 效果如下所示:

二、异步HTTP请求

  • 以下图为例,异步HTTP请求的概念为:

    • 客户端可以连接多个服务端,并且针对每一个服务端创建对应的套接字
    • 套接字创建完成之后,客户端可以向各个客户端发送请求,发送完请求完之后不阻塞,直接返回
    • 并且将套接字使用select或epoll进行轮询,当有哪个套接字有数据返回时就处理该部分数据

  • 异步的返回会调用回调函数
  • 同步与异步的区别:
    • 同步:客户端发出请求,然后等待服务端返回数据
    • 异步:发送请求,不等待响应(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;//创建socketsockfd = 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 = (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 = (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请求相关推荐

  1. 【OkHttp】OkHttp Get 和 Post 请求 ( 同步 Get 请求 | 异步 Get 请求 | 同步 Post 请求 | 异步 Post 请求 )

    OkHttp 系列文章目录 [OkHttp]OkHttp 简介 ( OkHttp 框架特性 | Http 版本简介 ) [OkHttp]Android 项目导入 OkHttp ( 配置依赖 | 配置 ...

  2. 请求异步js,请求完成后执行代码

    要确定请求完成 js 文件,才执行相关的代码. 场景,引用了百度地图的 js-sdk, 需要实例化 SDK 对象,但是这个引用加载JS-SDK文件其实是异步的,在没请求完成之前就实例化对象就会报错,提 ...

  3. 【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )

    OkHttp 系列文章目录 [OkHttp]OkHttp 简介 ( OkHttp 框架特性 | Http 版本简介 ) [OkHttp]Android 项目导入 OkHttp ( 配置依赖 | 配置 ...

  4. IOS - IOS之同步请求、异步请求、GET请求、POST请求

    1.同步请求可以从因特网请求数据,一旦发送同步请求,程序将停止用户交互,直至服务器返回数据完成,才可以进行下一步操作, 2.异步请求不会阻塞主线程,而会建立一个新的线程来操作,用户发出异步请求后,依然 ...

  5. iOS 中的网络请求 (同步请求、异步请求、GET请求、POST请求)

    1.同步请求可以从因特网请求数据,一旦发送同步请求,程序将停止用户交互,直至服务器返回数据完成,才可以进行下一步操作, 2.异步请求不会阻塞主线程,而会建立一个新的线程来操作,用户发出异步请求后,依然 ...

  6. iOS网络开发中的同步、异步和请求队列

    在iOS网络编程中,我们经常会遇到线程的同步和异步问题,同时为了对异步请求更加精准丰富的控制,我们还常常在iOS中使用请求队列,下面就来谈谈iOS开发中同步.异步以及请求队列的使用方法. 1. 同步意 ...

  7. IOS之同步请求、异步请求、GET请求、POST请求(转)

    1.同步请求可以从因特网请求数据,一旦发送同步请求,程序将停止用户交互,直至服务器返回数据完成,才可以进行下一步操作, 2.异步请求不会阻塞主线程,而会建立一个新的线程来操作,用户发出异步请求后,依然 ...

  8. IOS之同步请求、异步请求、GET请求、POST请求

    1.同步请求可以从因特网请求数据,一旦发送同步请求,程序将停止用户交互,直至服务器返回数据完成,才可以进行下一步操作, 2.异步请求不会阻塞主线程,而会建立一个新的线程来操作,用户发出异步请求后,依然 ...

  9. swift 同步 网络请求_IOS开发中异步网络请求上实现同步逻辑

    IOS开发中异步网络请求上实现同步逻辑 前提: 可能遇到一些问题,比如上传多个数据,需要等多个数据上传成功后做一定的处理,而且一个个上传,万一哪个上传失败了,后面就不需要上传了,直接报错. 之前ASI ...

最新文章

  1. B S架构 服务器虚拟化,B/S 架构及 Tomcat
  2. 全志 添加外挂RTC Hym8563
  3. GNU/Linux与开源文化的那些人和事
  4. JDK语言功能预览:切换表达式
  5. IIS虚拟目录实现与文件服务器网络驱动器映射共享
  6. String转换为int类型
  7. coursera_ML_1
  8. 关乎Python lambda你也看得懂
  9. 8个JavaScript题目
  10. 接种新冠疫苗到实现完全免疫最快需要35天
  11. htc 常见错误和解决方案
  12. 无线摄像头ftp服务器设置方法,camhi摄像头怎么连接ftp服务器
  13. C# 客户端手动配置数据证书 WCF Certificate
  14. 从挣扎突破到英雄联盟!中国SaaS头部企业阵营渐显
  15. kalipython图形界面_Kali入侵入门版笔记!!!
  16. 亲测:真正免费的音频转文字软件
  17. golang读取pdf
  18. 乞丐乞讨:天桥有一个乞丐,每天去遵义路天桥要钱,每天要到的钱都是上一天的两倍。如: 第一天要了 1 块钱 第二天要了 2 块钱 第三天要了 4 块钱 第四天要了 8 块钱 以此类推, 问: 乞丐
  19. dubbo SPI机制与@Adaptive自适应扩展机制
  20. 转:钉钉群直播提取视频文件-电脑版

热门文章

  1. 东师《近代物理实验》离线作业网考
  2. [网鼎杯 2020 玄武组]SSRFMe【redis主从复制ssrf】
  3. 马化腾内部分享:产品经理的必修课
  4. 在Android Studio中添加mp3音频文件
  5. 揭秘 vivo 如何打造千万级 DAU 活动中台 - 启航篇
  6. npm install 你很明白吗
  7. 京东一面+京东物流二面+京东秋招一二面
  8. 电脑开始怎么设置计算机用户名,如何修改电脑登录用户名以及密码呢?5秒钟让你学会...
  9. 中国石油大学《化工原理二》第二阶段在线作业
  10. vue中mint-ui使用详解