当前位置: 首页 > news >正文

基于 Reactor 模式的 HTTP 协议扩展实现

        为让Reactor模式下的TCP服务器具备网页服务能力,通过集成HTTP协议,对其进行网页端功能扩展,使其升级为可响应浏览器请求的Web服务器。

        TCP服务器实现部分Reactor 模式实现:从 epoll 到高并发调试-CSDN博客

1.功能实现

        将底层I/O与上层业务进行分离,提高I/O性能,更适配高并发,也便于维护和扩展

1.1全局变量部分

        conn结构体中加入状态机,共三个阶段

0(生成并发送响应头)   服务器向客户端发送数据的准备阶段,将响应头写入缓冲区,修改缓冲区大小,后进入1阶段

1(发送响应体)         向客户端发送数据的发送阶段,若数据长度过大,分段多次发送,发送完毕后进入2阶段

2(清空缓存)           发送数据的结束阶段,缓冲区置为空,长度置为0,进入0阶段

1.2上层业务部分

        webserver.c文件,接收新数据前的准备函数

int http_request(struct conn *c){//打印提示printf("request: %s\n", c->rbuffer);//清空数据,长度归零memset(c->wbuffer, 0, BUFFER_LENGTH);c->wlength = 0;//设置状态为0,进入发送数据的准备阶段c->status = 0;
}

        发送数据的具体实现

int http_response(struct conn *c){//简化版,无状态机
#if 1//生成响应头并写入缓冲区c->wlength = sprintf(c->wbuffer,"HTTP/1.1 200 OK\r\n""Content-Type: text/html\r\n""Accept-Ranges: bytes\r\n""Content-Length: 82\r\n""Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n""<html><head><title>Avogado6</title></head><body><h1>Avogado6</h1></body></html>\r\n\r\n");//将html文件映射到网页中
#elif 0//创建文件描述符只读模式获取文件内容int filefd = open("index.html", O_RDONLY);//定义文件状态结构体,用于获取文件长度struct stat stat_buf;//获取文件数据fstat(filefd, &stat_buf);if(c->status == 0){//准备阶段//生成响应头并写入缓冲区//printf("c->status == 1");c->wlength = sprintf(c->wbuffer,"HTTP/1.1 200 OK\r\n""Content-Type: text/html\r\n""Accept-Ranges: bytes\r\n""Content-Length: %ld\r\n""Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",stat_buf.st_size);//更新状态至发送阶段c->status = 1;}else if(c->status == 1){//发送阶段//调用sendfile函数,一次性映射完整文件(简化操作)int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);//映射失败时的处理if(ret == -1){printf("send file error: %d\n", errno);}//发送完成,更新状态至清空缓存c->status = 2;}else if(c->status == 2){//清空缓存//缓冲区数据置空,长度置0c->wlength = 0;memset(c->wbuffer, 0, BUFFER_LENGTH);//发送数据结束,更新状态至准备阶段,准备下一次发送数据c->status = 0;}//发送完成,关闭文件描述符close(filefd);//将图片文件映射到网页中
#elif  0//创建文件描述符只读模式获取图片内容int filefd = open("Avogado6.jpg", O_RDONLY);//定义文件状态结构体,获取图片大小struct stat stat_buf;fstat(filefd, &stat_buf);if(c->status == 0){//准备阶段//生成响应头并写入缓冲区c->wlength = sprintf(c->wbuffer,"HTTP/1.1 200 OK\r\n""Content-Type: image/jpg\r\n"  //发送图片更改文件格式"Accept-Ranges: bytes\r\n""Content-Length: %ld\r\n""Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",stat_buf.st_size);//更新状态至发送阶段c->status = 1;}else if(c->status == 1){//发送阶段//调用sendfile函数,一次性映射完整文件int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);//映射失败时的处理if(ret == -1){printf("send file error: %d\n", errno);}//发送完成,更新状态至清空缓存c->status = 2;}else if(c->status == 2){//清空缓存//缓冲区数据置空,长度置0c->wlength = 0;memset(c->wbuffer, 0, BUFFER_LENGTH);//发送数据结束,更新状态至准备阶段,准备下一次发送数据c->status = 0;}//发送完成,关闭文件描述符close(filefd);#endifreturn c->wlength;
}

