Book: Programming with Libevent(1)--Learning Libevent
Book: Programming with Libevent
Chapter -1:Partial Introduction for Libevent
因为生成和读取 select() 位数组所花费的时间与您为 select() 提供的最大 fd 成正比,所以当套接字数量很高时, select() 调用的缩放比例非常大。
[在用户空间方面,可以使生成和读取位数组花费的时间与您为 select() 提供的 fds 的数量成正比。但是在内核方面,读取位数组所花费的时间与位数组中最大的 fd 成正比,这往往是整个程序中使用的fd总数左右,无论有多少fd添加到集合中选择()。]
不同的操作系统为 select 提供了不同的替换功能。其中包括 poll()、epoll()、kqueue()、evports 和 /dev/poll。所有这些都比 select() 提供了更好的性能,除了 poll() 之外,所有这些都提供了 O(1) 的性能,用于添加套接字、删除套接字以及注意到套接字已准备好进行 IO。
不幸的是,没有一个高效的接口是普遍存在的标准。Linux 有 epoll(),BSD(包括 Darwin)有 kqueue(),Solaris 有 evports 和 /dev/poll …这些操作系统都没有其他任何操作系统。因此,如果您想编写一个可移植的高性能异步应用程序,您将需要一个包含所有这些接口的抽象,并提供其中一个最有效的接口。
这就是最低级别的 Libevent API 为您所做的。它为各种 select() 替换提供了一致的接口,使用它运行的计算机上可用的最有效的版本。
Chapter 0: About this document
http://www.wangafu.net/~nickm/libevent-book/00_about.html
这些文件版权所有 © 2009-2012 by Nick Mathewson,并在知识共享署名-非商业性使用-相同方式共享许可 3.0 版下提供。未来版本可能会在限制较少的许可下提供。
此外,这些文档中的源代码示例也在所谓的“3-Clause”或“Modified”BSD 许可下获得许可。有关完整条款,请参阅 随这些文档一起分发的 license_bsd 文件。
本文档的最新版本参见 http://www.wangafu.net/~nickm/libevent-book/TOC.html
要获取本文档最新版本的源代码,请安装 git 并运行“git clone git://github.com/nmathewson/libevent-book.git”
关于本文档
本文档将教你如何使用 Libevent 2.0(及更高版本)用 C 语言编写快速可移植的异步网络 IO 程序。我们假设:
-
你已经知道 C.
-
您已经了解基本的 C 网络调用(
socket()、connect()等)。
关于示例的说明
本文档中的示例应该可以在 Linux、FreeBSD、OpenBSD、NetBSD、Mac OS X、Solaris 和 Android 上正常运行。某些示例可能无法在 Windows 上编译。
Chapter 1: A tiny introduction to asynchronous IO.
http://www.wangafu.net/~nickm/libevent-book/01_intro.html
异步 IO 简介
大多数初级程序员都是从阻塞 IO 调用开始的。一个 IO 调用是同步的,如果当你调用它时,它在操作完成之前不返回,或者直到你的网络堆栈放弃足够的时间。例如,当您在 TCP 连接上调用connect()时,您的操作系统会将一个 SYN 数据包排队发送到 TCP 连接另一端的主机。它不会将控制权返回给您的应用程序,直到它收到来自对方主机的 SYN ACK 数据包,或者直到它决定放弃的足够时间过去。
这是一个使用阻塞网络调用的非常简单的客户端示例。它打开一个到 www.google.com 的连接,向它发送一个简单的 HTTP 请求,并将响应打印到标准输出。
示例:一个简单的阻塞 HTTP 客户端
/* For sockaddr_in */
#include <netinet/in.h>
/* For socket functions */
#include <sys/socket.h>
/* For gethostbyname */
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main(int c, char **v)
{
const char query[] =
"GET / HTTP/1.0\r\n"
"Host: www.google.com\r\n"
"\r\n";
const char hostname[] = "www.google.com";
struct sockaddr_in sin;
struct hostent *h;
const char *cp;
int fd;
ssize_t n_written, remaining;
char buf[1024];
/* Look up the IP address for the hostname. Watch out; this isn't
threadsafe on most platforms. */
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;
}
/* Allocate a new socket */
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
/* Connect to the remote host. */
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;
}
/* Write the query. */
/* XXX Can send succeed partially? */
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;
}
/* Get an answer back. */
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;
}
上面代码中的所有网络调用都是阻塞的:gethostbyname 在解析 www.google.com 成功或失败之前不会返回;连接在连接之前不会返回;recv 调用在接收到数据或关闭之前不会返回;并且发送调用在至少将其输出刷新到内核的写入缓冲区之前不会返回。
现在,阻塞 IO 不一定是邪恶的。如果在此期间您不希望程序执行任何其他操作,则阻塞 IO 对您来说会很好。但是假设您需要编写一个程序来同时处理多个连接。为了使我们的示例具体:假设您想从两个连接读取输入,并且您不知道哪个连接将首先获得输入。你不能说
坏例子
/* This won't work. */
char buf[1024];
int i, n;
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)
handle_error(fd[i], errno);
else
handle_input(fd[i], buf, n);
}
}
因为如果数据首先到达 fd[2],你的程序甚至不会尝试从 fd[2] 读取,直到从 fd[0] 和 fd[1] 读取一些数据并完成。
有时人们用多线程或多进程服务器来解决这个问题。执行多线程的最简单方法之一是使用单独的进程(或线程)来处理每个连接。由于每个连接都有自己的进程,因此等待一个连接的阻塞 IO 调用不会使任何其他连接的进程阻塞。
这是另一个示例程序。它是一个简单的服务器,它在端口 40713 上侦听 TCP 连接,一次从其输入中读取一行数据,并在每行到达时写出行的 ROT13 混淆。它使用 Unix fork() 调用为每个传入连接创建一个新进程。
/* For sockaddr_in */
#include <netinet/in.h>
/* For socket functions */
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#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 只有几个线程那样有效。
但是,如果线程不是拥有多个连接的答案,那又是什么呢?在 Unix 范式中,您使套接字非阻塞。执行此操作的 Unix 调用是:
fcntl(fd, F_SETFL, O_NONBLOCK);
其中 fd 是套接字的文件描述符。
[文件描述符是内核在打开套接字时分配给套接字的编号。你使用这个数字来进行 Unix 调用并引用套接字。]
一旦你使 fd(套接字)非阻塞,从那时起,每当你对fd进行网络调用时,调用要么立即完成操作,要么返回一个指示"我现在无法取得任何进展,再试一次"的特殊错误代码。因此,我们的双套接字示例可能会天真地写为:
错误示例:忙轮询所有套接字
/* This will work, but the performance will be unforgivably bad. */
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);
}
}
}
现在我们正在使用非阻塞套接字,上面的代码可以 工作……但只是勉强。性能会很糟糕,有两个原因。首先,当任何一个连接上都没有要读取的数据时,循环将无限期旋转,耗尽所有 CPU 周期。其次,如果您尝试使用这种方法处理一两个以上的连接,您将对每个连接进行一次内核调用,无论它是否有任何数据供您使用。所以我们需要一种方法来告诉内核“等到这些套接字中的一个准备好给我一些数据,然后告诉我哪些已经准备好了。”
人们仍然使用的最古老的解决方案是 select()。select() 调用采用三组 fds(实现为位数组):一组用于读取,一组用于写入,一组用于“异常”。它一直等到其中一组中的一个套接字准备就绪,然后将这些组更改为仅包含准备使用的套接字。
这是我们的示例,使用 select:
示例:使用select()
/* If you only have a couple dozen fds, this version won't be awful */
fd_set readset;
int i, n;
char buf[1024];
while (i_still_want_to_read()) {
int maxfd = -1;
FD_ZERO(&readset);
/* Add all of the interesting fds to readset */
for (i=0; i < n_sockets; ++i) {
if (fd[i]>maxfd) maxfd = fd[i];
FD_SET(fd[i], &readset);
}
/* Wait until one or more fds are ready to read */
select(maxfd+1, &readset, NULL, NULL, NULL);
/* Process all of the fds that are still set in 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 服务器的重新实现,这次使用 select()。
示例:基于 select() 的 ROT13 服务器
/* For sockaddr_in */
#include <netinet/in.h>
/* For socket functions */
#include <sys/socket.h>
/* For fcntl */
#include <fcntl.h>
/* for select */
#include <sys/select.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#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() 位数组所花费的时间与您为 select() 提供的最大 fd 成正比,所以当套接字数量很高时, select() 调用的缩放比例非常大。
[在用户空间方面,可以使生成和读取位数组花费的时间与您为 select() 提供的 fds 的数量成正比。但是在内核方面,读取位数组所花费的时间与位数组中最大的 fd 成正比,这往往是整个程序中使用的fd总数左右,无论有多少fd添加到集合中选择()。]
不同的操作系统为 select 提供了不同的替换功能。其中包括 poll()、epoll()、kqueue()、evports 和 /dev/poll。所有这些都比 select() 提供了更好的性能,除了 poll() 之外,所有这些都提供了 O(1) 的性能,用于添加套接字、删除套接字以及注意到套接字已准备好进行 IO。
不幸的是,没有一个高效的接口是普遍存在的标准。Linux 有 epoll(),BSD(包括 Darwin)有 kqueue(),Solaris 有 evports 和 /dev/poll …这些操作系统都没有其他任何操作系统。因此,如果您想编写一个可移植的高性能异步应用程序,您将需要一个包含所有这些接口的抽象,并提供其中一个最有效的接口。
这就是最低级别的 Libevent API 为您所做的。它为各种 select() 替换提供了一致的接口,使用它运行的计算机上可用的最有效的版本。
这是我们异步 ROT13 服务器的另一个版本。这一次,它使用 Libevent 2 而不是 select()。请注意,现在 fd_sets 已经消失了:相反,我们将事件与结构 event_base 关联和解除关联,这可能在 select()、poll()、epoll()、kqueue() 等方面实现。
示例:带有 Libevent 的低级 ROT13 服务器
/* For sockaddr_in */
#include <netinet/in.h>
/* For socket functions */
#include <sys/socket.h>
/* For fcntl */
#include <fcntl.h>
#include <event2/event.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#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;
}
(在代码中需要注意的其他事项:我们使用类型 evutil_socket_t,而不是将套接字键入为“int”。我们调用 evutil_make_socket_nonblocking,而不是调用 fcntl(O_NONBLOCK) 使套接字非阻塞。这些更改使我们的与 Win32 网络 API 的不同部分兼容的代码。)
便利性怎么样?(Windows 呢?)
您可能已经注意到,随着我们的代码变得更加高效,它也变得更加复杂。回到分叉时,我们不必为每个连接管理一个缓冲区:我们只是为每个进程分配了一个单独的堆栈缓冲区。我们不需要明确跟踪每个套接字是在读还是在写:这是隐含在我们在代码中的位置。我们不需要一个结构来跟踪每个操作完成了多少:我们只使用循环和堆栈变量。
此外,如果您对 Windows 上的网络有丰富的经验,您会意识到 Libevent 在如上例中使用时可能无法获得最佳性能。在 Windows 上,您**执行快速异步 IO 的方式不是使用类似 select() 的接口:它是通过使用 IOCP(IO 完成端口)API。**与所有快速网络 API 不同,当套接字准备好执行程序必须执行的操作时,IOCP 不会提醒您的程序。相反,程序会告诉 Windows 网络堆栈开始网络操作,而 IOCP 会在操作完成时告诉程序。
幸运的是,Libevent 2 的bufferevents接口解决了这两个问题:它使程序编写起来更加简单,并且提供了一个 Libevent 可以在 Windows和 Unix 上高效实现的接口。
这是我们的 ROT13 服务器最后一次使用 bufferevents API。
示例:带有 Libevent 的更简单的 ROT13 服务器
/* For sockaddr_in */
#include <netinet/in.h>
/* For socket functions */
#include <sys/socket.h>
/* For fcntl */
#include <fcntl.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#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;
}
浙公网安备 33010602011771号