Nginx源码分析 - 基础数据结构篇 - hash表结构 ngx_hash.c(07)

nginx源码分析(1):hash的使用 在nginx源码中提供了一个比较重要的hash结构,可以为我们带来高效的kv查找。该hash的实现比较简单,但却非常的高效。该hash结构是只读的,在创建之后,以后只能提供查询功能。该hash结构体,刚开始理解起来比较费劲,而且在使用时也会有不爽的感觉,需要好几个结构体,以及好几个函数配合才能完成初始化及查找。在本文中,对于通配符的使用,我们先不作介绍。我们先看看如何使用吧。创建一个hash结构体的过程是:1. 构造一个ngx_hash_key_t为成员的数组,然后用我们需要hash的key、value以及 阅读详情
AI摘要

本文深入剖析Nginx的Hash表结构与实现原理,包括ngx_hash_elt_t、ngx_hash_t及ngx_hash_init_t的数据结构定义,展示了Nginx如何在初始化阶段优化内存分配与查询效率。

目录

一、数据结构定义

1. ngx_hash_elt_t hash表的元素结构

2. ngx_hash_t hash表结构

3. ngx_hash_init_t hash表初始化结构

二、数据结构图

三、具体函数实现

1. 查找一个元素 ngx_hash_find

2. 创建一个hash表 ngx_hash_init


Nginx的hash表结构和我们之前阅读memcached的时候看到的会有很大的差别。笔者在阅读Nginx的hash模块的时候,阅读了好几天,比较不容易理解,但是Nginx的hash模块包含了对内存利用最大化、CPU利用最大化的很多设计细节,非常值得推荐和学习。

Nginx的hash表结构主要几个特点:

  • 静态只读。当初始化生成hash表结构后,是不能动态修改这个hash表结构的内容。
  • 将内存利用最大化。Nginx的hash表,将内存利用率发挥到了极致,并且很多设计上面都是可以供我们学习和参考的。
  • 查询速度快。Nginx的hash表做了内存对齐等优化。
  • 主要解析配置数据。

一、数据结构定义


1. ngx_hash_elt_t hash表的元素结构


/**
 * 存储hash的元素
 */
typedef struct {
    void             *value; 	/* 指向value的指针 */
    u_short           len;   	/* key的长度 */
    u_char            name[1]; 	/* 指向key的第一个地址,key长度为变长(设计上的亮点)*/
} ngx_hash_elt_t;

2. ngx_hash_t hash表结构


/**
 * Hash的桶
 */
typedef struct {
    ngx_hash_elt_t  **buckets; 	/* hash表的桶指针地址值 */
    ngx_uint_t        size; 	/* hash表的桶的个数*/
} ngx_hash_t;

3. ngx_hash_init_t hash表初始化结构


/**
 * hash表主体结构
 */
typedef struct {
    ngx_hash_t       *hash;	/* 指向hash数组结构 */
    ngx_hash_key_pt   key;  /* 计算key散列的方法 */

    ngx_uint_t        max_size; 	/* 最大多少个 */
    ngx_uint_t        bucket_size; 	/* 桶的存储空间大小 */

    char             *name; /* hash表名称 */
    ngx_pool_t       *pool; /* 内存池 */
    ngx_pool_t       *temp_pool; /* 临时内存池*/
} ngx_hash_init_t;

二、数据结构图


说明:

  • Nginx的hash表主要存放在ngx_hash_t数据结构上。ngx_hash_t主要存放桶的指针值和桶的个数。
  • Nginx的hash表中桶的个数会在初始化的时候进行“探测”,会探测出合适的痛的个数。
  • Nginx的hash表在初始化的时候就决定了hash表的桶的个数以及元素个数和大小,所以所有元素都会被分配到一个大的连续的内存块上。
  • 每个bucket的长度会根据元素个数的实际长度决定,并且每个bucket之间通过NULL指针进行分割。
  • 每个桶都保存了桶的第一个元素ngx_hash_elt_t的指针值。
  • NULL指针会在查找元素的时候用到,具体看下面的源码阅读。
  • ngx_hash_elt_t存储每个元素的数据结构,并且key的长度是非定长的。

