Nginx的一些特点

  • 高性能

    采用事件驱动模型,可以无阻塞的处理海量并发连接

  • 高稳定性

    内存池避免了 c 程序常见的资源泄漏问题

    模块化架构使得各个功能模块完全解耦

    one master / mutil workers 进程池设计实现了自我监控管理,保证一个 worker 挂掉也能快速恢复服务

  • 低资源消耗

    不使用传统的进程或线程服务器模型,没有切换成本

    使用很多节约系统资源的编程技巧

  • 高扩展性

    模块化架构,可以根据需要开发适合自己业务逻辑的模块


Nginx自定义整数类型

跨平台兼容

// 定义在 core/ngx_config.h
typedef intptr_t ngx_int_t; // 有符号整数
typedef uintptr_t ngx_uint_t; // 无符号整数
typedef intptr_t ngx_flag_t; // 标志整数类型
  • intptr_t、uintptr_t 是C/C++标准里 大小足够容纳指针的整数类型
// 定义在 core/ngx_rbtree.h 红黑树的键(key)类型
typedef ngx_uint_t ngx_rbtree_key_t;
typedef ngx_int_t ngx_rbtree_key_int_t; // 定义在 os/unix/ngx_time.h 毫秒的整数类型
typedef ngx_rbtree_key_t ngx_msec_t;
typedef ngx_rbtree_key_int_t ngx_msec_int_t;

无效值:

在Nginx中,定义了类似python的None,初始化变量以 UNSET = -1 表示未初始化

由于C/C++是强类型语言,Nginx为 -1 定义了不同类型转换的宏

// 定义在 core/ngx_conf_file.h
#define NGX_CONF_UNSET -1 // 通用无效值
#define NGX_CONF_UNSET_UINT (ngx_uint_t) -1 // 无符号整数的无效值
#define NGX_CONF_UNSET_PTR (void *) -1 // 指针类型的无效值
...

有了UNSET概念,Nginx 以宏的形式提供了初始化和条件赋值函数:

// 定义在 core/ngx_conf_file.h
#define ngx_conf_init_value(conf, default) \
if(conf == NGX_CONF_UNSET) \
{ \
conf = default; \
}

当conf未初始化,初始化为default默认值


异常机制错误处理

分离代码逻辑里的正常部分和异常部分,使代码的结构更加清晰

Nginx用宏定义了七个常用的错误码,类型是 ngx_int_t

// 定义在 core/ngx_core.h
#define NGX_OK 0 // 执行成功,无错误
#define NGX_ERROR -1 // 执行失败,最常见的错误
#define NGX_AGAIN -2 // 未准备好,需要重试
#define NGX_BUSY -3 // 后端服务正忙
#define NGX_DONE -4 // 执行成功,但还需要有后序操作
#define NGX_DECLINED -5 // 执行成功,但未做处理
#define NGX_ABORT -6 // 发生了严重的错误

内存池

减少了系统调用的次数,而且能够很好的避免内存碎片和泄漏

// 定义在 ngx_core.h
typedef struct ngx_pool_s ngx_pool_t; // 简化定义 // 定义在 ngx_palloc.h
struct ngx_pool_s
{
...
ngx_pool_cleanup_t *cleanup; // 析构时的清理动作
ngx_log_t *log; // 关联的日志对象
}

Nginx 会为每一个 TCP/HTTP 请求创建一个独立的内存池————也就是 ngx_pool_t 对象

当请求结束时自动销毁 ngx_pool_t 对象,释放内存池和它拥有的所有内存

// 使用了内存对齐,速度快,但可能会有少量内存浪费
void * ngx_palloc(ngx_pool_t * pool, size_t size);
// 没有使用内存对齐
void * ngx_pnalloc(ngx_pool_t * pool, size_t size);
// 内部调用了 ngx_palloc(),并且把内存块清零
void * ngx_pcalloc(ngx_pool_t * pool, size_t size);
// 释放内存
ngx_int_t ngx_free(ngx_pool_t * pool, void * p);

