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取地址
还有其他一些编码解码字符串的函数,如有兴趣可以自己看看源码~~
- nginx源码初读(4)--让烦恼从数据结构开始(ngx_str)
- nginx源码初读(1)--让烦恼从数据结构开始(ngx_cdecl/ngx_int/ngx_log)
- nginx源码初读(2)--让烦恼从数据结构开始(ngx_buf/ngx_chain)
- nginx源码初读(3)--让烦恼从数据结构开始(ngx_pool)
- nginx源码初读(5)--让烦恼从数据结构开始(ngx_array)
- nginx源码初读(6)--让烦恼从数据结构开始(ngx_list)
- nginx源码初读(7)--让烦恼从数据结构开始(ngx_queue)
- nginx源码初读(8)--让烦恼从数据结构开始(ngx_event)
- nginx源码初读(9)--让烦恼从数据结构开始(ngx_listening/ngx_connection)
- nginx源码初读(10)--让烦恼从数据结构开始(ngx_cycle)
- nginx源码初读(11)--让烦恼从数据结构开始(ngx_command/ngx_module/ngx_conf)
- nginx源码初读(1)--让烦恼从数据结构开始(ngx_cdecl/ngx_int/ngx_log)
- nginx源码初读(1)--让烦恼从数据结构开始(ngx_cdecl/ngx_int/ngx_log)
- 第一个数据结构ngx_str
- nginx源码剖析--从main函数开始
- nginx源码剖析(2)----从main()函数开始
- nginx源码剖析(2)----从main()函数开始
- 图的遍历 ---从数据结构开始4
- A ndroid 获取屏幕高度、标题高度、状态栏高度详解
- iOS关于CAShapeLayer与UIBezierPath的知识内容
- MapReduce中的分区方法Partitioner
- 类View
- 高性能WEB开发之Web性能测试工具推荐
- nginx源码初读(4)--让烦恼从数据结构开始(ngx_str)
- runOnUiThread使用实例
- leetcode 279. Perfect Squares
- RYU应用程序API详解
- Dungeon Master
- Android Browser源码分析(二)BrowserActivity初始化
- iOS绘图教程
- 可以让你掌握安卓开发环境部署比预期要好的5种方法
- 数据库知识