三、具体函数实现


1. 查找一个元素 ngx_hash_find


/**
 * 从hash表中读取一个元素
 */
void *
ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
{
	ngx_uint_t i;
	ngx_hash_elt_t *elt;

#if 0
	ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "hf:\"%*s\"", len, name);
#endif

	/* 获取对应的桶 */
	elt = hash->buckets[key % hash->size];

	if (elt == NULL) {
		return NULL;
	}

	/* 在桶的链表上,查找具体的值;elt元素最后一个elt->value==NULL */
	while (elt->value) {
		if (len != (size_t) elt->len) {
			goto next;
		}

		for (i = 0; i < len; i++) {
			if (name[i] != elt->name[i]) {
				goto next;
			}
		}

		return elt->value;

		next:

		/* 因为在内存池上申请内存,并且是自己处理整块内存,为了CPU读取速度更快,进行了内存对齐 */
		elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
				sizeof(void *));
		continue;
	}

	return NULL;
}

2. 创建一个hash表 ngx_hash_init


获取元素大小的宏定义。

/**
 * 获取元素的大小
 * 元素大小主要是ngx_hash_elt_t结构,包括:
 * 1. name的长度	(name)->key.len
 * 2. len的长度	其中的"+2"是要加上该结构中len字段(u_short类型)的大小
 * 3. value指针的长度	"sizeof(void *)"相当于 value的长度
 */
#define NGX_HASH_ELT_SIZE(name)                                               \
    (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))

ngx_hash_init初始化一个hash表

  • Nginx的hash表是只读的,所以在初始化的时候就会生成固定的hash表。
  • 初始化过程中,先会根据实际key的大小来进行“探测”,得出一个合适的桶的个数。
  • 然后根据元素的大小,来确定每个桶具体的大小,并且分配完整的元素大内存块。
  • 然后将元素切割成ngx_hash_elt_t的结构,装入每一个bucket桶上。
  • 每个bucket的结尾都会有一个NULL空指针作为标识符号,该标识符号会强制换成ngx_hash_elt_t结构,并且设置value=NULL,在查询的时候用于判断桶的结尾部分。
