Libevent: evbuffer详解

暴露的接口均在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

主要关注点:
限制读取数据大小:

  1. 在读系统缓冲区时,先使用FIONREAD标志获取内核缓冲区的数据量。再根据该数据量来判断读多少内容,分配多大的空间。
  2. 当内核缓冲区可读数据多于用户指定的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真正需要通过系统调用扩容时,必须不满足:

  1. 剩余空间(totallen - (misalign + off) )大于datlen
  2. 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);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值