加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

Libevent:evbuffer缓冲区分析

(2012-02-29 10:55:32)
标签:

杂谈

分类: 各种库

Libeventevbuffer缓冲区分析

 

    在开发网络程序的时候,特别是TCP基于字节流的数据,需要从字节流数据中解析出自己的通讯协议,比如读一行数据:我们每次调用read函数的时候指定了我们期望读多少数据,但这个数据并不一定正好能读到‘\n’,这个时候就需要借助一个缓冲区来保存多余的数据,以便于和下一次读到的数据合并在一起继续分析。Evbuffer就是提供了这样一个缓冲区。

 

关于libevent的缓冲模块,主要就是围绕evbuffer结构体展开。先看下evbuffer的定义:

struct evbuffer{
  // 当前有效缓冲区的内存起始地址
 u_char *buffer; 
  // 整个分配(realloc)用来缓冲的内存起始地址
  u_char *orig_buffer; 
  // origin_bufferbuffer之间的字节数
 size_t misalign; 
  // 整个分配用来缓冲的内存字节数
 size_t totallen; 
  // 当前有效缓冲区的长度(字节数)
 size_t off; 
  //回到函数,当缓冲区有变化的时候会被调用
 void (*cb)(struct evbuffer *, size_t, size_t, void *);
  //回调函数的参数
 void *cbarg; 
};

 

下面简单介绍一下evbuffer提供的 API,我所使用的libevent版本是1.4.10-stable:

struct evbuffer* evbuffer_new(void)

动态分配一个struct evbuffer结构,需要调用evbuffer_free释放内存。

 

void evbuffer_free(struct evbuffer *buffer)

释放buffer所占用的内存。

 

int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen)

data追加到evbuffer

先判断缓冲区的大小是否可以容纳的下datlen大小, 如果不能,则使用evbuffer_expand扩充容量。然后将data追加到evbuffer->buffer + evbuffer->off后。 并且更新有效缓冲区长度off

如果datlen > 0, 并且设置了回调函数,则调用回调函数
返回值:成功返回0,失败返回-1

 

int evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf)

移动数据从一个evbuffer到另一个evbuffer

实际上还是调用了evbuffer_add添加数据到outbuf中。但会清除inbuf中的数据。

返回值:成功返回0,失败返回-1

 

int evbuffer_add_printf( struct evbuffer *, const char* fmt, ...)

添加一个格式化的字符串到evbuffer尾部。

 

int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap)

添加一个va_list格式的字符串到evbuffer尾部。

void evbuffer_drain(struct evbuffer *buf, size_t len)

evbuffer起始位置删除指定长度len字节数据

如果len的长度大于等于缓冲区的off的长度,则表明缓冲区的数据都被清空。 

如果缓冲区发生变化,并且设置了回调函数,则调用回调函数。

 

int evbuffer_expand(struct evbuffer *buf, size_t datlen)

该函数用于扩充evbuffer的容量。每次向evbuffer写数据时,都是将数据写到buffer+off后,bufferbuffer+off之间已被使用,保存的是有效数据,而orig_bufferbuffer之间则是因为读取数据移动指针而形成的无效区域。

 

evbuffer_expand的扩充策略在于:

1,计算出加上datlen后需要的缓冲区大小need

2, 判断当前缓冲区的长度是否可以容纳的下need大小,如果可以则不需要改变缓冲区的大小,直接返回。

3,如果当前缓冲哦你去的长度容纳不下need大小,则判断orig_bufferbuffer之间的空闲区域是否可以容纳添加的数据,如果

可以,则移动bufferbuffer+off之间的数据到orig_bufferorig_buffer+off之间,然后把新的数据拷贝到orig_buffer+off之后;

4,如果misalign不可以容纳,那么重新分配更大的空间(realloc),同样会移动数据。

 

扩充内存的策略为:确保新的内存区域最小尺寸为256,且以乘以2的方式逐步扩大(2565121024...)

返回值:成功返回0,失败返回-1

 

u_char *evbuffer_find(struct evbuffer *buffer, const u_char *what, size_tlen)

查找缓冲区中是否存在指定的字符串what

注意这里使用的是u_char类型,说明有可能查找的数据不是以’\0’结尾