清理机制:

Nginx框架自动管理内存池的生命周期,当请求结束时内存池里的内存会完全归还给系统;

内存只是系统资源的一个方面,其他的系统资源(如文件句柄)并不会随着内存池的销毁而一并释放;

如果不做特殊的操作就可能造成资源泄漏。

这种清理机制就是C++里的析构函数思想,在对象销毁的时候自动调用析构函数

Nginx 定义了一个保存清理信息的结构 ngx_pool_cleanup_t,用来在内存销毁时执行清理动作

// 定义在 ngx_palloc.h
typedef void (*ngx_pool_cleanup_t) void * data; // 清理函数指针原型 struct ngx_pool_cleanup_s // 清理信息结构体
{
ngx_pool_cleanup_pt handler; // 清理动作,函数指针
void *data; // 清理所需数据
ngx_pool_cleanup_t *next; // 后续链表指针
}; // 定义在 ngx_palloc.h
ngx_pool_cleanup_pt * ngx_pool_cleanup_add(ngx_pool_t * p, size_t size);

这个函数使用 size 为 ngx_pool_cleanup_t::data 分配内存,返回清理信息 ngx_pool_cleanup_t 对象,

设置它的 handler 和 data,就可以达到向内存池"注册"清理函数的目的

std::vector 有两个模板参数:

template<class T,								// 容纳的元素类型
class Alloctor = std::allocator<T> // 内存配置器
> class vector

我们可以自定义内存配置器替换第二个参数,vector就能使用Nginx的内存池


字符串

ngx_str_t 不是传统意义上的字符串,准确的说应该是一个 内存块引用

// 定义在 ngx_string.h
typedef struct
{
size_t len; // 字符串长度
u_char * data; // 字符串所在地址
}ngx_str_t;

这种设计的好处是字符串的操作非常廉价,只有两个整数的成本,

不需要复制大量数据,所以对它的拷贝、修改都非常高效,也节约了内存的使用

(类似 boost::string_ref 或者 std::string_view C++17 )

缺点也显而易见,由于 ngx_str_t 只是引用内存,所以应该尽量以只读方式去使用

多个 ngx_str_t 共享一块内存,擅自修改内容会影响到其他的引用

同时引用的内存地址可能失效,访问到错误的内存区域

初始化与赋值:

#define ngx_string(str)		{sizeof(str) - 1, (u_char *) str}
#define ngx_null_string {0, NULL} #define ngx_str_set(str, text) \
...
#define ngx_str_null(str) \
...

基本操作:

#define ngx_strcmp(s1, s2)		// 大小写敏感比较,参数是 u_char*
#define ngx_strncmp(s1, s2, n) // 大小写敏感比较,有长度参数 #define ngx_strstr(s1, s2) // 查找子串
#define ngx_strlen(s) // 使用'\0'计算字符串长度 // 大小写不敏感字符串比较,参数是 u_char*
ngx_int_t ngx_strcasecmp(u_char * s1, u_char * s2);
ngx_int_t ngx_strncasecmp(u_char * s1, u_char * s2, size_t n); // 字符串转整数类型,参数是 u_char*
ngx_int_t ngx_atoi(u_char * line, size_t n); // 内存池复制字符串,参数是 ngx_str_t*
u_char * ngx_pstrdup(ngx_pool_t * pool, ngx_str_t * src);

格式化函数:

// 直接向 buf 输出格式化内容,不检查缓冲区的有效性
u_char * ngx_sprintf(u_char * buf, const char * fmt, ...);
// 参数 max 和 last 指明了缓冲区结束位置
u_char * ngx_snprintf(u_char * buf, size_t max, const char * fmt, ...);
u_char * ngx_slprintf(u_char * buf, u_char * last, const char * fmt, ...);

