(一)向输出缓冲添加数据
int bufferevent_write(struct bufferevent *bufev,
const void *data, size_t size);
int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf);
通过这两个函数,可以向bufferevent的输出缓冲区添加数据。
bufferevent_write_buffer
将remove
掉buf
中的所有数据,并将其添加到输出缓冲区末尾
(二)从输入缓冲区读入数据
例如服务端从网络套接子读入数据时,提供了如下两个接口:
size_t bufferevent_read(struct bufferevent *bufev,void *data,size_t size);
int bufferevent_read_buffer(struct bufferevent*bufev,struct evbuffer *buf);
同样的道理,只不过方向相反,是将bufferevent
中的数据移除。
这样就需要保证data
指针所指向的空间有足够的空间容纳size
大小的数据。
(三)示例
完整代码可以参考gayhub
比如在一个简单的echo服务中可以这么写:
void read_cb(struct bufferevent *bev, void *arg)
{
#define MAX_LINE 256
char line[MAX_LINE+1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) {
line[n] = '\0';
printf("fd=%u, read line: %s\n", fd, line);
bufferevent_write(bev, line, n);
}
}
(四)使用evbuffer
在这之前,可以先简单看一下evbuffer
和bufferevent
的关系
每一个bufferevent
中有一个input buffer
和output buffer
来看这个结构体:
struct bufferevent {
///....
/** An input buffer. Only the bufferevent is allowed to add data to
this buffer, though the user is allowed to drain it. */
struct evbuffer *input;
/** An input buffer. Only the bufferevent is allowed to drain data
from this buffer, though the user is allowed to add it. */
struct evbuffer *output;
///....
};
当我们使用bufferevent
来写数据的时候,我们实际是将数据添加到了output
中,
当我们读取bufferevent
的数据时,我们实际从input
读入。
再看bufferevent
的接口,实际上是对evbuffer
操作的封装,例如:
size_t
bufferevent_read(struct bufferevent *bufev, void *data, size_t size)
{
return (evbuffer_remove(bufev->input, data, size));
}
int
bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf)
{
return (evbuffer_add_buffer(buf, bufev->input));
}
在初始化bufferevent
的时候,会给evbuffer
申请空间:
struct evbuffer *
evbuffer_new(void)
{
struct evbuffer *buffer;
//底层是封装calloc
buffer = mm_calloc(1, sizeof(struct evbuffer));//申请空间
if (buffer == NULL)
return (NULL);
TAILQ_INIT(&buffer->callbacks);
buffer->refcnt = 1;
buffer->last_with_datap = &buffer->first;
return (buffer);
}
搞清楚evbuffer
和bufferevent
的关系后,我们可以使用一些更加灵活的接口:
struct evbuffer *bufferevent_get_input(struct bufferevent*bufev);
struct evbuffer *bufferevent_get_output(struct bufferevent*bufev);
直接获取evbuffer
的指针,源码中很简单的一个return
struct evbuffer *
bufferevent_get_input(struct bufferevent *bufev)
{
return bufev->input;
}
1.添加数据到evbuffer
中
int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);
int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)
//例如:
evbuffer_add(buf,"Hello world",11);
evbuffer_add_printf(buf,"Hello %s","zhangxiao");
2.evbuffer
之间的数据移动
int evbuffer_add_buffer(struct evbuffer*dst,struct evbuffer*src);
int evbuffer_remove_buffer(struct evbuffer *src,struct evbuffer*dst,size_t datlen);
虽然没有看过源码,这部分,根据测试得知,两个函数都实现了类似C++中的移动语义,完成操作后src
中的数据被转移到dst
3.在prepend添加数据*
所谓的头插法,因为底层是以链表的形式构造buffer块,所以这种方式很容易实现:
int evbuffer_prepend(struct evbuffer*buf, const void *data,size_t size);
int evbuffer_prepend_buffer(struct evbuffer *dst,struct evbuffer *src);
比如说,在echo中可以用prepend加一些前缀:
//evbuffer_new返回了新的empty evbuffer
//evbuffer_free删除
void read_cb(struct bufferevent *bev, void *arg)
{
struct evbuffer *input;
struct evbuffer *output;
output=bufferevent_get_output(bev);
input=bufferevent_get_input(bev);
printf("read %d data\r\n",evbuffer_get_length(input));
struct evbuffer * tmp = evbuffer_new();
const char t[]=">> ";
evbuffer_add_buffer(tmp,input);
evbuffer_prepend(tmp,t,strlen(t));
//evbuffer_remove_buffer(input,output,evbuffer_get_length(input));
//
evbuffer_add_buffer(output,tmp);
evbuffer_free(tmp);
}
4.pullup数据
不知道怎么翻译,总之就是可以把evbuffer
中的数据拿出来,由于在底层以链表的形式构造,这个函数可以线性化前size字节的数据。
unsigned char * evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size);
//当size<0 将返回所有数据
//
unsigned char * p = evbuffer_pullup(tmp,evbuffer_get_length(tmp));
printf("%s\r\n",p);
5.回收数据
libevent对于这种行为用了一个词叫”drain”,很贴切,这种行为就类似discard,在muduo中有retrieve,意思差不多吧。
//drain直接丢弃len长度的数据
//remove将数据拷贝到data中
int evbuffer_drain(struct evbuffer *buf, size_t len);
int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen);
6.按行读取数据
http协议中每个请求头都用\r\n
也就是CRLF
来隔开,evbuffer
提供了方便的接口来操作:
struct evbuffer*buf=bufferevent_get_input(buf);
request_line = evbuffer_readln(buf,&len,EVBUFFER_EOL_CRLF);
evbuffer的接口还有很多,这里就不一一列举了,接下来,该学习相关源码了。
(五)参考
1.blog.csdn.net/yusiguyuan
2.libevent深入浅出
3.libevent programming