Nginx模块开发之http过滤器filter

文章目录

  • 什么是过滤模块
  • Nginx相关数据结构介绍
    • ngx_module_t的数据结构
    • ngx_http_module_t数据结构
    • ngx_command_s数据结构
  • 相关宏定义
  • filter(过滤器)实现
    • Nginx模块开发流程
    • Nginx 模块执行
    • 具体实现流程
      • create_loc_conf
      • merge_loc_conf
      • postconfiguration
      • 修改header信息
      • 修改body信息
    • 示例代码
    • 编写config文件
    • 编译模块到Nginx源码中
    • 执行效果
  • 总结

什么是过滤模块

在这里插入图片描述
Nignx是一个代理服务器, 他前端被客户端请求,后端连接服务器。这里涉及的数据处理大概有

  1. 客户端请求数据,Nginx直接返回(handler 模块)
  2. 客户端请求数据,Nginx转发给服务器(upstream 模块)
  3. 服务器返回数据,Nginx转发给客户端(filter 模块)

Nginx相关数据结构介绍

ngx_module_t的数据结构

struct ngx_module_s {ngx_uint_t            ctx_index; //是哪个进程ngx_uint_t            index;	//进程idchar                 *name;ngx_uint_t            spare0;ngx_uint_t            spare1;ngx_uint_t            version; //版本号const char           *signature; //签名证书void                 *ctx;//上下文ngx_command_t        *commands;//命令ngx_uint_t            type;// nginx模块类型ngx_int_t           (*init_master)(ngx_log_t *log);//ngx_int_t           (*init_module)(ngx_cycle_t *cycle); //模块启动时候ngx_int_t           (*init_process)(ngx_cycle_t *cycle); //进程启动时候ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);void                (*exit_thread)(ngx_cycle_t *cycle);void                (*exit_process)(ngx_cycle_t *cycle);void                (*exit_master)(ngx_cycle_t *cycle);uintptr_t             spare_hook0;uintptr_t             spare_hook1;uintptr_t             spare_hook2;uintptr_t             spare_hook3;uintptr_t             spare_hook4;uintptr_t             spare_hook5;uintptr_t             spare_hook6;uintptr_t             spare_hook7;
};
typedef struct ngx_module_s ngx_module_t;

ngx_http_module_t数据结构

typedef struct {void        **main_conf;void        **srv_conf;void        **loc_conf;
} ngx_http_conf_ctx_t;typedef struct {ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);	// 解析配置文件之前ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);	// 解析配置文件完成之后
// **_main_ **解析配置文件中http关键字的内部void       *(*create_main_conf)(ngx_conf_t *cf);	char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
// **_srv_ **解析配置文件中server关键字的内部void       *(*create_srv_conf)(ngx_conf_t *cf);	char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
// **_loc_ **解析配置文件中location关键字的内部void       *(*create_loc_conf)(ngx_conf_t *cf);char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

ngx_command_s数据结构

struct ngx_command_s {ngx_str_t             name;ngx_uint_t            type;char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);ngx_uint_t            conf;ngx_uint_t            offset;void                 *post;
};

相关宏定义

#define NGX_MODULE_V1                                                         \NGX_MODULE_UNSET_INDEX, NGX_MODULE_UNSET_INDEX,                           \NULL, 0, 0, nginx_version, NGX_MODULE_SIGNATURE#define NGX_MODULE_V1_PADDING  0, 0, 0, 0, 0, 0, 0, 0#define NGX_HTTP_MODULE           0x50545448   /* "HTTP" 模块*//* 以下宏定义为了去确定该项配置属于哪个类目下 
比如service 
比如location
*/
#define NGX_HTTP_MAIN_CONF        0x02000000
#define NGX_HTTP_SRV_CONF         0x04000000
#define NGX_HTTP_LOC_CONF         0x08000000
#define NGX_HTTP_UPS_CONF         0x10000000
#define NGX_HTTP_SIF_CONF         0x20000000
#define NGX_HTTP_LIF_CONF         0x40000000
#define NGX_HTTP_LMT_CONF         0x80000000

filter(过滤器)实现

Nginx模块开发流程

(1)定义一个模块名,ngx_module_t,选择好http模块NGX_HTTP_MODULE。
(2)定义cmd命令,有多少条cmd写多少条cmd,ngx_command_t。
(3)定义用来解析http block,ngx_http_module_t。
(4)执行过程实现添加模块。

Nginx 模块执行

(1)初始化。当进程启动的时候进行的模块初始化。
(2)解析conf文件。解析conf文件中模块的相关命令和设置。
(3)Nginx启动之后,有命令或请求到来时,处理请求的流程。

