暴露的接口均在event.h
部分实现位于buffer.c
libevent: event.h File Reference
结构体定义
struct evbuffer {
u_char *buffer; // 缓冲区有效数据起始地址
u_char *orig_buffer;// 缓冲区起始地址
size_t misalign; // 被删除的字节数
size_t totallen; // 容量
size_t off; // 指向缓冲区空白处
/* 与 bufferevent 相关的evbuffer callback */
void (*cb)(struct evbuffer *, size_t, size_t, void *);
void *cbarg;
};
载入数据(到buffer中)
用户涉及写入\读取数据均可能回调buffer的callback,以告知某处buffer数据发生变化。
evbuffer_add
从用户的缓冲区中通过memcpy读取数据到buffer
evbuffer_read
功能: 从fd中读取数据到buffer
主要关注点:
限制读取数据大小:
- 在读系统缓冲区时,先使用
FIONREAD
标志获取内核缓冲区的数据量。再根据该数据量来判断读多少内容,分配多大的空间。 - 当内核缓冲区可读数据多于用户指定的much和libevent默认可读最大size(
EVBUFFER_MAX_READ
)时,totalall发挥什么作用?
以当前缓冲区总容量x4为界,超出则只读总容量x4,未超出就限制为EVBUFFER_MAX_READ
, 此处的边界总容量x4与evbuffer_expand
涉及计算扩容大小部分息息相关。
int
evbuffer_read(struct evbuffer *buf, int fd, int howmuch)
{
u_char *p;
size_t oldoff = buf->off;
int n = EVBUFFER_MAX_READ;
#if defined(FIONREAD)
#ifdef WIN32
long lng = n;
if (ioctlsocket(fd, FIONREAD, &lng) == -1 || (n=lng) <= 0) {
#else
if (ioctl(fd, FIONREAD, &n) == -1 || n <= 0) {
#endif
n = EVBUFFER_MAX_READ;
} else if (n > EVBUFFER_MAX_READ && n > howmuch) {
/*
* It's possible that a lot of data is available for
* reading. We do not want to exhaust resources
* before the reader has a chance to do something
* about it. If the reader does not tell us how much
* data we should read, we artifically limit it.
*/
if ((size_t)n > buf->totallen << 2)
n = buf->totallen << 2;
if (n < EVBUFFER_MAX_READ)
n = EVBUFFER_MAX_READ;
}
#endif
if (howmuch < 0 || howmuch > n)
howmuch = n;
/* If we don't have FIONREAD, we might waste some space here */
if (evbuffer_expand(buf, howmuch) == -1)
return (-1);
/* We can append new data at this point */
p = buf->buffer + buf->off;
#ifndef WIN32
n = read(fd, p, howmuch);
#else
n = recv(fd, p, howmuch, 0);
#endif
if (n == -1)
return (-1);
if (n == 0)
return (0);
buf->off += n;
/* Tell someone about changes in this buffer */
if (buf->off != oldoff && buf->cb != NULL)
(*buf->cb)(buf, oldoff, buf->off, buf->cbarg);
return (n);
}
evbuffer_readln
从evbuffer中读取一行。具体行标识由指定的CRLF匹配规则决定。
支持四种匹配规则。
规定前闭后开区间, 方便计算取多少数据,丢弃多少数据:
start_of_eol总是指向CRLF的第一个字符。
end_of_eol总是指向CRLF的后一个字符。
evbuffer_readline
与evbuffer_readln 有所不同,它支持读写的行由'\r\n', '\n\r' or '\r' or '\n'
这四种标识决定,比readln要宽松得多。
evbuffer_add_buffer
将一个buffer的数据移动到另一个buffer
实现没什么好说的。值得关注的是优化方面,如果buffer是空的,直接进行一次浅拷贝就好了。
只有空的outbuf,才会通知关注outbuf的事件数据发生改变。
int
evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf)
{
int res;
/* Short cut for better performance */
if (outbuf->off == 0) {
struct evbuffer tmp;
size_t oldoff = inbuf->off;
/* Swap them directly */
SWAP(&tmp, outbuf);
SWAP(outbuf, inbuf);
SWAP(inbuf, &tmp);
/*
* Optimization comes with a price; we need to notify the
* buffer if necessary of the changes. oldoff is the amount
* of data that we transfered from inbuf to outbuf
*/
if (inbuf->off != oldoff && inbuf->cb != NULL)
(*inbuf->cb)(inbuf, oldoff, inbuf->off, inbuf->cbarg);
if (oldoff && outbuf->cb != NULL)
(*outbuf->cb)(outbuf, 0, oldoff, outbuf->cbarg);
return (0);
}
res = evbuffer_add(outbuf, inbuf->buffer, inbuf->off);
if (res == 0) {
/* We drain the input buffer on success */
evbuffer_drain(inbuf, inbuf->off);
}
return (res);
}
注意:
evbuffer_drain
内部也会调用回调告知数据发生改变。
扩容 evbuffer_expand
evbuffer真正需要通过系统调用扩容时,必须不满足:
- 剩余空间(totallen - (misalign + off) )大于datlen
- buffer内存对齐后(将有效数据覆盖被删除的数据) 剩余空间大于datlen
int
evbuffer_expand(struct evbuffer *buf, size_t datlen)
{
size_t used = buf->misalign + buf->off;
size_t need;
assert(buf->totallen >= used);
/* If we can fit all the data, then we don't have to do anything */
if (buf->totallen - used >= datlen)
return (0);
/* If we would need to overflow to fit this much data, we can't
* do anything. */
if (datlen > SIZE_MAX - buf->off)
return (-1);
/*
* If the misalignment fulfills our data needs, we just force an
* alignment to happen. Afterwards, we have enough space.
*/
if (buf->totallen - buf->off >= datlen) {
evbuffer_align(buf);
} else {
void *newbuf;
size_t length = buf->totallen;
// need = old_datlen + new_datlen
// (不考虑misalign,因为会先对齐再realloc)
size_t need = buf->off + datlen;
// 计算扩容大小
if (length < 256)
length = 256;
if (need < SIZE_MAX / 2) {
while (length < need) {
length <<= 1;
}
} else {
length = need;
}
// 先对齐再扩容,方便realloc
if (buf->orig_buffer != buf->buffer)
evbuffer_align(buf);
if ((newbuf = realloc(buf->buffer, length)) == NULL)
return (-1);
buf->orig_buffer = buf->buffer = newbuf;
buf->totallen = length;
}
return (0);
}
扩容内存限制
size_t length = buf->totallen;
// need = old_datlen + new_datlen
size_t need = buf->off + datlen;
// 尽可能对齐
if (length < 256)
length = 256;
/*以SIZE_MAX / 2为界, 少于则总是原容量*2倍拓展
多于则直接赋值,防止申请内存迅速越过SIZE_MAX(uint64_t)
*/
if (need < SIZE_MAX / 2) {
while (length < need) {
length <<= 1;
}
} else {
length = need;
}
内存对齐 evbuffer_align
只有在evbuffer_expand
时才会涉及evbuffer_align
的调用。
不了解缓冲区各字段的分布建议先看看
evbuffer_drain
的实现 或者 看看下面举的memmove
例子。
static void
evbuffer_align(struct evbuffer *buf)
{
memmove(buf->orig_buffer, buf->buffer, buf->off);
buf->buffer = buf->orig_buffer;
buf->misalign = 0;
}
注意到上述内容使用了memmove
,但其实evbuffer只是为了将有效数据部分覆盖掉失效数据部分,因此此处使用memcpy也没有什么问题,甚至速度更快。
我来列一个直观一些的说明:
evbuffer_align
就像下面所示
我特别标识了字符数组buf的内容。
‘x’字符用于标识失效数据部分。 即misalign字段的部分
‘1’~’5‘字符标识有效数据部分。 即buf ~ buf + off 标识的部分
‘0’ 字符标识缓冲区中未使用部分。即buf + off ~ buf + totallen标识的位置。
char orig_buf[] = "xxxxx1234500000";
int off = 5;
int misalign = 5;
char* buf = orig_buf + misalign;
int totallen = sizeof(buf)/sizeof(char);
memmove (orig_buf, buf, off);
puts (orig_buf); // 123451234500000
就算是换成memcpy
又有什么不同呢?
读取数据从buffer到外部(用户)
evbuffer_remove
通过memcpy将buffer中数据输出到用户缓冲区, 与evbuffer_add
相衬。
evbuffer_write
实现没什么特别,只是尝试将buffer数据都写入到fd中。
int
evbuffer_write(struct evbuffer *buffer, int fd)
{
int n;
#ifndef WIN32
n = write(fd, buffer->buffer, buffer->off);
#else
n = send(fd, buffer->buffer, buffer->off, 0);
#endif
if (n == -1)
return (-1);
if (n == 0)
return (0);
evbuffer_drain(buffer, n);
return (n);
}
格式化输入
evbuffer_add_printf 和 evbuffer_add_vprintf
删除数据: evbuffer_drain
为了避免缓冲区数据的搬迁,evbuffer通过misalign
字段记录“不对齐”的范围。
直接将头指针往后移,而misalign
相当于记录被删除区域的大小。那么此时,orig_buffer
字段保留了原缓冲区的最初始的地址。
void
evbuffer_drain(struct evbuffer *buf, size_t len)
{
size_t oldoff = buf->off;
/*删除全部数据,也代表着自动对齐了*/
if (len >= buf->off) {
buf->off = 0;
buf->buffer = buf->orig_buffer;
buf->misalign = 0;
goto done;
}
buf->buffer += len;
buf->misalign += len;
buf->off -= len;
done:
/* Tell someone about changes in this buffer */
if (buf->off != oldoff && buf->cb != NULL)
(*buf->cb)(buf, oldoff, buf->off, buf->cbarg);
}
evbuffer_find
在buffer中查找子串, 使用的算法是朴素字符串匹配
u_char *
evbuffer_find(struct evbuffer *buffer, const u_char *what, size_t len)
{
u_char *search = buffer->buffer, *end = search + buffer->off;
u_char *p;
while (search < end &&
(p = memchr(search, *what, end - search)) != NULL) {
if (p + len > end)
break;
if (memcmp(p, what, len) == 0)
return (p);
search = p + 1;
}
return (NULL);
}