1.3底层I/O处理部分

        recv_cb函数中调用http_request函数,对当前连接执行http请求的初始化操作

http_request(&conn_list[fd]);

        send_cb先调用http_response函数对当前连接执行http的生成响应内容操作

http_response(&conn_list[fd]);

        再通过状态机,对不同阶段进行不同处理

if(conn_list[fd].status == 1){//状态为发送数据阶段时//调用send函数将发送缓冲区中的数据发送至客户端fdcount = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);//调用set_event函数将该fd连接信息中的关注事件修改为可写,以便继续发送剩余数据set_event(fd, EPOLLOUT, 0);
}else if(conn_list[fd].status == 2){//状态为清空缓存阶段时//调用set_event函数将该fd连接信息中的关注事件修改为可写,准备执行清空缓存操作set_event(fd, EPOLLOUT, 0);
}else if(conn_list[fd].status == 0){//状态为准备阶段时//处理不使用状态机的简单发送操作if (conn_list[fd].wlength != 0) {count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);}//调用set_event函数将该fd连接信息中的关注事件修改为可读,准备接收数据set_event(fd, EPOLLIN, 0);
}

2.功能测试

2.1固定 HTML 响应生成

        运行该部分

//生成响应头并写入缓冲区
c->wlength = sprintf(c->wbuffer,"HTTP/1.1 200 OK\r\n""Content-Type: text/html\r\n""Accept-Ranges: bytes\r\n""Content-Length: 82\r\n""Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n""<html><head><title>Avogado6</title></head><body><h1>Avogado6</h1></body></html>\r\n\r\n");

        运行结果,网页端访问该端口

        对该功能进行并发性能测试(简化输出)
         wrk -c50 -t10 -d10s http://192.168.147.130:2000
        50个并发连接,10个线程,持续10s的压力测试

        测试结果

Running 10s test @ http://192.168.147.130:2000
  10 threads and 50 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   542.43us  145.23us   8.75ms   80.37%
    Req/Sec     9.11k     0.89k   13.51k    72.42%
  914084 requests in 10.10s, 178.71MB read
Requests/sec:  90504.27
Transfer/sec:     17.69MB

        共处理90万次,平均延迟低于半毫秒,延迟波动小

2.2HTML 文件响应

        运行该部分

//创建文件描述符只读模式获取文件内容
int filefd = open("index.html", O_RDONLY);//定义文件状态结构体,用于获取文件长度
struct stat stat_buf;
//获取文件数据
fstat(filefd, &stat_buf);if(c->status == 0){//准备阶段//生成响应头并写入缓冲区//printf("c->status == 1");c->wlength = sprintf(c->wbuffer,"HTTP/1.1 200 OK\r\n""Content-Type: text/html\r\n""Accept-Ranges: bytes\r\n""Content-Length: %ld\r\n""Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",stat_buf.st_size);//更新状态至发送阶段c->status = 1;}else if(c->status == 1){//发送阶段//调用sendfile函数,一次性映射完整文件(简化操作)int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);//映射失败时的处理if(ret == -1){printf("send file error: %d\n", errno);}//发送完成,更新状态至清空缓存c->status = 2;
}else if(c->status == 2){//清空缓存//缓冲区数据置空,长度置0c->wlength = 0;memset(c->wbuffer, 0, BUFFER_LENGTH);//发送数据结束,更新状态至准备阶段,准备下一次发送数据c->status = 0;
}//发送完成,关闭文件描述符
close(filefd);

        运行结果,网页端访问该端口

2.3图片文件响应

        运行该部分