/**
 * 初始化一个hash表
 */
ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
		ngx_uint_t nelts)
{
	u_char *elts;
	size_t len;
	u_short *test;
	ngx_uint_t i, n, key, size, start, bucket_size;
	ngx_hash_elt_t *elt, **buckets;

	/**
	 * 先检查每个元素是否会超过bucket_size的限制
	 * 如果超过限制,则说明需要重新处理
	 * hash表的每一个bucket桶中的元素elt都是被分配到一块完整的内存块上的,
	 * 每个bucket的内存块结尾会有一个void *的空指针作为表示符号用于分隔bucket
	 */
	for (n = 0; n < nelts; n++) {
		if (hinit->bucket_size
				< NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *)) {
			ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
					"could not build the %s, you should "
							"increase %s_bucket_size: %i", hinit->name,
					hinit->name, hinit->bucket_size);
			return NGX_ERROR;
		}
	}

	/*
	 * test是用来做探测用的,探测的目标是在当前bucket的数量下,冲突发生的是否频繁。
	 * 过于频繁则需要调整桶的个数。
	 * 检查是否频繁的标准是:判断元素总长度和bucket桶的容量bucket_size做比较
	 */
	test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
	if (test == NULL) {
		return NGX_ERROR;
	}

	/**
	 * 每个桶的元素实际所能容纳的空间大小
	 * 需要减去尾部的NULL指针结尾符号
	 */
	bucket_size = hinit->bucket_size - sizeof(void *);

	/**
	 * 通过一定的小算法,计算得到从哪个桶开始test(探测)
	 */
	start = nelts / (bucket_size / (2 * sizeof(void *)));
	start = start ? start : 1;

	if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) {
		start = hinit->max_size - 1000;
	}

	/**
	 * 这边就是真正的探测逻辑
	 * 探测会遍历所有的元素,并且计算落到同一个bucket上元素长度的总和和bucket_size比较
	 * 如果超过了bucket_size,则说明需要调整
	 * 最终会探测出比较合适的桶的个数 :size
	 */
	for (size = start; size < hinit->max_size; size++) {

		ngx_memzero(test, size * sizeof(u_short));

		for (n = 0; n < nelts; n++) {
			if (names[n].key.data == NULL) {
				continue;
			}

			key = names[n].key_hash % size;
			test[key] = (u_short)(test[key] + NGX_HASH_ELT_SIZE(&names[n]));

#if 0
			ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
					"%ui: %ui %ui \"%V\"",
					size, key, test[key], &names[n].key);
#endif

			/* 比较bucket_size和落到该bucket上的元素长度总和*/
			if (test[key] > (u_short) bucket_size) {
				goto next;
			}
		}

		goto found;

		next:

		continue;
	}

	ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
			"could not build the %s, you should increase "
					"either %s_max_size: %i or %s_bucket_size: %i", hinit->name,
			hinit->name, hinit->max_size, hinit->name, hinit->bucket_size);

	ngx_free(test);

	return NGX_ERROR;

	/**
	 * 探测成功,则size为bucket桶的个数
	 */
	found:

	/**
	 * 为了确定bucket的实际长度,初始化每个桶的长度计数器,初始值为一个NULL空指针长度
	 * 前面说过,每个bucket的内存块之间,使用一个NULL空指针进行分割,所以长度需要加上去
	 */
	for (i = 0; i < size; i++) {
		test[i] = sizeof(void *);
	}

	/**
	 * 通过遍历,计算每个桶的大小。并且将每个桶的大小存储在test[n]数组上
	 */
	for (n = 0; n < nelts; n++) {
		if (names[n].key.data == NULL) {
			continue;
		}

		key = names[n].key_hash % size;
		test[key] = (u_short)(test[key] + NGX_HASH_ELT_SIZE(&names[n]));
	}

	len = 0;

	/**
	 * 获取所有元素需要分配的内存的总大小
	 * len = 总的内存大小,所有的桶都会放在一块内存上,并且做了手工内存对齐
	 */
	for (i = 0; i < size; i++) {
		if (test[i] == sizeof(void *)) {
			continue;
		}

		/* 总内存大小,需要通过内存对齐函数 */
		test[i] = (u_short)(ngx_align(test[i], ngx_cacheline_size));

		len += test[i];
	}

	/**
	 * 分配一块内存空间,存储:ngx_hash_t *hash和ngx_hash_elt_t *
	 * ngx_hash_elt_t用于存储桶。指针指向元素地址
	 */
	if (hinit->hash == NULL) {
		hinit->hash = ngx_pcalloc(hinit->pool,
				sizeof(ngx_hash_wildcard_t) + size * sizeof(ngx_hash_elt_t *));
		if (hinit->hash == NULL) {
			ngx_free(test);
			return NGX_ERROR;
		}

		buckets = (ngx_hash_elt_t **) ((u_char *) hinit->hash
				+ sizeof(ngx_hash_wildcard_t));

	} else {
		buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));
		if (buckets == NULL) {
			ngx_free(test);
			return NGX_ERROR;
		}
	}

	/**
	 * 分配一个桶,用于存储所有元素数据
	 */
	elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);
	if (elts == NULL) {
		ngx_free(test);
		return NGX_ERROR;
	}

	elts = ngx_align_ptr(elts, ngx_cacheline_size); //内存对齐

	/**
	 * 将elts元素的内存,分割到buckets桶上
	 */
	for (i = 0; i < size; i++) {
		if (test[i] == sizeof(void *)) {
			continue;
		}

		buckets[i] = (ngx_hash_elt_t *) elts;
		elts += test[i];

	}

	/**
	 * 将test清空,利用test于元素填充计数器
	 */
	for (i = 0; i < size; i++) {
		test[i] = 0;
	}

	/**
	 * 往bucket的元素位上填充数据
	 */
	for (n = 0; n < nelts; n++) {
		if (names[n].key.data == NULL) {
			continue;
		}

		/* 计算在哪个桶上 */
		key = names[n].key_hash % size;
		elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);

		elt->value = names[n].value;
		elt->len = (u_short) names[n].key.len;

		/* 拷贝key数据,并且小写 */
		ngx_strlow(elt->name, names[n].key.data, names[n].key.len);

		/* test计数器计算新元素需要存放的位置 */
		test[key] = (u_short)(test[key] + NGX_HASH_ELT_SIZE(&names[n]));
	}

	/**
	 *  设置bucket桶上最后一个元素设置为value为NULL
	 */
	for (i = 0; i < size; i++) {
		if (buckets[i] == NULL) {
			continue;
		}
		/**
		 * 这边的设计 Nice!!!
		 * test[i] 其实是bucket的元素块的结束位置
		 * 由于前面bucket的处理中多留出了一个指针的空间,而此时的test[i]是bucket中实际数据的共长度,
		 * 所以bucket[i] + test[i]正好指向了末尾null指针所在的位置。处理的时候,把它当成一个ngx_hash_elt_t结构看,
		 * 在该结构中的第一个元素,正好是一个void指针,我们只处理它,别的都不去碰,所以没有越界的问题。
		 */
		elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);

		elt->value = NULL;
	}

	ngx_free(test);

	hinit->hash->buckets = buckets;
	hinit->hash->size = size;

	return NGX_OK;
}

 

 

 

 

 

