对于一个web服务器程序来说,对字符串处理的需求是必须的。由于web环境下的各种编码,也导致了web服务器程序字符串处理的繁杂性。在nginx源码中,ngx_string.c 这个文件就是来应对字符串处理的一些源码,源码中经常的使用到了这里中的函数,本文对ngx_string.c 进行一些简单的分析,以方便阅读其他源码。
我们来看它的基本数据结构:
typedef struct { size_t len; u_char *data; } ngx_str_t;
比较明显的可以看出,ngx_str_t 只是将字符串添加了一个标志长度的字段,并无其他特殊结构。
再来看它的功能函数的特点,和前面分析内存池(pool)中功能管理函数一样许多函数直接以宏定义的形式给出,如:
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str }
初始化ngx_str_t,len中保存str字符串的出去结束符的长度,data中保存str字符串。
#define ngx_tolower(c) (u_char) ((c >= 'A' && c <= 'Z') ? (c | 0x20) : c)
如果字符c是大写字符就将其转为小写,直接用位操作进行。(为什么 |0x20 就能实现?有兴趣的把大写字符用二进制表示,而后试试就明白了)
#define ngx_strcmp(s1, s2) strcmp((const char *) s1, (const char *) s2)
比较,s1 和 s2 两个字符串,实质就是调用了strcmp。
#define ngx_memzero(buf, n) (void) memset(buf, 0, n)
初始化,buf为0。
等等,这样的一些宏定义函数,在这里就不一一详细说明了,都比较简单容易看懂。
接下来就是一些nginx自己编写的函数了,如:
void ngx_strlow(u_char *dst, u_char *src, size_t n) { while (n) { *dst = ngx_tolower(*src); dst++; src++; n--; } }
将字符串src中前n个字符全部变成小写,放在dst中,调用的 ngx_tolower 就是前面介绍了的宏定义的函数,想想如果dst=src 呢?把本身的字符串中前n个变成小写。还有ngx_cpystrn、ngx_pstrdup等函数,这部分函数代码结构比较清晰易懂,不详细一一说明了。
再来看,这样的一组函数:
ngx_sprintf(u_char *buf, const char *fmt, …)
ngx_snprintf(u_char *buf, size_t max, const char *fmt, …)
ngx_slprintf(u_char *buf, u_char *last, const char *fmt, …)
ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args)
从命名来看,就能略知一二,这些函数是对字符串进行特定的标准格式的输出到buf中。取ngx_sprintf(u_char *buf, const char *fmt, …)来瞧一瞧:
u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...) { u_char *p; va_list args; va_start(args, fmt); p = ngx_vslprintf(buf, (void *) -1, fmt, args); va_end(args); return p; }
这是C语言标准的不定参数的写法:
va_list args;
va_start(args, fmt);
p = ngx_vslprintf(buf, (void *) -1, fmt, args);
va_end(args);
可能有不是很了解这种写法结构的,特别做下说明。va_start 是用来获取不定参数,实际上就是获取不定参数内存起始地址 放在va_list中,它的第一个参数为 va_list 保存地址,第二个参数为 最后一个确定参数名,具体到ngx_sprintf 这个函数就是,两个确定参数u_char *buf, const char *fmt,所以最后一个就是fmt。接下来将获取的va_list 和一些参数传给 ngx_vslprintf。值得注意下的是 (void *) -1 这种写法,指的是将整数-1 转化为空指针地址,实际上就是 0xFFFFFFFF(对于32位机)这个 参数干嘛什么用呢?这个得看 ngx_vslprintf。 ngx_vslprintf的代码就比较长了,但是功能很明确就是对于nginx自定义的数据结构进行标准格式化输出,就像vprintf 一样。而ngx_sprintf就相当于sprintf 命名应该也是这么来的。源码中的这段注释也是很明细的告诉我们这点:
/* * supported formats: * %[0][width][x][X]O off_t * %[0][width]T time_t * %[0][width][u][x|X]z ssize_t/size_t * %[0][width][u][x|X]d int/u_int * %[0][width][u][x|X]l long * %[0][width|m][u][x|X]i ngx_int_t/ngx_uint_t * %[0][width][u][x|X]D int32_t/uint32_t * %[0][width][u][x|X]L int64_t/uint64_t * %[0][width|m][u][x|X]A ngx_atomic_int_t/ngx_atomic_uint_t * %[0][width][.width]f double, max valid number fits to %18.15f * %P ngx_pid_t * %M ngx_msec_t * %r rlim_t * %p void * * %V ngx_str_t * * %v ngx_variable_value_t * * %s null-terminated string * %*s length and string * %Z '\0' * %N '\n' * %c char * %% % * * reserved: * %t ptrdiff_t * %S null-terminated wchar string * %C wchar */
那么参数 0xFFFFFFFF 功能呢?来截取部分ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args) 代码来看,看到开头的这句:while (*fmt && buf < last)。明白了吧last指的就是格式后输出到buf中的尾部界定指针,那么如果last= 0xFFFFFFFF 即代表了 buf < last 永远成立,也就是说将fmt 的所有内容格式化后输出的buf中去。看完这里我们也可以知道ngx_snprintf(u_char *buf, size_t max, const char *fmt, …),ngx_slprintf(u_char *buf, u_char *last, const char *fmt, …)这两个函数的功用了,一个是给定buf的最大值,一个是给定buf的界定。为了更为明晰的理解,ngx_vslprintf 这个函数,我们给出解析 %V 也就是nginx中ngx_str_t * 这个结构体的标准格式化输出过程,的代码分析图,如图1所示:
图1 解析 %V 示意图
而在 case V 中我们可以看到,获取不定参数中ngx_str_t 的指针,代码为va_arg(args,ngx_str_t),然后确定进入buf的长度,拷贝ngx_str_t 中data的字符串,最后完成%V的格式化,继续下面的字符的分析。其他的格式化也是相似的过程,不过由于各个的特殊性而有所不同。
ngx_string.c中还包括了对uft8 编码、urf的解析等操作,涉及到编码的规则和一些相关标准,其实现的功能一般都能从其函数命名中得知,源码中遇到也能理解一二,本篇对于ngx_string.c简单分析就到此结束。