前言
可以说对于任何网络库(模块)而言,一个缓冲模块都是必不可少的。缓冲模块主要用于缓冲从网络接收到的数据,以及
用户提交的数据(用于发送)。很多时候,我们还需要将网络模块层(非TCP层)的这些缓冲数据拷贝到用户层,而这些内存拷贝
都会消耗时间。
在这里,我简要分析下libevent的相关代码(event.h和buffer.c)。
结构
关于libevent的缓冲模块,主要就是围绕evbuffer结构体展开。先看下evbuffer的定义:
struct
evbuffer{
//
当前有效缓冲区的内存起始地址
u_char
*
buffer;
//
整个分配(realloc)用来缓冲的内存起始地址
u_char
*
orig_buffer;
//
origin_buffer和buffer之间的字节数
size_t misalign;
//
整个分配用来缓冲的内存字节数
size_t totallen;
//
当前有效缓冲区的长度(字节数)
size_t off;
//
回调函数,当缓冲区有变化的时候会被调用
void
(
*
cb)(
struct
evbuffer
*
, size_t, size_t,
void
*
);//这个部分也造就了evbuffer不同平常的缓冲功能
//
回调函数的参数
void
*
cbarg;
};
libevent的缓冲是一个连续的内存区域,其处理数据的方式(写数据和读数据)更像一个队列操作方式:从后写入,从前
读出。evbuffer分别设置相关指针(一个指标)用于指示读出位置和写入位置。其大致结构如图:
orig_buffer指向由realloc分配的连续内存区域,buffer指向有效数据的内存区域,totallen表示orig_buffer指向的内存
区域的大小,misalign表示buffer相对于orig_buffer的偏移,off表示有效数据的长度。
实际运作
这里我将结合具体的代码分析libevent是如何操作上面那个队列式的evbuffer的,先看一些辅助函数:
void
evbuffer_drain(
struct
evbuffer
*
buf, size_t len)
在这个函数中,最后一个部分是调用了evbuffer中的回调,如果这个buffer已经被修改,那么直接调用这个buffer中的回调!
该函数主要操作一些指标,当每次从evbuffer里读取数据时,libevent便会将buffer指针后移,同时增大misalign,减小off,
而该函数正是做这件事的。说白了,该函数就是用于调整缓冲队列的前向指标。
int
evbuffer_expand(
struct
evbuffer
*
buf, size_t datlen)
该函数用于扩充evbuffer的容量。每次向evbuffer写数据时,都是将数据写到buffer+off后,buffer到buffer+off之间已被
使用,保存的是有效数据,而orig_buffer和buffer之间则是因为读取数据移动指标而形成的无效区域。
evbuffer_expand的扩充策略在于,首先判断如果让出orig_buffer和buffer之间的空闲区域是否可以容纳添加的数据,如果
可以,则移动buffer和buffer+off之间的数据到orig_buffer和orig_buffer+off之间(有可能发生内存重叠,所以这里移动调用的
是memmove),然后把新的数据拷贝到orig_buffer+off之后;如果不可以容纳,那么重新分配更大的空间(realloc),同样会移动
数据。
扩充内存的策略为:确保新的内存区域最小尺寸为256,且以乘以2的方式逐步扩大(256、512、1024、...)。
了解了以上两个函数,看其他函数就比较简单了。可以看看具体的读数据和写数据:
int
evbuffer_add(
struct
evbuffer
*
buf,
const
void
*
data, size_t datlen)
该函数用于添加一段用户数据到evbuffer中。很简单,就是先判断是否有足够的空闲内存,如果没有则调用evbuffer_expand
扩充之,然后直接memcpy,更新off指标。
int
evbuffer_remove(
struct
evbuffer
*
buf,
void
*
data, size_t datlen)
该函数用于将evbuffer中的数据复制给用户空间(读数据)。简单地将数据memcpy,然后调用evbuffer_drain移动相关指标。
struct
evbuffer
*
evbuffer_new(
void
)
动态分配一个struct evbuffer结构,需要调用evbuffer_free释放内存。
void
evbuffer_free(
struct
evbuffer
*
buffer)
释放buffer所占用的内存。
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尾部。
u_char
*
evbuffer_find(
struct
evbuffer
*
buffer,
const
u_char
*
what, size_t len)
查找缓冲区中是否存在指定的字符串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”结尾的字符串。
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
#include
<
stdio.h
>
#include
<
string
.h
>
#include
<
assert.h
>
//
引入libevent头文件
#include
"
event.h
"
int
main(
int
argc,
char
**
argv)
{
struct
evbuffer
*
buff
=
NULL;
char
c, c2[
3
]
=
{
0
};
buff
=
evbuffer_new();
assert(buff
!=
NULL);
evbuffer_add(buff,
"
1
"
,
1
);
evbuffer_add(buff,
"
2
"
,
1
);
evbuffer_add(buff,
"
3
"
,
1
);
evbuffer_add_printf(buff,
"
%d%d
"
,
4
,
5
);
assert(buff
->
off
==
5
);
evbuffer_remove(buff,
&
c,
sizeof
(
char
));
assert(c
==
'
1
'
);
evbuffer_remove(buff,
&
c,
sizeof
(
char
));
assert(c
==
'
2
'
);
evbuffer_remove(buff,
&
c,
sizeof
(
char
));
assert(c
==
'
3
'
);
evbuffer_remove(buff, c2,
2
);
assert(strcmp(c2,
"
45
"
)
==
0
);
assert(buff
->
off
==
0
);
evbuffer_add(buff,
"
test\r\n
"
,
6
);
assert(buff
->
off
==
6
);
char
*
line
=
evbuffer_readline(buff);
assert(strcmp(line,
"
test
"
)
==
0
);
assert(buff
->
off
==
0
);
free(line);
evbuffer_free(buff);
printf(
"
ok\n
"
);
return
0
;
}