Nginx模块开发之http handler实现流量统计(2)

news2024/9/24 9:21:25

文章目录

  • 一、概述
  • 二、Nginx handler模块开发
    • 2.1、代码实现
    • 2.2、编写config文件
    • 2.3、编译模块到Nginx源码中
    • 2.4、修改conf文件
    • 2.5、执行效果
  • 总结

一、概述

上一篇【Nginx模块开发之http handler实现流量统计(1)】使用数组在单进程实现了IP的流量统计,这一篇将进行优化,使用红黑树的数据结构以及共享内存的方式实现进程间通信。

进程间通信的方式:
(1)进程在不同的机器中,使用网络进行通信。
(2)进程在同一个机器,并且进程间的关系是父子进程关系,可以使用共享内存。
(3)使用unix_sock,比如文件soket。在MySQL使用的就是这种进程通信方式。
(4)pipe,管道。

二、Nginx handler模块开发

2.1、代码实现

在重点地方添加了注释,主要是红黑树的添加和使用,以及共享内存的使用。

核心:
1.nginx获取请求。ngx_command_t中设置ngx_http_pagecount_set。
2.conf文件解析到模块的cmd时,初始化共享内存以及互斥锁。
3.红黑树的初始化。
4.组织网页时,需要遍历红黑树找到IP并做流量统计。


#include <ngx_http.h>
#include <ngx_config.h>
#include <ngx_core.h>

#define ENABLE_RBTREE	1

static char *ngx_http_pagecount_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_pagecount_handler(ngx_http_request_t *r);
static ngx_int_t ngx_http_pagecount_init(ngx_conf_t *cf);
static void  *ngx_http_pagecount_create_location_conf(ngx_conf_t *cf);
static ngx_int_t ngx_http_pagecount_shm_init (ngx_shm_zone_t *zone, void *data);
static void ngx_http_pagecount_rbtree_insert_value(ngx_rbtree_node_t *temp,
        ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);


// 命令解析
static ngx_command_t count_commands[] = {
	{
		ngx_string("count"),
		NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
		ngx_http_pagecount_set,//遇到模块命令时调用
		NGX_HTTP_LOC_CONF_OFFSET,
		0, 
		NULL
	},
	ngx_null_command
};

static ngx_http_module_t count_ctx = {
	NULL,
	ngx_http_pagecount_init,//初始化
	
	NULL,
	NULL,

	NULL,
	NULL,

	// conf文件解析到location时调用
	ngx_http_pagecount_create_location_conf,
	NULL,
};

//ngx_http_count_module 
ngx_module_t ngx_http_pagecount_module = {
	NGX_MODULE_V1,
	&count_ctx,
	count_commands,
	NGX_HTTP_MODULE,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NGX_MODULE_V1_PADDING
};

typedef struct {
	int count; //count
} ngx_http_pagecount_node_t;

typedef struct {

	ngx_rbtree_t rbtree;
	ngx_rbtree_node_t sentinel;
	
} ngx_http_pagecount_shm_t;

// 共享内存结构体
typedef struct
{
    ssize_t shmsize;
 
    ngx_slab_pool_t *shpool;// 互斥锁
 
    ngx_http_pagecount_shm_t *sh;
} ngx_http_pagecount_conf_t;

// 共享内存初始化
ngx_int_t ngx_http_pagecount_shm_init (ngx_shm_zone_t *zone, void *data) {

	ngx_http_pagecount_conf_t *conf;
	ngx_http_pagecount_conf_t *oconf = data;

	conf = (ngx_http_pagecount_conf_t*)zone->data;
	if (oconf) {
		conf->sh = oconf->sh;
		conf->shpool = oconf->shpool;
		return NGX_OK;
	}

	//printf("ngx_http_pagecount_shm_init 0000\n");
	
	// 初始化锁
	conf->shpool = (ngx_slab_pool_t*)zone->shm.addr;
	conf->sh = ngx_slab_alloc(conf->shpool, sizeof(ngx_http_pagecount_shm_t));
	if (conf->sh == NULL) {
		return NGX_ERROR;
	}

	conf->shpool->data = conf->sh;

	//printf("ngx_http_pagecount_shm_init 1111\n");
	
	// 共享内存创建完之后初始化红黑树,
	// 要提供插入函数
	ngx_rbtree_init(&conf->sh->rbtree, &conf->sh->sentinel, 
		ngx_http_pagecount_rbtree_insert_value);


	return NGX_OK;

}


