libevent源码分析(2)-bufferevent机制

1,066 阅读4分钟

本文是对上篇的延续。

基本结构体分析

在使用时是一个fd对应创建一个bufferevent的结构体

struct bufferevent {
	struct event ev_read;   // 读事件
	struct event ev_write;  // 写事件

	struct evbuffer *input;  // 读取的缓存区
	struct evbuffer *output; // 写入的缓存区

	struct event_watermark wm_read;   // 读取的水位
	struct event_watermark wm_write;  // 写入的水位

	evbuffercb readcb;    // 读回调
	evbuffercb writecb;   // 写回调
	everrorcb errorcb;    // 错误回调
	void *cbarg;          // 回调参数

	int timeout_read; 	// 读超时时间
	int timeout_write;	// 写超时时间

	short enabled;    // 位标志,使能读或写
};
struct evbuffer {
	u_char *buffer;         // 缓冲区
	u_char *orig_buffer;    // 在进行扩容时先暂存原有的缓冲区

	size_t misalign;        // 
	size_t totallen;        // buffer缓冲区的长度
	size_t off;             // buffer缓冲区目前写入的字节数

	void (*cb)(struct evbuffer *, size_t, size_t, void *);  // 水位变化时的回调函数
	void *cbarg;
};

对于水位的理解

struct event_watermark {
	size_t low;    // 低水位
	size_t high;   // 高水位
};

读低水位:当读取缓冲区中的数据量大于等于低水位值后会调用读回调函数
读高水位:当读取缓冲区中的数据量大于等于高水位值后会暂停从套接字中继续读取数据,直到读取缓冲区中的数据量小于高水位值后重新启动从套接字中读取数据
写低水位:当发送缓冲区中的数据量小于等于低水位值后会调用写回调函数
写高水位:暂时未使用
所以,理解bufferevent就是要理解当水位变化的时候应该做那些事情。

读取操作的理解

相关函数

// 对外的接口函数,用于从bufferevent中读取数据
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
// fd可读时的回调函数
static void bufferevent_readcb(int fd, short event, void *arg)
// 监控读取的水位变化的回调
void bufferevent_read_pressure_cb(struct evbuffer *, size_t, size_t, void *);

bufferevent_readcb 函数分析

static void
bufferevent_readcb(int fd, short event, void *arg)
{
	struct bufferevent *bufev = arg;
	int res = 0;
	short what = EVBUFFER_READ;
	size_t len;
    
	res = evbuffer_read(bufev->input, fd, -1);
	if (res == -1) {
		if (errno == EAGAIN || errno == EINTR)
			goto reschedule;
		what |= EVBUFFER_ERROR;
	} else if (res == 0) {
        // 读取文件结束了或者socket断开了
		what |= EVBUFFER_EOF;
	}
    
	if (res <= 0)
		goto error;
    
	bufferevent_add(&bufev->ev_read, bufev->timeout_read);
    
	len = EVBUFFER_LENGTH(bufev->input);
	if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
        // 缓冲区的字节数小于低水位,则啥都不做
		return;
	if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) {
        // 边缘触发,当读取的字节数大于高水位的时候,则暂时删除read事件
		event_del(&bufev->ev_read);
        
        // 且设置监控水位变化的回调函数
        struct evbuffer *buf = bufev->input;
		evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev);
		return;
	}
    
    // 大于低水位且小于高水位则回调readcb
	(*bufev->readcb)(bufev, bufev->cbarg);
	return;

 reschedule:
	bufferevent_add(&bufev->ev_read, bufev->timeout_read);
	return;

 error:
	(*bufev->errorcb)(bufev, what, bufev->cbarg);
}

当读缓冲区的数据量大于高水位的时候,设置了监控水位降低的回调函数时,那什么时候水位会降低呢?当然是用户将数据读走的时候,即用户调用bufferevent_read后。

size_t
bufferevent_read(struct bufferevent *bufev, void *data, size_t size)
{
	struct evbuffer *buf = bufev->input;

	if (buf->off < size)
		size = buf->off;
        
	memcpy(data, buf->buffer, size);

	if (size)
		evbuffer_drain(buf, size);

	return (size);
}
// 继续分析evbuffer_drain
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:
	if (buf->off != oldoff && buf->cb != NULL)
    	// 当水位发生变化的时候调用回调函数,此时的回调函数是bufferevent_read_pressure_cb
		(*buf->cb)(buf, oldoff, buf->off, buf->cbarg);

}

在此要特别注意buf->cb大部分时间都是NULL,只有当读取缓存区的数据量大于高水位的时候,才会设置cb为bufferevent_read_pressure_cb

bufferevent_read_pressure_cb 分析

void
bufferevent_read_pressure_cb(struct evbuffer *buf, size_t old, size_t now, void *arg) {
	struct bufferevent *bufev = arg;
    
	if (bufev->wm_read.high == 0 || now < bufev->wm_read.high) {
    	// 读取缓冲区的数据量已经低于高水位了,则设置cb为NULL
		evbuffer_setcb(buf, NULL, NULL);
		// 且重新添加读事件
		if (bufev->enabled & EV_READ)
			bufferevent_add(&bufev->ev_read, bufev->timeout_read);
	}
}

写入操作的理解

相对于读取操作,写入操作会比较简单写。相关函数有:

// 对外的接口函数,用于向bufferevent中写入数据
int bufferevent_write(struct bufferevent *bufev, void *data, size_t size);
// fd可写时候的回调函数
static void bufferevent_writecb(int fd, short event, void *arg);

需要注意的是只有当写缓存区有数据或者数据还没有写完的时候,才会注册fd的写事件 bufferevent_write分析

int
bufferevent_write(struct bufferevent *bufev, void *data, size_t size)
{
	int res;

	res = evbuffer_add(bufev->output, data, size);

	if (res == -1)
		return (res);
    
	if (size > 0 && (bufev->enabled & EV_WRITE))
        // 写缓冲区不空的时候,注册写事件
        // 此处注册的是一定性的写事件
		bufferevent_add(&bufev->ev_write, bufev->timeout_write);

	return (res);
}

当fd可写的时候回调bufferevent_writecb

static void
bufferevent_writecb(int fd, short event, void *arg)
{
	struct bufferevent *bufev = arg;
	int res = 0;
	short what = EVBUFFER_WRITE;
    
	if (EVBUFFER_LENGTH(bufev->output)) {
        // 写缓冲区不空,则向fd中写入数据
	    res = evbuffer_write(bufev->output, fd);
	    if (res == -1) {
		    if (errno == EAGAIN || errno == EINTR || errno == EINPROGRESS)
			    goto reschedule;
		    what |= EVBUFFER_ERROR;
	    } else if (res == 0) {
		    what |= EVBUFFER_EOF;
	    }
	    if (res <= 0)
		    goto error;
	}
    
	if (EVBUFFER_LENGTH(bufev->output) != 0)
        // 写缓冲区的数据还有剩余,则继续注册写事件
		bufferevent_add(&bufev->ev_write, bufev->timeout_write);
        
	if (EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
        // 写缓冲区的数据量已经低于写低水位了,则主动通知上层
		(*bufev->writecb)(bufev, bufev->cbarg);
	return;
    
 reschedule:
	if (EVBUFFER_LENGTH(bufev->output) != 0)
		bufferevent_add(&bufev->ev_write, bufev->timeout_write);
	return;
    
 error:
	(*bufev->errorcb)(bufev, what, bufev->cbarg);
}