如果存在返回指向字符串what的指针,没有则返回NULL

 

int evbuffer_read(struct evbuffer *buf, int fd, int howmuch)

调用read/recv函数,从文件描述符fd上读取数据到evbuffer中。如果缓冲区不够,调用evbuffer_expand扩充缓冲区。

int evbuffer_write(struct evbuffer *buffer, int fd)

把缓冲区中的数据,调用send/write函数写入文件描述符fd上, 如果send/write函数写入的字节数大于0,则调用evbuffer_drain删除已写的数据。

char *evbuffer_readline(struct evbuffer *buffer)

读取数据以"\r\n","\n\r", "\r" 或者 "\n"结尾。

返回动态分配内存,需要调用者自己使用free来释放内存。返回一个以\0结尾的字符串。

int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen)

evbuffer缓冲区中的数据读到data中, 最多读datlen字节。如果缓冲区里的数据小于datlen,则拷贝缓冲区中全部数据。

然后调用evbuffer_drain删除已读数据。

 

void evbuffer_setcb(struct evbuffer *buffer,
    void (*cb)(struct evbuffer *, size_t, size_t, void *),
    void *cbarg)

设置回调函数。当缓冲区中发生变化时, 调用设置的回调函数。

 

Evbuffer提供的API已经全部介绍完毕,接下来我们通过一个实例进一步学习如何使用evbuffer, 想要使用evbuffer,系统里必须已经安装了libevent

例子代码如下:evbuffer-test.c

 

      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <assert.h>
      4
      5 //引入libevent头文件
      6 #include "event.h"
      7
      8 int main(int argc, char** argv)
      9 {
     10     struct evbuffer* buff = NULL;
     11     char c, c2[3] = {0};
     12
     13     buff = evbuffer_new();
     14     assert(buff != NULL);
     15
     16     evbuffer_add(buff, "1", 1);
     17     evbuffer_add(buff, "2", 1);
     18     evbuffer_add(buff, "3", 1);
     19     evbuffer_add_printf(buff, "%d%d", 4, 5);
     20     assert(buff->off == 5);
     21
     22     evbuffer_remove(buff, &c, sizeof(char));
     23     assert(c == '1');
     24     evbuffer_remove(buff, &c, sizeof(char));
     25     assert(c == '2');
     26     evbuffer_remove(buff, &c, sizeof(char));
     27     assert(c == '3');
     28     evbuffer_remove(buff, c2, 2);
     29     assert(strcmp(c2, "45") == 0);
     30
     31     assert(buff->off == 0);
     32
     33     evbuffer_add(buff, "test\r\n", 6);
     34     assert(buff->off == 6);
     35
     36     char* line = evbuffer_readline(buff);
     37     assert(strcmp(line, "test") ==0);
     38     assert(buff->off == 0);
     39     free(line);
     40
     41     evbuffer_free(buff);
     42
     43     printf("ok\n");
     44
     45     return 0;
     46 }

 

其中使用assert断言来测试是否达到我们的预期,如果assert失败,程序会被中断,并且输出类似的错误信息:

[zhanghua@localhost test]$ ./evbuffer-test evbuffer-test: evbuffer-test.c:31: main: Assertion `buff->off == 2' failed. Aborted (core dumped)

 

如果程序所有assert 全部达到我们的预期,程序会输出”OK”

我们使用如下指令编译程序:

gcc -I/home/zhanghua/project/athena/third_party/Linux/2.6-kernal/libevent-1.4.10-stable/include/ evbuffer-test.c -o evbuffer-test -L /home/zhanghua/project/athena/third_party/Linux/2.6-kernal/libevent-1.4.10-stable/lib –levent

 

其中需要说明的是 –I –L后面的路径是libevent 头文件和动态库所在的路径,这个需要和自己实际工作环境做调整。

-levent 是我们需要链接libevent 的动态库。

执行程序:

[zhanghua@localhost test]$ ./evbuffer-test 

 ok

 

    到这里evbuffer就简单的介绍完了,已经感觉到evbuffer的实用和强大了吧,在这里感谢开源者们提供众多优秀的代码供我们学习和使用。

 

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有