分类 wifidog分析 下的文章

wifidog HTTP Lighttpd1.4.20源码分析之buffer.c(h)--------字符串内存管理(2)

一些工具性的函数:
1、int LI_ltostr(char *buf, long val);
将长整型val转化成字符串,并存入buf中。

Code
int LI_ltostr(char *buf, long val) 
{
    char swap;
    char *end;
    int len = 1;
    //val为负数,加一个负号,然后转化成正数
    if (val < 0) 
    {
        len++;
        *(buf++) = '-';
        val = -val;
    }
    end = buf;
    /*
          这里val必须设置为大于9,并在循环外在做一次转换
            (*(end) = '0' + val)!
             因为如果val设置为大于0,当val为0时,将不进入循环,那么循
           环后面直接在buf中
     * 追加'\0'。这样0就被转化成了空串!!
     * 这里val转化后的字符串是逆序存放在buf中的,在后面要反转,
     * 以得到正确的顺序。
     */
    while (val > 9) 
    {
        *(end++) = '0' + (val % 10);
        val = val / 10;
    }
    *(end) = '0' + val;
    *(end + 1) = '\0';
    len += end - buf;
    //将字符串反转,
    while (buf < end) 
    {
        swap = *end;
        *end = *buf;
        *buf = swap;
        buf++;
        end--;
    }
    return len;
}

2、char hex2int(unsigned char c);
  converts hex char (0-9, A-Z, a-z) to decimal.returns 0xFF on
invalid input. 将16进制的字符转化成对应的数字,非法输入返回0xFF。忽略c的大小写。
3、char int2hex(char i);
  将i转化成对应的16进制形式
4、int light_isdigit(int c);
  c是否是数字。0-9
5、int light_isxdigit(int c);
  c是否是十六进制的数字0-9 a-f
6、int light_isalpha(int c);
  c是否是字母。
7、int light_isalnum(int c);
  c是否是字母或数字。

  以上几个函数在处理大小写的时候,都使用了c |= 32;将c转换成小写的形式,无论c原来是大写还是小写。原理在函数buffer_caseless_compare中讲解过。
8、int buffer_to_lower(buffer * b);
  将b中的数据转换成小写。
9、int buffer_to_upper(buffer * b);
  将b中的数据转换成大写。
  以上两个函数没有在buffer.c中定义。

  下面的几个宏定义一些方便的操作。

Code
#define BUFFER_APPEND_STRING_CONST(x, y) \
    buffer_append_string_len(x, y, sizeof(y) - 1)

#define BUFFER_COPY_STRING_CONST(x, y) \
    buffer_copy_string_len(x, y, sizeof(y) - 1)
//在buffer中追加一个‘/’,如果最后一个字符是‘/’,则不追加。
#define BUFFER_APPEND_SLASH(x) \
    if (x->used > 1 && x->ptr[x->used - 2] != '/') 
{ BUFFER_APPEND_STRING_CONST(x, "/"); }

#define CONST_STR_LEN(x)  x, x ? sizeof(x) - 1 : 0
#define CONST_BUF_LEN(x)  x->ptr, x->used ? x->used - 1 : 0

#define SEGFAULT() 
do { 
fprintf(stderr, "%s.%d: aborted\n", __FILE__, __LINE__); abort(); 
} while(0)
#define

以下的函数操作涉及到编码问题。在lighttpd中,使用到的编码有六种。具体的类型定义在下面的结构体中。