开发模块时,需要实现的主要是这三个流程的功能。

具体实现流程

create_loc_conf

内存池中分配一片kong空间,用以存储配置文件中指令对应的值

// void       *(*create_loc_conf)(ngx_conf_t *cf);
// 解析conf文件location关键字之前的动作
void  *ngx_http_fly_filter_create_loc_conf(ngx_conf_t *cf)
{ngx_http_filter_conf_t *conf = ngx_palloc(cf->pool, sizeof(ngx_http_filter_conf_t));if (conf == NULL)return NULL;conf->enable = NGX_CONF_UNSET;return conf;
}

merge_loc_conf

char  *ngx_http_fly_filter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{ngx_http_filter_conf_t *prev = (ngx_http_filter_conf_t*)parent;//如果prefix中是on,那么next->enable的值就为1,这个是在ngx_conf_set_flag_slot//函数中设置的,即可以理解为将配置//文件中的on或者off转换为nginx内存中的1或者0ngx_http_filter_conf_t *next = (ngx_http_filter_conf_t*)child;ngx_conf_merge_value(next->enable, prev->enable, 0);return NGX_CONF_OK;
}

其中 ngx_conf_merge_value

#define ngx_conf_merge_value(conf, prev, default)                            \if (conf == NGX_CONF_UNSET) {                                            \conf = (prev == NGX_CONF_UNSET) ? default : prev;                    \

postconfiguration

  1. 解析完毕conf文件后执行该指令,设置运行时候的回调函数
  2. 使用头插法,将header_filter 与 body_filter插入filter队列的头部
// ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);
// 解析完配置文件之后的动作,也就是解析完http关键字模块之后
ngx_int_t ngx_http_fly_filter_init(ngx_conf_t *cf)
{// 模块的初始化// http {  }// O->O->O->O// 多个模块的头插法,取出最前面的模块//top指向第一个,next指向第二个ngx_http_next_header_filter = ngx_http_top_header_filter;ngx_http_top_header_filter = ngx_http_fly_header_filter;ngx_http_next_body_filter = ngx_http_top_body_filter;ngx_http_top_body_filter = ngx_http_fly_body_filter;return NGX_OK;
}

修改header信息

这里仅仅修改要回发的内容长度,由于修改了body内容,那么header中的length字段自然要做出相应的修改

