Libevent源码分析(五)--- evbuffer的基本操作
来源:互联网 发布:房屋装修效果图软件 编辑:程序博客网 时间:2024/05/20 14:18
之前几节分析了libevent底层的结构和运行机制,接下来的几节将会分析Bufferevents,Bufferevents在event的基础上加入了数据缓存逻辑,使得事件和数据结合在一起。libevent的bufferevent有六种类型,分别是:bufferevent_async,bufferevent_filter,bufferevent_openssl,bufferevent_pair,bufferevent_ratelim和bufferevent_sock。其中最常用的是bufferevent_sock类型。
evbuffer
每一个bufferevent 都有两个evbuffer 分被作为读写缓存,evbuffer 处理真正的数据,下面是evbuffer的定义:
struct evbuffer { /** The first chain in this buffer's linked list of chains. */ struct evbuffer_chain *first; /** The last chain in this buffer's linked list of chains. */ struct evbuffer_chain *last; /** Pointer to the next pointer pointing at the 'last_with_data' chain. * * To unpack: * * The last_with_data chain is the last chain that has any data in it. * If all chains in the buffer are empty, it is the first chain. * If the buffer has no chains, it is NULL. * * The last_with_datap pointer points at _whatever 'next' pointer_ * points at the last_with_datap chain. If the last_with_data chain * is the first chain, or it is NULL, then the last_with_datap pointer * is &buf->first. */ struct evbuffer_chain **last_with_datap; /** Total amount of bytes stored in all chains.*/ size_t total_len; /** Number of bytes we have added to the buffer since we last tried to * invoke callbacks. */ size_t n_add_for_cb; /** Number of bytes we have removed from the buffer since we last * tried to invoke callbacks. */ size_t n_del_for_cb;#ifndef _EVENT_DISABLE_THREAD_SUPPORT /** A lock used to mediate access to this buffer. */ void *lock;#endif /** True iff we should free the lock field when we free this * evbuffer. */ unsigned own_lock : 1; /** True iff we should not allow changes to the front of the buffer * (drains or prepends). */ unsigned freeze_start : 1; /** True iff we should not allow changes to the end of the buffer * (appends) */ unsigned freeze_end : 1; /** True iff this evbuffer's callbacks are not invoked immediately * upon a change in the buffer, but instead are deferred to be invoked * from the event_base's loop. Useful for preventing enormous stack * overflows when we have mutually recursive callbacks, and for * serializing callbacks in a single thread. */ unsigned deferred_cbs : 1;#ifdef WIN32 /** True iff this buffer is set up for overlapped IO. */ unsigned is_overlapped : 1;#endif /** Zero or more EVBUFFER_FLAG_* bits */ ev_uint32_t flags; /** Used to implement deferred callbacks. */ struct deferred_cb_queue *cb_queue; /** A reference count on this evbuffer. When the reference count * reaches 0, the buffer is destroyed. Manipulated with * evbuffer_incref and evbuffer_decref_and_unlock and * evbuffer_free. */ int refcnt; /** A deferred_cb handle to make all of this buffer's callbacks * invoked from the event loop. */ struct deferred_cb deferred; /** A doubly-linked-list of callback functions */ TAILQ_HEAD(evbuffer_cb_queue, evbuffer_cb_entry) callbacks; /** The parent bufferevent object this evbuffer belongs to. * NULL if the evbuffer stands alone. */ struct bufferevent *parent;};
evbuffer包含一个evbuffer_chain链表,数据都是存储在各个evbuffer_chain中,evbuffer有三个指针:first,last和last_with_datap,分别指向evbuffer_chain列表的第一个元素,最后一个元素,以及有数据的最后一个元素;total_len表示数据的总长度,n_add_for_cb和n_del_for_cb记录回调之间数据的变化;lock和own_lock用于锁相关;freeze_start和freeze_end用于标记首尾evbuffer_chain是否锁定;deferred_cbs用于标记是否使用延迟回调;is_overlapped标记是否使用iocp;flag用于设置状态,目前可以设置的值只有0和EVBUFFER_FLAG_DRAINS_TO_FD,后者标记是否用于bufferevent_sock类型。cb_queue指向event_base中的defer_queue,deferred作为一个延迟回调会加入到的defer_queue中,deferred触发后会调用所有的callbacks,最后parent变量设置evbuffer对应的bufferEvent。
下面是evbuffer_chain的定义:
struct evbuffer_chain { /** points to next buffer in the chain */ struct evbuffer_chain *next; /** total allocation available in the buffer field. */ size_t buffer_len; /** unused space at the beginning of buffer or an offset into a * file for sendfile buffers. */ ev_misalign_t misalign; /** Offset into buffer + misalign at which to start writing. * In other words, the total number of bytes actually stored * in buffer. */ size_t off; /** Set if special handling is required for this chain */ unsigned flags;#define EVBUFFER_MMAP 0x0001 /**< memory in buffer is mmaped */#define EVBUFFER_SENDFILE 0x0002 /**< a chain used for sendfile */#define EVBUFFER_REFERENCE 0x0004 /**< a chain with a mem reference */#define EVBUFFER_IMMUTABLE 0x0008 /**< read-only chain */ /** a chain that mustn't be reallocated or freed, or have its contents * memmoved, until the chain is un-pinned. */#define EVBUFFER_MEM_PINNED_R 0x0010#define EVBUFFER_MEM_PINNED_W 0x0020#define EVBUFFER_MEM_PINNED_ANY (EVBUFFER_MEM_PINNED_R|EVBUFFER_MEM_PINNED_W) /** a chain that should be freed, but can't be freed until it is * un-pinned. */#define EVBUFFER_DANGLING 0x0040 /** Usually points to the read-write memory belonging to this * buffer allocated as part of the evbuffer_chain allocation. * For mmap, this can be a read-only buffer and * EVBUFFER_IMMUTABLE will be set in flags. For sendfile, it * may point to NULL. */ unsigned char *buffer;};
evbuffer_chain比较简单,next用于链表,buffer_len代表evbuffer_chain的总长度,misalign标记偏移,off记录当前数据的长度,flags是一个状态标记位,接下来的一组宏定义给出了flags可以设置的状态,最后的buffer指向了数据缓存。
evbuffer的相关操作非常多,初次接触可能比较乱,下面是他比较重要的几个方法:
evbuffer_chain_new & evbuffer_chain_free
static struct evbuffer_chain * evbuffer_chain_new(size_t size){ struct evbuffer_chain *chain; size_t to_alloc; size += EVBUFFER_CHAIN_SIZE; /* get the next largest memory that can hold the buffer */ to_alloc = MIN_BUFFER_SIZE; while (to_alloc < size) to_alloc <<= 1; /* we get everything in one chunk */ if ((chain = mm_malloc(to_alloc)) == NULL) return (NULL); 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);}static inline void evbuffer_chain_free(struct evbuffer_chain *chain){ if (CHAIN_PINNED(chain)) { chain->flags |= EVBUFFER_DANGLING; return; } if (chain->flags & (EVBUFFER_MMAP|EVBUFFER_SENDFILE| EVBUFFER_REFERENCE)) { if (chain->flags & EVBUFFER_REFERENCE) { struct evbuffer_chain_reference *info = EVBUFFER_CHAIN_EXTRA( struct evbuffer_chain_reference, chain); if (info->cleanupfn) (*info->cleanupfn)(chain->buffer, chain->buffer_len, info->extra); }#ifdef _EVENT_HAVE_MMAP if (chain->flags & EVBUFFER_MMAP) { struct evbuffer_chain_fd *info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain); if (munmap(chain->buffer, chain->buffer_len) == -1) event_warn("%s: munmap failed", __func__); if (close(info->fd) == -1) event_warn("%s: close(%d) failed", __func__, info->fd); }#endif#ifdef USE_SENDFILE if (chain->flags & EVBUFFER_SENDFILE) { struct evbuffer_chain_fd *info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain); if (close(info->fd) == -1) event_warn("%s: close(%d) failed", __func__, info->fd); }#endif } mm_free(chain);}
这里注意的是调用free方法时flag可能有三个状态,EVBUFFER_REFERENCE代表这是一个引用类型的evbuffer_chain,这个标记在evbuffer_add_reference中设置:
int evbuffer_add_reference(struct evbuffer *outbuf, const void *data, size_t datlen, evbuffer_ref_cleanup_cb cleanupfn, void *extra){ struct evbuffer_chain *chain; struct evbuffer_chain_reference *info; int result = -1; // evbuffer_chain_new方法申请了evbuffer_chain_reference大小的空间,真正的数据是传入的data chain = evbuffer_chain_new(sizeof(struct evbuffer_chain_reference)); if (!chain) return (-1); chain->flags |= EVBUFFER_REFERENCE | EVBUFFER_IMMUTABLE; chain->buffer = (u_char *)data; chain->buffer_len = datlen; chain->off = datlen; info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_reference, chain); info->cleanupfn = cleanupfn; info->extra = extra; EVBUFFER_LOCK(outbuf); if (outbuf->freeze_end) { /* don't call chain_free; we do not want to actually invoke * the cleanup function */ mm_free(chain); goto done; } evbuffer_chain_insert(outbuf, chain); outbuf->n_add_for_cb += datlen; evbuffer_invoke_callbacks(outbuf); result = 0;done: EVBUFFER_UNLOCK(outbuf); return result;}
当使用evbuffer_add_reference时,evbuffer_chain指向一块已经存在的内存,evbuffer_chain只需要申请一个evbuffer_chain_reference大小的变量涌来保存cleanupfn和extra参数即可。EVBUFFER_MMAP主要用与mmap操作,EVBUFFER_SENDFILE则直接用于发送文件,这两个参数后面还会详细分析。
evbuffer_add & evbuffer_remove
evbuffer作为读写缓存,它的操作基本都是成对出现的,比如下面的一组add和remove:
int evbuffer_add(struct evbuffer *buf, const void *data_in, size_t datlen){ struct evbuffer_chain *chain, *tmp; const unsigned char *data = data_in; size_t remain, to_alloc; int result = -1; EVBUFFER_LOCK(buf); if (buf->freeze_end) { goto done; } chain = buf->last; /* If there are no chains allocated for this buffer, allocate one * big enough to hold all the data. */ if (chain == NULL) { chain = evbuffer_chain_new(datlen); if (!chain) goto done; evbuffer_chain_insert(buf, chain); } if ((chain->flags & EVBUFFER_IMMUTABLE) == 0) { remain = (size_t)(chain->buffer_len - chain->misalign - chain->off); if (remain >= datlen) { /* there's enough space to hold all the data in the * current last chain */ memcpy(chain->buffer + chain->misalign + chain->off, data, datlen); chain->off += datlen; buf->total_len += datlen; buf->n_add_for_cb += datlen; goto out; } else if (!CHAIN_PINNED(chain) && evbuffer_chain_should_realign(chain, datlen)) { /* we can fit the data into the misalignment */ evbuffer_chain_align(chain); memcpy(chain->buffer + chain->off, data, datlen); chain->off += datlen; buf->total_len += datlen; buf->n_add_for_cb += datlen; goto out; } } else { /* we cannot write any data to the last chain */ remain = 0; } /* we need to add another chain */ to_alloc = chain->buffer_len; if (to_alloc <= EVBUFFER_CHAIN_MAX_AUTO_SIZE/2) to_alloc <<= 1; if (datlen > to_alloc) to_alloc = datlen; tmp = evbuffer_chain_new(to_alloc); if (tmp == NULL) goto done; if (remain) { memcpy(chain->buffer + chain->misalign + chain->off, data, remain); chain->off += remain; buf->total_len += remain; buf->n_add_for_cb += remain; } data += remain; datlen -= remain; memcpy(tmp->buffer, data, datlen); tmp->off = datlen; evbuffer_chain_insert(buf, tmp); buf->n_add_for_cb += datlen;out: evbuffer_invoke_callbacks(buf); result = 0;done: EVBUFFER_UNLOCK(buf); return result;}/** Helper: realigns the memory in chain->buffer so that misalign is 0. */static voidevbuffer_chain_align(struct evbuffer_chain *chain){ EVUTIL_ASSERT(!(chain->flags & EVBUFFER_IMMUTABLE)); EVUTIL_ASSERT(!(chain->flags & EVBUFFER_MEM_PINNED_ANY)); memmove(chain->buffer, chain->buffer + chain->misalign, chain->off); chain->misalign = 0;}#define MAX_TO_COPY_IN_EXPAND 4096#define MAX_TO_REALIGN_IN_EXPAND 2048/** Helper: return true iff we should realign chain to fit datalen bytes of data in it. */static intevbuffer_chain_should_realign(struct evbuffer_chain *chain, size_t datlen){ return chain->buffer_len - chain->off >= datlen && (chain->off < chain->buffer_len / 2) && (chain->off <= MAX_TO_REALIGN_IN_EXPAND);}
evbuffer_add比较简单,需要注意的是调用evbuffer_add要确保last指向的即使最后一块有数据的chain,另外 evbuffer非常注重效率和空间利用率的平衡,evbuffer_chain_should_realign就是判断当前的chain是否需要移动的,只有满足条件移动数据才有意义。evbuffer_chain_should_realign的判定条件移动后的容量足够datlen长度,并且当前数据长度不超过总长度的1/2并且小于MAX_TO_REALIGN_IN_EXPAND。下面是remove函数:
/* Reads data from an event buffer and drains the bytes read */intevbuffer_remove(struct evbuffer *buf, void *data_out, size_t datlen){ ev_ssize_t n; EVBUFFER_LOCK(buf); n = evbuffer_copyout(buf, data_out, datlen); if (n > 0) { if (evbuffer_drain(buf, n)<0) n = -1; } EVBUFFER_UNLOCK(buf); return (int)n;}ev_ssize_tevbuffer_copyout(struct evbuffer *buf, void *data_out, size_t datlen){ /*XXX fails badly on sendfile case. */ struct evbuffer_chain *chain; char *data = data_out; size_t nread; ev_ssize_t result = 0; EVBUFFER_LOCK(buf); chain = buf->first; if (datlen >= buf->total_len) datlen = buf->total_len; if (datlen == 0) goto done; if (buf->freeze_start) { result = -1; goto done; } nread = datlen; while (datlen && datlen >= chain->off) { memcpy(data, chain->buffer + chain->misalign, chain->off); data += chain->off; datlen -= chain->off; chain = chain->next; EVUTIL_ASSERT(chain || datlen==0); } if (datlen) { EVUTIL_ASSERT(chain); memcpy(data, chain->buffer + chain->misalign, datlen); } result = nread;done: EVBUFFER_UNLOCK(buf); return result;}
evbuffer_remove调用两个函数,evbuffer_copyout和evbuffer_drain,前者主要是从evbuffer中拷贝出datlen大小的数据到data_out中,后者从evbuffer中移除指定大小的数据。evbuffer_drain时evbuffer中比较常用的函数之一:
int evbuffer_drain(struct evbuffer *buf, size_t len){ struct evbuffer_chain *chain, *next; size_t remaining, old_len; int result = 0; EVBUFFER_LOCK(buf); old_len = buf->total_len; if (old_len == 0) goto done; if (buf->freeze_start) { result = -1; goto done; } if (len >= old_len && !HAS_PINNED_R(buf)) { len = old_len; for (chain = buf->first; chain != NULL; chain = next) { next = chain->next; evbuffer_chain_free(chain); } ZERO_CHAIN(buf); } else { if (len >= old_len) len = old_len; buf->total_len -= len; remaining = len; for (chain = buf->first; remaining >= chain->off; chain = next) { next = chain->next; remaining -= chain->off; if (chain == *buf->last_with_datap) { buf->last_with_datap = &buf->first; } if (&chain->next == buf->last_with_datap) buf->last_with_datap = &buf->first; if (CHAIN_PINNED_R(chain)) { EVUTIL_ASSERT(remaining == 0); chain->misalign += chain->off; chain->off = 0; break; } else evbuffer_chain_free(chain); } buf->first = chain; if (chain) { chain->misalign += remaining; chain->off -= remaining; } } buf->n_del_for_cb += len; /* Tell someone about changes in this buffer */ evbuffer_invoke_callbacks(buf);done: EVBUFFER_UNLOCK(buf); return result;}
EVBUFFER_MEM_PINNED_R标记主要用于iocp,使用方式也将在iocp的章节详细分析。
evbuffer_read & evbuffer_write
看完add和remove,接下来的一队时read和write,这两个函数主要用于套接字的读写:
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) { ev_ssize_t space = (ev_ssize_t) CHAIN_SPACE_LEN(*chainp); if (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;}
evbuffer_read函数受限通过get_n_bytes_readable_on_socket获取当前最大的可读入数据,然后根据USE_IOVEC_IMPL宏定义判定是否使用IOVEC方式发送。IOVEC方式可以一次发送多段数据,和evbuffer中的chain搭配使用可以减少系统调用次数,提高效率。_evbuffer_expand_fast函数扩充evbuffer的chain,保证尾部的n(或者小于n)个chain有至少datlen大小的空余空间可供写入数据。
int _evbuffer_expand_fast(struct evbuffer *buf, size_t datlen, int n){ struct evbuffer_chain *chain = buf->last, *tmp, *next; size_t avail; int used; ASSERT_EVBUFFER_LOCKED(buf); EVUTIL_ASSERT(n >= 2); if (chain == NULL || (chain->flags & EVBUFFER_IMMUTABLE)) { /* There is no last chunk, or we can't touch the last chunk. * Just add a new chunk. */ chain = evbuffer_chain_new(datlen); if (chain == NULL) return (-1); evbuffer_chain_insert(buf, chain); return (0); } used = 0; /* number of chains we're using space in. */ avail = 0; /* how much space they have. */ /* How many bytes can we stick at the end of buffer as it is? Iterate * over the chains at the end of the buffer, tring to see how much * space we have in the first n. */ for (chain = *buf->last_with_datap; chain; chain = chain->next) { if (chain->off) { size_t space = (size_t) CHAIN_SPACE_LEN(chain); EVUTIL_ASSERT(chain == *buf->last_with_datap); if (space) { avail += space; ++used; } } else { /* No data in chain; realign it. */ chain->misalign = 0; avail += chain->buffer_len; ++used; } if (avail >= datlen) { /* There is already enough space. Just return */ return (0); } if (used == n) break; } /* There wasn't enough space in the first n chains with space in * them. Either add a new chain with enough space, or replace all * empty chains with one that has enough space, depending on n. */ if (used < n) { /* The loop ran off the end of the chains before it hit n * chains; we can add another. */ EVUTIL_ASSERT(chain == NULL); tmp = evbuffer_chain_new(datlen - avail); if (tmp == NULL) return (-1); buf->last->next = tmp; buf->last = tmp; /* (we would only set last_with_data if we added the first * chain. But if the buffer had no chains, we would have * just allocated a new chain earlier) */ return (0); } else { /* Nuke _all_ the empty chains. */ int rmv_all = 0; /* True iff we removed last_with_data. */ chain = *buf->last_with_datap; if (!chain->off) { EVUTIL_ASSERT(chain == buf->first); rmv_all = 1; avail = 0; } else { avail = (size_t) CHAIN_SPACE_LEN(chain); chain = chain->next; } for (; chain; chain = next) { next = chain->next; EVUTIL_ASSERT(chain->off == 0); evbuffer_chain_free(chain); } tmp = evbuffer_chain_new(datlen - avail); if (tmp == NULL) { if (rmv_all) { ZERO_CHAIN(buf); } else { buf->last = *buf->last_with_datap; (*buf->last_with_datap)->next = NULL; } return (-1); } if (rmv_all) { buf->first = buf->last = tmp; buf->last_with_datap = &buf->first; } else { (*buf->last_with_datap)->next = tmp; buf->last = tmp; } return (0); }}
_evbuffer_read_setup_vecs函数用于填充evbuffer_iovec结构体,之后就可以通过readv或者WSARecv读取数据了。如果不是使用IOVEC则需要调用evbuffer_expand_singlechain和_evbuffer_expand_fast作用相似,
/* Expands the available space in the event buffer to at least datlen, all in * a single chunk. Return that chunk. */static struct evbuffer_chain *evbuffer_expand_singlechain(struct evbuffer *buf, size_t datlen){ struct evbuffer_chain *chain, **chainp; struct evbuffer_chain *result = NULL; ASSERT_EVBUFFER_LOCKED(buf); chainp = buf->last_with_datap; /* XXX If *chainp is no longer writeable, but has enough space in its * misalign, this might be a bad idea: we could still use *chainp, not * (*chainp)->next. */ if (*chainp && CHAIN_SPACE_LEN(*chainp) == 0) chainp = &(*chainp)->next; /* 'chain' now points to the first chain with writable space (if any) * We will either use it, realign it, replace it, or resize it. */ chain = *chainp; if (chain == NULL || (chain->flags & (EVBUFFER_IMMUTABLE|EVBUFFER_MEM_PINNED_ANY))) { /* We can't use the last_with_data chain at all. Just add a * new one that's big enough. */ goto insert_new; } /* If we can fit all the data, then we don't have to do anything */ if (CHAIN_SPACE_LEN(chain) >= datlen) { result = chain; goto ok; } /* If the chain is completely empty, just replace it by adding a new * empty chain. */ if (chain->off == 0) { goto insert_new; } /* If the misalignment plus the remaining space fulfills our data * needs, we could just force an alignment to happen. Afterwards, we * have enough space. But only do this if we're saving a lot of space * and not moving too much data. Otherwise the space savings are * probably offset by the time lost in copying. */ if (evbuffer_chain_should_realign(chain, datlen)) { evbuffer_chain_align(chain); result = chain; goto ok; } /* At this point, we can either resize the last chunk with space in * it, use the next chunk after it, or If we add a new chunk, we waste * CHAIN_SPACE_LEN(chain) bytes in the former last chunk. If we * resize, we have to copy chain->off bytes. */ /* Would expanding this chunk be affordable and worthwhile? */ if (CHAIN_SPACE_LEN(chain) < chain->buffer_len / 8 || chain->off > MAX_TO_COPY_IN_EXPAND) { /* It's not worth resizing this chain. Can the next one be * used? */ if (chain->next && CHAIN_SPACE_LEN(chain->next) >= datlen) { /* Yes, we can just use the next chain (which should * be empty. */ result = chain->next; goto ok; } else { /* No; append a new chain (which will free all * terminal empty chains.) */ goto insert_new; } } else { /* Okay, we're going to try to resize this chain: Not doing so * would waste at least 1/8 of its current allocation, and we * can do so without having to copy more than * MAX_TO_COPY_IN_EXPAND bytes. */ /* figure out how much space we need */ size_t length = chain->off + datlen; struct evbuffer_chain *tmp = evbuffer_chain_new(length); if (tmp == NULL) goto err; /* copy the data over that we had so far */ tmp->off = chain->off; memcpy(tmp->buffer, chain->buffer + chain->misalign, chain->off); /* fix up the list */ EVUTIL_ASSERT(*chainp == chain); result = *chainp = tmp; if (buf->last == chain) buf->last = tmp; tmp->next = chain->next; evbuffer_chain_free(chain); goto ok; }insert_new: result = evbuffer_chain_insert_new(buf, datlen); if (!result) goto err;ok: EVUTIL_ASSERT(result); EVUTIL_ASSERT(CHAIN_SPACE_LEN(result) >= datlen);err: return result;}
evbuffer_expand_singlechain会调用evbuffer_chain_should_realign来查看是否可以在把last_with_data指向的chain通过移动来扩展空间,如果不可以则看是否需要把last_with_data指向的chain移动到新的chain中,判断依据是该chain的空间利用率不足八分之一并且总的长度小于MAX_TO_COPY_IN_EXPAND。
看完read,接下来分析write:
intevbuffer_write(struct evbuffer *buffer, evutil_socket_t fd){ return evbuffer_write_atmost(buffer, fd, -1);}intevbuffer_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); n = send(fd, p, howmuch, 0);#else void *p = evbuffer_pullup(buffer, howmuch); n = write(fd, p, howmuch);#endif#ifdef USE_SENDFILE }#endif } if (n > 0) evbuffer_drain(buffer, n);done: EVBUFFER_UNLOCK(buffer); return (n);}
evbuffer_write_atmost函数有三种情况,第一种是chain->flags 有 EVBUFFER_SENDFILE标记时,调用evbuffer_write_sendfile方法:
#ifdef USE_SENDFILEstatic inline intevbuffer_write_sendfile(struct evbuffer *buffer, evutil_socket_t fd, ev_ssize_t howmuch){ struct evbuffer_chain *chain = buffer->first; struct evbuffer_chain_fd *info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain);#if defined(SENDFILE_IS_MACOSX) || defined(SENDFILE_IS_FREEBSD) int res; off_t len = chain->off;#elif defined(SENDFILE_IS_LINUX) || defined(SENDFILE_IS_SOLARIS) ev_ssize_t res; off_t offset = chain->misalign;#endif ASSERT_EVBUFFER_LOCKED(buffer);#if defined(SENDFILE_IS_MACOSX) res = sendfile(info->fd, fd, chain->misalign, &len, NULL, 0); if (res == -1 && !EVUTIL_ERR_RW_RETRIABLE(errno)) return (-1); return (len);#elif defined(SENDFILE_IS_FREEBSD) res = sendfile(info->fd, fd, chain->misalign, chain->off, NULL, &len, 0); if (res == -1 && !EVUTIL_ERR_RW_RETRIABLE(errno)) return (-1); return (len);#elif defined(SENDFILE_IS_LINUX) /* TODO(niels): implement splice */ res = sendfile(fd, info->fd, &offset, chain->off); if (res == -1 && EVUTIL_ERR_RW_RETRIABLE(errno)) { /* if this is EAGAIN or EINTR return 0; otherwise, -1 */ return (0); } return (res);#elif defined(SENDFILE_IS_SOLARIS) { const off_t offset_orig = offset; res = sendfile(fd, info->fd, &offset, chain->off); if (res == -1 && EVUTIL_ERR_RW_RETRIABLE(errno)) { if (offset - offset_orig) return offset - offset_orig; /* if this is EAGAIN or EINTR and no bytes were * written, return 0 */ return (0); } return (res); }#endif}#endif
该函数主要是在支持sendfile的系统上直接发送文件内容到套接字中,EVBUFFER_SENDFILE之前分析过。
第二种情况是调用evbuffer_write_iovec:
#ifdef USE_IOVEC_IMPLstatic inline intevbuffer_write_iovec(struct evbuffer *buffer, evutil_socket_t fd, ev_ssize_t howmuch){ IOV_TYPE iov[NUM_WRITE_IOVEC]; struct evbuffer_chain *chain = buffer->first; int n, i = 0; if (howmuch < 0) return -1; ASSERT_EVBUFFER_LOCKED(buffer); /* XXX make this top out at some maximal data length? if the * buffer has (say) 1MB in it, split over 128 chains, there's * no way it all gets written in one go. */ while (chain != NULL && i < NUM_WRITE_IOVEC && howmuch) {#ifdef USE_SENDFILE /* we cannot write the file info via writev */ if (chain->flags & EVBUFFER_SENDFILE) break;#endif iov[i].IOV_PTR_FIELD = (void *) (chain->buffer + chain->misalign); if ((size_t)howmuch >= chain->off) { /* XXXcould be problematic when windows supports mmap*/ iov[i++].IOV_LEN_FIELD = (IOV_LEN_TYPE)chain->off; howmuch -= chain->off; } else { /* XXXcould be problematic when windows supports mmap*/ iov[i++].IOV_LEN_FIELD = (IOV_LEN_TYPE)howmuch; break; } chain = chain->next; } if (! i) return 0;#ifdef WIN32 { DWORD bytesSent; if (WSASend(fd, iov, i, &bytesSent, 0, NULL, NULL)) n = -1; else n = bytesSent; }#else n = writev(fd, iov, i);#endif return (n);}#endif
iovec可以结合chain使用,减少系统调用。
第三种情况是只能使用send或者wtire发送单块的内存数据,这时需要调用evbuffer_pullup将evbuff前面size长度的数据放置到一块chain中:
unsigned char *evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size){ struct evbuffer_chain *chain, *next, *tmp, *last_with_data; unsigned char *buffer, *result = NULL; ev_ssize_t remaining; int removed_last_with_data = 0; int removed_last_with_datap = 0; EVBUFFER_LOCK(buf); chain = buf->first; if (size < 0) size = buf->total_len; /* if size > buf->total_len, we cannot guarantee to the user that she * is going to have a long enough buffer afterwards; so we return * NULL */ if (size == 0 || (size_t)size > buf->total_len) goto done; /* No need to pull up anything; the first size bytes are * already here. */ if (chain->off >= (size_t)size) { result = chain->buffer + chain->misalign; goto done; } /* Make sure that none of the chains we need to copy from is pinned. */ remaining = size - chain->off; EVUTIL_ASSERT(remaining >= 0); for (tmp=chain->next; tmp; tmp=tmp->next) { if (CHAIN_PINNED(tmp)) goto done; if (tmp->off >= (size_t)remaining) break; remaining -= tmp->off; } if (CHAIN_PINNED(chain)) { size_t old_off = chain->off; if (CHAIN_SPACE_LEN(chain) < size - chain->off) { /* not enough room at end of chunk. */ goto done; } buffer = CHAIN_SPACE_PTR(chain); tmp = chain; tmp->off = size; size -= old_off; chain = chain->next; } else if (chain->buffer_len - chain->misalign >= (size_t)size) { /* already have enough space in the first chain */ size_t old_off = chain->off; buffer = chain->buffer + chain->misalign + chain->off; tmp = chain; tmp->off = size; size -= old_off; chain = chain->next; } else { if ((tmp = evbuffer_chain_new(size)) == NULL) { event_warn("%s: out of memory", __func__); goto done; } buffer = tmp->buffer; tmp->off = size; buf->first = tmp; } /* TODO(niels): deal with buffers that point to NULL like sendfile */ /* Copy and free every chunk that will be entirely pulled into tmp */ last_with_data = *buf->last_with_datap; for (; chain != NULL && (size_t)size >= chain->off; chain = next) { next = chain->next; memcpy(buffer, chain->buffer + chain->misalign, chain->off); size -= chain->off; buffer += chain->off; if (chain == last_with_data) removed_last_with_data = 1; if (&chain->next == buf->last_with_datap) removed_last_with_datap = 1; evbuffer_chain_free(chain); } if (chain != NULL) { memcpy(buffer, chain->buffer + chain->misalign, size); chain->misalign += size; chain->off -= size; } else { buf->last = tmp; } tmp->next = chain; if (removed_last_with_data) { buf->last_with_datap = &buf->first; } else if (removed_last_with_datap) { if (buf->first->next && buf->first->next->off) buf->last_with_datap = &buf->first->next; else buf->last_with_datap = &buf->first; } result = (tmp->buffer + tmp->misalign);done: EVBUFFER_UNLOCK(buf); return result;}
evbuffer_pullup将evbuffer中的前面size大小的数据放入到一个chain中,这样可以减少send或者write的系统调用次数。即使进行内存移动也是值得的。如果first指向的chain大小不足,则需要将后面chain中的数据移入到第一个chain中,for循环会检测之后用到的chain是否有CHAIN_PINNED标记,如果有则操作失败,因为CHAIN_PINNED标记的chain不能移动。同时也要检测first指向的chain是否可以移动,如果有CHAIN_PINNED标记,则不能移动,需要检测off加上余下的空间是否足够size大小,足够才可以继续移动。
evbuffer_reserve_space & evbuffer_commit_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;}int evbuffer_commit_space(struct evbuffer *buf, struct evbuffer_iovec *vec, int n_vecs){ struct evbuffer_chain *chain, **firstchainp, **chainp; int result = -1; size_t added = 0; int i; EVBUFFER_LOCK(buf); if (buf->freeze_end) goto done; if (n_vecs == 0) { result = 0; goto done; } else if (n_vecs == 1 && (buf->last && vec[0].iov_base == (void*)CHAIN_SPACE_PTR(buf->last))) { /* The user only got or used one chain; it might not * be the first one with space in it. */ if ((size_t)vec[0].iov_len > (size_t)CHAIN_SPACE_LEN(buf->last)) goto done; buf->last->off += vec[0].iov_len; added = vec[0].iov_len; if (added) advance_last_with_data(buf); goto okay; } /* Advance 'firstchain' to the first chain with space in it. */ firstchainp = buf->last_with_datap; if (!*firstchainp) goto done; if (CHAIN_SPACE_LEN(*firstchainp) == 0) { firstchainp = &(*firstchainp)->next; } chain = *firstchainp; /* pass 1: make sure that the pointers and lengths of vecs[] are in * bounds before we try to commit anything. */ for (i=0; i<n_vecs; ++i) { if (!chain) goto done; if (vec[i].iov_base != (void*)CHAIN_SPACE_PTR(chain) || (size_t)vec[i].iov_len > CHAIN_SPACE_LEN(chain)) goto done; chain = chain->next; } /* pass 2: actually adjust all the chains. */ chainp = firstchainp; for (i=0; i<n_vecs; ++i) { (*chainp)->off += vec[i].iov_len; added += vec[i].iov_len; if (vec[i].iov_len) { buf->last_with_datap = chainp; } chainp = &(*chainp)->next; }okay: buf->total_len += added; buf->n_add_for_cb += added; result = 0; evbuffer_invoke_callbacks(buf);done: EVBUFFER_UNLOCK(buf); return result;}
evbuffer_reserve_space调用evbuffer_expand_singlechain或者_evbuffer_expand_fast来预留空间,这两个函数在之前分析过。evbuffer_commit_space在写入数据之后把更改记录到evbuffer之中。evbuffer_commit_space需要检测vec和当前evbuffer中的chain数据是否匹配,只有全部检测通过才能改变chain的记录数据。这两个函数一定要成对使用,并且在中间不能使用其他会改变chain数据的函数,否则会导致错误。
evbuffer_add_file
evbuffer可以直接发送文件,为了实现零拷贝,evbuffer在支持sendfile的系统上直接使用sendfile发送,在支持mmap的系统上使用mmap发送,如果不支持这两种方式才使用拷贝的方式。
intevbuffer_add_file(struct evbuffer *outbuf, int fd, ev_off_t offset, ev_off_t length){#if defined(USE_SENDFILE) || defined(_EVENT_HAVE_MMAP) struct evbuffer_chain *chain; struct evbuffer_chain_fd *info;#endif#if defined(USE_SENDFILE) int sendfile_okay = 1;#endif int ok = 1; if (offset < 0 || length < 0 || ((ev_uint64_t)length > EVBUFFER_CHAIN_MAX) || (ev_uint64_t)offset > (ev_uint64_t)(EVBUFFER_CHAIN_MAX - length)) return (-1);#if defined(USE_SENDFILE) if (use_sendfile) { EVBUFFER_LOCK(outbuf); sendfile_okay = outbuf->flags & EVBUFFER_FLAG_DRAINS_TO_FD; EVBUFFER_UNLOCK(outbuf); } if (use_sendfile && sendfile_okay) { chain = evbuffer_chain_new(sizeof(struct evbuffer_chain_fd)); if (chain == NULL) { event_warn("%s: out of memory", __func__); return (-1); } chain->flags |= EVBUFFER_SENDFILE | EVBUFFER_IMMUTABLE; chain->buffer = NULL; /* no reading possible */ chain->buffer_len = length + offset; chain->off = length; chain->misalign = offset; info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain); info->fd = fd; EVBUFFER_LOCK(outbuf); if (outbuf->freeze_end) { mm_free(chain); ok = 0; } else { outbuf->n_add_for_cb += length; evbuffer_chain_insert(outbuf, chain); } } else#endif#if defined(_EVENT_HAVE_MMAP) if (use_mmap) { void *mapped = mmap(NULL, length + offset, PROT_READ,#ifdef MAP_NOCACHE MAP_NOCACHE |#endif#ifdef MAP_FILE MAP_FILE |#endif MAP_PRIVATE, fd, 0); /* some mmap implementations require offset to be a multiple of * the page size. most users of this api, are likely to use 0 * so mapping everything is not likely to be a problem. * TODO(niels): determine page size and round offset to that * page size to avoid mapping too much memory. */ if (mapped == MAP_FAILED) { event_warn("%s: mmap(%d, %d, %zu) failed", __func__, fd, 0, (size_t)(offset + length)); return (-1); } chain = evbuffer_chain_new(sizeof(struct evbuffer_chain_fd)); if (chain == NULL) { event_warn("%s: out of memory", __func__); munmap(mapped, length); return (-1); } chain->flags |= EVBUFFER_MMAP | EVBUFFER_IMMUTABLE; chain->buffer = mapped; chain->buffer_len = length + offset; chain->off = length + offset; info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain); info->fd = fd; EVBUFFER_LOCK(outbuf); if (outbuf->freeze_end) { info->fd = -1; evbuffer_chain_free(chain); ok = 0; } else { outbuf->n_add_for_cb += length; evbuffer_chain_insert(outbuf, chain); /* we need to subtract whatever we don't need */ evbuffer_drain(outbuf, offset); } } else#endif { /* the default implementation */ struct evbuffer *tmp = evbuffer_new(); ev_ssize_t read; if (tmp == NULL) return (-1);#ifdef WIN32#define lseek _lseeki64#endif if (lseek(fd, offset, SEEK_SET) == -1) { evbuffer_free(tmp); return (-1); } /* we add everything to a temporary buffer, so that we * can abort without side effects if the read fails. */ while (length) { ev_ssize_t to_read = length > EV_SSIZE_MAX ? EV_SSIZE_MAX : (ev_ssize_t)length; read = evbuffer_readfile(tmp, fd, to_read); if (read == -1) { evbuffer_free(tmp); return (-1); } length -= read; } EVBUFFER_LOCK(outbuf); if (outbuf->freeze_end) { evbuffer_free(tmp); ok = 0; } else { evbuffer_add_buffer(outbuf, tmp); evbuffer_free(tmp);#ifdef WIN32#define close _close#endif close(fd); } } if (ok) evbuffer_invoke_callbacks(outbuf); EVBUFFER_UNLOCK(outbuf); return ok ? 0 : -1;}
USE_SENDFILE模式和_EVENT_HAVE_MMAP模式都需要申请evbuffer_chain_fd大小的数据,该数据不存储实际数据而是存储一个对应的文件fd。EVBUFFER_SENDFILE标记的数据需要使用evbuffer_write_sendfile单独处理,_EVENT_HAVE_MMAP则可以和普通存储数据的chain一样处理,只是在释放chain的时候需要注意调用unmap。但是这段代码中调用evbuffer_drain的目的笔者不是很理解。
- Libevent源码分析(五)--- evbuffer的基本操作
- Libevent源码分析-----evbuffer结构与基本操作
- Libevent源码分析-----evbuffer结构与基本操作
- Libevent源码分析-----evbuffer结构与基本操作
- Libevent源码分析-----evbuffer结构与基本操作
- Libevent源码分析-----evbuffer结构与基本操作
- Libevent源码分析-----更多evbuffer操作函数
- Libevent源码分析-----更多evbuffer操作函数
- Libevent源码分析-----更多evbuffer操作函数
- libevent 源码分析:evbuffer缓冲
- libevent源码分析--evbuffer缓冲
- libevent源码分析--evbuffer和bufferevent的关系
- libevent 源码分析:evbuffer缓冲(附带libevent vs2005完整包下载)
- Libevent源码分析(一)--- 基本数据结构
- libevent evbuffer
- libevent学习---使用evbuffer操作数据
- evbuffer结构与基本操作
- libevent的evbuffer跟bufferevent的区别
- JS中关于clientWidth offsetWidth scrollWidth 等的含义
- TrustZone初探 (三)
- dom对象模型简介
- 部署.NET Webservice
- html元素的分类
- Libevent源码分析(五)--- evbuffer的基本操作
- 夯实基础——类、抽象类和接口的关系
- 集合数组排序之冒泡
- java Date加一天(往后加一天)
- HDU 2099 整除的尾数(格式易题)
- dw1.modify(modstring)
- LeetCode OJ: 12 Integer to Roman
- 【红宝书笔记】DOM2级事件小相关
- PAT1038统计同成绩学生(20)