static char *ngx_http_pagecount_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {

	ngx_shm_zone_t *shm_zone;
	ngx_str_t name = ngx_string("pagecount_slab_shm");

	ngx_http_pagecount_conf_t *mconf = (ngx_http_pagecount_conf_t*)conf;
	ngx_http_core_loc_conf_t *corecf;
	
	//ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, "ngx_http_pagecount_set000");
	
	// 设置共享内存的大小
	mconf->shmsize = 1024*1024;
	
	shm_zone = ngx_shared_memory_add(cf, &name, mconf->shmsize, &ngx_http_pagecount_module);
	if (NULL == shm_zone) {
		return NGX_CONF_ERROR;
	}

	
	shm_zone->init = ngx_http_pagecount_shm_init;
	shm_zone->data = mconf;

	corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
	// 计数统计主函数
	corecf->handler = ngx_http_pagecount_handler;

	return NGX_CONF_OK;
}

ngx_int_t   ngx_http_pagecount_init(ngx_conf_t *cf) {


	return NGX_OK;
}


// conf文件解析到location时进入此函数。
// 此函数为模块创建所需要的结构体, 后面会使用。
void  *ngx_http_pagecount_create_location_conf(ngx_conf_t *cf) {

	ngx_http_pagecount_conf_t *conf;
	
	// 共享内存的结构体
	conf = ngx_palloc(cf->pool, sizeof(ngx_http_pagecount_conf_t));
	if (NULL == conf) {
		return NULL;
	}

	// 初始化共享内存的大小
	conf->shmsize = 0;
	//ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, "ngx_http_pagecount_create_location_conf");

	// init conf data
	// ... 

	return conf;

}

// 红黑树的插入函数
static void
ngx_http_pagecount_rbtree_insert_value(ngx_rbtree_node_t *temp,
        ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
   ngx_rbtree_node_t **p;
   //ngx_http_testslab_node_t *lrn, *lrnt;
 
    for (;;)
    {
        if (node->key < temp->key)
        {
            p = &temp->left;
        }
        else if (node->key > temp->key) {
           	p = &temp->right;
        }
        else
        {
          	return ;
        }
		// 没有找到,直接返回
        if (*p == sentinel)
        {
            break;
        }
 
        temp = *p;
    }
 
    *p = node;
 
    node->parent = temp;
    node->left = sentinel;
    node->right = sentinel;
    ngx_rbt_red(node);
}

// 查找IP和访问计数统计
static ngx_int_t ngx_http_pagecount_lookup(ngx_http_request_t *r, ngx_http_pagecount_conf_t *conf, ngx_uint_t key) {

	ngx_rbtree_node_t *node, *sentinel;

	node = conf->sh->rbtree.root;
	sentinel = conf->sh->rbtree.sentinel;

	ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " ngx_http_pagecount_lookup 111 --> %x\n", key);
	
	while (node != sentinel) {

		if (key < node->key) {
			node = node->left;
			continue;
		} else if (key > node->key) {
			node = node->right;
			continue;
		} else { // key == node
			node->data ++;
			return NGX_OK;
		}
	}
	
	ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " ngx_http_pagecount_lookup 222 --> %x\n", key);
	
	// insert rbtree
	
	
	node = ngx_slab_alloc_locked(conf->shpool, sizeof(ngx_rbtree_node_t));
	if (NULL == node) {
		return NGX_ERROR;
	}

	node->key = key;
	node->data = 1;

	ngx_rbtree_insert(&conf->sh->rbtree, node);

	ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " insert success\n");
	

	return NGX_OK;
}


