看到一篇 libevent 的介绍文件,写得由浅入深,颇有趣味,随手翻译在此,并增加了一点 libevent 自身的介绍。
原文来自http://www.wangafu.net/~nickm/libevent-book/01_intro.html
These documents are Copyright (c) 2009-2012 by Nick Mathewson, and are made available under the Creative Commons Attribution-Noncommercial-Share Alike license, version 3.0. Future versions may be made available under a less restrictive license.
多数初级程序员是从阻塞式IO调用开始编程的。一个 IO 调用称为同步的,它直到操作完成或者操作超时了才会返回。
当你在 TCP 连接时调用了 “connect”,例如你的操作系统发送了一个 “SYN” 消息给TCP 连接另一端的主机。 它不会马上返回,会一直等到你的应用程序从对端收到了一个 “SYN ACK” 消息或者等待超时了。
- 示例 http_client.c
为方便运行,代码中的 google.com 改成了 baidu.com
/* For sockaddr_in */
#include
/* For socket functions */
#include
/* For gethostbyname */
#include
#include
#include
#include
int main(int c, char **v)
{
const char query[] =
"GET / HTTP/1.0\r\n"
"Host: www.baidu.com\r\n"
"\r\n";
const char hostname[] = "www.baidu.com";
struct sockaddr_in sin;
struct hostent *h;
const char *cp;
int fd;
ssize_t n_written, remaining;
char buf[1024];
/* 根据 hostname 查找 IP 地址,注意这个函数在多数平台上都不是线程安全的 */
h = gethostbyname(hostname);
if (!h) {
fprintf(stderr, "Couldn't lookup %s: %s", hostname, hstrerror(h_errno));
return 1;
}
if (h->h_addrtype != AF_INET) {
fprintf(stderr, "No ipv6 support, sorry.");
return 1;
}
/* 分配一个新的 socket */
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
/* 连接远程主机 */
sin.sin_family = AF_INET;
sin.sin_port = htons(80);
sin.sin_addr = *(struct in_addr*)h->h_addr;
if (connect(fd, (struct sockaddr*) &sin, sizeof(sin))) {
perror("connect");
close(fd);
return 1;
}
/* 将这个http 查询写入socket */
/* 为避免TCP 没能完整发送数据名,用一个循环来检查并发送余下字节 */
cp = query;
remaining = strlen(query);
while (remaining) {
n_written = send(fd, cp, remaining, 0);
if (n_written <= 0) {
perror("send");
return 1;
}
remaining -= n_written;
cp += n_written;
}
/* 接收发回的响应 */
while (1) {
ssize_t result = recv(fd, buf, sizeof(buf), 0);
if (result == 0) {
break;
} else if (result < 0) {
perror("recv");
close(fd);
return 1;
}
fwrite(buf, 1, result, stdout);
}
close(fd);
return 0;
}
- Makefile
http_client:
gcc -g -o http_client http_client.c
clean:
rm -f http_client
编译并测试一下
make http_client
./http_client
# 打印了接收的的 HTTP 消息
HTTP/1.0 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
...
以上所有的网络调用都是阻塞的
- gethostbyname 一直等到解析 www.baidu.com 成功或者失败才会返回
- connect 走到连接上了才会返回
- recv 调用一直等到接收到数据或者连接关闭才会返回
- send 调用会一直等到所输出到内核的写缓存中才会返回
阻塞式 IO不一定就不好。 如果你在此期间不想让程序执行其他任何操作,那么阻塞式 IO 工作得很好。 但是假设你需要编写一个程序来一次处理多个连接,那就两说了。 为了使我们的示例更具体:假设您想从两个连接中读取输入,并且您不知道哪个连接将首先获得输入。
你不能这样写代码:
/* 这块代码是无效的 */
char buf[1024];
int i, n;
while (i_still_want_to_read()) {
for (i=0; i
因为如果数据首先到达fd [2],则上述程序甚至不会尝试从fd [2]读取数据,直到从fd [0]和fd [1]读取数据并完成读取。
有时人们使用多线程或多进程服务器解决此问题。 执行多线程的最简单方法之一是使用单独的进程(或线程)来处理每个连接。 由于每个连接都有自己的进程,因此等待一个连接的阻塞IO调用不会阻止其他任何连接的进程。
这是另一个示例程序。 这是一个普通的服务器,它侦听端口40713上的因为数据先到达了 fd[2], 你的程序将不会尝试先从 TCP连接,一次从其输入的一行中读取数据,并在到达时写出每行的ROT13模糊处理。 它使用Unix fork() 调用为每个传入连接创建一个新进程。
(注:ROT 13 也称旋转13,是一种简单的字母替换加密算法,将此字母替换为它在字母表之后的第13个字母,由于只有26个字母,两次编码等于一次解码)
- 示例 rot13_server.c
/* For sockaddr_in */
#include
/* For socket functions */
#include
#include
#include
#include
#include
#define MAX_LINE 16384
char rot13_char(char c)
{
/* We don't want to use isalpha here; setting the locale would change
* which characters are considered alphabetical. */
if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
return c + 13;
else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
return c - 13;
else
return c;
}
void child(int fd)
{
char outbuf[MAX_LINE+1];
size_t outbuf_used = 0;
ssize_t result;
while (1) {
char ch;
result = recv(fd, &ch, 1, 0);
if (result == 0) {
break;
} else if (result == -1) {
perror("read");
break;
}
/* We do this test to keep the user from overflowing the buffer. */
if (outbuf_used < sizeof(outbuf)) {
outbuf[outbuf_used++] = rot13_char(ch);
}
if (ch == '\n') {
send(fd, outbuf, outbuf_used, 0);
outbuf_used = 0;
continue;
}
}
}
void run(void)
{
int listener;
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(40713);
listener = socket(AF_INET, SOCK_STREAM, 0);
#ifndef WIN32
{
int one = 1;
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
}
#endif
if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
perror("bind");
return;
}
if (listen(listener, 16)<0) {
perror("listen");
return;
}
while (1) {
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int fd = accept(listener, (struct sockaddr*)&ss, &slen);
if (fd < 0) {
perror("accept");
} else {
if (fork() == 0) {
child(fd);
exit(0);
}
}
}
}
int main(int c, char **v)
{
run();
return 0;
}
所以,是不是我们有了一个处理多个连接的完美解决方案了? 当然不是。
首先,创建进程或线程在一些平台上成本是很高的。在现实生活中,你会用线程池来代替创建新进程。但是从根本上说,线程不能象你希望的那样扩展。如果你的程序需要一次处理成千上万个连接,那么处理成千上万个线程的效率将远不如一个CPU 仅处理几个线程。
那么,线程化不是处理多个连接的好方法,什么方法才是呢?答案是让你的 socket 不要阻塞。
设置为非阻塞的调用如下
fcntl(fd, F_SETFL, O_NONBLOCK);
这里的 fd 是 socket 的文件描述符
(一个文件描述符是内核在打开socket 分配给它的编号,你可以用它进行指向 socket 的调用)
一旦你将 fd ( 对应的socket ) 设置为非阻塞的,从此以垢 ,每当对 fd 进行网络操作时,相应的调用立即返回或者返回特殊的错误代码以指示“我现在无法取得任何进展,请再试一次”。故此,我们的两路连接示例可以简略地写成
- 反例:
/* 这个例子虽然能工作,但是性能差到无法忍受 */
int i, n;
char buf[1024];
for (i=0; i < n_sockets; ++i)
fcntl(fd[i], F_SETFL, O_NONBLOCK);
while (i_still_want_to_read()) {
for (i=0; i < n_sockets; ++i) {
n = recv(fd[i], buf, sizeof(buf), 0);
if (n == 0) {
handle_close(fd[i]);
} else if (n < 0) {
if (errno == EAGAIN)
; /* The kernel didn't have any data for us to read. */
else
handle_error(fd[i], errno);
} else {
handle_input(fd[i], buf, n);
}
}
}
现在我们使用了非阻塞 socket 调用, 上面的代码也能工作,但是几乎不能用。其性能非常糟糕,有两个原因,其一,当在任何一个连接上都没有要读取的数据时,将陷入无限循环,从而耗尽所有CPU周期。 其二,如果你尝试使用这种方法处理一个或两个以上的连接,则将对每个连接进行内核调用,无论该连接是否具有任何数据。
因此,我们需要一种告诉内核 “等到这些 socket 中有一个准备好了给我一些数据,再告诉我哪些已经准备好”的方法。
人们仍然在使用的最古老的解决方案是select()。 select() 调用采用三组fds(以位数组的形式实现)
- 一组用于读取,
- 一组用于写入,
- 以及一组用于异常。
它等待直到其中一组中的一个socket 准备就绪,然后将这几组更改为仅包含可供使用的 socket。
这是我们再次使用select的示例
- 示例:使用 select
/* 如果你仅仅有一些文件句柄,这个版本的性能不会太差 */
fd_set readset;
int i, n;
char buf[1024];
while (i_still_want_to_read()) {
int maxfd = -1;
FD_ZERO(&readset);
/* 将所有感兴趣的文件句柄放入 readset */
for (i=0; i < n_sockets; ++i) {
if (fd[i]>maxfd) maxfd = fd[i];
FD_SET(fd[i], &readset);
}
/* 等待一个或多个文件句柄可读 */
select(maxfd+1, &readset, NULL, NULL, NULL);
/* 处理仍然在 readset 中的所有文件句柄 */
for (i=0; i < n_sockets; ++i) {
if (FD_ISSET(fd[i], &readset)) {
n = recv(fd[i], buf, sizeof(buf), 0);
if (n == 0) {
handle_close(fd[i]);
} else if (n < 0) {
if (errno == EAGAIN)
; /* The kernel didn't have any data for us to read. */
else
handle_error(fd[i], errno);
} else {
handle_input(fd[i], buf, n);
}
}
}
}
这样我们把 rot13_server.c 用 select() 重新实现一遍
- 示例 rot13_server_select.c
/* For sockaddr_in */
#include
/* For socket functions */
#include
/* For fcntl */
#include
/* for select */
#include
#include
#include
#include
#include
#include
#include
#define MAX_LINE 16384
char rot13_char(char c)
{
/* We don't want to use isalpha here; setting the locale would change
* which characters are considered alphabetical. */
if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
return c + 13;
else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
return c - 13;
else
return c;
}
struct fd_state {
char buffer[MAX_LINE];
size_t buffer_used;
int writing;
size_t n_written;
size_t write_upto;
};
struct fd_state * alloc_fd_state(void)
{
struct fd_state *state = malloc(sizeof(struct fd_state));
if (!state)
return NULL;
state->buffer_used = state->n_written = state->writing =
state->write_upto = 0;
return state;
}
void free_fd_state(struct fd_state *state)
{
free(state);
}
void make_nonblocking(int fd)
{
fcntl(fd, F_SETFL, O_NONBLOCK);
}
int do_read(int fd, struct fd_state *state)
{
char buf[1024];
int i;
ssize_t result;
while (1) {
result = recv(fd, buf, sizeof(buf), 0);
if (result <= 0)
break;
for (i=0; i < result; ++i) {
if (state->buffer_used < sizeof(state->buffer))
state->buffer[state->buffer_used++] = rot13_char(buf[i]);
if (buf[i] == '\n') {
state->writing = 1;
state->write_upto = state->buffer_used;
}
}
}
if (result == 0) {
return 1;
} else if (result < 0) {
if (errno == EAGAIN)
return 0;
return -1;
}
return 0;
}
int do_write(int fd, struct fd_state *state)
{
while (state->n_written < state->write_upto) {
ssize_t result = send(fd, state->buffer + state->n_written,
state->write_upto - state->n_written, 0);
if (result < 0) {
if (errno == EAGAIN)
return 0;
return -1;
}
assert(result != 0);
state->n_written += result;
}
if (state->n_written == state->buffer_used)
state->n_written = state->write_upto = state->buffer_used = 0;
state->writing = 0;
return 0;
}
void run(void)
{
int listener;
struct fd_state *state[FD_SETSIZE];
struct sockaddr_in sin;
int i, maxfd;
fd_set readset, writeset, exset;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(40713);
for (i = 0; i < FD_SETSIZE; ++i)
state[i] = NULL;
listener = socket(AF_INET, SOCK_STREAM, 0);
make_nonblocking(listener);
#ifndef WIN32
{
int one = 1;
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
}
#endif
if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
perror("bind");
return;
}
if (listen(listener, 16)<0) {
perror("listen");
return;
}
FD_ZERO(&readset);
FD_ZERO(&writeset);
FD_ZERO(&exset);
while (1) {
maxfd = listener;
FD_ZERO(&readset);
FD_ZERO(&writeset);
FD_ZERO(&exset);
FD_SET(listener, &readset);
for (i=0; i < FD_SETSIZE; ++i) {
if (state[i]) {
if (i > maxfd)
maxfd = i;
FD_SET(i, &readset);
if (state[i]->writing) {
FD_SET(i, &writeset);
}
}
}
if (select(maxfd+1, &readset, &writeset, &exset, NULL) < 0) {
perror("select");
return;
}
if (FD_ISSET(listener, &readset)) {
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int fd = accept(listener, (struct sockaddr*)&ss, &slen);
if (fd < 0) {
perror("accept");
} else if (fd > FD_SETSIZE) {
close(fd);
} else {
make_nonblocking(fd);
state[fd] = alloc_fd_state();
assert(state[fd]);/*XXX*/
}
}
for (i=0; i < maxfd+1; ++i) {
int r = 0;
if (i == listener)
continue;
if (FD_ISSET(i, &readset)) {
r = do_read(i, state[i]);
}
if (r == 0 && FD_ISSET(i, &writeset)) {
r = do_write(i, state[i]);
}
if (r) {
free_fd_state(state[i]);
state[i] = NULL;
close(i);
}
}
}
}
int main(int c, char **v)
{
setvbuf(stdout, NULL, _IONBF, 0);
run();
return 0;
}
但是,这还没完,由于生成和读取 select() 的 比特数据 (bit array ) 所花费的时间与最大的 fd 成正比,当 socket 的 fd 文件句柄编号很大时, select() 调用的时间会急剧增加。
(在用户空间方面,可以使生成和读取位数组的时间与你为select() 提供的fds的数量成比例。但是在内核空间方面,读取位数组所花费的时间与位数组中的最大fd成正比,不管在程序往select() 中添加了多少文件句柄,这个最大fd 都接近于在整个程序中使用的文件句柄的总数)
不同的操作系统提供了不同的 select() 替换函数以供选择,包括poll(),epoll(),kqueue(),evports 和 /dev/ poll。所有这些函数调用都提供比 select() 更好的性能,除了 poll() 之外,所有这些函数都为 O(1) 的性能来添加 socket ,移除 socket 以及通知 socket 已准备好进行读写。
不幸的是,没有一个有效的接口是普遍存在的标准。 Linux有epoll(),BSD(包括Darwin)有kqueue(),Solaris有evports 和 /dev/poll…,而这些操作系统都没有其他操作系统所拥有的接口。因此,如果要编写可移植的高性能异步应用程序,则需要一个包装所有这些接口的抽象,并能选取其中任何一个最有效的。
这就是 libevent API 所提供的最低级别的功能。它使用运行它的计算机上可用的最高效的版本,为各种select() 替换提供一致的界面。
- 示例, 使用 libevent 来实现 rot13_server: rot13_server_event.c
/* For sockaddr_in */
#include
/* For socket functions */
#include
/* For fcntl */
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_LINE 16384
void do_read(evutil_socket_t fd, short events, void *arg);
void do_write(evutil_socket_t fd, short events, void *arg);
char rot13_char(char c)
{
/* We don't want to use isalpha here; setting the locale would change
* which characters are considered alphabetical. */
if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
return c + 13;
else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
return c - 13;
else
return c;
}
struct fd_state {
char buffer[MAX_LINE];
size_t buffer_used;
size_t n_written;
size_t write_upto;
struct event *read_event;
struct event *write_event;
};
struct fd_state * alloc_fd_state(struct event_base *base, evutil_socket_t fd)
{
struct fd_state *state = malloc(sizeof(struct fd_state));
if (!state)
return NULL;
state->read_event = event_new(base, fd, EV_READ|EV_PERSIST, do_read, state);
if (!state->read_event) {
free(state);
return NULL;
}
state->write_event =
event_new(base, fd, EV_WRITE|EV_PERSIST, do_write, state);
if (!state->write_event) {
event_free(state->read_event);
free(state);
return NULL;
}
state->buffer_used = state->n_written = state->write_upto = 0;
assert(state->write_event);
return state;
}
void free_fd_state(struct fd_state *state)
{
event_free(state->read_event);
event_free(state->write_event);
free(state);
}
void do_read(evutil_socket_t fd, short events, void *arg)
{
struct fd_state *state = arg;
char buf[1024];
int i;
ssize_t result;
while (1) {
assert(state->write_event);
result = recv(fd, buf, sizeof(buf), 0);
if (result <= 0)
break;
for (i=0; i < result; ++i) {
if (state->buffer_used < sizeof(state->buffer))
state->buffer[state->buffer_used++] = rot13_char(buf[i]);
if (buf[i] == '\n') {
assert(state->write_event);
event_add(state->write_event, NULL);
state->write_upto = state->buffer_used;
}
}
}
if (result == 0) {
free_fd_state(state);
} else if (result < 0) {
if (errno == EAGAIN) // XXXX use evutil macro
return;
perror("recv");
free_fd_state(state);
}
}
void do_write(evutil_socket_t fd, short events, void *arg)
{
struct fd_state *state = arg;
while (state->n_written < state->write_upto) {
ssize_t result = send(fd, state->buffer + state->n_written,
state->write_upto - state->n_written, 0);
if (result < 0) {
if (errno == EAGAIN) // XXX use evutil macro
return;
free_fd_state(state);
return;
}
assert(result != 0);
state->n_written += result;
}
if (state->n_written == state->buffer_used)
state->n_written = state->write_upto = state->buffer_used = 1;
event_del(state->write_event);
}
void do_accept(evutil_socket_t listener, short event, void *arg)
{
struct event_base *base = arg;
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int fd = accept(listener, (struct sockaddr*)&ss, &slen);
if (fd < 0) { // XXXX eagain??
perror("accept");
} else if (fd > FD_SETSIZE) {
close(fd); // XXX replace all closes with EVUTIL_CLOSESOCKET */
} else {
struct fd_state *state;
evutil_make_socket_nonblocking(fd);
state = alloc_fd_state(base, fd);
assert(state); /*XXX err*/
assert(state->write_event);
event_add(state->read_event, NULL);
}
}
void run(void)
{
evutil_socket_t listener;
struct sockaddr_in sin;
struct event_base *base;
struct event *listener_event;
base = event_base_new();
if (!base)
return; /*XXXerr*/
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(40713);
listener = socket(AF_INET, SOCK_STREAM, 0);
evutil_make_socket_nonblocking(listener);
#ifndef WIN32
{
int one = 1;
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
}
#endif
if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
perror("bind");
return;
}
if (listen(listener, 16)<0) {
perror("listen");
return;
}
listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
/*XXX check it */
event_add(listener_event, NULL);
event_base_dispatch(base);
}
int main(int c, char **v)
{
setvbuf(stdout, NULL, _IONBF, 0);
run();
return 0;
}
( 代码中需要注意的其他事项:我们不是使用 int 来作为socket 的类型,而是使用 evutil_socket_t 为作为 socket 的类型。不是调用 fcntl(O_NONBLOCK) 来使 socket 成为非阻塞状态,而是调用evutil_make_socket_nonblocking 。这些更改使我们代码与 Win32 网络API的不同部分兼容。)
便利性呢? (Windows呢?)
您可能已经注意到,随着我们的代码变得越来越高效,它也变得越来越复杂。回到 forking 那个版本时,我们不必为每个连接管理缓冲区:我们为每个进程只有一个单独的堆栈分配缓冲区。我们不需要明确地跟踪每个套接字是否正在读取或写入:这在代码中是隐式的。而且,我们不需要一种结构来跟踪每个操作完成了多少:我们只使用了循环和堆栈变量。
此外,如果您对Windows上的联网具有深厚的经验,您会意识到 libevent在如上例中那样使用时可能无法获得最佳性能。在Windows上,执行快速异步IO的方法不是使用类似select() 的接口:而是使用IOCP(IO完成端口)API。与所有快速联网API不同,当 socket 准备好要执行的程序必须执行的操作时,IOCP 不会通知你的程序。而是由程序告诉Windows网络堆栈开始网络操作,而IOCP告诉程序操作何时完成。
幸运的是,Libevent 2“ bufferevents”接口解决了这两个问题:它使程序更易于编写,并提供了一个可以在Windows和Unix上有效实现的接口。
- 示例:使用 Libevent bufferevents API 的更简单的ROT13服务器 rot13_server_event2.c
/* For sockaddr_in */
#include
/* For socket functions */
#include
/* For fcntl */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_LINE 16384
void do_read(evutil_socket_t fd, short events, void *arg);
void do_write(evutil_socket_t fd, short events, void *arg);
char rot13_char(char c)
{
/* We don't want to use isalpha here; setting the locale would change
* which characters are considered alphabetical. */
if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
return c + 13;
else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
return c - 13;
else
return c;
}
void readcb(struct bufferevent *bev, void *ctx)
{
struct evbuffer *input, *output;
char *line;
size_t n;
int i;
input = bufferevent_get_input(bev);
output = bufferevent_get_output(bev);
while ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) {
for (i = 0; i < n; ++i)
line[i] = rot13_char(line[i]);
evbuffer_add(output, line, n);
evbuffer_add(output, "\n", 1);
free(line);
}
if (evbuffer_get_length(input) >= MAX_LINE) {
/* Too long; just process what there is and go on so that the buffer
* doesn't grow infinitely long. */
char buf[1024];
while (evbuffer_get_length(input)) {
int n = evbuffer_remove(input, buf, sizeof(buf));
for (i = 0; i < n; ++i)
buf[i] = rot13_char(buf[i]);
evbuffer_add(output, buf, n);
}
evbuffer_add(output, "\n", 1);
}
}
void errorcb(struct bufferevent *bev, short error, void *ctx)
{
if (error & BEV_EVENT_EOF) {
/* connection has been closed, do any clean up here */
/* ... */
} else if (error & BEV_EVENT_ERROR) {
/* check errno to see what error occurred */
/* ... */
} else if (error & BEV_EVENT_TIMEOUT) {
/* must be a timeout event handle, handle it */
/* ... */
}
bufferevent_free(bev);
}
void do_accept(evutil_socket_t listener, short event, void *arg)
{
struct event_base *base = arg;
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int fd = accept(listener, (struct sockaddr*)&ss, &slen);
if (fd < 0) {
perror("accept");
} else if (fd > FD_SETSIZE) {
close(fd);
} else {
struct bufferevent *bev;
evutil_make_socket_nonblocking(fd);
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, readcb, NULL, errorcb, NULL);
bufferevent_setwatermark(bev, EV_READ, 0, MAX_LINE);
bufferevent_enable(bev, EV_READ|EV_WRITE);
}
}
void run(void)
{
evutil_socket_t listener;
struct sockaddr_in sin;
struct event_base *base;
struct event *listener_event;
base = event_base_new();
if (!base)
return; /*XXXerr*/
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(40713);
listener = socket(AF_INET, SOCK_STREAM, 0);
evutil_make_socket_nonblocking(listener);
#ifndef WIN32
{
int one = 1;
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
}
#endif
if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
perror("bind");
return;
}
if (listen(listener, 16)<0) {
perror("listen");
return;
}
listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
/*XXX check it */
event_add(listener_event, NULL);
event_base_dispatch(base);
}
int main(int c, char **v)
{
setvbuf(stdout, NULL, _IONBF, 0);
run();
return 0;
}
libevent
libevent是用于开发可伸缩网络服务器的事件通知库。 libevent API提供了一种机制,该机制可在文件描述符上发生特定事件或达到超时后执行回调函数。此外,由于信号或定期超时,libevent还支持回调。
libevent用于替换事件驱动的网络服务器中的事件循环。应用程序只需要调用event_dispatch(),然后动态添加或删除事件,而无需更改事件循环。
Libevent还提供了用于缓冲网络IO的复杂框架,并支持套接字,过滤器,速率限制,SSL,零拷贝文件传输和IOCP。 Libevent包括对几种有用协议的支持,包括DNS,HTTP和最小的RPC框架。
- 标准用法
每个使用libevent的程序都必须包含
- 活动通知
对于要监视的每个文件描述符,必须声明一个事件结构并调用event_set() 来初始化该结构的成员。要启用通知,您可以通过调用event_add() 将结构添加到受监视事件的列表中。只要活动结构处于活动状态,它就必须保持分配状态,因此应在堆上进行分配。最后,调用event_dispatch() 循环和调度事件。
- I / O缓冲器
libevent在常规事件回调的基础上提供了一个抽象。这种抽象称为缓冲事件。缓冲事件提供输入和输出缓冲区,这些缓冲区会自动填充和耗尽。缓冲事件的用户不再直接处理I / O,而是从输入读取并写入输出缓冲区。
通过bufferevent_new() 初始化后,可以将bufferevent结构与bufferevent_enable() 和bufferevent_disable() 重复使用。与其直接读写套接字,不如调用bufferevent_read() 和bufferevent_write() 。
启用读取功能后,bufferevent将尝试从文件描述符读取并调用read回调。每当输出缓冲区的水位低于写低水位线(默认为0)时,就会执行写回调。
- 计时器
libevent也可用于创建计时器,该计时器在一定时间后将调用回调。 evtimer_set()函数准备要用作计时器的事件结构。要激活计时器,请调用evtimer_add()。可以通过调用evtimer_del() 禁用计时器。
- 超时时间
除了简单的计时器外,libevent还可将超时事件分配给文件描述符,只要经过一定时间而文件描述符上没有任何活动,就会触发该事件。 timeout_set() 函数初始化事件结构以用作超时。初始化后,必须使用timeout_add() 激活事件。要取消超时,请调用timeout_del() 。
- 异步DNS解析
libevent提供了应该使用的异步DNS解析器,而不是标准的DNS解析器功能。可以通过在程序中包含
- 事件驱动的HTTP服务器
libevent提供了一个非常简单的事件驱动的HTTP服务器,可以将其嵌入程序中并用于服务HTTP请求。
要使用此功能,您需要在程序中包含
- RPC服务器和客户端的框架
libevents提供了用于创建RPC服务器和客户端的框架。它负责封送和解封所有数据结构。
API Reference
主要接口参见如下的头文件
event.h The primary libevent header
evdns.h Asynchronous DNS resolution
evhttp.h An embedded libevent-based HTTP server
evrpc.h A framework for creating RPC servers and clients
编译
- Install openssl
- set environment of openssl
export PKG_CONFIG_PATH="/usr/local/opt/[email protected]/lib/pkgconfig” - cd libevent
- ./configure
- make
- make verify
- make install
以上示例的 make file
- Makefile
http_client:
gcc -g -o http_client http_client.c
rot13:
gcc -g -o rot13_server rot13_server.c
rot13_select:
gcc -g -o rot13_server_select rot13_server_select.c
rot13_event:
gcc -g -o rot13_server_event rot13_server_event.c -levent
rot13_event2:
gcc -g -o rot13_server_event2 rot13_server_event2.c -levent
clean:
rm -f http_client rot13_server rot13_server_select rot13_server_event rot13_server_event2
rot13_server.c
rot13_server_select.c
rot13_server_event.c
rot13_server_event2.c
测试, 先启动 rot13_server, 再用 nc 来简单测试rot13这个应用服务
./rot13_server
$ nc 127.0.0.1 40713
hello
uryyb
uryyb
hello
^C
参考资料
- libevent 官网
- libevent 代码库 https://github.com/libevent/libevent
- http://www.wangafu.net/~nickm/libevent-book/01_intro.html