分类 wifidog源码 下的文章

wifidog认证源码分析Lighttpd1.4.20源码分析之etag.c(h) -------HTTP/1.1中的Etag域

etag的全称是entity tag(标记实体值),在RFC2616中关于etag的定义如下:
The ETag response-header field provides the current value of the entity tag for the requested variant. The headers used with entity tags are described in sections 14.24, 14.26 and 14.44. The entity tag MAY be used for comparison with other entities from the same resource(see section 13.3.3).
ETag = "ETag" ":" entity-tag
Examples:
ETag: "xyzzy"
ETag: W/"xyzzy" (前面的W/表示这个是个弱Etag)
ETag: ""
巨长的RFC2616对Etag的描述就上面这么多。意思就是说Etag域提供了请求变体的一个实体标记值。这个值可以和If-Match和If-No-Match一起使用。RFC2616中对Etag的唯一要求就是它是一个双引号包围的字符串,至于怎么生成这个字符串以及怎么使用,由应用程序决定。
下面说一说在服务器程序中,一般是怎么使用Etag的(这个东西用好了还是很不错的。。。):
把Last-Modified和ETags请求的http报头一起使用,这样可利用客户端(例如浏览器)的缓存。
因为服务器首先产生Last-Modified/Etag标记,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。
过程如下:
1.客户端请求一个页面(A)。
2.服务器返回页面A,并在给A加上一个Last-Modified/ETag。
3.客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。
4.客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。
5.服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。
工作原理:
Etag由服务器端生成,客户端通过If-Match或者说If-None-Match这个条件判断请求来验证资源是否修改。常见的是使用If-None-Match.
请求一个文件的流程可能如下:
====第一次请求===
1.客户端发起 HTTP GET 请求一个文件;
2.服务器处理请求,返回文件内容和一堆Header,当然包括Etag(例如"2e681a-6-5d044840")(假设服务器支持Etag生成和已经开启了Etag).状态码200。
====第二次请求===
1.客户端发起 HTTP GET 请求一个文件,注意这个时候客户端同时发送一个If-None-Match头,这个头的内容就是第一次请求时服务器返回的Etag:2e681a-6-5d044840
2.服务器判断发送过来的Etag和计算出来的Etag匹配,因此If-None-Match为False,不返回200,返回304,客户端继续使用本地缓存;

流程很简单,问题是,如果服务器又设置了Cache-Control:max-age和Expires呢,怎么办?答案是同时使用,也就是说在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后,服务器才能返回304.
另外,使用Etag比使用Last-Modified接合If-Modified-Sience更有优势。如果一些文件经常被修改的不是文件的内容,而是文件的属性,如:文件的修改时间等。那么就没有必要重新发送文件,此时,Last-Modified不能判断其内容是否修改,所以只会重新发送。而使用Etag,可以通过检验如文件的i节点号,大小等来判断是否重传。
那么,在Lighttpd中,Etag到底是个什么东东呢?且听我慢慢道来。。。
在头文件Etag.h中定义了一个枚举类型

typedef enum 
{ 
    ETAG_USE_INODE = 1,      //包含文件的i节点号。
    ETAG_USE_MTIME = 2,      //包含文件最后一次修改的时间。
    ETAG_USE_SIZE = 4        //包含文件的byte数。
} etag_flags_t;

这个枚举类型决定了Etag中所包含的东西。注意,三个枚举量被定义为1,2,4.这样可以通过或的方式来包含多个内容。如::ETAG_USE_INODE | ETAG_USE_SIZE,表示Etag中既包含文件的i节点号,也包含文件的大小。
在头文件etag.h中,只声明了三个函数:
1、int etag_is_equal(buffer * etag, const char *matches);
这个是判断etag的内容是否和matches相同。相同返回1,不同返回0.
2、int etag_create(buffer * etag, struct stat *st, etag_flags_t flags);
这个是根据flags和st生成一个etag,存放在etag中传出。st是struct stat,在文件sys/stat.h中定义,用来表示文件的状态(目录也是文件哦)。可以使用stat函数获取一个文件的文件状态。具体操作请读者自己查阅。对于flags的设置,Lighttpd通过读取配置文件信息,设置flags。
3、int etag_mutate(buffer * mut, buffer * etag);
这个函数个etag生成一个哈希值,存放在mut中,并用双引号包围。里面使用的哈希方法是上一个结果左移五位异或上一个结果右移27位异或下一个字符,得到下一个结果。在实际应用中,这个哈希方法是很可靠的,基本上不会得到两个不同的字符串得到相同的结果,也就是基本不会出现碰撞。

三个函数的具体实现如下:

Code
int etag_is_equal(buffer * etag, const char *matches)
{
    if (etag && !buffer_is_empty(etag) && 0 == strcmp(etag->ptr, matches))
    {
        return 1;
    }
    return 0;
}

int etag_create(buffer * etag, struct stat *st, etag_flags_t flags)
{
    if (0 == flags)
        return 0;
    buffer_reset(etag);
    if (flags & ETAG_USE_INODE) //i节点号(serial number)
    {
        buffer_append_off_t(etag, st->st_ino);
        buffer_append_string_len(etag, CONST_STR_LEN("-"));
    }
    if (flags & ETAG_USE_SIZE) //普通文件的byte数
    {
        buffer_append_off_t(etag, st->st_size);
        buffer_append_string_len(etag, CONST_STR_LEN("-"));
    }
    if (flags & ETAG_USE_MTIME) //文件最后一次修改的时间。
    {
        buffer_append_long(etag, st->st_mtime);
    }
    return 0;
}

