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

news/2025/11/11 8:20:11/文章来源:https://www.cnblogs.com/slgkaifa/p/19208819

基于 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""Avogado6

Avogado6

\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""Avogado6

Avogado6

\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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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(¤t, NULL);//计算耗时int time_used = TIME_SUB_MS(current, begin);//更新每次时间开始值memcpy(&begin, ¤t, 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
#include
#include
#include
#include
#include
#include
#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""Avogado6

Avogado6

\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.mzph.cn/news/961968.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年热门的高速导轨厂家最新TOP实力排行

2025年热门的高速导轨厂家最新TOP实力排行行业背景与市场趋势随着工业4.0的深入推进和智能制造需求的持续增长,高速导轨作为自动化设备的核心部件,其市场规模呈现爆发式增长态势。据《2024-2025全球线性运动部件市场…

2025年口碑好的双轴芯导轨厂家最新实力排行

2025年口碑好的双轴芯导轨厂家最新实力排行行业背景与市场趋势随着工业自动化水平的不断提升,双轴芯导轨作为精密传动系统的核心部件,市场需求呈现稳定增长态势。据《2024-2025中国精密导轨行业白皮书》数据显示,20…

junit单元测试 - 实践

junit单元测试 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "…

2025年大型折叠遮阳蓬制造商排行榜单

2025年大型折叠遮阳蓬制造商排行榜单前言随着户外遮阳需求的不断增长,折叠遮阳蓬市场迎来了快速发展。作为行业领先的制造商,陕西海晟钢结构有限公司凭借卓越的产品质量和创新技术,在2025年的行业评选中表现突出。本…

2025年口碑好的AB枕芯厂家推荐及选择指南

2025年口碑好的AB枕芯厂家推荐及选择指南行业背景与市场趋势随着人们健康睡眠意识的不断提升,枕芯市场正迎来新一轮增长。根据中国睡眠研究会2024年发布的《中国睡眠健康白皮书》显示,2023年中国枕芯市场规模已达287…

2025年靠谱的组合式选粉机厂家最新推荐权威榜

2025年靠谱的组合式选粉机厂家最新推荐权威榜行业背景与市场趋势随着全球环保法规日益严格和工业4.0技术的普及,组合式选粉机作为水泥、冶金、化工等行业的关键设备,正经历着前所未有的技术革新。据中国建材机械工业…

使用 xmake 固定 MinGW 工具链路径的配置方法

使用 xmake 固定 MinGW 工具链路径的配置方法 问题背景 在使用 xmake 进行 C/C++ 项目构建时,发现默认使用的 MinGW 工具链路径不符合预期: xmake f -v # 查看详细配置信息输出显示: {"mingw": "E:…

2025年比较好的选粉机厂家推荐及选择参考

2025年比较好的选粉机厂家推荐及选择参考行业背景与市场趋势选粉机作为水泥、矿山、冶金等行业的关键设备,在物料分级和粉磨系统中发挥着不可替代的作用。随着国家"双碳"目标的持续推进和环保标准的不断提高…

2025年提分产品渠道哪家靠谱

2025年提分产品渠道首选:想象力智能中高考,让提分更精准高效!在2025年这个教育科技飞速发展的时代,选择靠谱的提分产品渠道至关重要。经过市场验证和用户口碑积累,想象力教育科技凭借其卓越的产品实力和技术优势,…

2025年平板类变压器实力厂家排行榜

2025年平板类变压器实力厂家排行榜随着新能源、光伏储能、电动汽车等行业的快速发展,平板类变压器作为高效能磁性器件的关键组件,市场需求持续攀升。2025年,全球平板类变压器行业竞争激烈,技术实力、产能规模及创新…

2025年知名的落石边坡防护网厂家最新推荐权威榜

2025年知名的落石边坡防护网厂家最新推荐权威榜行业背景与市场趋势随着我国基础设施建设的持续投入和地质灾害防治意识的提升,边坡防护网行业近年来呈现稳定增长态势。据中国地质灾害防治工程行业协会最新数据显示,2…

【动态规划】数位DP的原理、模板(封装类) - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

在JMeter中利用地名查询天气预报并查看响应结果

1. 新建线程组:打开JMeter,在测试计划上右键单击,选择“添加”→“线程组”,并为其命名,如“天气查询线程组”。 2. 添加HTTP请求获取城市代码:在线程组上右键单击,选择“添加”→“取样器”→“HTTP请求”,命…

2025年靠谱的主动边坡防护网最新TOP品牌厂家排行

2025年靠谱的主动边坡防护网最新TOP品牌厂家排行行业背景与市场趋势随着我国基础设施建设的持续投入和地质灾害防治意识的提升,边坡防护网行业迎来了快速发展期。据中国地质灾害防治工程行业协会最新数据显示,2024年…

2025年质量好的再生法兰绒行业内口碑厂家排行榜

2025年质量好的再生法兰绒行业内口碑厂家排行榜行业背景与市场趋势再生法兰绒作为环保纺织材料的重要组成部分,近年来在全球纺织行业获得了快速发展。根据中国纺织工业联合会最新发布的《2024-2025中国纺织行业发展趋…

biji-mysql

一、基础 1)基本概念 2)MySQL 支持的数据类型 3)MySQL 中的运算符 4)MySQL 常用函数 5)SQL 6)约束 7)多表查询 8)其它知识点 一、基本概念 1.数据库:存储数据的仓库,本质上是一个文件系统,将数据按照特定的…

2025年11月小户型油烟机型号排名榜:小厨房净烟方案实测

把厨房塞进8㎡以内,油烟却不想“蜗居”。开火三分钟,客厅就飘起葱蒜味;煎条鱼,橱柜门第二天就黏手——这是北京、上海、深圳等城市精装公寓住户的普遍痛点。住建部《2024住宅户型趋势报告》显示,全国60%新增住宅套…

哔哩哔哩网页端查看账号已注销视频

哔哩哔哩网页端查看账号已注销视频链接:https://www.bilibili.com/list/+uid如:https://www.bilibili.com/list/1606577968

DELPHI 构造函数传参

需求: 窗体A 调用窗体B ,窗体B又调用 FrameC.现在要把A中的一个值 S 传给FrameC.然后执行P过程.P在执行的过程中,需要用到P值. 我最开始的设想大概如下(这是不对的):窗体Aprocedure TMaterial2.ModifyInformationCl…

在AI技术快速实现创意的时代,挖掘数学学习新需求成为关键挑战

该项目是一个精心整理的数学资源集合,涵盖从基础数学到高级分支的各类学习资料,包括学习平台、工具软件、教材讲义和视频课程等,为数学学习者提供全面系统的学习支持。a.内容描述核心功能定位:该项目是一个精心整理…