在nginx源码目录的src/core/ngx_string.h|c里面,包含了字符串的封装以及字符串相关操作的api。nginx提供了一个带长度的字符串结构ngx_str_t,它的原型如下:
typedef struct {
size_t len; // 字符串长度
u_char *data; // 字符串数据的开头指针
} ngx_str_t;
nginx字符串和普通的C字符串实现基本一致, 区别在于,普通的字符串是以'\0'作为字符串的结束符号, 字符串的长度是需要自己通过strlen(str)来计算的。而ngx_str_t是通过字符串对象的len属性来决定字符串的结束位置, data来表示字符串的指针开始位置。
nginx字符串的优点:
- 通过长度来表示字符串长度,减少计算字符串长度的次数。
- nginx可以重复引用一段字符串内存,data可以指向任意内存,长度表示结束,而不用去copy一份自己的字符串。 (标准C的字符串以'\0'结束, 因为不能改变原字符串, 势必要拷贝一份作为新的字符串。) 这样可以减少不必要的内存分配和拷贝。
可以找到很多字符串引用一段内存的例子,比如request_line、uri、args等等,这些字符串的data部分,都是指向在接收数据时创建buffer所指向的内存中,uri,args就没有必要copy一份出来。
nginx字符串的缺点:
也正是因为nginx字符串有上述的优点是得益于不使用‘\0'表示字符串的结束, 这样就导致, 在ngx_str_t调用标准字符串api的时候, 需要自己进行手工转换,才能使用。 通常的转换方式有:
- 通过申请一份str->len + 1的内存,然后将data拷贝到里边, 最后以'\0'结尾, 然后传入标准API。
- hack方法, 将ngx_str_t的len+1位进行备份, 然后修改其为'\0',然后将data传入, 这样操作标准api后恢复这个位置的原始内容。但不推荐使用该方法。
- 修改ngx_str_t的data域的时候必须谨慎的去修改一个字符串。在修改字符串时需要认真的去考虑:是否可以修改该字符串;字符串修改后,是否会对其它的引用造成影响。
下面顺带介绍ngx_string.h|c中定义的其他几种数据结构:
1. ngx_keyval_t : key , value对数据结构
typedef struct {
ngx_str_t key;
ngx_str_t value;
} ngx_keyval_t;
该结构由两个域组成, key, value, 这两个域都是ngx_str_t类型,有点像PHP中的联系数组。
2. ngx_variable_value_t : 可变值类型
typedef struct {
unsigned len:28; // 字符串长度 位域28?
unsigned valid:1; // 有效性 位域1
unsigned no_cacheable:1; // 无可缓存的 位域1
unsigned not_found:1; // 未找到标志 位域1
unsigned escape:1; // 是否escape
u_char *data; // 数据区域
} ngx_variable_value_t;
下面看看ngx_str_t的相关API:
1. ngx_string(str) 将给定的标准C字符串转换为ngx_str_t类型:
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str }
注意, 上面的data域使用的是sizeof(), 因此传入宏的字符串必须是常量字符串。
2. ngx_null_string() 空的ngx_str_t的宏
#define ngx_null_string { 0, NULL }
以上两个宏定义的时候使用{,}, 因此只能用于ngx_str_t定义的时候初始化。 而不能用于赋值。下面介绍两个用于赋值的宏定义。
3. ngx_str_set(str, text) : 用于ngx_str_t类型的变量定义后的赋值, 其原型如下:
#define ngx_str_set(str, text) \
(str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
注意上面的宏是两个语句, 中间用‘;’分割, 因此在应用到循环语句或者条件语句的时候, 一定要用{}将其包围住。否则会出现编译错误。
eg :
ngx_str_t str1;
if(xx) {
ngx_str_set(str1, 'abcd');
}
4. ngx_str_null(str) : 将一个ngx_str_t类型重置为空的状态。
#define ngx_str_null(str) (str)->len = 0; (str)->data = NULL
这个宏的应用也需要注意和3中的一样, 这个宏是两个语句, 在条件语句中调用需要使用括号。
5. ngx_tolower(c) : 将字符c转换为小写的对应字符
#define ngx_tolower(c) (u_char) ((c >= 'A' && c <= 'Z') ? (c | 0x20) : c)
6. ngx_toupper(c) : 将字符c转换为相应的大写字符
#define ngx_toupper(c) (u_char) ((c >= 'a' && c <= 'z') ? (c & ~0x20) : c)
7. ngx_strncmp(s1, s2, n) : 比较字符串s1, s2的前面n位
#define ngx_strncmp(s1, s2, n) strncmp((const char *) s1, (const char *) s2, n)
参数s1, s2都是标准的C字符串, n为size_t。此宏仅仅是对标准api strncmp的包装。
8. ngx_strcmp(s1, s2) : 比较s1, s2字符串, 普通字符串的比较, 对strcmp的包装。
#define ngx_strcmp(s1, s2) strcmp((const char *) s1, (const char *) s2)
9. ngx_strstr(s1, s2) : 查找s1字符串中出现完整s2字符串的第一个位置, 如果没有找到返回null指针。strstr的包装。
#define ngx_strstr(s1, s2) strstr((const char *) s1, (const char *) s2)
10. ngx_strlen(str) : 获取C字符串str的长度, 即strlen()的包装。
#define ngx_strlen(s) strlen((const char *) s)
11. ngx_strchr(s1, c) : 查找C字符串s1中第一次出现c字符串的位置, 没有找到返回null指针。strchar的包装。
#define ngx_strchr(s1, c) strchr((const char *) s1, (int) c)
12. ngx_strlchr(u_char *p, u_char *last, u_char c) : 查找字符串p处到last位置处第一次出现字符c的位置, 未发现返回null. 该方法为静态内联函数。
static ngx_inline u_char *
ngx_strlchr(u_char *p, u_char *last, u_char c)
{
while (p < last) {
if (*p == c) {
return p;
}
p++;
}
return NULL;
}
13. ngx_memzero(buf, n) : 将buf字符串的前n位置为0, 该宏是对memset(str, value, n)的包装。其中value使用0.
#define ngx_memzero(buf, n) (void) memset(buf, 0, n)
ngx_memset(buf, value, n) : 是对memset(buf, value, n)的包装。更具一般性。
14. ngx_memcpy(dest, src, n) : memcpy(dest, src, n) 的封装宏. 从src指针指向的内存拷贝n位到dest所指的内存里边, 最后返回dest.
memcpy()对于dest, src有重叠部分的拷贝是不安全的, 更加安全的方法是memmove()方法, 两个的区别可单独百度查找。
#define ngx_memcpy(dst, src, n) (void) memcpy(dst, src, n)
15. ngx_cpymem(dest, src, n) : 功能和上面的类似, 只是返回的时候,返回的是拷贝后的dest结束位置, 而非开始位置。
#define ngx_cpymem(dst, src, n) (((u_char *) memcpy(dst, src, n)) + (n))
16. ngx_copy(u_char *dst, u_char *src, size_t len) : 该函数为简单内联循环拷贝可变字符串到16字节, 比_intel_fast_memcpy()自动检测的icc8函数快点。
static ngx_inline u_char *
ngx_copy(u_char *dst, u_char *src, size_t len)
{
if (len < 17) {
while (len) {
*dst++ = *src++;
len--;
}
return dst;
} else {
return ngx_cpymem(dst, src, len);
}
}
对于可变字符串dest如果不足16位, 则拷贝满16位结束, 超出16位的直接拷贝, 最终返回拷贝结束后的位置。
另外相应的memmove的两个宏:
ngx_memmove(dst, src, n) : 拷贝后返回dst指针的位置
ngx_movemem(dst, src, n) : 拷贝后返回dst拷贝后的位置指针
17. ngx_memcmp(s1, s2, n) :memcmp(s1, s2, n)的包装, 返回0 表示s1, s2的前n位内容相同, 大于0, s1的前n位比s2的前n位大, 小于0相反。
#define ngx_memcmp(s1, s2, n) memcmp((const char *) s1, (const char *) s2, n)
ngx_str_t相关的方法:
1. ngx_strlow(u_char *dst, u_char *src, size_t n) : 将src的前n个字符转换为小写保存于dst中:
void
ngx_strlow(u_char *dst, u_char *src, size_t n)
{
while (n) {
*dst = ngx_tolower(*src); // 调用转换小写字符的宏
dst++;
src++;
n--;
}
}
2. ngx_cpystrn(u_char *dst, u_char *src, size_t n) : 拷贝src的前n位到dst中, 遇到字符串结束符号即返回, 如果拷贝完没有遇到字符串结束符, 则自动添加结束符'\0'. 返回dst.
u_char *
ngx_cpystrn(u_char *dst, u_char *src, size_t n)
{
if (n == 0) {
return dst;
}
while (--n) {
*dst = *src;
if (*dst == '\0') {
return dst;
}
dst++;
src++;
}
*dst = '\0';
return dst;
}
3. ngx_pstrdup(ngx_pool_t *pool, ngx_str_t *src) : 在将ngx_str_t类型的字符串src拷贝至给定的内存池pool中, 并返回其在内存池中的句柄位置。
首先在内存池pool中为src申请一片内存, 然后将src的数据拷贝进去。
u_char *
ngx_pstrdup(ngx_pool_t *pool, ngx_str_t *src)
{
u_char *dst;
dst = ngx_pnalloc(pool, src->len);
if (dst == NULL) {
return NULL;
}
ngx_memcpy(dst, src->data, src->len);
return dst;
}
4. ngx_sprintf(u_char *buf, const char *fmt, ...) :nginx自己实现的字符串格式化方法。类似于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
*/
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;
}
它的姊妹函数:
ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...) : 最多展示buf开始到max的位置的字符串格式化
ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...) : 直到last的位置的字符串格式化。
具体代码如下:
u_char * ngx_cdecl
ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...)
{
u_char *p;
va_list args;
va_start(args, fmt);
p = ngx_vslprintf(buf, buf + max, fmt, args);
va_end(args);
return p;
}
u_char * ngx_cdecl
ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...)
{
u_char *p;
va_list args;
va_start(args, fmt);
p = ngx_vslprintf(buf, last, fmt, args);
va_end(args);
return p;
}
5. ngx_strcasecmp(u_char *s1, u_char *s2) :