nginx源码初读(4)--让烦恼从数据结构开始(ngx_str)

来源:互联网 发布:win7 java 编辑:程序博客网 时间:2024/05/23 23:00

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)/* 通过三元表达式实现字母的大小写转换 */voidngx_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取地址

还有其他一些编码解码字符串的函数,如有兴趣可以自己看看源码~~

0 0
原创粉丝点击