LibEvent中文帮助文档--第1、2、3、4章

返回主目录

Libevent

快速可移植非阻塞式网络编程

修订历史

版本

日期

作者

备注

V1.0

2016-11-15

周勇

Libevent编程中文帮助文档

文档是2009-2012年由Nick-Mathewson基于Attribution-Noncommercial-Share Alike许可协议3.0创建,未来版本将会使用约束性更低的许可来创建.

此外,本文档的源代码示例也是基于BSD的"3条款"或"修改"条款.详情请参考BSD文件全部条款.本文档最新下载地址:

英文:http://libevent.org/

中文:http://blog.csdn.net/zhouyongku/article/details/53431597

请下载并运行"gitclonegit://github.com/nmathewson/libevent- book.git"获取本文档描述的最新版本源码.

返回主目录

1.关于本文档

为了更好掌握Libevent(2.0)进行快速可移植的异步IO网络编程,你需要具备:

  • C语言基本知识

  • C语言网络开发函数调用(socket(),connect()等).

2.示例代码注意事项

本文档描述的源码示例需要运行在Linux、FreeBSD、OpenBSD、NetBSD、MacOSX、Solaris、Android这些操作系统中,而Windows环境下编译可能会有一些不兼容的情况发生.

3.一个小的异步IO例子

许多初学者往往都是使用阻塞式IO调用进行编程.当你调用一个同步IO的时候,除非操作系统已经完成了操作或者时间长到你的网络堆栈放弃的时候,否则系统是不会返回完成的.举个例子,当你调用"connect"做一个TCP连接的时候,你的操作系统必须排队处理来自发送到服务器的SYN包,除非等到SYN_ACK包从对面接收到,或者是超时,否则操作是不会返回给你的应用程序.

TCP三次握手

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers).第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手.

这里有一个很简单的阻塞式网络调用的例子,它打开一个连接到www.google.com,发送它简单的HTTP请求,并打印输出到stdout.

示例:一个简单的阻塞式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的时候不等到它成功或者失败不会返回;调用connect的时候不等到它连接成功不会返回;调用recv的时候不等到它接受到数据或关连接关闭的时候不会返回;同样,调用send的时候至少等到待把输出区间待发送数据刷新到内核的写缓冲区之后才会返回.

现在看起来阻塞式IO还没有让人多厌烦,因为当你的程序在处理网络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连接,每次一条一条地将数据读出来,当数据读出来的时候立刻进行R13加密,一条一条地写进输出缓冲区,在这个过程中它调用Unix fork()来创建一个新的过程来处理服务器接受到的连接.

示例:Forking ROT13 服务器

* 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));
}
#endifif (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编程中,你就需要将你的socket设置成为非阻塞模式.Unix的调用方法如下:

fcntl(fd, F_SETFL, O_NONBLOCK);

这里fd代表的是socket的文件描述符.一旦你设置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;其次,如果你想处理一个或两个以上连接,无论有没有数据你都需要为每个连接调用系统内核.所以我们需要一种方法告诉内核:你必须等到其中一个socket已经准备好给我数据才通知我,并且告诉我是哪一个socket.解决这个问题人们常用的是select()方法.Select()方法的调用需要三套fd(数组),一个作为写,一个作为读,一个作为异常.该函数等待socket集合,并且修改这个集合以知道哪些socket可以使用了.

