Nginx-string解析

我们先看看ngx_string中定义的几个数据结构

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;    
这个结构体就是最基础的nginx中的字符串结构了,其中 len表示这个字符串的长度, *data是一个指向无符号char类型的指针。

从这个结构体可以看出,后续关于字符串长度的操作strlen()都可以直接饮用len这个字段。 

除了这个最基本的结构体之外,还定义了另外两个字符串相关的结构体:

typedef struct {
    ngx_str_t   key;
    ngx_str_t   value;
} ngx_keyval_t;
这个结构体从定义来看就可以知道,这是一个关于字符串类型的key,value对的子结构,具体再哪能用到,还得往后看才能知道。

typedef struct {
    unsigned    len:28;
    unsigned    valid:1;
    unsigned    no_cacheable:1;
    unsigned    not_found:1;
    unsigned    escape:1;
    u_char     *data;
} ngx_variable_value_t;

这个结构体有点点奇怪,从定义来看,前面5个成员才占用了一个int,看上去只是ngx_str_t的一个扩展版本,其中

len: 只用了28bit来表示长度即最大字符串的长度不超过1-2^29.

valid:  

no_cacheable:

not_fount:

escape:

data: 这个字段和ngx_str_t里的对应字段含义一样。

这里就是ngx_string 里所有的关于字符串的结构体了,下面来看一些关于对于字符串操作的接口和宏定义

#define ngx_string(str)     { sizeof(str) - 1, (u_char *) str }

这个宏可以映射到c++中的构造函数,传入一个字符串,然后构造出一个ngx_str_t的对象。

#define ngx_null_string { 0, NULL }

这个宏实际上是上一个宏的特例,构造了一个空的ngx_str_t。 
#define ngx_str_set(str, text)                                               \
    (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
这个宏也可以看成是一个队ngx_str_t的构造函数,但是与前面ngx_string()宏的区别是,ngx_string()宏里传入的变量必须是一个字符串常量,但是这里的参数text可以是一个变量。 
#define ngx_str_null(str)   (str)->len = 0; (str)->data = NULL
这个宏和之前的ngx_null_string的作用也挺像,也是构造一个空的字符串常量,但是这里是显示的把某一个ngx_str_t对象置空或者可以看出是初始化。
#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)

这两个宏,顾名思义,是将字符c进行大小写转换。

void
ngx_strlow(u_char *dst, u_char *src, size_t n)
{
    while (n) {
        *dst = ngx_tolower(*src);
        dst++;
        src++;
        n--; 
    }    
}
这个函数是将字符串src里的字符全部转成小写字符,值得注意的是,这个函数里没有对指针的有效性进行判断,所以需要在外围传入的时候进行保证,否则就会出现段错误。这个函数就使用到了刚刚定义的ngx_tolower这个宏。 同样的办法也可以定义一个转换成大写的函数。

#define ngx_strncmp(s1, s2, n)  strncmp((const char *) s1, (const char *) s2, n)
/* msvc and icc7 compile strcmp() to inline loop */
#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)
这一系列的宏定义,实际上是对string.h里一些常见的结构进行了封装,这么做的好处就保证了再Nginx中保持了命名风格保持了一致。

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_inline这个是一个宏定义,实际上就是c语言中的inline,同样是为了命名规范。这个函数的作用从代码可以看出从指针p位置到last位置开始循环寻找字符c,如果找到了那么返回指针所在的位置,否则就返回空指针

#define ngx_memzero(buf, n)       (void) memset(buf, 0, n)
#define ngx_memset(buf, c, n)     (void) memset(buf, c, n)
这两个宏定义,是对memset的封装,主要是对内存初始化的操作。

#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

/*
 * gcc3, msvc, and icc7 compile memcpy() to the inline "rep movs".
 * gcc3 compiles memcpy(d, s, 4) to the inline "mov"es.
 * icc8 compile memcpy(d, s, 4) to the inline "mov"es or XMM moves.
 */
#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
这段代码首先涉及到在makefile中用宏定义进行条件编译的知识,如果在makefile中编译的时候有 -D NGX_MEMCPY_LIMIT=1 这个参数,那么运行的时候就会执行if后面的声明,否则是使用的else里面的声明。

#if (NGX_MEMCPY_LIMIT)

void *
ngx_memcpy(void *dst, const void *src, size_t n)
{
    if (n > NGX_MEMCPY_LIMIT) {
        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "memcpy %uz bytes", n);
        ngx_debug_point();
    }    

    return memcpy(dst, src, n);
}