int etag_mutate(buffer * mut, buffer * etag)
{
    size_t i;
    uint32_t h;
    //计算哈希值。
    for (h = 0, i = 0; i < etag->used; ++i)
    {
        h = (h << 5) ^ (h >> 27) ^ (etag->ptr[i]);
    }
    buffer_reset(mut);
    buffer_copy_string_len(mut, CONST_STR_LEN("\""));
    buffer_append_long(mut, h);
    buffer_append_string_len(mut, CONST_STR_LEN("\""));
    return 0;
}

本文章由 http://www.wifidog.pro/2015/04/16/wifidog%E8%AE%A4%E8%AF%81%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.html 整理编辑,转载请注明出处

wifidog 认证Lighttpd1.4.20源码分析之bitset.c(h) -------位集合的使用

使用一个比特位来表示一个事件的两种状态,即节省内存,又可以提高运行速度。在Lighttpd中,提供了一个bitset数据结构,用来管理使用一个比特位集合。
  在bitset.h中,比特位集合的数据结构定义如下:

typedef struct 
{
            size_t *bits;
            size_t nbits;
} bitset;

bits指向一个size_t类型的数组,存放bit集合。size_t类型通常被定义成一个无符号的整型(int或long),其长度和具体的机器有关,这个读者可以查阅相关的资料。nbits记录bitset中bit为的个数。其图示如下:

+-------------+
| bitset结构     |
+-------------+             +-----------------------------+
|   bits           |   -------->          |  |  |  |  |  |  |  |  |  |  | | | | | |
+-------------+             +-----------------------------+
|  nbits = 10   |
+-------------+

为了提高运行的速度,对与bitset的主要操作都有四个宏来实现:
各个宏的作用都在注释中说明。

Code
//计算一个size_t类型有占多少位。
//CHAR_BIT表示一个char类型占多少为,在/usr/include/limits.h中定义,本人机器中定义为8.
#define BITSET_BITS \
    ( CHAR_BIT * sizeof(size_t) )

/**
 * 得到一个pos位置为1,其他位置都为0的size_t类型的掩码。
 * 其中pos位置是这个位在bitset中的位置,因此要模一个BITSET_BITS才是其在size_t中的位置。
 */
#define BITSET_MASK(pos) \
    ( ((size_t)1) << ((pos) % BITSET_BITS) )
/**
 * 计算pos位在set中的bits数组中的位置。
 * 也就是,pos位在数组bits中,包含在那个size_t类型的成员中。
 */
#define BITSET_WORD(set, pos) \
    ( (set)->bits[(pos) / BITSET_BITS] )

/**
 * 由于bitset中是用size_t类型数组来存放bit位的,因此实际开的空间应该是size_t的整数倍。
 * 这个宏就是用来计算在需要nbits个位的情况下,要开多少内存空间。
 * 也就是计算nbits是BITSET_BITS的整数倍加一。
 */
#define BITSET_USED(nbits) \
    ( ((nbits) + (BITSET_BITS - 1)) /

操作函数都比较简单短小,直接贴出来了。

/**
   * 初始化一个bitset为nbits位
   */
  bitset *bitset_init(size_t nbits)
  {
      bitset *set;

      set = malloc(sizeof(*set));
      assert(set);
     //分配空间并初始化为0.
     set->bits = calloc(BITSET_USED(nbits), sizeof(*set->bits));
     set->nbits = nbits;

     assert(set->bits);

     return set;
 }

 /**
  * 将set中的所有位重置为0
  */
 void bitset_reset(bitset * set)
 {
     memset(set->bits, 0, BITSET_USED(set->nbits) * sizeof(*set->bits));
 }

 //释放set
 void bitset_free(bitset * set)
 {
     free(set->bits);
     free(set);
 }

 //将pos位设置为0.
 void bitset_clear_bit(bitset * set, size_t pos)
 {
     if (pos >= set->nbits)
     {
         SEGFAULT();
     }
      BITSET_WORD(set, pos) &= ~BITSET_MASK(pos);
 }
 //将pos为设置为1
 void bitset_set_bit(bitset * set, size_t pos)
 {
     if (pos >= set->nbits)
     {
         SEGFAULT();
     }

     BITSET_WORD(set, pos) |= BITSET_MASK(pos);
 }
 //测试pos位置是否是1
 int bitset_test_bit(bitset * set, size_t pos)
 {
     if (pos >= set->nbits)
     {
         SEGFAULT();
     }

     return (BITSET_WORD(set, pos) & BITSET_MASK(pos)) != 0;
 }

Lighttpd中的bit接合操作剪断精悍,所有的代码都已经在本文中贴出来了。当然,头文件中的函数声明没有贴出来。。。

本文章由 http://www.wifidog.pro/2015/04/15/wifidog%E8%AE%A4%E8%AF%81lighttpd%E4%BD%8D%E9%9B%86%E5%90%88%E4%BD%BF%E7%94%A8.html 整理编辑,转载请注明出处

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 整理编辑,转载请注明出处