//创建文件描述符只读模式获取图片内容
int filefd = open("Avogado6.jpg", O_RDONLY);//定义文件状态结构体,获取图片大小
struct stat stat_buf;
fstat(filefd, &stat_buf);if(c->status == 0){//准备阶段//生成响应头并写入缓冲区c->wlength = sprintf(c->wbuffer,"HTTP/1.1 200 OK\r\n""Content-Type: image/jpg\r\n"  //发送图片更改文件格式"Accept-Ranges: bytes\r\n""Content-Length: %ld\r\n""Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",stat_buf.st_size);//更新状态至发送阶段c->status = 1;
}else if(c->status == 1){//发送阶段//调用sendfile函数,一次性映射完整文件int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);//映射失败时的处理if(ret == -1){printf("send file error: %d\n", errno);}//发送完成,更新状态至清空缓存c->status = 2;
}else if(c->status == 2){//清空缓存//缓冲区数据置空,长度置0c->wlength = 0;memset(c->wbuffer, 0, BUFFER_LENGTH);//发送数据结束,更新状态至准备阶段,准备下一次发送数据c->status = 0;
}//发送完成,关闭文件描述符
close(filefd);

        运行结果,网页端访问该端口

3.方法总结

        整个web服务器遵循底层I/O与上层业务分离的原则,通过Reactor模式处理I/O事件+状态机管理HTTP响应流程的方法,实现高效、可扩展的web服务器的相应功能,若需扩展功能,只需修改http业务部分

        http_request函数进行http请求的初始化,作为业务逻辑的入口
        http_response函数通过条件编译实现三种业务功能
                固定HTML响应,生成响应头后直接写入缓冲区
                HTML文件/图片响应, 读取文件后,根据状态机阶段,分别进行生成响应头写入缓冲区,发送完整数据,清空缓存三个阶段
                调用send_file函数进行发送数据操作,提升文件传输效率
        send_cb函数根据状态机的不同阶段,实现向客户端发送数据的操作

4.完整代码

        server.h

#ifndef __SERVER_H__
#define __SERVER_H__#define BUFFER_LENGTH       1024//声明处理事件的回调函数类型,统一接口便于分发时间
typedef int (*RCALLBACK)(int fd);//连接信息结构体
struct conn{//套接字,客户端fd或者监听fdint fd;//设置状态机,共三个阶段//0(生成并发送响应头)   服务器向客户端发送数据的准备阶段,将响应头写入缓冲区,修改缓冲区大小,后进入1阶段//1(发送响应体)         向客户端发送数据的发送阶段,若数据长度过大,分段多次发送,发送完毕后进入2阶段//2(清空缓存)           发送数据的结束阶段,缓冲区置为空,长度置为0,进入0阶段int status;//读写数据的缓冲区数组和大小char rbuffer[BUFFER_LENGTH];int rlength;char wbuffer[BUFFER_LENGTH];int wlength;//把回调函数的指针加入结构体RCALLBACK send_callback;//互斥状态的两个回调函数指针,共同体的形式加入结构体(客户端fd调用recv_callback,监听fd调用accept_callback)//共同体,所有成员公用同一块内存空间,节省内存union{RCALLBACK recv_callback;RCALLBACK accept_callback;} r_action;};int http_request(struct conn *c);int http_response(struct conn *c);#endif