#endif           
看到这段代码就明白NGX_MEMCPY_LIMIT这个参数的意思了,它是对内存拷贝长度的一个限制,如果拷贝的长度超过了这个限制,那么是会报错的。这个函数实际上就是对系统函数memcpy的一个封装返回的是dst的起始地址。感兴趣的可以进一步的去考察memcpy这个函数的实现方式,也比较有意思。

#define ngx_cpymem(dst, src, n)   (((u_char *) ngx_memcpy(dst, src, n)) + (n))
这个宏定义很有意思,调用了ngx_memcpy,最后宏返回的结果是dst+n,也就是拷贝后的最后一个指针的位置,具体为什么要这么做还需要往后看才知道具体的应用场景。

#define ngx_memcpy(dst, src, n)   (void) memcpy(dst, src, n)
#define ngx_cpymem(dst, src, n)   (((u_char *) memcpy(dst, src, n)) + (n))
如果编译的时候没有定义NGX_MEMCPY_LIMIT,那么 上面定义的ngx_memcpy以及ngx_cpymem实现是对memcpy的一个简单封装。但是都做到了接口统一。

#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 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                       
这段代码可以看出,源码作者对于nginx的性能真的是苦心孤诣。从实现来看,如果字符串的长度不超过16个字节并且要符合某个cpu架构的条件下直接循环逐个字符的复制,比直接调用系统函数要快!

#define ngx_memmove(dst, src, n)   (void) memmove(dst, src, n)
#define ngx_movemem(dst, src, n)   (((u_char *) memmove(dst, src, n)) + (n))
这两个宏是关于内存的移动,其实前面有不少关于内存移动的函数了,比如ngx_memcpy,但是为什么这里还要对memmove进行封装呢? 原因是:当dst和src在内存出现重叠的时候,memmove能保证dst和src一样,但是src的内容会被修改

用一小段代码来说明下:

#include<stdio.h>
#include <string.h>

int main()
{
    char s[]="hello word!";
    memmove(s,s+3,4);
    printf("%s\n",s);                                                                                        
    return 0;
}

这个代码的输出为:lo wo word!。

#define ngx_memcmp(s1, s2, n)  memcmp((const char *) s1, (const char *) s2, n) 
这个宏定义是对两段长度为n的内存的大小比较,是对系统函数memcmp的一个简单封装。

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;
}
这个函数是一个字符串的拷贝函数,从src中拷贝n个字符到dst中。

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;
}
这个函数是想从pool中分配一个和str同样长度的空间,然后把src中的数据dump到pool中来。

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,
    ...);
u_char *ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args);
#define ngx_vsnprintf(buf, max, fmt, args)                                   \
    ngx_vslprintf(buf, buf + (max), fmt, args)
这几个函数是一系列的print函数,是通过格式化的方式将一些变量写入到另外的一个字符串中

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_vslprintf这个函数实际上是对自动机,对于各种格式化输出的处理,可以自行参考代码。 

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_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_strcasecmp这个函数,实际上在函数内部把u_char字符转换成了ngx_uint. 而ngx_uint实际上是 uintptr_t. 这里把u_char转换成ngx_unit的原因是:转换后比较操作能稍微快一些。 

而uintptr_t的定义在/usr/includ/stdint.h头文件中

#if __WORDSIZE == 64
# ifndef __intptr_t_defined
typedef long int                intptr_t;
#  define __intptr_t_defined
# endif
typedef unsigned long int       uintptr_t;                                                                                          
#else
# ifndef __intptr_t_defined
typedef int                     intptr_t;
#  define __intptr_t_defined
# endif
typedef unsigned int            uintptr_t;
#endif

从这段代码可以看出,uintptr_t要么是int,要么是unsigned int。 对于64位的机器是  long 或者unsigned long。