static int ngx_encode_http_page_rb(ngx_http_pagecount_conf_t *conf, char *html) {

	sprintf(html, "<h1>Source Insight </h1>");
	strcat(html, "<h2>");

	//ngx_rbtree_traversal(&ngx_pv_tree, ngx_pv_tree.root, ngx_http_count_rbtree_iterator, html);
	ngx_rbtree_node_t *node = ngx_rbtree_min(conf->sh->rbtree.root, conf->sh->rbtree.sentinel);

	do {

		char str[INET_ADDRSTRLEN] = {0};
		char buffer[128] = {0};

		sprintf(buffer, "req from : %s, count: %d <br/>",
			inet_ntop(AF_INET, &node->key, str, sizeof(str)), node->data);

		strcat(html, buffer);

		node = ngx_rbtree_next(&conf->sh->rbtree, node);

	} while (node);
	

	strcat(html, "</h2>");

	return NGX_OK;
}



static ngx_int_t ngx_http_pagecount_handler(ngx_http_request_t *r) {


	u_char html[1024] = {0};
	int len = sizeof(html);
	
	ngx_rbtree_key_t key = 0;


	struct sockaddr_in *client_addr =  (struct sockaddr_in*)r->connection->sockaddr;
	

	ngx_http_pagecount_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_pagecount_module);
	key = (ngx_rbtree_key_t)client_addr->sin_addr.s_addr;

	ngx_log_error(NGX_LOG_EMERG, r->connection->log, ngx_errno, " ngx_http_pagecount_handler --> %x\n", key);

	ngx_shmtx_lock(&conf->shpool->mutex);
	ngx_http_pagecount_lookup(r, conf, key);	
	ngx_shmtx_unlock(&conf->shpool->mutex);
	
	ngx_encode_http_page_rb(conf, (char*)html);

	//header
	r->headers_out.status = 200;
	ngx_str_set(&r->headers_out.content_type, "text/html");
	ngx_http_send_header(r);

	//body
	ngx_buf_t *b = ngx_pcalloc(r->pool,  sizeof(ngx_buf_t));

	ngx_chain_t out;
	out.buf = b;
	out.next = NULL;

	b->pos = html;
	b->last = html+len;
	b->memory = 1;
	b->last_buf = 1;

	return ngx_http_output_filter(r, &out);
	
	
}

2.2、编写config文件

创建:

touch config

内容:

ngx_addon_name=ngx_http_pagecount_module
HTTP_MODULES="$HTTP_MODULES ngx_http_pagecount_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_pagecount_module.c"

注意,config文件要和模块的代码在相同目录。

2.3、编译模块到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/nginx-module/ngx_http_pagecount_module

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

configuring additional modules
adding module in /mnt/hgfs/sourcecode_learning/nginx-module/ngx_http_pagecount_module
 + ngx_http_pagecount_module was configured
creating objs/Makefile

(2)编译安装:

make && sudo make install

2.4、修改conf文件

编译安装完成后,conf文件添加count;


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;
		location / {
			count;
		}
       }
	server {
                listen 8890;
        }
	server {
                listen 8891;
        }



}

2.5、执行效果

关闭已启动的nginx:

sudo /usr/local/nginx/sbin/nginx -s stop

启动nginx:

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

在网页输入IP和端口,执行效果如下:
在这里插入图片描述

总结

  1. 实现一个基于Nginx的IP统计或访问流量统计,可以借鉴以上代码;只要做一些业务上的修改就可以直接使用。可能需改动的地方就是红黑树的key、value的数据结构,以及ngx_encode_http_page_rb函数的业务代码,其他可以基本不用改动就可以二次开发。
  2. Nginx需要熟悉的数据结构:内存池、queue、list、array、shmem等。同时需要清楚Nginx的11个状态。
  3. 在实际应用中,需要掌握Nginx的conf文件配置(https的配置、负载均衡的配置、反向代理、CPU亲缘性配置等)以及模块开发(filter、handler等)。

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

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

相关文章

698. 划分为k个相等的子集