Code
typedef enum 
{
    ENCODING_UNSET,
    ENCODING_REL_URI,                /* for coding a rel-uri (/withspace/and%percent) nicely as part of a href */
    ENCODING_REL_URI_PART,        /* same as ENC_REL_URL plus coding / too as %2F */
    ENCODING_HTML,                /* & becomes &amp; and so on */
    ENCODING_MINIMAL_XML,        /* minimal encoding for xml */
    ENCODING_HEX,                    /* encode string as hex */
    ENCODING_HTTP_HEADER            /* encode \n with \t\n */

于此对应的 buffer.c 源文件中给出了
const char encoded_chars_rel_uri_part[]
const char encoded_chars_rel_uri[]
const char encoded_chars_html[]
const char encoded_chars_minimal_xml[]
const char encoded_chars_hex[]
const char encoded_chars_http_header[]
六个标志数组,数组中值为 1 的元素表示对应下标值大小的字符需要被编码转换,否则不需要转换可以直接使用(即编码前和编码后是同一个值)。例如对于 encoded_chars_rel_uri数组,encoded_chars_rel_uri[32]值为1表示该对应的字符(32对应的是空格,因为空格的十进制值为 32)需要被 uri 编码(被编码为“%20”),而对于值为 0的 encoded_chars_rel_uri[48],其对应的字符就不需要编码(48 对应的是字符‘0’,而字符‘0’并不是特殊字符,因此不用编码。)。对于具体的编码方式,请查阅相关资料。
1、int buffer_append_string_encoded(buffer * b, const char *s,size_t s_len, buffer_encoding_t encoding);
  将字符串s以指定的编码方式存入b中。encoding指定编码的方式。

/**
    * 将字符串s以指定的编码方式存入b中。
    * encoding指定编码的方式。
    */
   int buffer_append_string_encoded(buffer *b, const char *s, size_t s_len, buffer_encoding_t encoding) 
   {
       unsigned char *ds, *d;
       size_t d_len, ndx;
       const char *map = NULL;

      if (!s || !b) return -1;

      //b中存放的不是亦'\0'结尾的字符串。报错。
      if (b->ptr[b->used - 1] != '\0') 
      {
          SEGFAULT();
      }

      if (s_len == 0) return 0;

      //根据编码方式,选择对应的编码数组,就是上面的那六个数组。
      switch(encoding) {
      case ENCODING_REL_URI:
          map = encoded_chars_rel_uri;
          break;
      case ENCODING_REL_URI_PART:
          map = encoded_chars_rel_uri_part;
          break;
      case ENCODING_HTML:
          map = encoded_chars_html;
          break;
      case ENCODING_MINIMAL_XML:
          map = encoded_chars_minimal_xml;
          break;
      case ENCODING_HEX:
          map = encoded_chars_hex;
          break;
      case ENCODING_HTTP_HEADER:
          map = encoded_chars_http_header;
          break;
      case ENCODING_UNSET:
          break;
      }

      assert(map != NULL);

      /* 
       * count to-be-encoded-characters 
       * 计算经过编码转换后的字符串s的长度。
       * 不同的编码方式,对与不同的字符,其转换后的字符长度不同。
       */
      for (ds = (unsigned char *)s, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) 
      {
         if (map[*ds]) 
          {
              switch(encoding) 
              {
              case ENCODING_REL_URI:
              case ENCODING_REL_URI_PART:
                  d_len += 3;
                  break;
             case ENCODING_HTML:
             case ENCODING_MINIMAL_XML:
                  d_len += 6;
                  break;
              case ENCODING_HTTP_HEADER:
              case ENCODING_HEX:
                  d_len += 2;
                  break;
              case ENCODING_UNSET:
                  break;
              }
          } 
          else //字符不需要转换 
          {
              d_len ++;
          }
      }

      buffer_prepare_append(b, d_len);

      //下面这个循环就是开始做实际的编码转换工作。
     //ds指向字符串s中的字符。d指向b的数据去存放字符的位置。
      for (ds = (unsigned char *)s, d = (unsigned char *)b->ptr + b->used - 1, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) 
      {
         if (map[*ds]) 
          {
              switch(encoding) 
              {
              case ENCODING_REL_URI:             //此编码不需要转换
              case ENCODING_REL_URI_PART:     //将字符ASCII码转化成其对应的十六进制的形式,并在前面加上'%'
                  d[d_len++] = '%';
                  d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
                  d[d_len++] = hex_chars[(*ds) & 0x0F];
                  break;
              case ENCODING_HTML:             //不需要转换
              case ENCODING_MINIMAL_XML:         //也是转换成ASCII编码的十六进制形式,前面要加上"&#x",尾随一个';'
                  d[d_len++] = '&';
                  d[d_len++] = '#';
                 d[d_len++] = 'x';
                 d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
                 d[d_len++] = hex_chars[(*ds) & 0x0F];
                 d[d_len++] = ';';
                 break;
             case ENCODING_HEX:                 //直接转换成ASCII码对应的十六进制。
                 d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
                 d[d_len++] = hex_chars[(*ds) & 0x0F];
                 break;
             case ENCODING_HTTP_HEADER:        //这个处理HTTP头中的换行,统一转换成'\n\t'
                 d[d_len++] = *ds;
                 d[d_len++] = '\t';
                 break;
             case ENCODING_UNSET:
                 break;
             }
        } 
         else 
         {
             d[d_len++] = *ds;
         }
     }     
     /* 
      * terminate buffer and calculate new length 
      * 在新字符串尾部加上一个'\0' 
     */
     b->ptr[b->used + d_len - 1] = '\0';     
     b->used += d_len;         //新的字符串长度。
     return 0;
 }

2、static int buffer_urldecode_internal(buffer *url, int is_query)
  将rul中存放的特殊编码的字符转换成正常的字符。这里的编码是指上面六种编码中的ENCODING_REL_RUL_PART.

/* 
   * decodes url-special-chars inplace.
   * replaces non-printable characters with '_'
   * 将rul中存放的特殊编码的字符转换成正常的字符。这里的编码是指上面六种编码中的ENCODING_REL_RUL_PART.
   * 也就是把ASCII码的16进制表示,转换成正常的ASCII码。转换后的结果直接存放在url中。
   *
   * 这个is_query参数的作用仅仅控制是否将字符串中的'+'转换成空格' '。
   */

 static int buffer_urldecode_internal(buffer *url, int is_query) 
 {
     unsigned char high, low;
     const char *src;
     char *dst;

     if (!url || !url->ptr) return -1;

     //源字符串和目的字符串是同一个串。
     src = (const char*) url->ptr;
     dst = (char*) url->ptr;

     while ((*src) != '\0') 
     {
         if (is_query && *src == '+') 
         {
             *dst = ' ';
         } 
         else if (*src == '%') 
         {
             *dst = '%';
             //将后面16进制表示的ASCII码转换成正常的ASCII码。
             high = hex2int(*(src + 1));          //高四位
             if (high != 0xFF)                   //0xFF表示转换出错。
             {
                 low = hex2int(*(src + 2));         //低四位
                 if (low != 0xFF) 
                 {
                     high = (high << 4) | low;      //合并
                     /* map control-characters out  判断是否是控制字符。*/
                    if (high < 32 || high == 127) 
                         high = '_';
                     *dst = high;
                     src += 2;     
                     //这个转换过程中,三个源字符转换成一个目的字符。
                    //虽然是一个字符串,但源字符串遍历的更快,不会冲突。
                 }
             }
         } 
         else 
         {
             *dst = *src;
         }

         dst++;
        src++;
     }

     *dst = '\0';     //新结尾。
     url->used = (dst - url->ptr) + 1;

     return 0;
 }

3、int buffer_path_simplify(buffer * dest, buffer * src);
  删除路径字符串中的"/../","//"和"/./",简化路径,并不是简单的删除。

/* Remove "/../", "//", "/./" parts from path.
    *
    * /blah/..         gets  /
    * /blah/../foo     gets  /foo
    * /abc/./xyz       gets  /abc/xyz
    * /abc//xyz        gets  /abc/xyz
    *
    * NOTE: src and dest can point to the same buffer, in which case,
    *       the operation is performed in-place.
   *
   * 删除路径字符串中的"/../","//"和"/./",简化路径,并不是简单的删除。
   * 对于"/../"在路径中相当与父目录,因此,实际的路径相当于删除"/../"和其前面的一个"/XX/".
   * 如: /home/test/../foo   ->   /home/foo
   * 而"//"和"/./"表示当前目录,简单的将其删去就可以了。
  * NOTE: 源缓冲src和目的缓冲可以指向同一个缓冲,在这种情况下,操作将源缓冲中的数据替换。
   */

  int buffer_path_simplify(buffer *dest, buffer *src)
  {
      int toklen;
      char c, pre1;
      char *start, *slash, *walk, *out;
      unsigned short pre;     //pre两个字节,char一个字节,pre中可以存放两个字符。

     if (src == NULL || src->ptr == NULL || dest == NULL)
          return -1;

      if (src == dest)
          buffer_prepare_append(dest, 1);
      else
          buffer_prepare_copy(dest, src->used + 1);

      walk  = src->ptr;
      start = dest->ptr;
      out   = dest->ptr;
      slash = dest->ptr;


  #if defined(__WIN32) || defined(__CYGWIN__)
      /* 
       * cygwin is treating \ and / the same, so we have to that too
       * cygwin中\和/相同。转化之。
       */

      for (walk = src->ptr; *walk; walk++) 
      {
          if (*walk == '\\') *walk = '/';
      }
      walk = src->ptr;
  #endif
      //过滤掉开始的空格。
      while (*walk == ' ') 
      {
          walk++;
      }

      pre1 = *(walk++);
      c    = *(walk++);
      pre  = pre1;
      if (pre1 != '/')  //路径不是以'/'开始,在目的路径中加上'/'
      {
          pre = ('/' << 8) | pre1; //将prel指向的字符存放在pre的高八位。
         *(out++) = '/';
      }
      *(out++) = pre1;

     if (pre1 == '\0')          //转换结束
      {
          dest->used = (out - start) + 1;
          return 0;
      }

      while (1) 
      {
          if (c == '/' || c == '\0') 
          {
              toklen = out - slash; //slash指向距离c指向的字符前面最近的一个'/'。
              if (toklen == 3 && pre == (('.' << 8) | '.')) // "/../"
             {
                 out = slash;
                 if (out > start) //删除"/../"前面的一层目录"/XX/".
                  {
                      out--;
                     while (out > start && *out != '/') 
                      {
                          out--;
                      }
                  }

                  if (c == '\0')
                      out++;
              } 
              else if (toklen == 1 || pre == (('/' << 8) | '.')) // "//" 和 "/./"
              {
                  out = slash;
                 if (c == '\0')
                      out++;
              }

             slash = out;
         }

         if (c == '\0')
             break;

         pre1 = c;
         pre  = (pre << 8) | pre1; //pre始终存放的是prel指向的字符和其前一个字符。
         c    = *walk;
         *out = pre1;

         out++;
         walk++;
     }

     *out = '\0';
     dest->used = (out - start) + 1;

     return 0;
 }

总得来说,buffer的内容比较简单,其他的函数读者可以自行查看。

本文章由 http://www.wifidog.pro/2015/04/15/wifidog%E8%AE%A4%E8%AF%81lighttpd%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86-2.html 整理编辑,转载请注明出处

wifidog HTTP Lighttpd1.4.20源码分析之buffer.c(h)--------字符串内存管理(1)

  在web服务器中,通常要设计很多字符串的处理。比如客户端请求的 URI地址、发送的 query参数、post 提交的数据等等都是一串字符。因此,提供对字符串的灵活高效的处理,对lighttpd的效率至关重要。
  在lighttpd中,buffer提供了对字符串的处理。在buffer.h中,有如下的数据结构定义:
  //定义buffer

typedef struct 
{
    char *ptr;     //指向存储空间,一个字符串组
    size_t used;     //buffer中数据的长度
    size_t size;     //buffer的长度
} buffer;

  上面的结构体定义了lighttpd中,对字符串处理的基本结构。其具体含义如上。
Code

//定义buffer数组
typedef struct 
{
    buffer **ptr;     //buffer指针数组
    size_t used;     //buffer数组中数据的个数
    size_t size;     //buffer数组的大小
} buffer_array;
/*
 * 这个比较有意思
 */
typedef struct 
{
    char *ptr;
    size_t offset;    /* input-pointer */
    size_t used;    /* output-pointer */
    size_t size;
}read_buffer

这个结构体比较有意思,具体干什么的我还没有发现。。。不过从其定义中猜测,应该和输入输出缓冲有关。

围绕buffer结构体和buffer_array结构体,在buffer.h中定义了很多操作函数,其具体的作用将在下文中一一说明。其中比较有意思有技巧的函数还将就其实现代码进行分析。大部分的函数都很简单,读者可以自行阅读。
首先是buffer_array的操作函数:
1、buffer_array *buffer_array_init(void);
  初始化一个buffer_array,返回其指针并分配空间。
2、void buffer_array_free(buffer_array * b);
  释放b指向的buffer_array的空间。
3、void buffer_array_reset(buffer_array * b);
重置buffer_array。并递归重置数组中的数据。
4、buffer *buffer_array_append_get_buffer(buffer_array * b);
  返回数组中第一个未使用的buffer结构体的指针。如果数组已满,则对数组进行扩容,并初始化第一个为使用的buffer的指针。

下面是buffer的操作函数:
1、buffer *buffer_init(void);
初始化一个buffer。
2、buffer *buffer_init_buffer(buffer * b);
用b初始化一个buffer。相当于复制b。
3、buffer *buffer_init_string(const char *str);
用str初始化一个buffer。把str指向的字符串复制到buffer中。
4、void buffer_free(buffer * b);
释放buffer的空间。
5、void buffer_reset(buffer * b);
这个比较有意思。重置b所指向的buffer结构。一般情况下,都是把buffer数据区中ptr指向的字符数组的第一个元素ptr[0]设置为’\0’,然后把buffer的size设置为0。但当buffer的大小超过BUFFER_MAX_REUSE_SIZE时,则直接释放buffer的ptr指向的空间并把size设置为0。
6、int buffer_prepare_copy(buffer * b, size_t size);
为复制准备size大小的空间。如果b的空间大于size则仅仅将b的使用空间used设置为0.如果b的空间小于size,则重新分配size大小的空间。另外,为了防止内存碎片,在每次重新分配空间时,都将所分配的空间凑成BUFFER_PIECE_SIZE的整数倍:

1 b->size += BUFFER_PIECE_SIZE - (b->size % BUFFER_PIECE_SIZE);

7、int buffer_prepare_append(buffer * b, size_t size);
  为追加size大小的数据准备空间。操作和上一个函数查不多。
8、int buffer_copy_string(buffer * b, const char *s);
  将字符串s复制到b中。

Code
int buffer_append_string(buffer *b, const char *s)
{
    size_t s_len;
    if (!s || !b) return -1;
    s_len = strlen(s);
    buffer_prepare_append(b, s_len + 1);
    /*
     * 如果buffer中原来有数据(字符串),那么最后一个字符是NULL,
     * 在复制的时候,要覆盖这个字符。
      * 但当buffer为空时,就不需要覆盖NULL字符,因此,需要加一,
     * 以便和有数据的情况下处理相同。
     */
    if (b->used == 0)
        b->used++;
    //覆盖原来数据最后一个字符NULL,同时,也将s中的NULL复制到b中。
    memcpy(b->ptr + b->used - 1, s, s_len + 1);
    b->used += s_len;
    return 0;
}

9、int buffer_copy_string_len(buffer * b, const char *s, size_t s_len);
  将字符串s复制到b中。s_len是s的长度。s被看作是一个不以'\0'结尾的字符串,s_len是s的长度。最终b中的数据以'\0'结尾。也就是说,如果s的结尾是'\0',那么,最终,b中的数据末尾有两个'\0',而且b中used表示的数据长度,包括其中一个'\0'!
10、int buffer_copy_string_buffer(buffer * b, const buffer * src);
  将src中的数据复制到b中。
11、int buffer_copy_string_hex(buffer * b, const char *in ,size_t in_len);
  将字符串In转化成十六进制形式,复制到b中。
12、int buffer_copy_long(buffer * b, long val);
  将val以字符串的形式复制到b中。
13、int buffer_copy_memory(buffer * b, const char *s, size_t s_len);
  复制s指向的内存区域中的数据到b中。
14、int buffer_append_string(buffer * b, const char *s);
  将字符串s追加大b中。
15、int buffer_append_string_len(buffer * b, const char *s, size_t s_len);
  将字符串s追加到b中。s_len为s的长度。
  具体的处理与上面的复制函数差不多。
16、int buffer_append_string_buffer(buffer * b, const buffer * src);
  将src的数据追加到b中。
17、int buffer_append_string_lfill(buffer * b, const char *s, size_t maxlen);
  这个函数在buffer.c中没有实现。
18、int buffer_append_string_rfill(buffer * b, const char *s, size_t maxlen);
  将字符串s追加到b中。其中maxlen为字符串s的最大长度。如果
  字符串s的长度小于maxlen,那么追加空格,使其长度达到maxlen。在 函数实现中。如果s的长度大于maxlen,则可能溢出。。。
19、int buffer_append_long_hex(buffer * b, unsigned long len);
  将无符号长整型value转化成对应的十六进制的字符串形式。并将字符串复制到b中。其中涉及到数值转16进制的问题。代码如下:

Code
static const char hex_chars[] = "0123456789abcdef";
int buffer_append_long_hex(buffer *b, unsigned long value) 
{
    char *buf;
    int shift = 0;
    unsigned long copy = value;
    //计算十六进制表示的value的长度
    while (copy) 
    {
        copy >>= 4;
        shift++;
    }
    if (shift == 0)
        shift++;
    /*
     * 保证追加的字符串为偶数位。
     * 如若不是偶数位,则在最前面加一个'0'.
     */
    if (shift & 0x01)
        shift++;
    buffer_prepare_append(b, shift + 1);//最后一个'\0'
    if (b->used == 0)
        b->used++;
    //buf指向开始存放的位置
    buf = b->ptr + (b->used - 1);
    b->used += shift;
    /*
     * 每四位一组,转化value为十六进制形式的字符串
     */
    shift <<= 2;
    while (shift > 0) 
    {
        shift -= 4;
        *(buf++) = hex_chars[(value >> shift) & 0x0F];
    }
    *buf = '\0';
    return 0;
}

20、int buffer_append_long(buffer * b, long val);
  将val以字符串的形式追加大b中。

  下面的宏定义使用来处理off_t和long类型。
  如果long和off_t相同,则用处理long的函数来处理off_t。如果不相同,则另行定义off_t的处理函数。

Code
#if defined(SIZEOF_LONG) && (SIZEOF_LONG == SIZEOF_OFF_T)
#define buffer_copy_off_t(x, y)        buffer_copy_long(x, y)
#define buffer_append_off_t(x, y)    buffer_append_long(x, y)
#else
int buffer_copy_off_t(buffer * b, off_t val);
int buffer_append_off_t(buffer * b, off_t val);
#endif

22、int buffer_append_memory(buffer * b, const char *s, size_t s_len);
  将s指向的内存区的数据复制到b中,s_len是s的长度。
23、char *buffer_search_string_len(buffer * b, const char *needle, size_t len);
  判断b中是否含有字符串needle,needle的长度为len。如果存在,则返回needle在b中的指针位置,否则返回NULL
24、int buffer_is_empty(buffer * b);
  判断b是否为空。
25、int buffer_is_equal(buffer * a, buffer * b);
  判断a和b中的数据是相同。
26、int buffer_is_equal_right_len(buffer * a, buffer * b, size_t len);
  判断b1和b2中,最右边的len个字符是否相同。
27、int buffer_is_equal_string(buffer * a, const char *s, size_t b_len);
  b中的数据是否等于s,b_len为s的长度。
28、int buffer_caseless_compare(const char *a, size_t a_len,const char *b, size_t b_len);
  比较字符串a和b,忽略大小写。

Code
/** 
* simple-assumption:
* most parts are equal and doing a case conversion needs time
* 假设比较的部分相同的较多且大小写转换需要时间。 
*/
int buffer_caseless_compare(const char *a, size_t a_len, const char *b, size_t b_len) 
{
        size_t ndx = 0, max_ndx;
        size_t *al, *bl;
        size_t mask = sizeof(*al) - 1;
        al = (size_t *)a;
        bl = (size_t *)b;
                /* 一开始,将字符串数组转化成size_t类型的数组,通过比较size_t类型来比较是否相同 */
        /* libc的字符串比较函数也使用了相同的技巧,可有效的加快比较速度 */
        /* 检查a1和b1的位置是否对齐(size_t的类型长度的倍数?) ? */
        if ( ((size_t)al & mask) == 0 &&
                     ((size_t)bl & mask) == 0 ) 
        {
            /* 确定比较的长度 */
            max_ndx = ((a_len < b_len) ? a_len : b_len) & ~mask;

            for (; ndx < max_ndx; ndx += sizeof(*al)) 
                       {
                if (*al != *bl) break;
                al++; bl++;
            }
        }
        /* 相同的部分比较完毕 */
        /* 开始比较字符串,并忽略大小写 */
        a = (char *)al;
        b = (char *)bl;
        max_ndx = ((a_len < b_len) ? a_len : b_len);
        for (; ndx < max_ndx; ndx++) 
               {
            char a1 = *a++, b1 = *b++;
            /*
                'A'的二进制表示为0100 0001,'a'的二进制表示为0110 0001,
                大写字母比小写字母的ASCII值小了32。
                通过或上一个32,可以使所有的字母全部转换成大写字母。
            */
            if (a1 != b1) 
                       {
                if ((a1 >= 'A' && a1 <= 'Z') && (b1 >= 'a' && b1 <= 'z'))
                    a1 |= 32;
                else if ((a1 >= 'a' && a1 <= 'z') && (b1 >= 'A' && b1 <= 'Z'))
                    b1 |= 32;
                if ((a1 - b1) != 0) return (a1 - b1);
            }
        }
        /* all chars are the same, and the length match too。 they are the same */
        if (a_len == b_len) return 0;
        /* if a is shorter then b, then b is larger */
        return (a_len - b_len);
}

本文章由 http://www.wifidog.pro/2015/04/15/wifidog%E8%AE%A4%E8%AF%81lighttpd-%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86-1.html 整理编辑,转载请注明出处

wifidog自带HTTP 服务器 Lighttpd1.4.20源码分析之array.c(h) -----通用数组(2)

上文讲解到
array.h中还有一个定义:

 typedef struct {
  DATA_UNSET;
  array *value;
 } data_array;

这个定义了一个array类型的数据,也就是说,通用数组中存放的数据可以数通用数组,这样可以形成多维的通用数组。
在array.h中定义了如下的通用数组操作函数:
1、array *array_init(void);
初始化数组,分配空间。
2、array *array_init_array(array * a);
用数组a来初始化一个数组。也就是得到一个a的深拷贝。
3、void array_free(array * a);
释放数组。释放所有空间。
4、void array_reset(array * a);
重置data中的所有数据(调用UNSET类型数据中的reset函数),并将used设为0。相当于清空数组。
5、int array_insert_unique(array * a, data_unset * str);
将str插入到数组中。
6、data_unset *array_pop(array * a);
弹出data中的最后一个元素,返回奇指针,data中的最后一个位置设为NULL。
7、int array_print(array * a, int depth);
打印数组中的内容。depth参数用于在打印多维数组时,实现缩进。
8、a_unset *array_get_unused_element(array * a, data_type_t t);
返回第一个未使用的数据,也就是used位置的数据,这个数据不在数组中,返回这个数据指针后,将data[unsed]设为NULL。可能返回NULL。
9、data_unset *array_get_element(array * a, const char *key);
根据key值,返回数组中key值与之相同的数据
10、data_unset *array_replace(array * a, data_unset * du);
如果数组中有与du的key值相同的数据,则用du替换那个数据,并返回那个数据的指针。如果不存在,则把du插入到数组中。(调用data_insert_unique函数)
11、 int array_strcasecmp(const char *a, size_t a_len, const char *b, size_t b_len);
这个函数并没用实现,仅仅给出了上面的定义。也许这个是用来比较两个字符串,并且可能会忽略大小写。
12、void array_print_indent(int depth);
根据depth打印空白,实现缩进。
13、size_t array_get_max_key_length(array * a);
返回数组中最长的key的长度。

另外,在array.c中定义了一个辅助函数static intarray_get_index(array *a, const char *key, size_t keylen, int *rndx)。这个函数的作用是通过key值,查找数据,返回其在数组data中的下标位置,并通过参数rndx返回其下标在数组sorted中的位置。
函数的定义如下:

Code
static int array_get_index(array *a, const char *key, size_t keylen, int *rndx) 
{
    /*参数keylen是key的长度*/
    int ndx = -1;
    int i, pos = 0;
    if (key == NULL) return -1;
    /* try to find the string */
    /* 
* sorted数组是个下标数组,存放的是排好序的输入元素的下标,
* 相当于一个排好序的数组。
     * 利用sorted数组进行二分查找。
     * 若找到,返回元素在data数组中的位置,并通过rndx返回
* 其在sorted数组中的位置。
     * 若没有找到,通过rndx返回此元素在sorted中的位置,并返回-1
     */
/* pos中存放的是元素在数组data中的位置 */
/*  
当data的空间不够时,通用数组每次为data增加16个空间,第一次初始化时,
data的长度为16。因此,size始终是16的倍数。
used可以为任何数值,当然要大于等于0,小于size。
而next_power_of_2是大于used最小的2的倍数,如used=5,那么
next_power_of_2就等于8。
这样,used始终大于等于next_power_of_2的1/2。
*/
/*
 在这儿的二分搜索中,next_power_of_2是个很有创意的技巧。
next_power_of_2类似于一个标杆,利用这个标杆进行二分搜索可以减少很多
出错的几率,也使程序更加易懂。效率上当然没有什么损失。下面的程序读者可
自行看看,并不是很难。
 */
    for (i = pos = a->next_power_of_2 / 2; ; i >>= 1) 
{
 int cmp;
 if (pos < 0) {
 pos += i;
 } else if (pos >= (int)a->used) {
 pos -= i;
 } else {
 /* 比较两个元素的key值 */
 cmp = buffer_caseless_compare(key, keylen
, a->data[a->sorted[pos]]->key->ptr
, a->data[a->sorted[pos]]->key->used
);
 if (cmp == 0) {
 /* found */
 ndx = a->sorted[pos];
 break;
 } else if (cmp < 0) {/* 所找数据在前半部分 */
 pos -= i;
 } else { /*  所找数据在后半部分*/
 pos += i;
 }
 }
 if (i == 0) break;
    }
    if (rndx) *rndx = pos;
    return ndx;
}

在上面列出的函数中,还有一个函数要重点讲解一下,也是最复杂的一个函数:int array_insert_unique(array *a, data_unset *str)。这个函数将数据str插入到数组中,当并不是单纯的插入,如果数组中存在key于str相同的数据,则把str的内容拷贝到这个数据中。

Code
int array_insert_unique(array *a, data_unset *str) {
    int ndx = -1;
    int pos = 0;
    size_t j;
    /* generate unique index if neccesary */
    if (str->key->used == 0 || str->is_index_key) {
               buffer_copy_long(str->key, a->unique_ndx++);
                   str->is_index_key = 1;
    }
    /* 在数组中查找与str具有相同key的数据 */
    if (-1 != (ndx = array_get_index(a, str->key->ptr, str->key->used, &pos))) 
        {
            /* 找到,复制 */
             if (a->data[ndx]->type == str->type) 
              {
                       str->insert_dup(a->data[ndx], str);
              } 
              else 
              {
                       fprintf(stderr, "a\n");
              }
              return 0;
    }
    /* 当数组的长度大于最大值时,不进行插入,并返回-1 */
    if (a->used+1 > INT_MAX) {
                    /* we can't handle more then INT_MAX entries: see array_get_index() */
                   return -1;
    }

    if (a->size == 0) {
               /* 数组为空 */
               /* 初始data的长度为16 */
               a->size   = 16;
               a->data   = malloc(sizeof(*a->data)     * a->size);
               a->sorted = malloc(sizeof(*a->sorted)   * a->size);
               assert(a->data);
               assert(a->sorted);
               for (j = a->used; j < a->size; j++) 
                            a->data[j] = NULL;
    } 
        else if (a->size == a->used) 
        {
          /* data已经满了,对data进行扩容,增加16个空间。 */
          /* 这就是为什么size一定是16的倍数 */
               a->size  += 16;
               a->data   = realloc(a->data, sizeof(*a->data) * a->size);
               a->sorted = realloc(a->sorted, sizeof(*a->sorted) * a->size);
               assert(a->data);
               assert(a->sorted);
               for (j = a->used; j < a->size; j++)
                             a->data[j] = NULL;
    }
    ndx = (int) a->used;
    a->data[a->used++] = str;
    /*
               在上面调用函数array_get_index的时候,
              已将str应该在数组sorted中位置存放在了pos中。 
        */
    if (pos != ndx /* 要插入的位置在中部 */&&((pos < 0) /* 在开始位置插入 */
                            ||buffer_caseless_compare(str->key->ptr
                                          , str->key->used
                                          , a->data[a->sorted[pos]]->key->ptr
                                          , a->data[a->sorted[pos]]->key->used
                            ) > 0)) 
        {
               /* 判断当前pos所对应的元素是否比str小,若是,这pos后移一位 */
               pos++;
    }
    /* 移动sorted数组中后面的数据,腾出位置。 */
    if (pos != ndx) {
               memmove(a->sorted + (pos + 1), a->sorted + (pos), (ndx - pos) * sizeof(*a->sorted));
    }
    /* insert */
    a->sorted[pos] = ndx;
    /* 如果used==next_power_of_2时,扩展next_power_of_2 */
    if (a->next_power_of_2 == (size_t)ndx) 
              a->next_power_of_2 <<= 1;
    return 0;
}

其他函数都很简单,读者可自己查看。另外,print函数虽然复杂,但对整个程序的意义不大,读者可自行查看。

总结:
  Lighttpd中的通用数组的设置主要是使用的面向对象的思想,使数组具有很好的扩展性和适应性。通用数组中二分查找的实现也是一个特色。还有就是使用sorted数组只对data中的数据的下标排序,这也是一个很有用的技巧。
  以上就是我的一点拙见,还望读者网友对疏漏之处进行批评指正。

本文章由 http://www.wifidog.pro/2015/04/13/wifidog-lighttpd%E5%88%86%E6%9E%90%E9%80%9A%E7%94%A8%E6%95%B0%E7%BB%84-2.html 整理编辑,转载请注明出处

wifidog自带HTTP 服务器 Lighttpd1.4.20源码分析之array.c(h) -----通用数组(1)

Lighttpd提供了一个通用数组,这个数组与程序的其他部分练习较少,因此可以单独进行分析。
首先要说一下Lighttpd中的定义的一些数据结构。
在array.h中有下面的定义:

Code
typedef enum { 
        TYPE_UNSET,         /* 数据的类型未设置,
                               这几种数据类型使用了面向对象的设计思想,
                               这个类型相当于父类型,继承关系见后面 
                             */
        TYPE_STRING,         /* 字符串类型 */
        TYPE_COUNT,         /* COUNT类型 */
        TYPE_ARRAY,         /* 数组类型 */
        TYPE_INTEGER,     /* 整数类型 */
        TYPE_FASTCGI,     /* FASTCGI类型 */
        TYPE_CONFIG         /* CONFIG类型 */
} data_type_t;

这是一个枚举类型,定义了各个数据类型的标志。从中可以看出程序中所定义使用的数据类型的种类和个数。
Lighttpd在定义数据类型的时候使用了面向对象的思想,因此,程序具有很好的扩展性和适应性。这些类型中,最重要的是UNSET类型,这个类型在所有的数据类型中,起到了父类型的作用。在array.h中,UNSET类型的定义如下:

Code
#define DATA_UNSET \
    data_type_t type; \
    buffer *key; \
    int is_index_key; /* 1 if key is a array index */ \
    struct data_unset *(*copy)(const struct data_unset *src); \
    void (* free)(struct data_unset *p); \
    void (* reset)(struct data_unset *p); \
        int (*insert_dup)(struct data_unset *dst, struct data_unset *src); \
    void (*print)(const struct data_unset *p, int depth)

typedef struct data_unset {
    DATA_UNSET;
} data_unset;

其中,UNSET类型数据的定义中,数据的实际定义部分使用宏DATA_UNSET,这样可以方便其他类型在定义中直接引用DATA_UNSET宏来模拟继承。在宏DATA_UNSET中,定义了下面五个函数指针:

Code
struct data_unset *(*copy)(const struct data_unset *src); 
void (* free)(struct data_unset *p); 
void (* reset)(struct data_unset *p); 
int (*insert_dup)(struct data_unset *dst, struct data_unset *src); 
void (*print)(const struct data_unset *p, int depth)

这五个函数指针相当于UNSET的成员函数,其他类型可以通过对这五个指针赋值来实现成员函数的重写(Overwrite)。每种类型都配有自己特有的初始化函数,形式为:data_XXXXX *data_XXXXX_init(void)。在这些初始化函数中,对上面这五个函数指针进行赋值,当然,赋值的函数都应先定义好。
这几种类型的继承关系图如下:(类图)

wifidog libhttpd源码分析.jpg

下面分析一下STRING类型的初始化函数data_string * data_string_init(void):

Code
data_string *data_string_init(void)
{
    data_string *ds;
    /*
分配内存空间。
这里用的是calloc函数,分配的空间会自动清零。
*/
    ds = calloc(1, sizeof(*ds));
    assert(ds);
    /*
初始化各个数据成员,
这里调用buffer_init函数,主要就是分配内存空间
*/
    ds->key = buffer_init();
    ds->value = buffer_init();
    /*确定成员函数的具体调用函数,对函数指针赋值*/
    ds->copy = data_string_copy;
    ds->free = data_string_free;
    ds->reset = data_string_reset;
    ds->insert_dup = data_string_insert_dup;
    ds->print = data_string_print;
    ds->type = TYPE_STRING;
    return ds;
}

其他类型的init函数,以及其他函数都不难,读者可自行查看代码。
至于各个类型的用处以及各个类型中个成员变量的含义,暂且不用关心,只要知道这七个类型之间的关系即可,除了UNSET类型,其他类型的操作函数的实现都在文件data_XXXXX.c中。这些函数的实现都很简单,不在一一介绍,读者可自己看看。这七个类型构成了通用数组所要处理的类型,其中,在数组的定义和实现中只使用UNSET类型,利用上面的定义,通用数组可以不用关心数组中存储的到底是哪种具体的类型,只需将其按照UNSET类型来处理就可以了。这就实现了通用数组。
下面这个定义是通用数组的核心定义,也就是定义了数组。。。

Code
typedef struct 
{
    /* UNSET类型的指针型数组,存放数组中的元素 */
    data_unset **data;
    /* 存放着排好序的各个元素的下标的数组 */
    size_t *sorted;
    size_t used;    /* data中使用了的长度,也就是数组中元素个数 */

        /* data的大小。data的大小会根据数据的多少变化,会为以后的数据预先分
           配空间 */
    size_t size;    

        size_t unique_ndx;        /*  */
        /* 比used大的最小的2的倍数。也就是离used最近的且比used大的2的倍
              数 ,用于在数组中利用二分法查找元素*/
    size_t next_power_of_2;
        /* data is weakref, don't bother the data */
        /* data就是一个指针,不用关系其所指向的内容 */
    int is_weakref;                
} array;

各个变量的含义见上。
array.h中还有一个定义:

typedef struct {
  DATA_UNSET;
  array *value;
} data_array;

本文章由 http://www.wifidog.pro/2015/04/13/wifidog-lighttpd%E5%88%86%E6%9E%90%E9%80%9A%E7%94%A8%E6%95%B0%E7%BB%84-1.html 整理编辑,转载请注明出处