前言
Nginx的字符串表示方法和C语言中的字符串表示方法不太一样,在C语言中一个字符串是从某一个地址开始,然后往后遍历直到遇到\0
才表示这个字符串结束了。但是在Nginx中,根据实际的使用场景,在C语言的基础上对其稍加封装了一下,字符串的结束位置不再以\0
作为结束标志,而是给出一个长度来计算结束位置。总的来说C语言中的字符串是通过起始位置和结束位置来确认,Nginx中则是通过起始位置和长度来确认。
详细说明可参照源码中src/ngx_string.h|.c
文件。
储备知识
数据结构定义
1 2 3 4
| typedef struct { size_t len; u_char *data; } ngx_str_t;
|
字符串在Nginx中是一个名为ngx_str_t
的结构体,在这个结构体当中我们可以看到包含了一个指示字符串长度的len
和一个指向具体内容的指针变量data
。这种表示方法和C语言中的字符串表示方法有点不太一样,熟悉C语言的应该知道字符串的表示只有一个字符指针来指向具体内容的开始位置,往后一直遇到\0
为止。造成这种差异的主要原因是因为在Nginx涉及到字符串计算的地方,会经常性的遇到要求某一个字符串长度的情况。在这种应用场景之下,这种表示方法能够更方便快捷的得到结果。PHP的字符串实现上也是采用的这种方式,有兴趣的可以去看一下PHP的相关源码。
Nginx中采用这种方法来表示字符串还有一个原因是共用内存,减小开销。比如在Nginx解析http请求的时候,会把请求的内容放一个字符串中。在这个字符串中,某一部分是Content-Length
,另一部分是User-Agent
这样等等。如果采用C语言的字符串表示的话,那么解析的过程中就需要去申请多块内存,每一块内存中分别去写入不同属性的内容。如果采用Nginx的这种表示方法,只需要把表示各种属性字符串的data
指针指向请求的不同位置就好了,用len
来表示结束的位置,这样能极大的减少内存的开销。
由于Nginx的字符串和C语言的字符串的表示差异,一些C语言的自带的字符串函数就不能直接作用到Nginx当中了,因此Nginx实现一批适应于ngx_str_t
字符串操作的字符串函数,我们可以在ngx_string.h
中可以找到。
主要函数
ngx_string
通过C标准的字符串来构造一个ngx_str_t
格式的字符串,由于在ngx_str_t
中不需要保存\0
,因此len
的值应该减1
使用这个宏定义的时候参数一定要是一个常量字符串,而不应该是一个字符指针,否则sizeof(str)
的计算结果会大不一样
1
| #define ngx_string(str) { sizeof(str) - 1, (u_char *) str }
|
ngx_null_string
构造一个空的ngx_str_t
格式的字符串
1
| #define ngx_null_string { 0, NULL }
|
ngx_str_set
通过C标准的字符串,给ngx_str_t
格式的字符串赋值
1 2
| #define ngx_str_set(str, text) \ (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
|
ngx_str_null
置空ngx_str_t
字符串
1
| #define ngx_str_null(str) (str)->len = 0; (str)->data = NULL
|
字符大小写转换
1 2 3
| #define ngx_tolower(c) (u_char) ((c >= 'A' && c <= 'Z') ? (c | 0x20) : c)
#define ngx_toupper(c) (u_char) ((c >= 'a' && c <= 'z') ? (c & ~0x20) : c)
|
为什么要把这个字符大小写单独拿出来说那,因为Nginx对字符的大小写转换的实现有点不太一样。我们都知道的同一个字母的大写和小写的Ascall码值差了32,从大写到小写就加上32,从小写到大写就减去32,很简单,一般大部分的人都会写出下面的这种方式:
1 2 3
| #define ngx_tolower(c) (u_char) ((c >= 'A' && c <= 'Z') ? (c + 0x20) : c)
#define ngx_toupper(c) (u_char) ((c >= 'a' && c <= 'z') ? (c - ~0x20) : c)
|
对比一下可以看出Nginx采用了位操作的办法,这是因为A-Z的Ascall二进制表示为1000001
-1011010
,a-z的Ascall二进制表示为1100001
-1111010
,差值32的二进制为0100000
。A-Z的二进制从右向左数第6位是0,a-z的二进制从右向左数第6位是1。第6位代表的就是32,因此在转换的时候,我们只要去变换一位就可以了。
ngx_strlow
该方法就是使用上面的ngx_tolower
对一个字符串的n个字符进行小写转换,感觉循环里面的前三行可以用一行代码去搞定,这样的操作在其他的文件中经常能够看见,不知道是因为写这个文件的人的风格还是别的原因。
1 2 3 4 5 6 7 8 9 10
| void ngx_strlow(u_char *dst, u_char *src, size_t n) { while (n) { *dst = ngx_tolower(*src); dst++; src++; n--; } }
|
字符串拷贝相关宏定义
下面的这些宏定义就是把一些C语言字符串操作的方法换了一个名字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #define ngx_strncmp(s1, s2, n) strncmp((const char *) s1, (const char *) s2, n)
#define ngx_strcmp(s1, s2) strcmp((const char *) s1, (const char *) s2)
#define ngx_strstr(s1, s2) strstr((const char *) s1, (const char *) s2)
#define ngx_strlen(s) strlen((const char *) s)
#define ngx_strchr(s1, c) strchr((const char *) s1, (int) c)
|
ngx_strlchr
在一个字符串[p,last]范围内查找字符c在左侧出现的首个位置,找到的话返回相应的指针位置,否则返回NULL
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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; }
|
ngx_memcpy & ngx_cpymem
这两个函数都是拷贝n个字符到另一个字符,根据NGX_MEMCPY_LIMIT
的不同来分别定义这两个函数的实现
ngx_memcpy:从src拷贝n个字符到dst
ngx_cpymem:从src拷贝n个字符到dst并返回拷贝后的指针地址,此时指针指向第n+1的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #if (NGX_MEMCPY_LIMIT)
void *ngx_memcpy(void *dst, const void *src, size_t n); #define ngx_cpymem(dst, src, n) (((u_char *) ngx_memcpy(dst, src, n)) + (n))
#else
#define ngx_memcpy(dst, src, n) (void) memcpy(dst, src, n) #define ngx_cpymem(dst, src, n) (((u_char *) memcpy(dst, src, n)) + (n))
#endif
|
ngx_copy
从src拷贝len长度的字符到dst,这个函数根据编译器的类型不同实现的具体方法也不同。在第一种方式里面可以看到如果拷贝的长度在17个字符以内,采用的是一个循环去拷贝,官方注释里面说是这样的情况下效率更高,这里就不深究了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #if ( __INTEL_COMPILER >= 800 )
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); } }
#else
#define ngx_copy ngx_cpymem
#endif
|
ngx_memmove
从src拷贝n个字节到dst,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中
1
| #define ngx_memmove(dst, src, n) (void) memmove(dst, src, n)
|
ngx_movemem
在ngx_memmove功能的基础上,返回指向n+1字节的指针
1
| #define ngx_movemem(dst, src, n) (((u_char *) memmove(dst, src, n)) + (n))
|
ngx_memcmp
比较s1,s2两块内存的前n个字节
1
| #define ngx_memcmp(s1, s2, n) memcmp((const char *) s1, (const char *) s2, n)
|
ngx_cpystrn
从src字符串中拷贝n个字符到dst字符串中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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; }
|
ngx_pstrdup
从内存池里面申请一块内存,并用src里面的内容填充,并返回该内存的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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; }
|
ngx_strcasecmp
比较两个字符串,忽略大小写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| ngx_int_t ngx_strcasecmp(u_char *s1, u_char *s2) { ngx_uint_t c1, c2;
for ( ;; ) { c1 = (ngx_uint_t) *s1++; c2 = (ngx_uint_t) *s2++;
c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1; c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2;
if (c1 == c2) {
if (c1) { continue; }
return 0; }
return c1 - c2; } }
|
ngx_strncasecmp
比较两个字符串前n个字符,忽略大小写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| ngx_int_t ngx_strncasecmp(u_char *s1, u_char *s2, size_t n) { ngx_uint_t c1, c2;
while (n) { c1 = (ngx_uint_t) *s1++; c2 = (ngx_uint_t) *s2++;
c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1; c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2;
if (c1 == c2) {
if (c1) { n--; continue; }
return 0; }
return c1 - c2; }
return 0; }
|
ngx_strnstr
在字符串中查找另一个子串(在第一个字符串的len长度内)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| u_char * ngx_strnstr(u_char *s1, char *s2, size_t len) { u_char c1, c2; size_t n;
c2 = *(u_char *) s2++;
n = ngx_strlen(s2);
do {
do { if (len-- == 0) { return NULL; }
c1 = *s1++;
if (c1 == 0) { return NULL; }
} while (c1 != c2);
if (n > len) { return NULL; }
} while (ngx_strncmp(s1, (u_char *) s2, n) != 0);
return --s1; }
|
ngx_strstrn
在字符串中去查找另一个子串(字符串是一个以\0
结尾的静态字符串)
注意
这里n为s2的长度减一 因为代码中是在确定了s1中s2首字母出现的位置后,从下一个字符开始比较的,具体看下面代码中的注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| u_char * ngx_strstrn(u_char *s1, char *s2, size_t n) { u_char c1, c2;
c2 = *(u_char *) s2++;
do { do { c1 = *s1++;
if (c1 == 0) { return NULL; }
} while (c1 != c2);
} while (ngx_strncmp(s1, (u_char *) s2, n) != 0);
return --s1; }
|
ngx_strcasestrn
和ngx_strstrn
的功能一样都是在字符串中查找子串,只是这个函数里面是忽略了大小写的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| u_char * ngx_strcasestrn(u_char *s1, char *s2, size_t n) { ngx_uint_t c1, c2;
c2 = (ngx_uint_t) *s2++;
c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2;
do do { c1 = (ngx_uint_t) *s1++;
if (c1 == 0) { return NULL; }
c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1;
} while (c1 != c2);
} while (ngx_strncasecmp(s1, (u_char *) s2, n) != 0);
return --s1; }
|
ngx_strlcasestrn
在ngx_strcasestrn
的基础上对被搜索的字符串加上了一个范围限制,即在[s1,last]范围内去查找子串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| u_char * ngx_strlcasestrn(u_char *s1, u_char *last, u_char *s2, size_t n) { ngx_uint_t c1, c2;
c2 = (ngx_uint_t) *s2++; c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2; last -= n;
do { do { if (s1 >= last) { return NULL; }
c1 = (ngx_uint_t) *s1++;
c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1;
} while (c1 != c2);
} while (ngx_strncasecmp(s1, s2, n) != 0);
return --s1; }
|
ngx_rstrncmp
从右开始比较两个字符串的n个字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ngx_int_t ngx_rstrncmp(u_char *s1, u_char *s2, size_t n) { if (n == 0) { return 0; }
n--;
for ( ;; ) { if (s1[n] != s2[n]) { return s1[n] - s2[n]; }
if (n == 0) { return 0; }
n--; } }
|
ngx_rstrncasecmp
从右开始比较两个字符串的n个字符,忽略大小写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| ngx_int_t ngx_rstrncasecmp(u_char *s1, u_char *s2, size_t n) { u_char c1, c2;
if (n == 0) { return 0; }
n--;
for ( ;; ) { c1 = s1[n]; if (c1 >= 'a' && c1 <= 'z') { c1 -= 'a' - 'A'; }
c2 = s2[n]; if (c2 >= 'a' && c2 <= 'z') { c2 -= 'a' - 'A'; }
if (c1 != c2) { return c1 - c2; }
if (n == 0) { return 0; }
n--; } }
|