Nginx源码分析:一个HTTP请求的安检之旅——Nginx ACCESS_PHASE源码全景 Nginx选择"简单正确"而不是"优化但复杂",这是典型的工程判断。原因很直觉——子请求是Nginx内部发起的,不应该再被主请求的访问控制规则拦截,否则auth_request就会死循环:主请求触发认证子请求,认证子请求又触发新的认证子请求,永远走不出来。这种"不做判断就弃权"的约定让模块之间零耦合——access模块不知道auth_basic存在,auth_basic不知道auth_request存在,它们各自只关心自己的配置和逻辑,组合方式完全由checker和satisfy决定。 阅读详情

相关推荐

Xilinx UltraScale+系列FPGA实现USB3.0 DEV 设备 部分原理图+代码分享

Zynq UltraScale+ MPSoC 系列是 Xilinx 新一代 Zynq 平台。在 FPGA 里有完整的 ARM 处理(PS),包含了四核或双核 Cortex-A53 处理器 ,处理器子系统中有DDR内存控制器等大量的外设,在FPGA中完全独立于可编程逻辑单元(PL)如果暂时没有用到PL部分,PS也能单独工作,相当于可以当成一个ARM来用了(以前的不行)。 ARM也包含USB3.0控制器,这是实现USB3.0DEV的关键,根据官方手册(ug1085)描述,USB主(Host)从(Device)

Pierre 1万+

09Nginx源码分析之哈希结构(ngx_hash.c)

一 哈希结构 关于哈希的相关概念,可以先参考以下文章。 https://blog.csdn.net/weixin_44517656/article/details/105215349 注意: 下面的词语桶和槽是一样的,有些人习惯将桶称之为槽。 在阅读Nginx的hash结构时,一定不能着急,否则会让自己怒火中烧,建议大家都使用3-4天去消化。 Nginx的hash表结构主要几个特点: 1)静态只读。当初始化生成hash表结构后,是不能动态修改这个hash表结构的内容。 2)将内存利用最大化。Nginx的

weixin_44517656的博客 766

【IC】Redhawk-SC输入件配置

Redhawk sc

u011808788的博客 1776

nginx源码分析—hash结构ngx_hash_t

Content 0.序 1.hash结构 1.1ngx_hash_t结构 1.2ngx_hash_init_t结构 1.3ngx_hash_key_t结构 1.4hash的逻辑结构 2.hash操作 2.1NGX_HASH_ELT_SIZE宏 2.2hash函数 2.3hash初始化 2.4hash查找 3.一个例子 3.1代码 3.2如何编译 3.3运行结果 3....

有趣的人生 一半是山川湖海 一半是初心情怀 941

anaconda的conda国内加速镜像源(pytorch 快速安装)