函数执行完返回 u_char* 指针,指示格式化输出后在 buf 里的结束位置,可以用来判断结果的长度


时间与日期

Nginx定义了专用的时间数据结构 ngx_time_t:

// 定义在 core/ngx_times.h
typedef struct
{
time_t sec; // 自 epoch 以来的秒数,即时间戳
ngx_uint_t msec; // 秒数后的小数部分,单位是毫秒
ngx_int_t gmtoff; // GMT时区偏移量
} ngx_time_t; Nginx 内部使用了cache机制来存放时间值,使用一个全局指针 ngx_cached_time 指示当前缓存的时间 volatile ngx_time_t * ngx_cached_time; // 当前缓存的时间 // 获取当前时间的秒数
#define ngx_time() ngx_cached_time->sec
// 获取完整的时间结构体
#define timeofday() (ngx_time_t *)ngx_cached_time // 强制更新缓存时间(需要使用锁,成本较高,当必须获得当前精确时间时调用)
void ngx_time_update(void)

日期结构,是标准C结构< ctime>里的tm

// 定义在 os/unix/ngx_time.h
typedef struct tm ngx_tm_t;

日期操作函数

// 定义在 core/ngx_times.h
void ngx_gmtime()time_t t, ngx_tm_t * tp);
void ngx_localtime(time_t t, ngx_tm_t * tp);

将 time_t 转换为格林尼治时间/本地时间

u_char * ngx_http_time(u_char * buf, time_t t);
u_char * ngx_http_cookie_time(u_char * buf, time_t t);

上面两个函数调用了 ngx_gmtime() 和 ngx_sprintf(), 把 time_t 转换成日期字符串

// 定义在 core/ngx_parse_time.h
time_t ngx_parse_http_time(u_char * value, size_t len);

解析字符串形式的日期时间,转换为 time_t

同时,Nginx 使用全局变量提供缓存好的日期字符串,减少频繁调用的成本:

ngx_str_t ngx_cached_err_log_time;		// 错误日志的日期字符串
ngx_str_t ngx_cached_http_time; // HTTP格式的日期字符串
ngx_str_t ngx_cached_http_log_time; // HTTP日志的日期字符串
ngx_str_t ngx_cached_http_log_iso8601; // ISO8601 格式日期字符串
ngx_str_t ngx_cached_syslog_time; // 系统日志格式日期字符串

运行日志

Nginx 使用结构体 ngx_log_t 表示运行日志

// 定义在 core/ngx_log.h
struct ngx_log_s
{
...
ngx_uint_t log_level; // 日志级别
ngx_log_t * next; // 日志对象链表指针
}; // 定义在 core/ngx_log.h
void ngx_log_error_core(ngx_uint_t level, ngx_log_t * log, ngx_err_t err,
const char * fmt, ...);

使用 ngx_log_t 对象记录 level 级别的日志,字符串消息的格式语法与 ngx_sprintf() 相同

日志级别参数 level 取决于下列的宏,他们对应配置文件里的

debug | info | notice | warn | error | crit | alert | emerg

#define NGX_LOG_STDERR		0	// 最高级别
#define NGX_LOG_EMERG 1
#define NGX_LOG_ALERT 2
#define NGX_LOG_CRIT 3
#define NGX_LOG_ERR 4 // 常用级别
#define NGX_LOG_WARN 5
#define NGX_LOG_NOTICE 6
#define NGX_LOG_INFO 7
#define NGX_LOG_DEBUG 8 // 最低级别

STDERR 是比 emerg 还要高的错误级别,如果使用这个级别来记录日志,

那么 Nginx 将直接输出日志到标准错误输出(通常为终端屏幕)而非写入日志文件。

常用的日志级别为 NGX_LOG_ERR 和 NGX_LOG_WARN

err 参数表示调用失败返回的错误码

// 定义在 os/unix/errno.h
typedef int ngx_err_t;

日志宏