u_char *ngx_strstrn(u_char *s1, char *s2, size_t n);                                                         
u_char *ngx_strcasestrn(u_char *s1, char *s2, size_t n); 
u_char *ngx_strlcasestrn(u_char *s1, u_char *last, u_char *s2, size_t n); 
/*
 * ngx_strstrn() and ngx_strcasestrn() are intended to search for static
 * substring with known length in null-terminated string. The argument n
 * must be length of the second substring - 1.
 */

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;
}
这个函数的功能是在s1字符串中找到第一个s2子串出现的位置,没有找到就不返回空指针,否则返回出现的位置。

ngx_strstrn这个函数上面有段注释,很有意思,主要是有两点需要注意

1,s2必须是一个长度已知的静态的字符串。

2, n这个参数必须是s2长度-1,这里为什么要-1呢? 这里还是需要仔细看代码才能明白,因为在内层的do while循环了已经比较了一个字符了,所以就不需要比较开头的字符了,所以需要减掉1,效率,效率,一切都是为了效率。

ngx_strcasestrn这个函数只是一个大小写不敏感的函数而已。

ngx_strlcasestrn这个函数增加了一个参数last,这个函数实现的功能是在s1到last这个区间内找到第一个出现s2子串的位置并且大小写不敏感。

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); 
ngx_int_t ngx_memn2cmp(u_char *s1, u_char *s2, size_t n1, size_t n2);
ngx_int_t ngx_dns_strcmp(u_char *s1, u_char *s2);
这几个函数从函数名的后缀来看就可以知道是比较函数,那么先来看看第一个的实现

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这个函数的实现了,基于上一个函数增加大小写不敏感的处理。

ngx_int_t
ngx_memn2cmp(u_char *s1, u_char *s2, size_t n1, size_t n2)
{
    size_t     n;
    ngx_int_t  m, z;

    if (n1 <= n2) {
        n = n1;
        z = -1;

    } else {
        n = n2;
        z = 1;
    }

    m = ngx_memcmp(s1, s2, n);

    if (m || n1 == n2) {
        return m;
    }

    return z;
}
这个函数从实现上来看一开始从n1和n2中取到了一个 较小的数,然后比较s1和s2的前缀的大小,最后的一个if语句实际上处理了前缀相同但是字符串的总长度不相同的情况,这里看到的是 当前缀相同,但是长度不同的时候,较长的大。

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;

        if (c1 == c2) {

            if (c1) {
                continue;
            }

            return 0;
        }

        /* in ASCII '.' > '-', but we need '.' to be the lowest character */

        c1 = (c1 == '.') ? ' ' : c1;
        c2 = (c2 == '.') ? ' ' : c2;

        return c1 - c2;
    }
}
这是一个域名比较的函数,其中需要特别注意的地方是在遇到 dot和minus这两个符号进行比较的时候,实际需要的是 minus > dot,所以在遇到 dot的时候做了特殊处理,转化成了 space, 这三个符号的ASCII值为 {'.':46, '-':45,' ':32}。

ngx_int_t ngx_atoi(u_char *line, size_t n);
ngx_int_t ngx_atofp(u_char *line, size_t n, size_t point);
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);
ngx_int_t ngx_hextoi(u_char *line, size_t n);
这几个函数都是关于字符串到数字的转换。

