1、背景介绍
轻量级,开源高性能网络库。跨平台,支持Windows、Linux、*BSD和Mac Os;
1)支持用户三种类型的事件(事件驱动(event-driven)):支持网络I/O,定时器和信号等事件。定时器的数据结构使用最小堆(Min Heap),以提高效率。网络IO和信号的数据结构采用了双向链表(TAILQ)。在实现上主要有3种链表:EVLIST_INSERTED, EVLIST_ACTIVE, EVLIST_TIMEOUT,一个ev在这3种链表之间被插入或删除,处于EVLIST_ACTIVE链表中的ev最后将会被调度执行。
2)支持多种I/O多路复用技术, epoll、poll、dev/poll、select和kqueue等;
3)注册事件优先级
2、安装
tar zxvf libevent-2.0.22-stable.tar.gz
cd libevent-2.0.22-stablesudo make install
安装之后,再重启下。
也可以采用以下:
进行安装。apt-cache search libevent 和
apt-get install libevent-dev
3、linux下用qtcreator进行编程的时候注意点
在安装之后,利用Qtcreator进行项目管理的时候,需要在pro文件中添加如下:
正如在gcc中编译的时候,添加如下:
gcc -o basic basic.c -levent。
否则会出现未定义的情况。
4、使用例子(服务器回显):
//创建一个event_base
struct event_base *base = event_base_new();
assert(base != NULL);
struct event_base *base = event_base_new()用以创建一个事件处理的全局变量,可以理解为这是一个负责集中处理各种出入IO事件的总管家,它负责接收和派发所有输入输出IO事件的信息,这里调用的是函数event_base_new(), 很多程序里这里用的是event_init(),区别就是前者是线程安全的、而后者是非线程安全的,后者在其官方说明中已经被标志为过时的函数、且建议用前者代替,libevent中还有很多类似的函数,比如建议用event_base_dispatch代替event_dispatch,用event_assign代替event_set和event_base_set等,关于libevent接口的详细说明见其官方说明libevent_doc。
//创建并绑定一个event
struct event *listen_event;
//参数:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base);
//参数:event,超时时间(struct timeval *类型的,NULL表示无超时设置)
event_add(listen_event, NULL);
//启动事件循环
event_base_dispatch(base);
typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)
整理后的完整代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 25341
#define BACKLOG 5
#define MEM_SIZE 1024
struct event_base* base;
struct sock_ev {
struct event* read_ev;
struct event* write_ev;
char* buffer;
};
void release_sock_event(struct sock_ev* ev)
{
event_del(ev->read_ev);
free(ev->read_ev);
free(ev->write_ev);
free(ev->buffer);
free(ev);
}
void on_accept(int sock, short event, void* arg);
void on_read(int sock, short event, void* arg);
void on_write(int sock, short event, void* arg);
int main(int argc, char* argv[])
{
struct sockaddr_in my_addr;
int sock;
//创建套接字描述符,实质是一个文件描述符
//AF_INET表示使用IP地址,SOCK_STREAM表示使用流式套接字
sock = socket(AF_INET, SOCK_STREAM, 0);
int yes = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
memset(&my_addr, 0, sizeof(my_addr));
//实例化对象的属性
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
//将套接字地址和套接字描述符绑定起来
bind(sock, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));
//监听该套接字,连接的客户端数量最多为BACKLOG
listen(sock, BACKLOG);
//声明事件
struct event listen_ev;
//创建基事件
base = event_base_new();
//设置回调函数.将event对象监听的socket托管给event_base,指定要监听的事件类型,并绑上相应的回调函数
event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL);//
//上述操作说明在listen_ev这个事件监听sock这个描述字的读操作(EV_READ),当读消息到达则调用on_accept函数,EV_PERSIST参数告诉系统持续的监听sock上的读事件,
//不指定这个属性的话,回调函数被触发后,事件会被删除.所以,如果不加该参数,每次要监听该事件时就要重复的调用event_add函数,从前面的代码可知,
//sock这个描述字是bind到本地的socket端口上,因此其对应的可读事件自然就是来自客户端的连接到达,我们就可以调用accept无阻塞的返回客户的连接了。
//使从属于基事件.将listen_ev注册到base这个事件中,相当于告诉处理IO的管家请留意我的listen_ev上的事件。
event_base_set(base, &listen_ev);
//有时候看到使用event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base)代替event_set和event_base_set这两个函数
//添加到事件队列当中.相当于告诉处理IO的管家,当有我的事件到达时你发给我(调用on_accept函数),至此对listen_ev的初始化完毕
event_add(&listen_ev, NULL);
//开始循环.正式启动libevent的事件处理机制,使系统运行起来.event_base_dispatch是一个无限循环
event_base_dispatch(base);
return 0;
}
void on_accept(int sock, short event, void* arg)
{
struct sockaddr_in cli_addr;
int newfd;
socklen_t sin_size;
// read_ev must allocate from heap memory, otherwise the program would crash from segmant fault
struct event* read_ev = (struct event*)malloc(sizeof(struct event));
sin_size = sizeof(struct sockaddr_in);
newfd = accept(sock, (struct sockaddr*)&cli_addr, &sin_size);//指定服务端去接受客户端的连接
//客户的描述字newfd上监听可读事件,当有数据到达是调用on_read函数
event_set(read_ev, newfd, EV_READ|EV_PERSIST, on_read, read_ev);
event_base_set(base, read_ev);
event_add(read_ev, NULL);
//这里需要注意两点,一是read_ev需要从堆里malloc出来,如果是在栈上分配,那么当函数返回时变量占用的内存会被释放,
//因此事件主循环event_base_dispatch会访问无效的内存而导致进程崩溃(即crash);第二个要注意的是event_set中,read_ev作为参数传递给了on_read函数
}
void on_read(int sock, short event, void* arg)
{
struct event* write_ev;
int size;
char* buffer = (char*)malloc(MEM_SIZE);
bzero(buffer, MEM_SIZE);
size = recv(sock, buffer, MEM_SIZE, 0);
printf("receive data:%s, size:%d\n", buffer, size);
if (size == 0)//当从socket读返回0标志,对方已经关闭了连接,因此这个时候就没必要继续监听该套接口上的事件
{
event_del((struct event*)arg);
//由于EV_READ在on_accept函数里是用EV_PERSIST参数注册的,因此要显示的调用event_del函数取消对该事件的监听
free((struct event*)arg);
close(sock);
return;
}
write_ev = (struct event*) malloc(sizeof(struct event));
event_set(write_ev, sock, EV_WRITE, on_write, buffer);//写时调用on_write函数,注意将buffer作为参数传递给了on_write
event_base_set(base, write_ev);
event_add(write_ev, NULL);
}
// on_write函数中向客户端回写数据,然后释放on_read函数中malloc出来的buffer。在很多书合编程指导中都很强调资源的所有权,经常要求谁分配资源、就由谁释放资源,
//这样对资源的管理指责就更明确,不容易出问题,但是通过该例子我们发现在异步编程中资源的分配与释放往往是由不同的所有者操作的,因此也是比较容易出问题的地方。
void on_write(int sock, short event, void* arg)
{
char* buffer = (char*)arg;
send(sock, buffer, strlen(buffer), 0);
free(buffer);
}
再来看看前面提到的on_read函数中存在的问题,首先write_ev是动态分配的内存,但是没有释放,因此存在内存泄漏,另外,on_read中进行malloc操作,那么当多次调用该函数的时候就会造成内存的多次泄漏。这里的解决方法是对socket的描述字可以封装一个结构体来保护读、写的事件以及数据缓冲区,
2)Libevent buffer实现异步传输
在Linux下有epoll,BSDS有kqueue,Solaris有evport和/dev/poll等等可以实现异步传输,但是没有哪一个操作系统拥有他们全部,而libevent就是把这些接口都封装起来,并且无论哪一个系统使用它都是最高效的。
于是代码简化到只需要几行的read_cb和error_cb函数即可:
void read_cb(struct bufferevent *bev, void *arg) {
char line[256];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, 256), n > 0)
bufferevent_write(bev, line, n);
}
void error_cb(struct bufferevent *bev, short event, void *arg) {
bufferevent_free(bev);
}
综合上述,代码如下:
#include
#include
#include
#include
#include
#include
#define LISTEN_PORT 9999
#define LISTEN_BACKLOG 32
void do_accept(evutil_socket_t listener, short event, void *arg);
void read_cb(struct bufferevent *bev, void *arg);
void error_cb(struct bufferevent *bev, short event, void *arg);
void write_cb(struct bufferevent *bev, void *arg);
int main()
{
//int ret;
evutil_socket_t listener;
listener = socket(AF_INET, SOCK_STREAM, 0);
assert(listener > 0);
evutil_make_listen_socket_reuseable(listener);
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(LISTEN_PORT);
if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("bind");
return 1;
}
if (listen(listener, LISTEN_BACKLOG) < 0) {
perror("listen");
return 1;
}
printf ("Listening...\n");
evutil_make_socket_nonblocking(listener);
struct event_base *base = event_base_new();
assert(base != NULL);
struct event *listen_event;
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
event_add(listen_event, NULL);
event_base_dispatch(base);
printf("The End.");
return 0;
}
void do_accept(evutil_socket_t listener, short event, void *arg)
{
struct event_base *base = (struct event_base *)arg;
evutil_socket_t fd;
struct sockaddr_in sin;
socklen_t slen = sizeof(sin);
fd = accept(listener, (struct sockaddr *)&sin, &slen);
if (fd < 0) {
perror("accept");
return;
}
if (fd > FD_SETSIZE) { //这个if是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改
perror("fd > FD_SETSIZE\n");
return;
}
printf("ACCEPT: fd = %u\n", fd);
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST);
}
void read_cb(struct bufferevent *bev, void *arg)
{
#define MAX_LINE 256
char line[MAX_LINE+1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) {
line[n] = '\0';
printf("fd=%u, read line: %s\n", fd, line);
bufferevent_write(bev, line, n);
}
}
void write_cb(struct bufferevent *bev, void *arg) {}
void error_cb(struct bufferevent *bev, short event, void *arg)
{
evutil_socket_t fd = bufferevent_getfd(bev);
printf("fd = %u, ", fd);
if (event & BEV_EVENT_TIMEOUT) {
printf("Timed out\n"); //if bufferevent_set_timeouts() called
}
else if (event & BEV_EVENT_EOF) {
printf("connection closed\n");
}
else if (event & BEV_EVENT_ERROR) {
printf("some other error\n");
}
bufferevent_free(bev);
}
为完整起见,在此分别附上客户端和服务端的代码。
服务端代码:
//server端的代码
#include
#include
#include
#include
#include
#include
#include
#include
#define LISTEN_PORT 9999
#define LISTEN_BACKLOG 32
#define MAX_LINE 256
void do_accept(evutil_socket_t listener, short event, void *arg);
void read_cb(struct bufferevent *bev, void *arg);
void error_cb(struct bufferevent *bev, short event, void *arg);
void write_cb(struct bufferevent *bev, void *arg);
int main()
{
//int ret;
evutil_socket_t listener;//用于跨平台表示socket的ID(在Linux下表示的是其文件描述符)
listener = socket(AF_INET, SOCK_STREAM, 0);
assert(listener > 0);
//用于跨平台将socket设置为可重用(实际上是将端口设为可重用
evutil_make_listen_socket_reuseable(listener);
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(LISTEN_PORT);
if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("bind");
return 1;
}
if (listen(listener, LISTEN_BACKLOG) < 0) {
perror("listen");
return 1;
}
printf ("Listening...\n");
/* 用于跨平台将socket设置为非阻塞,使用bufferevent需要 */
evutil_make_socket_nonblocking(listener);
//主要记录事件的相关属性
struct event_base *base = event_base_new();
assert(base != NULL);
/* Register listen event. */
struct event *listen_event;
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
event_add(listen_event, NULL);
/* Start the event loop. */
event_base_dispatch(base);
printf("The End.");
//close(listener);
return 0;
}
void do_accept(evutil_socket_t listener, short event, void *arg)
{
struct event_base *base = (struct event_base *)arg;
evutil_socket_t fd;
struct sockaddr_in sin;
socklen_t slen = sizeof(sin);
fd = accept(listener, (struct sockaddr *)&sin, &slen);
if (fd < 0) {
perror("accept");
return;
}
if (fd > FD_SETSIZE) { //这个if是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改
perror("fd > FD_SETSIZE\n");
return;
}
printf("ACCEPT: fd = %u\n", fd);
//关联该sockfd,托管给event_base.
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
//设置读写对应的回调函数
bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
//启用读写事件,其实是调用了event_add将相应读写事件加入事件监听队列poll.
//如果相应事件不置为true,bufferevent是不会读写数据的
bufferevent_enable(bev, EV_READ|EV_PERSIST);
// 进入bufferevent_setcb回调函数:
// 在readcb里面从input中读取数据,处理完毕后填充到output中;
// writecb对于服务端程序,只需要readcb就可以了,可以置为NULL;
// errorcb用于处理一些错误信息
}
void read_cb(struct bufferevent *bev, void *arg)
{
char line[MAX_LINE+1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, MAX_LINE), n > 0)
{
line[n] = '\0';
printf("fd=%u, Server gets the message from client read line: %s\n", fd, line);
//直接将读取的结果,不做任何修改(本文是跳过前两个字符),直接返回给客户端
bufferevent_write(bev, line+2, n);//方案1
}
}
void write_cb(struct bufferevent *bev, void *arg)
{
printf("HelloWorld\n");//直接空代码即可,因为这里并不会被触发调用
}
void error_cb(struct bufferevent *bev, short event, void *arg)
{
evutil_socket_t fd = bufferevent_getfd(bev);
printf("fd = %u, ", fd);
if (event & BEV_EVENT_TIMEOUT) {
printf("Timed out\n"); //if bufferevent_set_timeouts() called
}
else if (event & BEV_EVENT_EOF) {
printf("connection closed\n");
}
else if (event & BEV_EVENT_ERROR) {
printf("some other error\n");
}
bufferevent_free(bev);
}
客户端代码:
//客户端代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 9999
#define MAX_LINE 1024
void cmd_msg_cb(int fd, short event, void *arg);
void read_cb(struct bufferevent *bev, void *arg);
void error_cb(struct bufferevent *bev, short event, void *arg);
int main(int argc, char *argv[])
{
if(argc < 2)
{
perror("usage: echocli ");
return 1;
}
evutil_socket_t sockfd;
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket\n");
return 1;
}
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 1)
{
perror("inet_ntop\n");
return 1;
}
if(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
{
perror("connect\n");
return 1;
}
evutil_make_socket_nonblocking(sockfd);
printf("Connect to server sucessfully!\n");
// build event base
struct event_base *base = event_base_new();
if(base == NULL)
{
perror("event_base\n");
return 1;
}
const char *eventMechanism = event_base_get_method(base);
printf("Event mechanism used is %s\n", eventMechanism);
printf("sockfd = %d\n", sockfd);
struct bufferevent *bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);
struct event *ev_cmd;
ev_cmd = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, cmd_msg_cb, (void *)bev);
event_add(ev_cmd, NULL);
bufferevent_setcb(bev, read_cb, NULL, error_cb, (void *)ev_cmd);
bufferevent_enable(bev, EV_READ | EV_PERSIST);
event_base_dispatch(base);
printf("The End.");
return 0;
}
void cmd_msg_cb(int fd, short event, void *arg)
{
char msg[MAX_LINE];
int nread = read(fd, msg, sizeof(msg));
if(nread < 0)
{
perror("stdio read fail\n");
return;
}
struct bufferevent *bev = (struct bufferevent *)arg;
bufferevent_write(bev, msg, nread);
}
void read_cb(struct bufferevent *bev, void *arg)
{
char line[MAX_LINE + 1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while((n = bufferevent_read(bev, line, MAX_LINE)) > 0)
{
line[n] = '\0';
printf("fd = %u, read from server: %s", fd, line);
}
}
void error_cb(struct bufferevent *bev, short event, void *arg)
{
evutil_socket_t fd = bufferevent_getfd(bev);
printf("fd = %u, ", fd);
if(event & BEV_EVENT_TIMEOUT)
printf("Time out.\n"); // if bufferevent_set_timeouts() is called
else if(event & BEV_EVENT_EOF)
printf("Connection closed.\n");
else if(event & BEV_EVENT_ERROR)
printf("Some other error.\n");
bufferevent_free(bev);
struct event *ev = (struct event *)arg;
event_free(ev);
}
运行结果如下所示:本文为了方便区别,将客户端发送到服务端再回显到客户端的过程,在从服务端回显到客户端的时候,去掉前两个字符。如,客户端输入1234567,则从服务端读取到的数据为34567。
客户端的输入和输出结果:
服务端的输入和输出结果:
参考:
http://www.cnblogs.com/cnspace/archive/2011/07/19/2110891.html
http://www.felix021.com/blog/read.php?2068
http://www.bkjia.com/ASPjc/886985.html
http://blog.csdn.net/tujiaw/article/details/8872943
http://popozhu.github.io/2013/06/26/libevent_r5_bufferevent%E5%9F%BA%E7%A1%80%E5%92%8C%E6%A6%82%E5%BF%B5/
http://blog.csdn.net/bestone0213/article/details/46729091
https://www.ibm.com/developerworks/cn/aix/library/au-libev/