Author : Kevin Lynx
前言
可以说对于任何网络库(模块)而言,一个缓冲模块都是必不可少的。缓冲模块主要用于缓冲从网络接收到的数据,以及
用户提交的数据(用于发送)。很多时候,我们还需要将网络模块层(非TCP层)的这些缓冲数据拷贝到用户层,而这些内存拷贝
都会消耗时间。
在这里,我简要分析下libevent的相关代码(event.h和buffer.c)。
结构
关于libevent的缓冲模块,主要就是围绕evbuffer结构体展开。先看下evbuffer的定义:
/**//*event.h*/
struct evbuffer {
u_char *buffer;
u_char *orig_buffer;
size_t misalign;
size_t totallen;
size_t off;
void (*cb)(struct evbuffer *, size_t, size_t, void *);
void *cbarg;
};
libevent的缓冲是一个连续的内存区域,其处理数据的方式(写数据和读数据)更像一个队列操作方式:从后写入,从前
读出。evbuffer分别设置相关指针(一个指标)用于指示读出位置和写入位置。其大致结构如图:
orig_buffer指向由realloc分配的连续内存区域,buffer指向有效数据的内存区域,totallen表示orig_buffer指向的内存
区域的大小,misalign表示buffer相对于orig_buffer的偏移,off表示有效数据的长度。
实际运作
这里我将结合具体的代码分析libevent是如何操作上面那个队列式的evbuffer的,先看一些辅助函数:
evbuffer_drain:
该函数主要操作一些指标,当每次从evbuffer里读取数据时,libevent便会将buffer指针后移,同时增大misalign,减小off,
而该函数正是做这件事的。说白了,该函数就是用于调整缓冲队列的前向指标。
evbuffer_expand:
该函数用于扩充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、...)。
了解了以上两个函数,看其他函数就比较简单了。可以看看具体的读数据和写数据:
evbuffer_add:
该函数用于添加一段用户数据到evbuffer中。很简单,就是先判断是否有足够的空闲内存,如果没有则调用evbuffer_expand
扩充之,然后直接memcpy,更新off指标。
evbuffer_remove:
该函数用于将evbuffer中的数据复制给用户空间(读数据)。简单地将数据memcpy,然后调用evbuffer_drain移动相关指标。
其他
回过头看看libevent的evbuffer其实是非常简单的(跟我那个kl_net里的buffer一样),不知道其他人有没有更优的缓冲管理
方案。evbuffer还提供了两个函数:evbuffer_write和evbuffer_read,用于直接在套接字(其他文件描述符)上写/读数据。
另外,关于libevent,因为官方提供的VC工程文件有问题,很多人在windows下编译不过。金庆曾提供过一种方法。其实主要
就是修改event-config.h文件,修改编译相关配置。这里我也提供一个解决步骤,顺便提供完整包下载:
1. vs2005打开libevent.dsw,转换四个工程(event_test, libevent, signal_test, time_test)
2. 删除libevent项目中所有的文件,重新添加文件,文件列表如下:
buffer.c
evbuffer.c
evdns.c
evdns.h
event.c
event.h
event_tagging.c
event-config.h
event-internal.h
evhttp.h
evrpc.h
evrpc.c
evrpc-internal.h
evsignal.h
evutil.c
evutil.h
http.c
http-internal.h
log.c
log.h
min_heap.h
strlcpy.c
strlcpy-internal.h
tree.h
win32.c
config.h
signal.c
3. 替换event-config.h,使用libevent-iocp中的
4. 项目设置里添加HAVE_CONFIG_H预处理宏
5. 修改win32.c中win32_init函数,加入WSAStartup函数,类似于:
WSADATA wd;
int err;
struct win32op *winop;
size_t size;
if( ( err = WSAStartup( MAKEWORD( 2, 2 ), &wd ) ) != 0 )
event_err( 1, "winsock startup failed : %d", err );
6. 修改win32.c中win32_dealloc函数,在函数末尾加上WSACleanup的调用:
WSACleanup();
6. 至此libevent编译成功;
7. 几个例子程序,只需要加入HAVE_CONFIG_H预处理宏,以及连接ws2_32.lib即可;
(time_test需要修改time-test.c文件,即在包含event.h前包含windows.h)
libevent-1.4.5-stable-vs2005.zip下载