ngx_int_t
ngx_atofp(u_char *line, size_t n, size_t point)
{
    ngx_int_t   value;
    ngx_uint_t  dot;

    if (n == 0) {
        return NGX_ERROR;
    }

    dot = 0;

    for (value = 0; n--; line++) {

        if (point == 0) {
            //小数点每往后移动一位,point要减掉1,如果point都变成0了,这个循环还没有结束,那么和这个函数的本意就相违背了。
            return NGX_ERROR;
        }

        if (*line == '.') {
            if (dot) {
                //多次找到了dot,那么这个浮点数是非法的。
                return NGX_ERROR;
            }
	    //字符串中找到了dot,那么就进行标记
            dot = 1;
            continue;
        }

        if (*line < '0' || *line > '9') {
            return NGX_ERROR;
        }

        value = value * 10 + (*line - '0');
        point -= dot;
    }      
    while (point--) {
        value = value * 10;
    }
    if (value < 0) {
        return NGX_ERROR;
    } else {
        return value;
    }     


这个函数需要认真琢磨下,先说下函数各个参数的含义

line: 这个比较明了就是这个字符串。

n: 这里是表示输出整数的位数。

point: 是指浮点字符串dot往后移动的位数。

这里可以有一个隐含的条件是n-point应该就是line中整数部分位数,如果不相等,那么参数错误了。

这个函数的场景其实还是很少见的,后续见到了再回过头来看看。

void ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src);                                                      
ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);
ngx_int_t ngx_decode_base64url(ngx_str_t *dst, ngx_str_t *src);
static ngx_int_t ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis)

这一组函数是关于base64的编解码的接口

void
ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src)
{
    u_char         *d, *s;
    size_t          len;
    /*编码后输出的字符集*/
    static u_char   basis64[] =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 

    len = src->len;
    s = src->data;
    d = dst->data;

    while (len > 2) {
        /*因为3个字节刚好是24位可以组合成4个编码后的字符,所以循环判断的时候是len>2*/
        *d++ = basis64[(s[0] >> 2) & 0x3f]; //第一个字符的高6位
        *d++ = basis64[((s[0] & 3) << 4) | (s[1] >> 4)]; //第一个字符的低2位和第二个字符的高4位
        *d++ = basis64[((s[1] & 0x0f) << 2) | (s[2] >> 6)]; //第二个字符的低4位和第三个字符的高2位
        *d++ = basis64[s[2] & 0x3f]; //第三个字符的低6位

        s += 3;
        len -= 3;
    }

    if (len) {
        //字符长度不是3的倍数
        *d++ = basis64[(s[0] >> 2) & 0x3f]; //剩下字符的第一个字符的高6位

        if (len == 1) { //字符串的长度膜3余1
            *d++ = basis64[(s[0] & 3) << 4]; //剩下一个字符的低2位变成高2位,后面四位补0
            *d++ = '='; //特殊标记
        } else { //字符串长度模3余2
            *d++ = basis64[((s[0] & 3) << 4) | (s[1] >> 4)]; //剩下字符的第一个字符的低2位和第二个字符的高4位
            *d++ = basis64[(s[1] & 0x0f) << 2]; //剩下字符的第二个字符的低4位,不足补0
        }

        *d++ = '='; //特殊标记。
    }
    //从整个编码来看,如果最后没有'='那么原字符串的长度刚好是3的倍数,如果有1个那么模3余2,如果有1个那么是模3余1.
    dst->len = d - dst->data;
}
这个是标准的base64的编码方式,具体说明可以参考代码中的注释。

ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);
ngx_int_t ngx_decode_base64url(ngx_str_t *dst, ngx_str_t *src);
static ngx_int_t ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis)
前面两个函数,统一调用的是最下面接口,参数唯一区别是解码表有点点区别。先来看看decode函数

static ngx_int_t
ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis)
{
    size_t          len;
    u_char         *d, *s;

    for (len = 0; len < src->len; len++) {
        if (src->data[len] == '=') { //找到'=',那么后面的任何字符都不是编码得到的了。
            break;
        }
        /*字符的有效性验证,因为之前都是把字符串当成bit串然后6位为一组进行编码的,那么得到的编码结果每个字符都应该映射回去,如果映射到77了,那么说明出现了错误。*/
        if (basis[src->data[len]] == 77) { 
            return NGX_ERROR;
        }
    }
    /*这里为什么要判定模4余1为非法,需要先看看编码结尾部分的处理,就很好理解了。*/
    if (len % 4 == 1) {
        return NGX_ERROR;
    }

    s = src->data;
    d = dst->data;

    while (len > 3) {
        *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);//第一个字符的低6位左移2位,和第二个字符的低6位右移4位,这样就还原了第一个字符。
        *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);//第二个字符的
        *d++ = (u_char) (basis[s[2]] << 6 | basis[s[3]]);
        s += 4;
        len -= 4;
    }


    if (len > 1) {
        *d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);
    }


    if (len > 2) {
        *d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);
    }


    dst->len = d - dst->data;


    return NGX_OK;
}