        reactor.c

#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<pthread.h>
#include<unistd.h>
#include<poll.h>
#include<sys/epoll.h>
#include<sys/time.h>#include "server.h"//宏定义设定缓冲区和连接列表的大小
#define CONNECTION_SIZE     1048576
#define MAX_PORTS            20//计算耗时
#define TIME_SUB_MS(tv1, tv2)   ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)//epoll实例的全局变量,各函数中操作同一个epoll
int epfd = 0;
//具体回调函数的声明
int recv_cb(int fd);
int accept_cb(int fd);
int send_cb(int fd);//创建开始时时间结构体变量
struct timeval begin;//用数组存储所有连接
struct conn conn_list[CONNECTION_SIZE] = {0};//添加/修改epoll事件
int set_event(int fd, int event, int flag){//flag非零时,添加epoll事件if(flag){//no-zero add//创建epoll_event类型变量struct epoll_event ev;//设定关注的事件类型ev.events = event;//将当前fd存入ev的data.fd对象中ev.data.fd = fd;//添加到epfd的epoll实例中epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);}else{//zero modstruct epoll_event ev;ev.events = event;ev.data.fd = fd;//修改epfd中的epoll实例epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);}
}//为epoll实例中添加新的客户端连接
int event_register(int fd, int event){if(fd < 0) return -1;//初始化连接信息,绑定fd,设置回调函数conn_list[fd].fd = fd;//添加连接,共同体中选择recv_cb回调函数conn_list[fd].r_action.recv_callback = recv_cb;conn_list[fd].send_callback = send_cb;//初始化缓冲区conn_list[fd].rlength = 0;conn_list[fd].wlength = 0;//调用set_event函数添加事件,并监控可读事件set_event(fd, EPOLLIN, 1);
}//listen(sockfd) --> EPOLLIN --> accept_cb
//接收客户端连接请求,创建客户端fd并完成初始化
int accept_cb(int fd){//定义客户端地址结构体,计算长度struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);//调用accept函数,从监听fd接收数据并创建对应地址的客户端fdint clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);//printf("accept finished: %d\n", clientfd);if(clientfd < 0){printf("accept error: %d\n", errno);return -1;}//调用event_register函数初始化连接信息,关注可读事件event_register(clientfd, EPOLLIN);if(clientfd % 1000 == 0){//获取每建立1000个连接时的时间struct timeval current;gettimeofday(&current, NULL);//计算耗时int time_used = TIME_SUB_MS(current, begin);//更新每次时间开始值memcpy(&begin, &current, sizeof(struct timeval));printf("accept finished: %d, time_used: %d\n", clientfd, time_used);}return 0;
}//客户端fd触发EPOLLIN事件的回调函数,接收客户端发送的数据
int recv_cb(int fd){memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH );//调用recv接收客户端数据,存入该连接的接收缓冲区,通过接收数据长度判断接收状态int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);//状态为零时客户端主动断开连接if(count == 0){printf("clientfd disconnect: %d\n", fd);//关闭断开的客户端fdclose(fd);//将该fd从epfd的epoll实例中删除,不再监控epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);// 无需ev结构体return 0;}else if(count < 0){//处理异常连接printf("count: %d, errno: %d, %s\n", count, errno, strerror(errno));close(fd);epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);return 0;}//存储数据长度conn_list[fd].rlength  = count;//打印接收数据//printf("RRECV: %s\n", conn_list[fd].rbuffer);#if 0 //echo  回声模式开关,1开启//将接收缓冲区的数据和数据长度存储到发送缓冲区中,用于send函数conn_list[fd].wlength = conn_list[fd].rlength;memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, conn_list[fd].wlength);printf("[%d]recv: %s\n", conn_list[fd].rlength, conn_list[fd].rbuffer);#else//对当前连接执行http请求的初始化操作http_request(&conn_list[fd]);#endif//调用set_event函数修改该fd的关注事件为EPOLLOUT可写//让epoll后续触发可写事件,调用send_cb发送缓冲区中数据set_event(fd, EPOLLOUT, 0);return count;
}//客户端fd触发可写事件后,发送缓冲区中的数据
int send_cb(int fd){#if 1//对当前连接执行http的生成响应内容操作http_response(&conn_list[fd]);#endifint count = 0;if(conn_list[fd].status == 1){//状态为发送数据阶段时//调用send函数将发送缓冲区中的数据发送至客户端fdcount = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);//调用set_event函数将该fd连接信息中的关注事件修改为可写,以便继续发送剩余数据set_event(fd, EPOLLOUT, 0);}else if(conn_list[fd].status == 2){//状态为清空缓存阶段时//调用set_event函数将该fd连接信息中的关注事件修改为可写,准备执行清空缓存操作set_event(fd, EPOLLOUT, 0);}else if(conn_list[fd].status == 0){//状态为准备阶段时//处理不使用状态机的简单发送操作if (conn_list[fd].wlength != 0) {count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);}//调用set_event函数将该fd连接信息中的关注事件修改为可读,准备接收数据set_event(fd, EPOLLIN, 0);}return count;
}//创建服务器,开启监听fd
int init_server(unsigned short port){//创建TCP流式套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);//设置服务器地址信息,IPv4,绑定所有本地网卡,绑定端口struct sockaddr_in servaddr;servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0servaddr.sin_port = htons(port); //0-1023//绑定套接字到服务器地址和端口if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){printf("bind failed: %s\n", strerror(errno));}//开启监听套接字,最大等待队列为10listen(sockfd, 4096);//printf("listen finished: %d\n", sockfd);return sockfd;
}int main(){//设置端口unsigned short port = 2000;//调用epoll_create函数创建一个epoll实例epfd = epoll_create(1);for(int i = 0;i < MAX_PORTS;i ++){//初始化服务器,创建套接字,绑定地址,开始监听//返回监听套接字int sockfd = init_server(port + i);//将监听套接字存入连接列表中conn_list[sockfd].fd = sockfd;//设置accept_cb为共同体回调函数的处理,即监听到可读事件后调用accept_cb函数conn_list[sockfd].r_action.recv_callback = accept_cb;//调用set_event函数将监听套接字加入到epoll实例中,监控其可读事件set_event(sockfd, EPOLLIN, 1);}//获取开始时的时间gettimeofday(&begin,NULL);//主循环,处理新连接,收发客户端数据while(1){//mainloop//创建数组存储就绪事件,初始化为0struct epoll_event events[1024] = {0};//调用epoll_wait阻塞等待,直到监控的fd触发了就绪事件,并统计数量int nready = epoll_wait(epfd, events, 1024, -1);//循环遍历所有就绪事件(时间复杂度为O(k))int i = 0;for(i = 0;i < nready;i ++){//获取当前就绪事件对应的fd,存入connfd中,简化操作int connfd = events[i].data.fd;//当就绪事件触发可读事件时,执行连接列表中该fd的recv_callback回调函数,添加新连接或读取数据if(events[i].events & EPOLLIN){conn_list[connfd].r_action.recv_callback(connfd);}//当就绪事件触发可写事件时,执行连接列表中该fd的send_callback回调函数,将发送缓冲区的数据发送至客户端if(events[i].events & EPOLLOUT){conn_list[connfd].send_callback(connfd);}}}
}

        webserver.c

