前言
以下内容是对《LibeventBook》的翻译
Chapter6 : Creating a event_base
Before you can use any interesting Libevent function, you need to allocate one or more event_base structures. Each event_base structure holds a set of events and can poll to determine which events are active.
使用libevent时,首先需要创建一个或多个event_base结构。每个event_base包含一些events,event_base能够poll出哪些事件发生了,并通知用户。
If an event_base is set up to use locking, it is safe to access it between multiple threads. Its loop can only be run in a single thread, however. If you want to have multiple threads polling for IO, you need to have an event_base for each thread.
如果一个event_base被设置成使用locking(锁),那么多个线程访问它是安全的(线程安全的)。不过它的事件循环只能在一个线程中运行。如果向让多个线程对IO进行polling, 那么需要让每个线程都拥有一个event_base。
Each event_base has a “method”, or a backend that it uses to determine which events are ready. The recognized methods are:
event_base有一个用来确定哪些事件就绪的method,包括:
- select
- poll
- epoll
- kqueue
- devpoll
- evport
- win32
用户可以通过环境变量来disable某种method,如设置EVENT_NOKQUEUE环境变量将禁止kqueue。在程序中可以使用event_config_avoid_method()
函数来禁用某种method。
获取一个默认的event_base
// <event2/event.h>
struct event_base *event_base_new(void);
event_base_new()函数分配并返回一个新的event_base(默认设置)。它会检查环境变量并返回一个指向新的event_base的指针。发生错误时,返回NULL。在选择method的时候,会选取当前OS支持的最快的method。
获取经过配置的event_base
struct event_config *event_config_new(void);
struct event_base *event_base_new_with_config(const struct event_config *cfg);
void event_config_free(struct event_config *cfg);
确定event_base可以使用哪些method
const char **event_get_supported_methods(void);
确定event_base在使用哪种method
const char *event_base_get_method(const struct event_base *base);
enum event_method_feature event_base_get_features(const struct event_base *base);
释放event_base
void event_base_free(struct event_base *base);
在fork()之后对event_base进行Reinitializing
int event_reinit(struct event_base *base);
测试程序
#include <event2/event.h>
int main()
{
/* 查看当前支持哪些method */
int i;
const char **methods = event_get_supported_methods();
printf("Starting Libevent %s. Available methods are:\n", event_get_version());
for (i=0; methods[i] != NULL; ++i) {
printf("%s\n", methods[i]);
}
/* 当前使用的什么method,以及该method的feature */
struct event_base *base;
enum event_method_feature f;
base = event_base_new();
if (!base) {
puts("Couldn’t get an event_base!");
} else {
printf("Using Libevent with backend method %s.",
event_base_get_method(base));
f = event_base_get_features(base);
if ((f & EV_FEATURE_ET))
printf(" Edge-triggered events are supported."); /* 支持边缘触发 Edge-Triggered*/
if ((f & EV_FEATURE_O1))
printf(" O(1) event notification is supported."); /* poll,添加,删除一个event是O(1)的 */
if ((f & EV_FEATURE_FDS))
printf(" All FD types are supported."); /* 支持任意类型的文件描述符(fd): 普通文件fd,socket fd */
puts("");
}
/* 释放event_base */
event_base_free(base);
return 0;
}
Chapter7 : Working with event loop
在创建了event_base,并在它上面注册了一些event之后,可以使用event_base_loop()
来检测事件是否就绪。
#define EVLOOP_ONCE 0x01
#define EVLOOP_NONBLOCK 0x02
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
int event_base_loop(struct event_base *base, int flags);
event_base_loop的伪代码
while (any events are registered with the loop,
or EVLOOP_NO_EXIT_ON_EMPTY was set) {
if (EVLOOP_NONBLOCK was set, or any events are already active)
If any registered events have triggered, mark them active.
else
Wait until at least one event has triggered, and mark it active.
for (p = 0; p < n_priorities; ++p {
if (any event with priority of p is active) {
Run all active events with priority of p.
break; /* Do not run any events of a less important priority */
}
}
if (EVLOOP_ONCE was set or EVLOOP_NONBLOCK was set)
break;
}
可以使用event_base_dispatch,它与event_base_loop相同,只不过没有flag参数,它会一直运行,直到没有注册事件或event_base_loopbreak() or event_base_loopexit()
被调用。
int event_base_dispatch(struct event_base *base);
Chapter8 : Working with events
libevent的基本操作单元是event,每个event代表了一些条件:
- A file descriptor being ready to read from or write to.
- A file descriptor becoming ready to read from or write to (Edge-triggered IO only).
- A timeout expiring.
- A signal occurring.
- A user-triggered event.
每个event的生命周期是这样的:
Events have similar lifecycles. Once you call a Libevent function to set up an event and associate it with an event base, it becomes initialized. At this point, you can add, which makes it pending in the base. When the event is pending, if the conditions that would trigger an event occur (e.g., its file descriptor changes state or its timeout expires), the event becomes active, and its (user-provided) callback function is run. If the event is configured persistent , it remains pending. If it is not persistent, it stops being pending when its callback runs. You can make a pending event non-pending by deleting it, and you can add a non-pending event to make it pending again.
创建一个event
/** Indicates that a timeout has occurred. It's not necessary to pass
* this flag to event_for new()/event_assign() to get a timeout. */
#define EV_TIMEOUT 0x01
/** Wait for a socket or FD to become readable */
#define EV_READ 0x02
/** Wait for a socket or FD to become writeable */
#define EV_WRITE 0x04
/** Wait for a POSIX signal to be raised*/
#define EV_SIGNAL 0x08
/**
* Persistent event: won't get removed automatically when activated.
*
* When a persistent event with a timeout becomes activated, its timeout
* is reset to 0.
*/
#define EV_PERSIST 0x10
/** Select edge-triggered behavior, if supported by the backend. */
#define EV_ET 0x20
typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
struct event *event_new(struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb, void *arg);
void event_free(struct event *event);
创建了一个event之后,使用event_add让event加入event_base, 进入pending状态。
int event_add(struct event *ev, const struct timeval *tv);
关于EV_PERSIST
:
Chapter10 : Bufferevents
This buffered IO pattern is common enough that Libevent provides a generic mechanism for it. A “bufferevent” consists of an underlying transport (like a socket), a read buffer, and a write buffer. Instead of regular events, which give callbacks when the underlying transport is ready to be read or written, a bufferevent invokes its user-supplied callbacks when it has read or written enough data.
通常的做法是在关注的socket可读或可写时调用callback;而bufferevent则在读取了足够的数据或写入了足够的数据时才去调用用户定义的回调函数。因此,前者是通知“就绪”,后者是通知“完成”。
bufferevent的类型:
1. socket-based bufferevents :
A bufferevent that sends and receives data from an underlying stream socket, using the event_* interface as its backend.
2. asynchronous-IO bufferevents :
A bufferevent that uses the Windows IOCP interface to send and receive data to an underlying stream socket. (Windows
only; experimental.)
3. filtering bufferevents :
A bufferevent that processes incoming and outgoing data before passing it to an underlying bufferevent object—for exam-
ple, to compress or translate data.
4. paired bufferevents :
Two bufferevents that transmit data to one another.
Bufferevents and evbuffers
每个Bufferevents都有两个数据区: input buffer 和 output buffer。类型都是struct evbuffer
。
当你有数据要写到bufferevent上时,就将数据添加到output buffer。
当bufferevent有数据可读时,可以从input buffer得到它们。
Callbacks and watermarks
Every bufferevent has two data-related callbacks: a read callback and a write callback. By default, the read callback is called whenever any data is read from the underlying transport, and the write callback is called whenever enough data from the output buffer is emptied to the underlying transport. You can override the behavior of these functions by adjusting the read and write “watermarks” of the bufferevent.
bufferevent有两个数据相关的回调函数:read callback and write callback
。默认情况下,read callback
会当 从关注的socket读到了任意的数据时被调用;write callback
在全部的数据从output buffer传送给了内核发送缓冲区
((注意,不是发送到peer)时被调用。可以通过修改bufferevent的读写watermarks(读写水位)来修改read callback and write callback
的行为。
4个watermarks:
Read low-water mark :
Whenever a read occurs that leaves the bufferevent’s input buffer at this level or higher, the bufferevent’s read callback is invoked. Defaults to 0, so that every read results in the read callback being invoked.( 读-低水位: read callback只有在inputbuffer的数据量大于等于该值时才会被调用。默认为0,所以只要inputbuffer有数据read callback就会被调用 )Read high-water mark :
If the bufferevent’s input buffer ever gets to this level, the bufferevent stops reading until enough data is drained from the input buffer to take us below it again. Defaults to unlimited, so that we never stop reading because of the size of the input buffer. ( 读-高水位:当inputbuffer的数据量大于或等于该值时,bufferevent将不再把内核接收缓冲区中的数据添加到inputbuffer,直到inputbuffer中有足够的数据被取出后,才会继续把内核接收缓冲区中的数据添加到readbuffer,默认是unlimited。如果不从inputbuffer取数据,或者发送者的发送速度很快,那么inputbuffer会暴涨,从muduo中学到的^_^. )Write low-water mark :
Whenever a write occurs that takes us to this level or below, we invoke the write callback. Defaults to 0, so that a write callback is not invoked unless the output buffer is emptied.(写-低水位:当writebuffer可用空间大小小于或等于该值时,write callback被调用。默认为0,所以只有当writebuffer为空时,write callback才会被调用。意思就是,当outputbuffer中的数据都发送出去之后[仅仅是放到了内核的发送缓冲区],write callback才被调用。)Write high-water mark :
Not used by a bufferevent directly, this watermark can have special meaning when a bufferevent is used as the underlying
transport of another bufferevent. See notes on filtering bufferevents below.(写-高水位:略)
Working with socket-based bufferevent
socket-based的bufferevent是最简单易用的bufferevent类型, 它使用Libevent的事件机制来检查socket是否“读就绪”或“写就绪”,并使用网络系统调用(readv, writev, WSASend, or WSARecv)来收发数据。
创建一个socket-based bufferevent
struct bufferevent *bufferevent_socket_new(
struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options);
在socket-based bufferevent上发起连接
int bufferevent_socket_connect(struct bufferevent *bev,
struct sockaddr *address, int addrlen);
enable / disable read write
You can enable or disable the events EV_READ, EV_WRITE, or EV_READ|EV_WRITE on a bufferevent. When reading or writing is not enabled, the bufferevent will not try to read or write data.
There is no need to disable writing when the output buffer is empty: the bufferevent automatically stops writing, and restarts again when there is data to write.
Similarly, there is no need to disable reading when the input buffer is up to its high-water mark: the bufferevent automatically stops reading, and restarts again when there is space to read.
By default, a newly created bufferevent has writing enabled, but not reading.
当output buffer为空时,即:数据已经从outputbuffer“发送出去后”[仅仅是移动到了内核的发送缓冲区],使用者无需disable EV_WRITE,因为bufferevent会自动disable EV_WRITE, 然后在output buffer中又有数据要发送时再enable EV_WRITE
当input buffer中的数据达到“高水位”时,使用者无需disable EV_READ, 因为bufferevent会自动disable EV_READ, 然后当inputbuffer中有可用空间时,载 enable EV_READ, 把kernel接收缓冲区的数据放到inputbuffer中。
此外,一个新创建的bufferevent默认是 enable EV_WRITE ,但是EV_READ没有enable
Rearranging the internal layout of an evbuffer
unsigned char *evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size);
The evbuffer_pullup() function “linearizes” the first size bytes of buf, copying or moving them as needed to ensure that they are all contiguous and occupying the same chunk of memory. If size is negative, the function linearizes the entire buffer. If size is greater than the number of bytes in the buffer, the function returns NULL. Otherwise, evbuffer_pullup() returns a pointer to the first byte in buf.
Calling evbuffer_pullup() with a large size can be quite slow, since it potentially needs to copy the entire buffer’s contents.
int parse_socks4(struct evbuffer *buf, ev_uint16_t *port, ev_uint32_t *addr)
{
/* Let's parse the start of a SOCKS4 request! The format is easy:
* 1 byte of version, 1 byte of command, 2 bytes destport, 4 bytes of
* destip. */
unsigned char *mem;
mem = evbuffer_pullup(buf, 8);
if (mem == NULL) {
/* Not enough data in the buffer */
return 0;
} else if (mem[0] != 4 || mem[1] != 1) {
/* Unrecognized protocol or command */
return -1;
} else {
memcpy(port, mem+2, 2);
memcpy(addr, mem+4, 4);
*port = ntohs(*port);
*addr = ntohl(*addr);
/* Actually remove the data from the buffer now that we know we
like it. */
evbuffer_drain(buf, 8);
return 1;
}
}
此函数可用于分析协议头。
Chapter13 : Connection Listener
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
static void
echo_read_cb(struct bufferevent *bev, void *ctx)
{
/* This callback is invoked when there is data to read on bev. */
struct evbuffer *input = bufferevent_get_input(bev);
struct evbuffer *output = bufferevent_get_output(bev);
/* Copy all the data from the input buffer to the output buffer. */
evbuffer_add_buffer(output, input);
}
static void
echo_event_cb(struct bufferevent *bev, short events, void *ctx)
{
if (events & BEV_EVENT_ERROR)
perror("Error from bufferevent");
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
bufferevent_free(bev);
}
}
static void
accept_conn_cb(struct evconnlistener *listener,
evutil_socket_t fd, struct sockaddr *address, int socklen,
void *ctx)
{
/* We got a new connection! Set up a bufferevent for it. */
struct event_base *base = evconnlistener_get_base(listener);
struct bufferevent *bev = bufferevent_socket_new(
base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, echo_read_cb, NULL, echo_event_cb, NULL);
bufferevent_enable(bev, EV_READ|EV_WRITE);
}
static void
accept_error_cb(struct evconnlistener *listener, void *ctx)
{
struct event_base *base = evconnlistener_get_base(listener);
int err = EVUTIL_SOCKET_ERROR();
fprintf(stderr, "Got an error %d (%s) on the listener. "
"Shutting down.\n", err, evutil_socket_error_to_string(err));
event_base_loopexit(base, NULL);
}
int main(int argc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
struct sockaddr_in sin;
int port = 9876;
if (argc > 1) {
port = atoi(argv[1]);
}
if (port<=0 || port>65535) {
puts("Invalid port");
return 1;
}
base = event_base_new();
if (!base) {
puts("Couldn’t open event base");
return 1;
}
/* Clear the sockaddr before using it, in case there are extra
* platform-specific fields that can mess us up. */
memset(&sin, 0, sizeof(sin));
/* This is an INET address */
sin.sin_family = AF_INET;
/* Listen on 0.0.0.0 */
sin.sin_addr.s_addr = htonl(0);
/* Listen on the given port. */
sin.sin_port = htons(port);
listener = evconnlistener_new_bind(base, accept_conn_cb, NULL,
LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
(struct sockaddr*)&sin, sizeof(sin));
if (!listener) {
perror("Couldn’t create listener");
return 1;
}
evconnlistener_set_error_cb(listener, accept_error_cb);
event_base_dispatch(base);
return 0;
}
Examples & Summary
libevent是一个跨平台的、异步非阻塞网络库。使用时只需要告诉libevent你关心什么事件,以及事件发生时要做什么(回调函数),底层是非阻塞socket
+IO multiplexing(select、epoll...)
。
异步非阻塞网络库是需要input buffer
和 output buffer
的,muduo中提到了原因。ibevent中的bufferevent拥有输入/输出缓冲区,并且可以设置高水位回调
和低水位回调
,这样就更加方便了。