下面是一个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.*/elsehandle_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 16384char rot13_char(char c)
{/* We don’t want to use isalpha here; setting the locale 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;elsereturn 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));}
#endifif (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()的二进制流花费的时间与需要的最大fd成正比,而当socket的数量非常大的时候,select()的花费将会更恐怖.

不同的操作系统提供了不同的替代select()功能的函数,例如poll()、eopll()、kqueqe()、evports和/dev/poll.这些都比select()具有更好的性能,除了poll()之外增加一个套接字、删除一个套接字以及通知套接字已经为IO准备好了这些动作的时间花费都是O(1).

不幸的是,这些都没有一个有效的接口统一标准.Linux有eopll(),BSD(包含Darwin)有kqueue(),Solaris有evports和/dev/poll,除此之外这些操作系统没有别的接口了.所以如果你想要写一个可移植的高效异步处理应用程序,你需要用抽象的方法封装这些所有的接口,并且提供其中最有效的方法.

这些都是LibEvent的API最底层工作的API,提供了可代替select()的各种方法的统一接口,在运行的计算机上使用可用的最有效的版本.

下面有另一个版本的ROT13异步服务器,这一次,我们将使用LibEvent2来代替select(),注意fd_sets已经变为:使用通过select()、poll()、epoll()、kqueue()等一系列函数实现的event_base结构来聚合和分离事件.

示例:一个给予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 wouldchange*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; elsereturn c;
}struct fd_state
{char buffer[MAX_LINE]; size_t buffer_used; size_t n_written; size_twrite_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 macroreturn ; 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 macroreturn ; 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;
}

代码中需要注意:socket的类型定义我们使用evutil_socket_t类型来代替int,使用evutil_make_socket_noblocking来代替fcntl(O_NOBLOCK)生成一个非阻塞式socket.这样使我们的代码能够很好的兼容win32网络API.

3.1怎样才能更方便?(Windows下怎么弄)

你可能也注意到了,随着我们的代码的效率越来越高,代码的也越来越复杂.回到之前,我们不必亲自管理每个连接的缓冲区,而只需要为每个过程分配栈内存.我们也不必去监控socket是可以读还是可写,这些都是自动隐含在我们的代码中的.我们也不需要一个结构体去跟踪每个操作有多少完成了,只需要使用循环和栈变量就够了.

此外,如果你深入Windows网络编程,你可能意识到在上面的例子中LibEvent可能并没有表现出最优的性能.在Windows下要进行高速的异步IO不会使用select()接口,而是使用IOCP(完成端口) API.与其他快速的网络API不同,当socket为操作准备好的时候,程序在即将执行操作之前IOCP是不会通知的.相反,程序会告诉Windows网络栈开始网络操作,IOCP会通知程序操作完成.

幸运的是,LibEvent2的bufferevents接口解决了这两个问题:首先使程序更加简单,其次提供了在Windows和Linux上高效运行的接口.

下面是我们用bufferevents编写的最后一个ROT13服务器.

示例:使用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 changewhich 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;elsereturn 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 sothat 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));}#endifif (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;
}

3.2这一切效率如何,当真?

在这里写了一个高效的代码.libevent页面上的基准是过时了

  • 这些文件是版权(c)2009 - 2012年由尼克·马修森和可用创意下议院Attribution-Noncommercial-Share都许可,3.0版.未来版本

  • 可能会用更少的限制性许可协议.

  • 此外,这些文档的源代码示例都是基于"3条款"或"修改的"BSD许可,请参考license_bsd文件全部条款.

  • 本文档的最新版本,请参阅【http://libevent.org/】

  • 本文档对应的最新版本代码,请安装git然后执行【git clone git://github.com/nmathewson/libevent-book.git】

4.正文前页

4.11000英尺看LibEvent

LibEvent是用于编写高速可移植的非阻塞IO库,它的目标是:

  • 可移植性:使用LibEvent编写的程序应该在LibEvent支持跨越的所有平台上工作,即使没有更好的方法来处理非阻塞式IO:LibEvent也应该支持一般的方法使程序可以运行在某些限制的环境中.

  • 速度:LibEvent试图在每一个平台实现最快的非阻塞式IO,而不会引入太多的额外开销.

  • 可扩展性:LibEvent设计为即使在成千上万的socket情况下也能良好工作.

  • 方便:无论在什么情况下,用LibEvent来编写程序最自然的方式都应该是稳定可靠的.

    LibEvent由下列组件构成:

  • evutil:用于抽象出不同平台网络实现的通用功能.

  • event and event_base:libevent的核心,为各种平台特定的、基于事件的非阻塞 IO后端提供抽象 API,让程序可以知道套接字何时已经准备好,可以读或者写,并且处理基本的超时功能,检测 OS信号.

  • eufferevent:为 libevent基于事件的核心提供使用更方便的封装.除了通知程序套接字已经准备好读写之外,还让程序可以请求缓冲的读写操作,可以知道何时 IO已经真正发生.(bufferevent接口有多个后端,可以采用系统能够提供的更快的非阻塞 IO方式 ,如 Windows中的 IOCP)

  • evbuffer:在 bufferevent层之下实现了缓冲功能,并且提供了方便有效的访问函数.

  • evhttp:一个简单的 HTTP客户端/服务器实现.

  • evdns:一个简单的 DNS客户端/服务器实现.

  • evrpc:一个简单的 RPC实现.

4.2

创建 libevent时,默认安装下列库:

  • libevent_core:所有核心的事件和缓冲功能,包含了所有的 event_base、evbuffer、bufferevent和工具函数.

  • libevent_extra:定义了程序可能需要,也可能不需要的协议特定功能,包括 HTTP、DNS和 RPC.

  • libevent:这个库因为历史原因而存在,它包含 libevent_core和 libevent_extra的内容 .不应该使用这个库未来版本的 libevent可能去掉这个库.

    某些平台上可能安装下列库:

  • libevent_pthreads:添加基于 pthread可移植线程库的线程和锁定实现.它独立于

  • libevent_core,这样程序使用 libevent时就不需要链接到 pthread,除非是以多线程方式使用 libevent.

  • libevent_openssl:这个库为使用 bufferevent和 OpenSSL进行加密的通信提供支持 .它独立于 libevent_core,这样程序使用 libevent时就不需要链接到 OpenSSL,除非是进行加密通信.

4.3头文件

libevent公用头文件都安装在 event2目录中,分为三类:

  • API头文件:定义 libevent公用接口.这类头文件没有特定后缀.

  • 兼容头文件:为已废弃的函数提供兼容的头部包含定义.不应该使用这类头文件,除非是在移植使用较老版本 libevent的程序时.

  • 结构头文件:这类头文件以相对不稳定的布局定义各种结构体.这些结构体中的一些是为了提供快速访问而暴露;一些是因为历史原因而暴露.直接依赖这类头文件中的任何结构体都会破坏程序对其他版本 libevent的二进制兼容性,有时候是以非常难以调试的方式出现.这类头文件具有后缀"_struct.h".(还存在不在 event2目录中的较老版本 libevent的头文件,请参考下节:如果需要使用老版本 libevent)

4.4如果需要使用老版本libevent

libevent 2.0以更合理的、不易出错的方式修正了 API.如果可能,编写新程序时应该使用libevent 2.0.但是有时候可能需要使用较老的 API,例如在升级已存的应用时,或者支持因为某些原因不能安装2.0或者更新版本 libevent的环境时.较老版本的 libevent头文件较少,也不安装在 event2目录中.

老版本头文件

当版本前头文件

event.h

event2/event*.h,event2/buffer*.h,

event2/bufferevent*.h,event2/tag*.h

evdns.h

event2/dns*.h

evhttp.h

event2/http*/

evrpc.h

event2/rpc*.h

evutil.h

event2/util*.h

在2.0以及以后版本的 libevent中,老的头文件仍然会作为新头文件的封装而存在.

其他关于使用较老版本的提示:

  • 1.4版之前只有一个库 libevent,它包含现在分散到 libevent_core和 libevent_extra中的所有功能.

  • 2.0版之前不支持锁定:只有确定不同时在多个线程中使用同一个结构体时,libevent才是线程安全的.

    下面的节还将讨论特定代码区域可能遇到的已经废弃的 API.

4.4.1版本状态告知

之前的LibEvent版本1.4.7应该是已经完全放弃了.版本1.3e之前的也应该是满是bug.(另外,请不要向LibEvent维护者发送任何在1.4x或更早版本的新的特色,这些版本都已经发行了realease版本.如果你在1.3x或更早的版本发现了一个bug,在你提交反馈报告之前请确认在最新版本也出现了这些bug.所以后续版本的发行都是有原因的.)

<<下一章>>





LibEvent中文帮助文档--第1、2、3、4章相关推荐

  1. 13、《Libevent中文帮助文档》学习笔记13:Linux下集成、运行libevent

    Linux下编译libevent的指导可以参考<4.<Libevent中文帮助文档>学习笔记4:Linux下编译libevent>,完成编译.安装,生成so库后,其他程序即可依 ...

  2. VIM7.3添加中文帮助文档

    安装中文帮助文档之前首先执行下列操作: 在home目录下列新建文件夹  : .vim ------------------>.vim是一个隐藏文件,不要漏了 "." .vim ...

  3. 强大的矢量图形库:Raphael JS 中文帮助文档及教程

    Raphael 是一个用于在网页中绘制矢量图形的 Javascript 库.它使用 SVG W3C 推荐标准和 VML 作为创建图形的基础,你可以通过 JavaScript 操作 DOM 来轻松创建出 ...

  4. jQuery1·3中文参考文档下载

    现在才接触jQuery,看来我是很火星了.  附上jQuery1·3中文参考文档下载:http://files.cnblogs.com/conan304/jQueryAPI_CHM.zip 转载于:h ...

  5. ffmpeg的中文学习文档

    ffmpeg的中文学习文档 文章目录: 一.ffmpeg介绍 二.学习参考文档 1.中文 一.ffmpeg介绍 ffmpeg是视频处理工具,可选参数非常多,功能也非常的强大,可以用来开发各种视频处理工 ...

  6. 【转】(六)unity4.6Ugui中文教程文档-------概要-UGUI Animation Integration

    原创至上,移步请戳:(六)unity4.6Ugui中文教程文档-------概要-UGUI Animation Integration 5.Animation Integration(动画集成) 动画 ...

  7. Vitamio中文API文档(1)—— MediaStore

    类概述 public final class MediaStore 媒体存储辅助类. 常量 public static final String AUTHORITY 常量值:me.abitno.vpl ...

  8. 【转】(五)unity4.6Ugui中文教程文档-------概要-UGUI Interaction Components

    原创至上,移步请戳:(五)unity4.6Ugui中文教程文档-------概要-UGUI Interaction Components 4.Interaction Components 本节涵盖了处 ...

  9. poi中文api文档

    POI中文API文档 一. POI简介 Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能. 二. HS ...

  10. EasyUI中文帮助文档

    **EasyUI中文帮助文档* class="preview-iframe" scrolling="no" src="http://www.java1 ...

最新文章

  1. pythonweb静态服务器_Python面向对象之Web静态服务器
  2. 如何完全卸载Java
  3. 手动编译Java Web 的Hibernate的工程
  4. 【汇编语言】8086汇编的loop循环与[bx]寻址(王爽第五章5.5节学习笔记)
  5. 集卡php源码,独角数卡自动发卡网PHP源码+教程:虚拟产品自助售卖
  6. mybatis依赖_这大概就是公司一直用Mybatis的原因!真的太强了
  7. python request headers获取_Python爬虫实战—— Request对象之header伪装策略
  8. 学制两年的计算机考研院校,研究生学制三年合适还是两年实用
  9. WORD宏VBA:标题应用样式+图表统一应用格式且居中
  10. centos7 “v篇n“工具
  11. mysql内表和外表_hive内表和外表的创建、载入数据、区别
  12. OSChina 周六乱弹 —— 召唤养我的富婆
  13. walking机器人仿真教程-应用-多点导航结合闹铃播放实现移动闹钟
  14. BAPI_PO_CREATE1 批量创建采购订单(可一个PO采购多个材料)
  15. 时间分割算法,Java将一段时间按星期来分割
  16. sgu482 Impudent Thief (动态规划)
  17. 【超全】Go语言超详细学习知识体系
  18. Windows 10 安装 Android 13版本的安卓子系统(带 Google Play 商店和 Magisk) 2210.40000.7.0
  19. hive的列分隔符和行分隔符的使用
  20. 2020年计算机科学与技术学校排名,2020年全国计算机科学与技术专业大学排名

热门文章

  1. 如何清理和删除 Docker 镜像
  2. EXCEL闪退的处理方法
  3. LeetCode答案详解
  4. 12万字 | 2021数据安全与个人信息保护技术白皮书(附下载)
  5. MySQL-JDBC
  6. VB程序设计练习题(2022年新)
  7. QQ机器人:群成员自我禁言管理【最新beta2版本】
  8. 乐优商城(04)--商品规格
  9. c盘瘦身(c盘瘦身最简单的方法win10)
  10. 西南科技大学OJ题 A+B Problem 1156