最近清华源不太好使了,所以推荐中科大镜像源,如下 1.还原原始通道 conda config --remove-key channels 2.

qq_41719643的博客 2万+

Windows server 2012R2安装Oracle11g

打开 开始——所有程序——Oracle-OraDb11g_home1——配置和移植工具——Database Configuration Assistant。打开 开始——所有程序——Oracle-OraDb11g_home1——配置和移植工具——Net Configuration Assistant。首先打开Oracle的安装位置找到下面的位置找到tnsnames.ora文件右键编辑。打开Oracle的安装位置找到下面的位置找到tnsnames.ora文件右键编辑。选择仅安装数据库软件。

qq_74770105的博客 3596

nginx 基本hash的初始化 源码分析

基本的散列表基本hash的数据结构typedef struct { ngx_hash_elt_t **buckets; // 保存hash元素 ngx_uint_t size; //bucket的个数 } ngx_hash_t;typedef struct { ngx_hash_t *hash; ngx_hash_

u010396781的博客 292

数字通信:信源编码、信道编码、调制、物理信道、解调、信道解码、信源解码

在通信系统中,信息的传输通常需要经过一系列的处理步骤,确保数据可以在信道中高效且可靠地传输。以下是信源编码、信道编码、调制、物理信道、解调、信道解码、信源解码的具体含义及其常见技术的解释。

weixin_44012667的博客 4806

Nginx源码分析:启动流程

nginx源码分析 nginx-1.11.1 参考书籍《深入理解nginx模块开发与架构解析》 nginx简介 Nginx的作为服务端软件,表现的主要特点是更快、高扩展、高可靠性、低内存消耗、单机支持10万以上的并发连接和热部署等优点。Nginx是一般运行模型master-worker,启动多个worker来处理请求,一般master进程不会对用户提供服务,只用于管理真正提供服务的wroker进...

qq_33339479的博客 483

无人机WIFI集群组网技术详解及成本分析

无人机WIFI集群组网技术是指利用无人机作为移动平台,通过集成高性能的WIFI模块,实现多架无人机之间以及无人机与地面控制站之间的无线通信组网。在协议层面,无人机WIFI集群组网需要遵循标准的无线通信协议,如IEEE 802.11系列(即WIFI标准),同时可能还需定制或优化路由协议、媒体访问控制(MAC)协议等,以确保高效、可靠的数据传输。数据安全与隐私保护:无人机集群组网中传输的数据可能涉及敏感信息,需采取加密、认证等安全措施,确保数据传输的安全性和用户隐私的保护。根据性能不同,价格差异较大。

专注多旋翼、复合翼无人机技术研究,从事低空经济行业。 1583

华为校招&实习&留学生机试真题目录+算法分类+在线OJ+通过率+视频讲解

全网最新最全华为校招&实习&留学生机试真题题库

m0_73659489的博客 6868

震撼!全网第一张源码分析全景图揭秘Nginx

不管是C/C++技术栈,还是PHP,Java技术栈,从事后端开发的朋友对nginx一定不会陌生。 想要深入学习nginx,阅读源码一定是非常重要的一环,但nginx源码量毕竟还是不算少,一不小心就容易陷入某个细节,迷失在茫茫码海之中。 如果有一张地图,让我们开启上帝视角,总览全局,帮助我们快速学习整体框架结构,又能不至于迷失其中那就再好不过了! 看到这篇文章的你有福了,笔者花了不少时间,把这件事...

xuanyuan_fsx的专栏 1万+

微博scheme

微博scheme

qq_42278595的博客 319

10.nginx源码分析之数据结构:ngx__rbtree_t

nginx源码分析之数据结构:ngx__rbtree_t 除了之前介绍的list、array、queue等线性数据结构之外,nginx还提供了查询效率较高的红黑树结构,这种二叉平衡查找树的使用极大提高了查找效率。关于红黑树的声明和定义在ngx_rbtree.h和ngx_rbtree.c中。 关于查找方式一般我们会给出两种数据结构:hash表和二叉平衡查找树。 (1)hash的代表产品是r

linux_player_c(系统&开发) 744

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Nginx数据结构---专研

nginx的数据结构(1)——ngx_int_t与ngx_rbtree_t https://blog.csdn.net/bipedal_bit/article/details/47306809 C语言指针转换为intptr_t类型 http://www.cnblogs.com/Anker/p/3438480.html nginx源码分析--数据结构 哈希 ngx_hash_t ...

happylzs2008的专栏 182

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

nginx源码分析--module模块的初始化

谈一下zeromq的整体架构: zeromq作为一种高性能的消息队列中间件,主要依赖于linux的异步机制,所有的操作全部在子线程中处理,不会阻塞主线程。 具体对线程的封装类: (1)io_thread: class io_thread_t : public object_t, public i_poll_events { public: io_thread_t (zmq::ctx_t *ctx_, uint32_t tid_); // Clean-up

mojianc的专栏 744

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

nginx源码分析之script (一)

nginx的变量的处理流程可以分为 变量的创建与变量对应值的获取流程。 nginx的变量存储也可以分为两部分 ngx_http_core_main_conf_t用于存放变量创建时的相关成员,ngx_http_request_t保存的变量对应的值。 1.nginx变量 相关的结构体: a.用于代表变量的ngx_http_variable_t,该变量被适用于ngx_http_core

hl001的专栏 2212

nginx-rtmp-module之ngx_rtmp_live_module.c代码详解

Nginx RTMP 直播模块通过这些功能使得流的管理、发布、播放、同步变得更加灵活和高效。模块通过 RTMP 协议进行流媒体的推送和拉取操作,并提供了流的生命周期管理(如暂停、恢复、开始和结束播放)。此外,还提供了带宽管理、流同步等功能,以确保直播体验的稳定性。

大牛直播SDK 1248

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_modules

Nginx 是一个高度模块化的 Web 服务器,无论是核心功能(如事件处理、HTTP 处理)还是扩展功能(如反向代理、负载均衡、SSL 支持),都是通过模块实现的。提供了一个集中式的注册表,Nginx 在启动时通过遍历这个数组来初始化和配置所有模块。是自动生成的代码,这个文件是在编译过程中由配置脚本根据用户的选择生成的。,它的作用是存储 Nginx 中所有模块的指针。是一个全局数组,类型为。

weixin_41812346的博客 623

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_str_node_t

通过继承通用红黑树节点,复用 Nginx 的红黑树实现(如插入、删除、平衡调整)。继承自通用红黑树节点结构,包含红黑树操作所需的核心属性。)与红黑树节点绑定,确保每个节点携带完整数据。快速比较长度,避免逐字节遍历。指针直接访问字符串内容,支持。作为结构体的第一个成员,指针,无需偏移计算。在插入/查找时,通过。将字符串的实际内容(

weixin_41812346的博客 463

Nginx源码分析 - 主流程篇 - Nginx的启动流程(09)

目录 一、Nginx的启动过程 二、重要流程分析 1. ngx_get_options 解析外部参数 2. init_cycle 初始化全局变量 3. 变量保存方法ngx_save_argv和ngx_process_options 4. 给模块打标ngx_preinit_modules 5. 创建PID文件ngx_create_pidfile 前几篇主要介绍了Nginx比较常用的一些基础数据结构,例如pool,buf,array,list等。通过对Nginx基础数据结构的理解,能更好的帮助我

alpha_love的博客 497

ngx_http_core_merge_loc_conf

ngx_http_core_merge_loc_conf

weixin_41812346的博客 41

【Nginx源码】ngx_command_t学习

【代码】【Nginx源码】ngx_command_t学习。

Edidaughter的博客 381

Nginx源码分析一

源码包解压之后,根目录下有几个子目录和几个文件

skyline 658
上一篇: Nginx源码分析 - 基础数据结构篇 - 单向链表结构 ngx_list.c(06)
下一篇: Nginx源码分析 - 基础数据结构篇 - 字符串结构 ngx_string.c(08)
老码农zhuli
老码农zhuli 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄14年 2456粉丝 312原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值