#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<sys/sendfile.h>
#include<errno.h>
#include "server.h"//接收新数据前的准备
int http_request(struct conn *c){//printf("request: %s\n", c->rbuffer);//清空数据,长度归零memset(c->wbuffer, 0, BUFFER_LENGTH);c->wlength = 0;//设置状态为0,进入发送数据的准备阶段c->status = 0;
}//发送数据的具体实现
int http_response(struct conn *c){//简化版,无状态机
#if 1//生成响应头并写入缓冲区c->wlength = sprintf(c->wbuffer,"HTTP/1.1 200 OK\r\n""Content-Type: text/html\r\n""Accept-Ranges: bytes\r\n""Content-Length: 82\r\n""Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n""<html><head><title>Avogado6</title></head><body><h1>Avogado6</h1></body></html>\r\n\r\n");//将html文件映射到网页中
#elif 0//创建文件描述符只读模式获取文件内容int filefd = open("index.html", O_RDONLY);//定义文件状态结构体,用于获取文件长度struct stat stat_buf;//获取文件数据fstat(filefd, &stat_buf);if(c->status == 0){//准备阶段//生成响应头并写入缓冲区//printf("c->status == 1");c->wlength = sprintf(c->wbuffer,"HTTP/1.1 200 OK\r\n""Content-Type: text/html\r\n""Accept-Ranges: bytes\r\n""Content-Length: %ld\r\n""Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",stat_buf.st_size);//更新状态至发送阶段c->status = 1;}else if(c->status == 1){//发送阶段//调用sendfile函数,一次性映射完整文件(简化操作)int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);//映射失败时的处理if(ret == -1){printf("send file error: %d\n", errno);}//发送完成,更新状态至清空缓存c->status = 2;}else if(c->status == 2){//清空缓存//缓冲区数据置空,长度置0c->wlength = 0;memset(c->wbuffer, 0, BUFFER_LENGTH);//发送数据结束,更新状态至准备阶段,准备下一次发送数据c->status = 0;}//发送完成,关闭文件描述符close(filefd);//将图片文件映射到网页中
#elif  0//创建文件描述符只读模式获取图片内容int filefd = open("Avogado6.jpg", O_RDONLY);//定义文件状态结构体,获取图片大小struct stat stat_buf;fstat(filefd, &stat_buf);if(c->status == 0){//准备阶段//生成响应头并写入缓冲区c->wlength = sprintf(c->wbuffer,"HTTP/1.1 200 OK\r\n""Content-Type: image/jpg\r\n"  //发送图片更改文件格式"Accept-Ranges: bytes\r\n""Content-Length: %ld\r\n""Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",stat_buf.st_size);//更新状态至发送阶段c->status = 1;}else if(c->status == 1){//发送阶段//调用sendfile函数,一次性映射完整文件int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);//映射失败时的处理if(ret == -1){printf("send file error: %d\n", errno);}//发送完成,更新状态至清空缓存c->status = 2;}else if(c->status == 2){//清空缓存//缓冲区数据置空,长度置0c->wlength = 0;memset(c->wbuffer, 0, BUFFER_LENGTH);//发送数据结束,更新状态至准备阶段,准备下一次发送数据c->status = 0;}//发送完成,关闭文件描述符close(filefd);#endifreturn c->wlength;
}
http://www.dtcms.com/a/469353.html