698. 划分为k个相等的子集 Java&#xff1a;回溯 class Solution {boolean[] used;int target;private boolean backtracking(int[] nums, int k, int sum, int start) {if (k 0) {return true; // 找到&#xff1a;立即中断栈&#xff01;并返回值}if (sum target) { // 构…

中国企业500强的排名也在不断变化。面对不确定性的挑战,企业如何应对?

随着全球经济的不断发展和变化&#xff0c;中国企业500强的排名也在不断变化。面对不确定性的挑战&#xff0c;企业如何应对&#xff1f;在本文中&#xff0c;挖数据平台将提供数据源探讨中国企业500强在应对不确定性方面的突围与变革。 一、数据挖掘与分析 从2006年到2023年&…

【电子通识】为什么说做产品不是简单的将不同的技术进行搭积木?

很多人说做产品的硬件工程师&#xff0c;其实就是将专项技术工程师已经调好的模块进行拼接。类似于小孩将积木搭成一个房子的形状&#xff0c;虽然不同人搭的房子风格迥异&#xff0c;但所使用的原材料却都是一样的。 首先我并不同意这种看法&#xff0c;原因是产品工程师是需要…

搞笑的客服日常,今天遇到的晒白客服,你遇到过吗?

大家看看这个 https://blog.csdn.net/a871923942/article/details/129778391?utm_mediumdistribute.pc_feed_404.none-task-blog-2 今天遇到傻逼审核 开头第一句就使用快捷话术 客服感觉自己发错了 然后就撤回消息 第二次继续发话术&#xff0c;说违规&#xff0c;审核不通过…

STM32入门--CAN

目录 一、bxCan简介 二、bxCAN总体描述 2.1概述 2.2CAN框图 三、bxCA的工作模式 3.1初始化模式 3.2正常模式 3.3睡眠模式&#xff08;低功耗&#xff09; 四、测试模式 4.1静默模式 4.2环回模式 五、bxCAN功能描述 5.1 发送处理 ​编辑 5.2接收管理 5.2.1 标识符过…

基于食肉植物算法优化概率神经网络PNN的分类预测 - 附代码

基于食肉植物算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于食肉植物算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于食肉植物优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

CAN实验

CAN 寄存器 HAL库函数 代码 #include "./BSP/CAN/can.h"CAN_HandleTypeDef g_can1_handle; CAN_TxHeaderTypeDef g_can1_txheader; CAN_RxHeaderTypeDef g_can1_rxheader;/* STM32F103 TS1 8 TS2 7 BRP 3 波特率&#xff1a;36000 / [(9 8 1) * 4] 500Kbps …

甲烷产生及氧化

温室气体排放被认为是加速气候变化的重要因素&#xff0c;甲烷(CH4)是仅次于二氧化碳(CO2)的重要温室气体&#xff0c;其百年温室效应潜势是CO2的28倍[1-2]。湿地中的CH4由产甲烷古菌在水体底部或沉积层严格厌氧环境下产生并释放进入水体&#xff0c;产生的CH4向上覆水运输过程…

【沐风老师】在3dMax中如何把对象随机散布在表面上?

在3dMax中如何把对象随机散布在表面上&#xff1f; 在这个教程中&#xff0c;给大家讲解在3dMax中如何把对象随机散布到另一个对象的表面上。有不少3dMax的初学者在将对象分布在随机表面上时感到手足无措。如果&#xff0c;将每个对象手动放置在表面上并花时间调整每个对象根本…

卷积神经网络(Inception V3)识别手语

文章目录 一、前言二、前期工作1. 设置GPU&#xff08;如果使用的是CPU可以忽略这步&#xff09;2. 导入数据3. 查看数据 二、数据预处理1. 加载数据2. 可视化数据3. 再次检查数据4. 配置数据集 三、构建Inception V3网络模型1.自己搭建2.官方模型 五、编译六、训练模型七、模型…

C++模拟如何实现vector的方法

