nginx作者定义的ngx_str_t中,字符串并不是以传统c的’\0’结尾的,使用了长度len配合data来表示一个数据段。所以我们要尽量使用nginx自带的api进行操作,如果没有自带,那就自己写一个api来进行相关操作,而不要随便使用c自带的字符串处理函数,那样很可能会导致内存越界,而且从原则上来说,这是违规行为。
看看ngx_str_t被定义成了什么:
typedef struct {
size_t len; // 字符数据的长度
u_char *data; // 存储的字符数据
} ngx_str_t;
这样做自然是有道理的,尤其在这个内存占用十分“小气”的nginx里。首先,通过长度来表示字符串长度,降低了长度计算次数。其次,nginx可以重复引用一段字符串内存,data可以指向任意内存,长度表示结束,而不用去copy一份自己的字符串(因为如果要以’\0’结束,而不能更改原字符串,所以势必要copy一段字符串)。这样做减少了很多不必要的内存分配和占用,有效的降低了内存使用量和长度的计算次数。
例如,如果用户请求”GET /test?a=1 http/1.1\r\n”存储在内存0x1d0b0000,这时只需要把r->method_name设置为{ len=3, data=0x1d0b0000}就可以表示方法名”GET”,而不需要单独为method_name再分配内存冗余的存储字符串。
由以上特性可知,我们在nginx中,必须谨慎的修改字符串,需要认真考虑修改后是否会对其它引用造成影响。
如果非要使用libc的函数处理字符串,有两个方案:
1. copy到一个新的buffer里,加上’\0’
2. 把要使用的部分后一位改成0,使用完再改回来(要确定改了后面的东西不会有不良反应0.0)
赋值和初始化操作:
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str }
/* 使用sizeof将一个传统字符串转换为nginx专用形式,因为使用了sizeof,所以str必须为常量
* 在传统c标准中只能用于做初始化时的赋值操作,普通的赋值是编译错误的(结构体的赋值操作)
* 在C99标准中,可以这样来赋值:str=(ngx_str_t)ngx_string("hello world")
*/
#define ngx_null_string { 0, NULL }
/* 用法同ngx_string,用于将字符串初始化为空 */
#define ngx_str_set(str, text) \
(str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
#define ngx_str_null(str) (str)->len = 0; (str)->data = NULL
/* 这两个函数就是直接调用的,用来给str赋值和重置的,str类型要求是指针,text必须为常量
* 这两个函数有一点要注意,因为它们是两个语句,并且没有用括号括住,所以在if等其中要用时要括住
其实保持良好的习惯就ok,碰到if-else不管是不是单句都括住
*/
大小写转换函数:
#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)
/* 通过三元表达式实现字母的大小写转换 */
void
ngx_strlow(u_char *dst, u_char *src, size_t n)
{
/* 将src的前n个字符转换成小写存放在dst字符串当中,需保证dst指向空间大于等于n且可写。*/
while (n) {
*dst = ngx_tolower(*src);
dst++;
src++;
n--;
}
}
/* 如果想要改变原字符串前n个字母为小写,可以ngx_strlow(str, str, n) */
字符串比较函数:
#define ngx_strncmp(s1, s2, n) strncmp((const char *) s1, (const char *) s2, n)
/* 字符串比较前n个字符,调用了libc的函数,因为只要指定了n就没问题,注意参数类型是char*不是ngx_str */
#define ngx_strcmp(s1, s2) strcmp((const char *) s1, (const char *) s2)
/* if (ngx_strcmp(var[i].data, "TZ") == 0
|| ngx_strncmp(var[i].data, "TZ=", 3) == 0) {
goto tz_found;
}
*/
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);
/* 不区分大小写的字符串比较 */
ngx_int_t ngx_rstrncmp(u_char *s1, u_char *s2, size_t n);
ngx_int_t ngx_rstrncasecmp(u_char *s1, u_char *s2, size_t n);
/* 从n-1开始往前比较字符串,加case的是不区分大小写的比较 */
#define ngx_memcmp(s1, s2, n) memcmp((const char *) s1, (const char *) s2, n)
ngx_int_t ngx_memn2cmp(u_char *s1, u_char *s2, size_t n1, size_t n2);
/* 分别是define的前n个字符比较和自定义的两个带长度的字符串比较 */
ngx_int_t ngx_dns_strcmp(u_char *s1, u_char *s2);
ngx_int_t ngx_filename_cmp(u_char *s1, u_char *s2, size_t n);
/* 分别为自定义的dns和filename比较函数,在其中分别令'.'和'/'成为了最小的字符 */
/* 学到的经典字符串比较函数:
ngx_int_t
ngx_dns_strcmp(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; // 可以用宏定义ngx_tolower(c)
if (c1 == c2) {
if (c1) { // 当前字符相当,如果还有后续字符,继续比较
continue;
}
return 0; // 当前两个字符串都到尾了,返回0
}
// in ASCII '.' > '-', but we need '.' to be the lowest character
c1 = (c1 == '.') ? ' ' : c1;
c2 = (c2 == '.') ? ' ' : c2;
return c1 - c2;
}
}
查找函数:
#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)
/* 封装的原libc中的三个字符串处理函数 */
static ngx_inline u_char* ngx_strlchr(u_char *p, u_char *last, u_char c)
/* 自己实现的在选定字段内查找c */
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) { // 第一个退出条件,s1找到len的位置了
return NULL;
}
c1 = *s1++;
if (c1 == 0) { // 第二个退出条件,s1找到结尾了(0)
return NULL;
}
} while (c1 != c2);
if (n > len) { // 找到相同字符,长度肯定不匹配了,放外层循环内
return NULL;
}
} while (ngx_strncmp(s1, (u_char *) s2, n) != 0);
return --s1;
}
/* 感觉这段代码写的挺好,没有什么可以挑剔的地方,性能逻辑都很棒,在s1的前len个位置查找s2 */
u_char *ngx_strcasestrn(u_char *s1, char *s2, size_t n);
/* 在s1中查找s2的前n个字符,调用了strncasecmp,不区分大小写 */
u_char *ngx_strlcasestrn(u_char *s1, u_char *last, u_char *s2, size_t n);
/* 在范围内实现上面函数的功能,不区分大小写 */
字符串设置函数:
#define ngx_memzero(buf, n) (void) memset(buf, 0, n)
#define ngx_memset(buf, c, n) (void) memset(buf, c, n)
/* 封装的memset函数 */
字符串复制函数:
#define ngx_memcpy(dst, src, n) (void) memcpy(dst, src, n)
#define ngx_cpymem(dst, src, n) (((u_char *) memcpy(dst, src, n)) + (n))
/* 封装了一个cpymem用来持续的给dst中复制信息,每次赋值完返回结尾的位置 */
#if ( __INTEL_COMPILER >= 800 )
/*
* the simple inline cycle copies the variable length strings up to 16
* bytes faster than icc8 autodetecting _intel_fast_memcpy()
*/
static ngx_inline/*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_copy替代cpymem,使用它的时候会根据编译环境和str长度进行最优选择(是否调用memcpy)*/
#define ngx_memmove(dst, src, n) (void) memmove(dst, src, n)
#define ngx_movemem(dst, src, n) (((u_char *) memmove(dst, src, n)) + (n))
/* 会处理内存重叠情况的memcpy */
u_char *ngx_cpystrn(u_char *dst, u_char *src, size_t n);
/* 从src中复制最多n个字符(遇0结束)到dst,并给dst后补一个0,然后返回0的这个位置 */
u_char *ngx_pstrdup(ngx_pool_t *pool, ngx_str_t *src);
/* 在pool中给src中的data申请一片内存存储,返回存储地址,失败返回NULL */
字符串转换函数:
ngx_int_t ngx_atoi(u_char *line, size_t n)
{
ngx_int_t value, cutoff, cutlim;
if (n == 0) {
return NGX_ERROR;
}
cutoff = NGX_MAX_INT_T_VALUE / 10;
cutlim = NGX_MAX_INT_T_VALUE % 10;
for (value = 0; n--; line++) {
// 出现了异常字符
if (*line < '0' || *line > '9') {
return NGX_ERROR;
}
// 发生越界的条件处理
if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
return NGX_ERROR;
}
value = value * 10 + (*line - '0');
}
return value;
}
/* 将line的前n个字符转成一个整形,越界处理代码很喜欢,收藏一下 */
ngx_int_t ngx_atofp(u_char *line, size_t n, size_t point)
{
ngx_int_t value, cutoff, cutlim;
ngx_uint_t dot;
if (n == 0) {
return NGX_ERROR;
}
cutoff = NGX_MAX_INT_T_VALUE / 10;
cutlim = NGX_MAX_INT_T_VALUE % 10;
dot = 0;
for (value = 0; n--; line++) {
if (point == 0) {
return NGX_ERROR;
}
if (*line == '.') {
// 如果出现两个小数点,报错!
if (dot) {
return NGX_ERROR;
}
dot = 1;
continue;
}
if (*line < '0' || *line > '9') {
return NGX_ERROR;
}
if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
return NGX_ERROR;
}
value = value * 10 + (*line - '0');
point -= dot; // 把小数点后的数直接加在value里,相当于point--
}
while (point--) {
if (value > cutoff) {
return NGX_ERROR;
}
value = value * 10;
}
return value;
}
/* 将line前n个字符指向的定点数转为整形并扩大point*10倍 */
ssize_t ngx_atosz(u_char *line, size_t n);
off_t ngx_atoof(u_char *line, size_t n);
time_t ngx_atotm(u_char *line, size_t n);
/* 将line转换为一个ssize_t/off_t/time_t类型的值,虽然都是整型但还是会有一些不同,增强nginx移植性,架构好 */
ngx_int_t ngx_hextoi(u_char *line, size_t n);
/* 将16进制的值转换为10进制 */
字符串格式化函数:
u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...);
u_char * ngx_cdecl ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...);
u_char * ngx_cdecl ngx_slprintf(u_char *buf, u_char *last, const char *fmt,
...);
/* 上面这三个函数用于字符串格式化,ngx_snprintf的第二个参数max指明buf的空间大小,
ngx_slprintf则通过last来指明buf空间的大小。推荐使用第二个或第三个函数来格式化字符串,
ngx_sprintf函数还是比较危险的,容易产生缓冲区溢出漏洞。*/
/* case 'V':
v = va_arg(args, ngx_str_t *);
len = ngx_min(((size_t) (last - buf)), v->len); // 长度限制,防止溢出
buf = ngx_cpymem(buf, v->data, len); // 调用安全的cpymem
fmt++;
continue;
*/
#define ngx_vsnprintf(buf, max, fmt, args) \
ngx_vslprintf(buf, buf + (max), fmt, args)
/* 宏定义的函数调用,max标识buf的长度 */
u_char *ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args);
/* 所有的sprintf都是调用这个接口实现的,buf~last标注buf的范围,fmt为格式化字符串,args为解析出的参数 */
在这一系列函数中,nginx在兼容glibc中格式化字符串的形式之外,还添加了一些方便格式化nginx类型的一些转义字符,比如%V用于格式化ngx_str_t结构。在nginx源文件的ngx_string.c中有说明:
/*
* 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
*/
这里特别要提醒的是,我们最常用于格式化ngx_str_t结构,其对应的转义符是%V,传给函数的一定要是指针类型,否则程序就会coredump掉。这也是我们最容易犯的错。比如:
ngx_str_t str = ngx_string("hello world");
char buffer[1024];
ngx_snprintf(buffer, 1024, "%V", &str); // 注意,str取地址
还有其他一些编码解码字符串的函数,如有兴趣可以自己看看源码~~