相关文章:

  • 2025 FastExcel在Java的Maven项目的导出和导入,简单易上手,以下为完整示例
  • 做的好点的外贸网站有哪些网站建设实训指导书
  • 【Linux】Centos 8 默认OpenSSH 升级OpenSSH9.8【升级其他OpenSSH版本通用】
  • 【Nginx开荒攻略】深度解析基于域名的虚拟主机配置:从域名解析到实战部署
  • 互联网网站样式坪山建设网站建站
  • 全链路智能运维中的业务影响度评估与资源动态优化机制
  • 微信小程序学习(五)
  • Jmeter接口的负载测试概念
  • Linux-CentOS 7 上安装 MySQL 8.0.43(保姆级教程)
  • 视频分辨率4K,比特率50000kbps,电脑播放时卡顿的原因
  • 使用aspx做电影网站网站建设专用术语
  • Linux内核网络优化:两个网络调优解决方案
  • day7_vite 啊哈哈啊哈哈哈哈哈
  • 化妆品产品的自建网站哟哪些能想到的域名都被注册了
  • 网络协议的零拷贝 和 操作系统的零拷贝异同
  • Apache Drill:一款开源的分布式SQL查询引擎
  • 八年磨一剑:中品维度如何用“分布式电商”为商家打开增长新通路?
  • Linux下的Rust 与 C 的互操作性解析
  • 从“用框架”到“控系统”———架构通用能力(模块边界、分层设计、缓存策略、事务一致性、分布式思维)
  • 云南省建设厅网站舉報十大购物网站排行榜
  • 做网站什么空间比较好短视频运营方案
  • golang 读写锁 RWMutex
  • centos系统将/home分区的空间分配给/
  • Kafka系列之:Kafka事务、幂等生产者、事务生产者
  • sftpgo汉化处理
  • Java打包时,不将本地Jar打包到项目的最终 JAR 中
  • Go语言泛型全面解析:从基础到高级应用
  • 在css里优雅地使用if函数
  • 中国建设银行个人网站银行欧美在线做视频网站
  • 2018年网站开发语言如何加强英文网站建设