任意位置插入&#xff0c;insert的返回值为新插入的第一个元素位置的迭代器&#xff1b;因为插入可能会进行扩容&#xff0c;导致start的值改变&#xff0c;所以先定义一个变量保存pos与start的相对位置&#xff1b;判断是否需要扩容&#xff1b;从插入位置开始&#xff0c;将所…

Qt学习(2)

1.QObject 只有继承了QObject类的类&#xff0c;才具有信号槽的能力。所以&#xff0c;为了使用信号槽&#xff0c;必须继承QObject。凡是QObject类&#xff08;不管是直接子类还是间接子类&#xff09;&#xff0c;都应该在第一行代码写上Q_OBJECT。不管是不是使用信号槽&…

dom api

dom的全称为Document Object Model,即文档对象模型.所谓文档就是html页面,对象就是js里的对象,通过这个模型把页面上的元素和js里的对象关联起来. 下面是关于dom api的一些常用方法 1.获取元素 使用querySelector()方法获取一个元素 使用querySelectorAll()方法获取所有元素 当…

MediaCodec详解

MediaCodec 是Android平台提供的一个API&#xff0c;用于对音频和视频数据进行编码&#xff08;转换为不同的格式&#xff09;和解码&#xff08;从一种格式转换回原始数据&#xff09;。它是Android 4.1&#xff08;API级别16&#xff09;及以上版本的一部分&#xff0c;允许开…

【C语言】函数(四):函数递归与迭代,二者有什么区别

目录 前言递归定义递归的两个必要条件接受一个整型值&#xff08;无符号&#xff09;&#xff0c;按照顺序打印它的每一位使用函数不允许创建临时变量&#xff0c;求字符串“abcd”的长度求n的阶乘求第n个斐波那契数 迭代总结递归与迭代的主要区别用法不同结构不同时间开销不同…

【Python】实现一个简单的区块链系统

本文章利用 Python 实现一个简单的功能较为完善的区块链系统&#xff08;包括区块链结构、账户、钱包、转账&#xff09;&#xff0c;采用的共识机制是 POW。 一、区块与区块链结构 Block.py import hashlib from datetime import datetimeclass Block:"""区…

智能头盔天眼摄像头、单兵执法记录仪等配合MESH自组网在应急指挥调度中的应用

智能头盔、天眼摄像头、头盔记录仪、头盔摄像头、单兵执法记录仪等配合MESH自组网在应急指挥调度中的应用。 20人背负单兵自组网&#xff08;带手咪&#xff09;到训练场&#xff0c;戴头盔&#xff0c;头盔上放头盔式摄像头&#xff0c;大功率自组网设置在制高点&#xff0c;…

改进YOLOv8 | YOLOv5系列:RFAConv续作,即插即用具有任意采样形状和任意数目参数的卷积核AKCOnv

RFAConv续作,构建具有任意采样形状的卷积AKConv 一、论文yolov5加入的方式论文 源代码 一、论文 基于卷积运算的神经网络在深度学习领域取得了显著的成果,但标准卷积运算存在两个固有缺陷:一方面,卷积运算被限制在一个局部窗口,不能从其他位置捕获信息,并且其采样形状是…

五种多目标优化算法(MOJS、NSGA3、MOGWO、NSWOA、MOPSO)求解微电网多目标优化调度(MATLAB代码)

一、多目标优化算法简介 &#xff08;1&#xff09;多目标水母搜索算法MOJS 多目标优化算法&#xff1a;多目标水母搜索算法MOJS&#xff08;提供MATLAB代码&#xff09;_水母算法-CSDN博客 &#xff08;2&#xff09;NSGA3 NSGA-III求解微电网多目标优化调度&#xff08;M…

c语言数字转圈

数字转圈 题干输入整数 N&#xff08;1≤N≤9&#xff09;&#xff0c;输出如下 N 阶方阵。 若输入5显示如下方阵&#xff1a; * 1** 2** 3** 4** 5* *16**17**18**19** 6* *15**24**25**20** 7* *14**23**22**21** 8* *13**12**11**10** 9*输入样例3输出样例* 1*…