static ngx_str_t prefix = ngx_string("<h2>FLY. </h2>");
ngx_int_t ngx_http_fly_header_filter(ngx_http_request_t *r) {if (r->headers_out.status != NGX_HTTP_OK) {// 不正常返回,则进行nextreturn ngx_http_next_header_filter(r);}//r->headers_out.content_type.len == sizeof("text/html")r->headers_out.content_length_n += prefix.len;return ngx_http_next_header_filter(r);
}

修改body信息

ngx_int_t ngx_http_fly_body_filter(ngx_http_request_t *r, ngx_chain_t *chain) {/** 关于ngx_chain_t:* 在nginx中,有一个数据链,存放要发送的数据。* O->O->O->O* 每次send的是ngx_chain_t中的一个ngx_buf_t*/// 添加一个chain bufferngx_buf_t *b = ngx_create_temp_buf(r->pool, prefix.len);b->start = b->pos = prefix.data;b->last = b->pos + prefix.len;ngx_chain_t *c1 = ngx_alloc_chain_link(r->pool);c1->buf = b;c1->next = chain;return ngx_http_next_body_filter(r, c1);}

示例代码

这里主要实现了在返回的网页中添加一个内容。里面在重点地方附上了详细注释。
ngx_http_filter_module.c


#include <ngx_config.h>
#include <ngx_http.h>
#include <ngx_core.h>typedef struct {ngx_flag_t enable;
}ngx_http_filter_conf_t;static ngx_str_t prefix = ngx_string("<h2>FLY. </h2>");static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;// ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r)
// 添加头,header
ngx_int_t ngx_http_fly_header_filter(ngx_http_request_t *r) {if (r->headers_out.status != NGX_HTTP_OK) {// 不正常返回,则进行nextreturn ngx_http_next_header_filter(r);}//r->headers_out.content_type.len == sizeof("text/html")r->headers_out.content_length_n += prefix.len;return ngx_http_next_header_filter(r);
}// ngx_int_t (*ngx_http_output_body_filter_pt)(ngx_http_request_t *r, ngx_chain_t *chain)
// 添加内容,body
ngx_int_t ngx_http_fly_body_filter(ngx_http_request_t *r, ngx_chain_t *chain) {/** 关于ngx_chain_t:* 在nginx中,有一个数据链,存放要发送的数据。* O->O->O->O* 每次send的是ngx_chain_t中的一个ngx_buf_t*/// 添加一个chain bufferngx_buf_t *b = ngx_create_temp_buf(r->pool, prefix.len);b->start = b->pos = prefix.data;b->last = b->pos + prefix.len;ngx_chain_t *c1 = ngx_alloc_chain_link(r->pool);c1->buf = b;c1->next = chain;return ngx_http_next_body_filter(r, c1);}// ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);
// 解析完配置文件之后的动作,也就是解析完http关键字模块之后
ngx_int_t ngx_http_fly_filter_init(ngx_conf_t *cf)
{// 模块的初始化// http {  }// O->O->O->O// 多个模块的头插法,取出最前面的模块ngx_http_next_header_filter = ngx_http_top_header_filter;ngx_http_top_header_filter = ngx_http_fly_header_filter;ngx_http_next_body_filter = ngx_http_top_body_filter;ngx_http_top_body_filter = ngx_http_fly_body_filter;return NGX_OK;
}// void       *(*create_loc_conf)(ngx_conf_t *cf);
// 解析conf文件location关键字之前的动作
void  *ngx_http_fly_filter_create_loc_conf(ngx_conf_t *cf)
{ngx_http_filter_conf_t *conf = ngx_palloc(cf->pool, sizeof(ngx_http_filter_conf_t));if (conf == NULL)return NULL;conf->enable = NGX_CONF_UNSET;return conf;
}// char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
// 解析完配置文件location关键字之后的动作
// 模块可能在多个地方定义,这个函数合并所有的值一起使用
char  *ngx_http_fly_filter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{ngx_http_filter_conf_t *prev = (ngx_http_filter_conf_t*)parent;ngx_http_filter_conf_t *next = (ngx_http_filter_conf_t*)child;// 合并enable的值ngx_conf_merge_value(next->enable, prev->enable, 0);return NGX_CONF_OK;
}/*
struct ngx_command_s {
ngx_str_t             name;
ngx_uint_t            type;
char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_uint_t            conf;
ngx_uint_t            offset;
void                 *post;
};
*//*
// conf文件命令解析
char  *ngx_http_fly_filter_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{char *p = conf;// 对应 ngx_http_fly_filter_create_loc_conf函数的conf->enable = NGX_CONF_UNSET;ngx_flag_t *flag = (p + cmd->offset);return NGX_CONF_OK;
}
*/// conf文件中的每一行都是一个指令指令
ngx_command_t ngx_http_fly_filter_module_cmd[] = {{//命令名称,比如listen,定义了就可以在conf文件中使用,注意不能和其他的起冲突ngx_string("predix"),// 指示name命令放的位置在哪里以及可以带多少个参数,NGX_CONF_FLAGE表示开关标志// predix on/offNGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG,// 命令解析,可以使用nginx内部的也可以自己实现ngx_conf_set_flag_slot,//ngx_http_fly_filter_set_slot,NGX_HTTP_LOC_CONF_OFFSET,// offsetof获取enable在结构体中的偏移位置offsetof(ngx_http_filter_conf_t,enable),NULL,},ngx_null_command
};// 用来解析对应的conf文件,其实表示的就是模块定义中的上下文
static ngx_http_module_t ngx_http_fly_filter_module_ctx = {NULL,ngx_http_fly_filter_init,NULL,NULL,NULL,NULL,ngx_http_fly_filter_create_loc_conf,ngx_http_fly_filter_merge_loc_conf
};// 模块定义
ngx_module_t ngx_http_fly_filter_module = {NGX_MODULE_V1,&ngx_http_fly_filter_module_ctx,ngx_http_fly_filter_module_cmd,// http的ascii值,指示是什么模块NGX_HTTP_MODULE,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NGX_MODULE_V1_PADDING	// 填充};

编写config文件

创建:

touch config

内容:

ngx_addon_name=ngx_http_fly_filter_module
HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_fly_filter_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_filter_module.c"

包含三部分信息,一个是模块的名称这里名称需要与代码中的定义的模块名称ngx_module_t一致;第二部分是指定模块的类型和名称,这里定义的是一个filter模块;最后是指定模块源文件路径。
注意,config文件要和模块的代码在相同目录。

编译模块到Nginx源码中

(1)配置中添加模块:

./configure --prefix=/usr/local/nginx --with-http_realip_module 
--with-http_addition_module --with-http_gzip_static_module 
--with-http_secure_link_module --with-http_stub_status_module 
--with-stream --with-pcre=/home/fly/workspace/pcre-8.41 
--with-zlib=/home/fly/workspace/zlib-1.2.11 
--with-openssl=/home/fly/workspace/openssl-1.1.0g 
--add-module=/mnt/hgfs/sourcecode_learning/ngx_http_filter_module

注意模块路径要正确。出现如下表示成功:

configuring additional modules
adding module in /mnt/hgfs/sourcecode_learning/ngx_http_filter_module+ ngx_http_fly_filter_module was configured
creating objs/Makefile

(2)查看是否添加模块到动态代码中:

vim objs/ngx_modules.c

(3)编译:

make
sudo make install

执行效果

编译安装完成后,在conf文件中添加模块的开关(predix on):

worker_processes 4;events {worker_connections 1024;
}http {upstream backend {server 192.168.7.146:8889;server 192.168.7.146:8890;}server {listen 8888;location / {proxy_pass http://backend;}}server {listen 8889;}server {listen 8890;predix on;}server {listen 8891;}
}

执行Nginx:

sudo /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/fly.conf 

在网页输入IP和端口,执行效果如下:
在这里插入图片描述
可以看到,返回的网页中多出来添加的内容(FLY.)。

总结

  1. Nginx中http模块非常多,每个模块都会有ngx_http_module_t,为了防止解析过程出现冲突,Nginx编译的时候会把所有的模块都集中起来,组织到/obj/ngx_module.c(以数组的方式)。
  2. 在编译模块时,需要编写config文件,这个文件最好不要使用笔记本编辑(notepad),容易造成编码方式的错误。
  3. 网页中常见的广告(其实里面存储了图片、链接、名称信息)等等其实就是通过nginx过滤器模块去实现的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/161834.shtml

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

相关文章

使用OkHttp库爬取百度云视频详细步骤

目录 摘要 一、OkHttp库简介 二、爬虫基本概念 三、使用OkHttp库爬取百度云视频 1、发送HTTP请求 2、处理响应 3、下载文件 四、可能遇到的问题及解决方案 五、注意事项 总结与建议 摘要 本文将详细介绍如何使用OkHttp库爬取百度云视频。文章首先简要介绍OkHttp库和…

GPIO模式详解:推挽/开漏/浮空/上拉/下拉/施密特(迟滞)输入

GPIO(General Purpose Input Output)可用于执行数字输入或输出功能。典型的应用包括从/向模拟或数字传感器/设备读写数值、驱动LED、为I2C通信驱动时钟、生成外部组件的触发、发出中断等。 文章目录 1 GPIO简介2 输出模式2.1 推挽输出2.2 开漏输出 3 输入模式3.1 高阻态(浮空)、…

推荐一款适合做智慧旅游的前端模板

目录 前言 一、功能介绍 二、前端技术介绍 三、功能及界面设计介绍 1、数据概览 2、车辆监控 3、地图界面 4、其它功能 四、扩展说明 总结 前言 智慧旅游是一种全新的旅游业务模式&#xff0c;它充分利用先进的信息技术&#xff0c;提升旅游体验&#xff0c;优化旅游管…

【Axure高保真原型】树形表格

今天和大家分享树形表格的原型模板&#xff0c;点击树的箭头可以打开或者收起子节点&#xff0c;点击表格内容&#xff0c;可以选中该行内容实现高亮变色效果&#xff0c;树形表格是通过中继器制作的&#xff0c;使用简单&#xff0c;只需要按要求填写中继器表格即可&#xff0…

2023亚太杯数学建模思路 - 案例:粒子群算法

文章目录 1 什么是粒子群算法&#xff1f;2 举个例子3 还是一个例子算法流程算法实现建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是粒子群算法&#xff1f; 粒子群算法&#xff08;Pa…

安防监控视频融合平台EasyCVR定制化页面开发

安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存储、回放与检索…

Django 模型和Admin站点管理(三)

一、定义模型 &#xff08;1&#xff09; 创建模型类&#xff0c;必须要继承自 models.Model from django.db import models# Create your models here. #设计数据库 #创建模型 class UserModel(models.Model):namemodels.CharField(max_length30) #对应于SQL name varchar(30…

K8s实战RestartPoliy策略

一、默认策略为Always cmd.yaml apiVersion: v1 kind: Pod metadata:name: myapp-pod labels:app: myapp spec: containers:- name: myapp-container image: busyboxcommand: [sh, -c, echo OK!&& sleep 60]首先我们根据这个yaml创建一个测试的pod 执行命令 kubec…

深度之眼Paper带读笔记GNN.08.GCN(下)

文章目录 前言细节四&#xff1a;卷积核介绍图卷积核初代目图卷积核二代目契比雪夫多项式例子小结 GCN公式推导 实验设置和结果分析数据集节点分类任务消息传递方式比较运行效率 总结关键点创新点启发点 代码复现train.pyutil.pymodel.pylayer.py 作业 前言 本课程来自深度之眼…

基于单片机直流电机调速(proteus仿真+源程序)

一、系统方案 1、本设计采用这51单片机作为主控器。 2、转速值送到液晶1602显示。 3、按键设加减速&#xff0c;开始暂停、正反转。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 en0; rw0; write_com(0x01); //lcd初始化 write_com(0x38)…

CQ 社区版 V2.6.0 发布 | SQL闪回、权限看板、新增数据源人大金仓等

前言 HELLO&#xff0c;大家好&#xff0c;又到了 CloudQuery 社区版发版时间&#xff01;本次更新版本为 v2.6.0&#xff0c;亮点多多&#xff0c;我们直入主题一起来看&#xff01; 一、本期亮点 新增 3 种数据源支持 V2.6.0&#xff0c;新增三种国产数据源支持&#xff…

cocos2dx ​​Animate3D (一)

3D相关的动画都是继承Grid3DAction 本质上是用GirdBase进行创建动画的小块。 Shaky3D 晃动特效 // 持续时间(时间过后不会回到原来的样子) // 整个屏幕被分成几行几列 // 晃动的范围 // z轴是否晃动 static Shaky3D* create(float initWithDuration, const Size& …

内存可见性与指令重排序

文章目录 内存可见性内存可见性问题代码演示JMM&#xff08;Java Memory Model&#xff09; 指令重排序指令重排序问题代码演示指令重排序分析 volatile关键字volatile 保证内存可见性 & 禁止指令重排序volatile 不保证原子性 在上一节介绍线程安全问题的过程中&#xff0c…

2023亚太杯数学建模B题思路 - 玻璃温室中的微气候法规

# 1 赛题 问题B 玻璃温室中的微气候法规 温室作物的产量受到各种气候因素的影响&#xff0c;包括温度、湿度和风速[1]。其中&#xff0c;适 宜的温度和风速是植物生长[2]的关键。为了调节玻璃温室内的温度、风速等气候因素 , 温室的设计通常采用带有温室风扇的通风系统&#x…

实验4.数据全量、增量、比较更新

【实验目的】 1.利用Kettle的“表输入”&#xff0c;“表输入出”&#xff0c;”JavaScript代码”组件&#xff0c;实现数据全量更新。 2.熟练掌握“JavaScript代码”&#xff0c;“表输入”&#xff0c;“表输入出”组件的使用&#xff0c;实现数据全量更新。 【实验原理】 …

二级指针

*代表指针变量。int*为p的类型。故pp第一个*表示pp为指针int** pp&#xff0c;指向p的二级指针。 p中储存a的地址&#xff0c;pp中储存p的地址。 打印&#xff0c;printf中**pp的表示&#xff1a;pp中储存的是p的地址&#xff0c;第一个*解引用地址p表示p的内容&#xff0c;p的…

回归算法优化过程推导

假设存在一个数据集&#xff0c;包含工资、年龄及贷款额度三个维度的数据。我们需要根据这个数据集进行建模&#xff0c;从而在给定工资和年龄的情况下&#xff0c;实现对贷款额度的预测。其中&#xff0c;工资和年龄是模型构建时的两个特征&#xff0c;额度是模型输出的目标值…

SPASS-ARIMA模型

基本概念 在预测中,对于平稳的时间序列,可用自回归移动平均(AutoRegres- sive Moving Average, ARMA)模型及特殊情况的自回归(AutoRegressive, AR)模型、移动平均(Moving Average, MA)模型等来拟合,预测该时间序列的未来值,但在实际的经济预测中,随机数据序列往往…

macos端文件夹快速访问工具 Default Folder X 最新for mac

Default Folder X 是一款实用的工具&#xff0c;提供了许多增强功能和快捷方式&#xff0c;使用户能够更高效地浏览和管理文件。它的快速导航、增强的文件对话框、自定义设置和快捷键等功能&#xff0c;可以大大提升用户的工作效率和文件管理体验。 快速导航和访问&#xff1a;…

2023亚太杯数学建模B题思路分析 - 玻璃温室中的微气候法规

1 赛题 问题B 玻璃温室中的微气候法规 温室作物的产量受到各种气候因素的影响&#xff0c;包括温度、湿度和风速[1]。其中&#xff0c;适 宜的温度和风速是植物生长[2]的关键。为了调节玻璃温室内的温度、风速等气候因素 , 温室的设计通常采用带有温室风扇的通风系统&#xf…