#define ngx_log_error(level, log, ...)		\
...

只有当消息的日志级别高于log对象级别(即消息的level值小)时才会调用函数记录日志

  • 实际开发中应该使用宏来记录日志 *

 

C++封装实现:https://github.com/chen892704/Nginx-Learning

Nginx学习笔记(四):基本数据结构的更多相关文章

  1. Nginx学习笔记4 源码分析

    Nginx学习笔记(四) 源码分析 源码分析 在茫茫的源码中,看到了几个好像挺熟悉的名字(socket/UDP/shmem).那就来看看这个文件吧!从简单的开始~~~ src/os/unix/Ngx_ ...

  2. openresty 学习笔记四:连接mysql和进行相关操作

    openresty 学习笔记四:连接mysql和进行相关操作 毕竟redis是作为缓存,供程序的快速读写,虽然reidis也可以做持久化保存,但还是需要一个做数据存储的数据库.比如首次查询数据在red ...

  3. C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻

    前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...

  4. IOS学习笔记(四)之UITextField和UITextView控件学习

    IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...

  5. java之jvm学习笔记四(安全管理器)

    java之jvm学习笔记四(安全管理器) 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一个重要组成部分安全管理器. 安全管理器 ...

  6. Learning ROS for Robotics Programming Second Edition学习笔记(四) indigo devices

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  7. Nginx学习笔记~目录索引

    回到占占推荐博客索引 前几天整理了<Docker的学习笔记索引>,受到了很多朋友的关注,今天把Nginx的文章也整理一下,以后将永久更新,像大叔之前的<EF文章系列>,< ...

  8. Typescript 学习笔记四:回忆ES5 中的类

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  9. ES6学习笔记<四> default、rest、Multi-line Strings

    default 参数默认值 在实际开发 有时需要给一些参数默认值. 在ES6之前一般都这么处理参数默认值 function add(val_1,val_2){ val_1 = val_1 || 10; ...

  10. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

随机推荐

  1. 查看日志tail命令

    打开终端,连接jboss: 命令: tail -f -n 500 /var/log/wildfly/wrapper.log

  2. android studio3.4打jar包

    第一步在build.gradle文件里的android{}里面加入下面内容 //生成jar包 task makeJar(type:Copy) { delete 'build/outputs/netwo ...

  3. html 获取地址栏信息

    <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8& ...

  4. docker-compose EFK查看docker及容器的日志

    上一篇<docker-compose ELK+Filebeat查看docker及容器的日志>已经演示了如何在docker中使用docker-compose创建容器,并将docker中的所有 ...

  5. js的基本数据类型

    Undefined.Null.Boolean.Number.String.ECMAScript 2015 新增:Symbol(创建后独一无二且不可变的数据类型 )

  6. 微信小程序之圆形进度条(自定义组件)

    思路 使用2个canvas 一个是背景圆环,一个是彩色圆环. 使用setInterval 让彩色圆环逐步绘制. 在看我的文章前,必须先看 ,下面转的文章,因为本文是在它们基础上修改的. 它们的缺点为: ...

  7. API 设计 POSIX File API

    小结: 1. https://mp.weixin.qq.com/s/qWrSyzJ54YEw8sLCxAEKlA API 设计最佳实践的思考 谷朴 阿里技术 昨天   阿里妹导读:API 是模块或者子 ...

  8. opencv常见示例

    1.批量转换灰度图并保存 #include <iostream> #include <opencv2/opencv.hpp> #include <string> u ...

  9. 常用学习&面试资源网站

    https://github.com/MZCretin/RollToolsApi  开源通用API https://github.com/SenhLinsh/Android-Hot-Libraries ...

  10. throws和throw的使用

    throws 用在方法定义上 后面跟一个或者多个异常名称 如果是多个异常名称,之间使用","隔开 , 表达的意思是给该方法添加一个或者多个异常声明; 告诉调用者该方法可能会出现问题 ...