1. 缓冲区的chunk的分配
在分配chunk时,其实chunk头包含一些附加信息,如chunk块存放数据的空间大小(buffer_len),起始地址(buffer),当前数据尾到起始片的偏移(off)等。chunk的大小算上头部信息,至少是1024个字节。
static struct evbuffer_chain *
evbuffer_chain_new(size_t size) //这个size不包括chunk头大小
{
struct evbuffer_chain *chain;
size_t to_alloc;
if (size > EVBUFFER_CHAIN_MAX - EVBUFFER_CHAIN_SIZE) //如果分配空间的大小加上头部大小大于chunk的最大值就直接返回。
return (NULL);
size += EVBUFFER_CHAIN_SIZE; //加上chunk头大小
/* get the next largest memory that can hold the buffer */
//计算容纳size大小的最小值
if (size < EVBUFFER_CHAIN_MAX / 2) {
to_alloc = MIN_BUFFER_SIZE;
while (to_alloc < size) {
to_alloc <<= 1;
}
} else {
to_alloc = size;
}
/* we get everything in one chunk */
if ((chain = mm_malloc(to_alloc)) == NULL)
return (NULL);
//chunk头部作息初始化
memset(chain, 0, EVBUFFER_CHAIN_SIZE);
chain->buffer_len = to_alloc - EVBUFFER_CHAIN_SIZE;
/* this way we can manipulate the buffer to different addresses,
* which is required for mmap for example.
*/
chain->buffer = EVBUFFER_CHAIN_EXTRA(u_char, chain);
return (chain);
}
2. 列表内容
因为缓冲区是通过evbuffer_chain的next指针连接起来的,这个是链表操作
static void
evbuffer_free_all_chains(struct evbuffer_chain *chain)
{
struct evbuffer_chain *next;
for (; chain; chain = next) {
next = chain->next;
evbuffer_chain_free(chain);
}
}
3. 缓冲区中添加chunk
将chunk添加到evbuffer的尾部时,会释放掉所有空的chunk,在添加chunk时,不会调用回调
static void
evbuffer_chain_insert(struct evbuffer *buf,
struct evbuffer_chain *chain)
{
ASSERT_EVBUFFER_LOCKED(buf);
if (*buf->last_with_datap == NULL) { //表示原来的缓冲区不存在为空,不存在chunk时,将first和last指针赋值为chain
/* There are no chains data on the buffer at all. */
EVUTIL_ASSERT(buf->last_with_datap == &buf->first);
EVUTIL_ASSERT(buf->first == NULL);
buf->first = buf->last = chain;
} else {
//在添加时,会释放尾部所有的空chunk
struct evbuffer_chain **ch = buf->last_with_datap;
/* Find the first victim chain. It might be *last_with_datap */
while ((*ch) && ((*ch)->off != 0 || CHAIN_PINNED(*ch)))
ch = &(*ch)->next;
if (*ch == NULL) {
/* There is no victim; just append this new chain. */
buf->last->next = chain;
if (chain->off) //如果添加的chunk包含有用数据,就修改last_with_datap指针
buf->last_with_datap = &buf->last->next;
} else {
/* Replace all victim chains with this chain. */
EVUTIL_ASSERT(evbuffer_chains_all_empty(*ch));
evbuffer_free_all_chains(*ch);
*ch = chain;
}
buf->last = chain;
}
buf->total_len += chain->off;
}
4. chunk块中flag标志的设置及取消
evbuffer_chain中的flag标志主要是锁定读还是写两种情况,在设置取消操作时比较简单
//设置
_evbuffer_chain_pin(struct evbuffer_chain *chain, unsigned flag)
{
EVUTIL_ASSERT((chain->flags & flag) == 0);
chain->flags |= flag;
}
//取消
void
_evbuffer_chain_unpin(struct evbuffer_chain *chain, unsigned flag)
{
EVUTIL_ASSERT((chain->flags & flag) != 0);
chain->flags &= ~flag;
if (chain->flags & EVBUFFER_DANGLING)
evbuffer_chain_free(chain);
}
5. 缓冲区回调的触发
在写操作使用非阻塞模式时,在调用网络接口可写状态监听时,总是满足的,所以在写操作完成后,会将对应的fd事件从事件循环中删除,而当向缓冲区中写数据时,会将对应的事件添加到event_base中,这时,这个回调就显得比较重要了
void
evbuffer_invoke_callbacks(struct evbuffer *buffer)
{
if (TAILQ_EMPTY(&buffer->callbacks)) {
buffer->n_add_for_cb = buffer->n_del_for_cb = 0;
return;
}
if (buffer->deferred_cbs) {
if (buffer->deferred.queued)
return;
_evbuffer_incref_and_lock(buffer);
if (buffer->parent)
bufferevent_incref(buffer->parent);
EVBUFFER_UNLOCK(buffer);
event_deferred_cb_schedule(buffer->cb_queue, &buffer->deferred); //将缓冲区回调添加加event_base中的回调队列中
}
evbuffer_run_callbacks(buffer, 0); //调用回调函数
}
6. 缓冲区大小的调节
在I/O上有数据可读时,将I/O上的数据读入到缓冲区,需要调节缓冲区的大小来填装从I/O上读到的数据。主要是函数evbuffer_reserve_space来完成。
int
evbuffer_reserve_space(struct evbuffer *buf, ev_ssize_t size,
struct evbuffer_iovec *vec, int n_vecs)
{
struct evbuffer_chain *chain, **chainp;
int n = -1;
EVBUFFER_LOCK(buf);
if (buf->freeze_end) //尾部禁止写,退出
goto done;
if (n_vecs < 1)
goto done;
if (n_vecs == 1) {
if ((chain = evbuffer_expand_singlechain(buf, size)) == NULL)
goto done;
vec[0].iov_base = CHAIN_SPACE_PTR(chain);
vec[0].iov_len = (size_t) CHAIN_SPACE_LEN(chain);
EVUTIL_ASSERT(size<0 || (size_t)vec[0].iov_len >= (size_t)size);
n = 1;
} else {
if (_evbuffer_expand_fast(buf, size, n_vecs)<0)
goto done;
n = _evbuffer_read_setup_vecs(buf, size, vec, n_vecs,
&chainp, 0);
}
done:
EVBUFFER_UNLOCK(buf);
return n;
}
7. 设置读入的缓冲区
在集中读时,设置iovec的缓冲区
int
_evbuffer_read_setup_vecs(struct evbuffer *buf, ev_ssize_t howmuch,
struct evbuffer_iovec *vecs, int n_vecs_avail,
struct evbuffer_chain ***chainp, int exact)
{
struct evbuffer_chain *chain;
struct evbuffer_chain **firstchainp;
size_t so_far;
int i;
ASSERT_EVBUFFER_LOCKED(buf);
if (howmuch < 0)
return -1;
so_far = 0;
/* Let firstchain be the first chain with any space on it */
firstchainp = buf->last_with_datap;
if (CHAIN_SPACE_LEN(*firstchainp) == 0) {
firstchainp = &(*firstchainp)->next;
}
chain = *firstchainp;
for (i = 0; i < n_vecs_avail && so_far < (size_t)howmuch; ++i) {
size_t avail = (size_t) CHAIN_SPACE_LEN(chain);
if (avail > (howmuch - so_far) && exact)
avail = howmuch - so_far;
vecs[i].iov_base = CHAIN_SPACE_PTR(chain);
vecs[i].iov_len = avail;
so_far += avail;
chain = chain->next;
}
*chainp = firstchainp;
return i;
}
8. 读入缓冲区
在I/O上有数据可读时就将数据读入evbuffer中,函数evbuffer_read来完成,同时设置缓冲区的回调(即应用层的处理)
int
evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch)
{
struct evbuffer_chain **chainp;
int n;
int result;
#ifdef USE_IOVEC_IMPL
int nvecs, i, remaining;
#else
struct evbuffer_chain *chain;
unsigned char *p;
#endif
EVBUFFER_LOCK(buf);
if (buf->freeze_end) {
result = -1;
goto done;
}
n = get_n_bytes_readable_on_socket(fd);
if (n <= 0 || n > EVBUFFER_MAX_READ)
n = EVBUFFER_MAX_READ;
if (howmuch < 0 || howmuch > n)
howmuch = n;
#ifdef USE_IOVEC_IMPL
/* Since we can use iovecs, we're willing to use the last
* NUM_READ_IOVEC chains. */
if (_evbuffer_expand_fast(buf, howmuch, NUM_READ_IOVEC) == -1) {
result = -1;
goto done;
} else {
IOV_TYPE vecs[NUM_READ_IOVEC];
#ifdef _EVBUFFER_IOVEC_IS_NATIVE
nvecs = _evbuffer_read_setup_vecs(buf, howmuch, vecs,
NUM_READ_IOVEC, &chainp, 1);
#else
/* We aren't using the native struct iovec. Therefore,
we are on win32. */
struct evbuffer_iovec ev_vecs[NUM_READ_IOVEC];
nvecs = _evbuffer_read_setup_vecs(buf, howmuch, ev_vecs, 2,
&chainp, 1);
for (i=0; i < nvecs; ++i)
WSABUF_FROM_EVBUFFER_IOV(&vecs[i], &ev_vecs[i]);
#endif
#ifdef WIN32
{
DWORD bytesRead;
DWORD flags=0;
if (WSARecv(fd, vecs, nvecs, &bytesRead, &flags, NULL, NULL)) {
/* The read failed. It might be a close,
* or it might be an error. */
if (WSAGetLastError() == WSAECONNABORTED)
n = 0;
else
n = -1;
} else
n = bytesRead;
}
#else
n = readv(fd, vecs, nvecs);
#endif
}
#else /*!USE_IOVEC_IMPL*/
/* If we don't have FIONREAD, we might waste some space here */
/* XXX we _will_ waste some space here if there is any space left
* over on buf->last. */
if ((chain = evbuffer_expand_singlechain(buf, howmuch)) == NULL) {
result = -1;
goto done;
}
/* We can append new data at this point */
p = chain->buffer + chain->misalign + chain->off;
#ifndef WIN32
n = read(fd, p, howmuch);
#else
n = recv(fd, p, howmuch, 0);
#endif
#endif /* USE_IOVEC_IMPL */
if (n == -1) {
result = -1;
goto done;
}
if (n == 0) {
result = 0;
goto done;
}
#ifdef USE_IOVEC_IMPL
remaining = n;
for (i=0; i < nvecs; ++i) {
/* can't overflow, since only mutable chains have
* huge misaligns. */
size_t space = (size_t) CHAIN_SPACE_LEN(*chainp);
/* XXXX This is a kludge that can waste space in perverse
* situations. */
if (space > EVBUFFER_CHAIN_MAX)
space = EVBUFFER_CHAIN_MAX;
if ((ev_ssize_t)space < remaining) {
(*chainp)->off += space;
remaining -= (int)space;
} else {
(*chainp)->off += remaining;
buf->last_with_datap = chainp;
break;
}
chainp = &(*chainp)->next;
}
#else
chain->off += n;
advance_last_with_data(buf);
#endif
buf->total_len += n;
buf->n_add_for_cb += n;
/* Tell someone about changes in this buffer */
evbuffer_invoke_callbacks(buf);
result = n;
done:
EVBUFFER_UNLOCK(buf);
return result;
}
9. 缓冲区中的写操作
将缓冲区中的数据写入I/O,在写时分为集中写与一般的写。在一般写的情况下,如果要写的字节数分散在多个chunk中,这就要将多个chunk中的数据合并到一个chunk中,而将其它的释放,这种处理是通过函数evbuffer_pullup来完成。将缓冲区的数据写到socket是通过evbuffer_write_atmost来完成。在写完后,会将缓冲区中的一部分chunk释放。
int
evbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd,
ev_ssize_t howmuch)
{
int n = -1;
EVBUFFER_LOCK(buffer);
if (buffer->freeze_start) {
goto done;
}
if (howmuch < 0 || (size_t)howmuch > buffer->total_len)
howmuch = buffer->total_len;
if (howmuch > 0) {
#ifdef USE_SENDFILE
struct evbuffer_chain *chain = buffer->first;
if (chain != NULL && (chain->flags & EVBUFFER_SENDFILE))
n = evbuffer_write_sendfile(buffer, fd, howmuch);
else {
#endif
#ifdef USE_IOVEC_IMPL
n = evbuffer_write_iovec(buffer, fd, howmuch); //集中写
#elif defined(WIN32) //一般写
/* XXX(nickm) Don't disable this code until we know if
* the WSARecv code above works. */
void *p = evbuffer_pullup(buffer, howmuch);
EVUTIL_ASSERT(p || !howmuch);
n = send(fd, p, howmuch, 0);
#else
void *p = evbuffer_pullup(buffer, howmuch);
EVUTIL_ASSERT(p || !howmuch);
n = write(fd, p, howmuch); //一般写
#endif
#ifdef USE_SENDFILE
}
#endif
}
if (n > 0)
evbuffer_drain(buffer, n); //释放
done:
EVBUFFER_UNLOCK(buffer);
return (n);
}