这里再简单介绍下编解码的过程。

解码: 解码的时候需要将字符串看成bit串(高位在左,低位在右),然后从左到右依次6bit的去取,因为6bit的数字大小在0-63之间,所以在编码的时候需要制定一个64个字符的编码表,然后用着6bit的数字作为下标取到对应的字符,需要特别注意的是,最后6bit不够的时候是采用低位补0的办法处理。

解码: 编码的时候有一个编码表,那么解码的时候同样是需要的,比如: 在编码的时候第一个6bit假设都是1,那么得到的下标是63,取到的字符是'/',那么在解码的时候用'/'的ASCII值作为下标在解码表中取到的值应该是63,这样就完成了解码。

需要特别注意的是,在对url进行base64的decode的时候,解码表中值为62,63的位置和标准的解码表位置不一样了,分别是'_' 和'-'ASCII值下标对应的值为63和62. 这是因为当url中传递的字符串在传递之前做base64encode的时候编码表示有差异的,如下:

标准的base64编码表: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

url做base64的编码表:  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

之所以对最后两个字符做了变化是因为在'+'和'/'在url中传递的时候是会被urlencode成%xx这样的字符。

这里很奇怪的是,没有提供url参数做base64编码的函数。

uint32_t ngx_utf8_decode(u_char **p, size_t n); 
size_t ngx_utf8_length(u_char *p, size_t n); 
u_char *ngx_utf8_cpystrn(u_char *dst, u_char *src, size_t n, size_t len);
这几个函数是关于字符串的在utf8编码上的一些处理,这里没有utf8的encode函数,但是提供了utf8的decode函数,要了解utf的decode函数需要先看看utf8的编码规则,可以先参考 维基百科-UTF8, 另外也可以参考一篇写的比较好的blog: UTF8编码规则

uint32_t
ngx_utf8_decode(u_char **p, size_t n)
{
    size_t    len;
    uint32_t  u, i, valid;

    u = **p;

    if (u >= 0xf0) {
        //leading byte = 11110xxx,  第一个字节不小于0xf0的话,说明这个字符是4字节组成

        u &= 0x07;
        valid = 0xffff;
        len = 3;

    } else if (u >= 0xe0) {
        //leading byte = 1110xxxx,  第一个字节不小于0xe0的话,说明这个字符是3字节组成
        u &= 0x0f;
        valid = 0x7ff;
        len = 2;

    } else if (u >= 0xc2) {
        //leading byte = 110xxxxx,  第一个字节不小于0xc2的话,说明这个字符是2字节组成,
       // 从utf编码的方式来看这里判断条件应该是 u>=0xc0? 已经在nginx论坛提问了 , 持续关注中。
        u &= 0x1f;
        valid = 0x7f;
        len = 1;

    } else {
        //leading byte = 0xxxxxxx,  
        (*p)++;
        return 0xffffffff;
    }

    if (n - 1 < len) {
        return 0xfffffffe;
    }

    (*p)++;
    //continue byte
    while (len) {
        i = *(*p)++;

        if (i < 0x80) {
            return 0xffffffff;
        }

        u = (u << 6) | (i & 0x3f);

        len--;
    }

    if (u > valid) {
        return u;
    }

    return 0xffffffff;  
}

size_t
ngx_utf8_length(u_char *p, size_t n)
{
    u_char  c, *last;
    size_t  len;

    last = p + n;

    for (len = 0; p < last; len++) {

        c = *p;

        if (c < 0x80) {
            p++;
            continue;
        }
        //UTF-8 : [0-0x10FFFF]
        if (ngx_utf8_decode(&p, n) > 0x10ffff) {
            /* invalid UTF-8 */
            return n;
        }
    }

    return len;
}

这个函数是实现,计算一个字符串中含有的字符的个数,以为是utf8编码,所以不能简单计算字节数来计算字符个数。


最后string部分还有几个关于url escape的接口和红黑树的接口,这部分后面其他部分再来看看。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值