跳到主要内容

六、Nginx 基本数据结构

基本数据结构

Nginx 的作者为追求极致的高效,自己实现了很多颇具特色的 Nginx 风格的数据结构以及公共函数。比如,Nginx 提供了带长度的字符串,根据编译器选项优化过的字符串拷贝函数 ngx_copy 等。所以,在我们写 Nginx 模块时,应该尽量调用 Nginx 提供的 api,尽管有些 api 只是对 glibc 的宏定义。本节,我们介绍 string、list、buffer、chain 等一系列最基本的数据结构及相关api的使用技巧以及注意事项。

ngx_str_t

在Nginx 源码目录的 src/core 下面的 ngx_string.h|c 里面,包含了字符串的封装以及字符串相关操作的 api。Nginx 提供了一个带长度的字符串结构 ngx_str_t,它的原型如下:

typedef struct {
size_t len;
u_char *data;
} ngx_str_t;

在结构体当中,data 指向字符串数据的第一个字符,字符串的结束用长度来表示,而不是由'\\0'来表示结束。所以,在写 Nginx 代码时,处理字符串的方法跟我们平时使用有很大的不一样,但要时刻记住,字符串不以'\\0'结束,尽量使用 Nginx 提供的字符串操作的 api 来操作字符串。

那么,Nginx 这样做有什么好处呢?首先,通过长度来表示字符串长度,减少计算字符串长度的次数。其次,Nginx 可以重复引用一段字符串内存,data 可以指向任意内存,长度表示结束,而不用去 copy 一份自己的字符串(因为如果要以'\\0'结束,而不能更改原字符串,所以势必要 copy 一段字符串)。我们在 ngx_http_request_t 结构体的成员中,可以找到很多字符串引用一段内存的例子,比如 request_line、uri、args 等等,这些字符串的 data 部分,都是指向在接收数据时创建 buffer 所指向的内存中,uri,args 就没有必要 copy 一份出来。这样的话,减少了很多不必要的内存分配与拷贝。

正是基于此特性,在 Nginx 中,必须谨慎的去修改一个字符串。在修改字符串时需要认真的去考虑:是否可以修改该字符串;字符串修改后,是否会对其它的引用造成影响。在后面介绍 ngx_unescape_uri 函数的时候,就会看到这一点。但是,使用 Nginx 的字符串会产生一些问题,glibc 提供的很多系统 api 函数大多是通过'\\0'来表示字符串的结束,所以我们在调用系统 api 时,就不能直接传入 str->data 了。此时,通常的做法是创建一段 str->len + 1 大小的内存,然后 copy 字符串,最后一个字节置为'\\0'。比较 hack 的做法是,将字符串最后一个字符的后一个字符 backup 一个,然后设置为'\\0',在做完调用后,再由 backup 改回来,但前提条件是,你得确定这个字符是可以修改的,而且是有内存分配,不会越界,但一般不建议这么做。接下来,看看 Nginx 提供的操作字符串相关的 api。