轻量级网络库libevent初探
本文是关于libevent库第一篇博文,主要由例子来说明如何利用该库。后续博文再深入研究该库原理。
libevent库简介
就如libevent官网上所写的“libevent - an event notification library”,libevent就是一个基于事件通知机制的库,支持/dev/poll、kqueue、event ports、select、poll和epoll事件机制,也因此它是一个跨操作系统的库(支持Linux、*BSD、Mac OS X、Solaris、Windows等)。目前应用该库的有Chromium、Memcached、NTP、tmux等应用。
libevent 库实际上没有更换select()、poll()或其他机制的基础,而是使用对于每个平台最高效的高性能解决方案,在其实现外加上一个包装器。
为了实际处理每个请求,libevent 库提供一种事件机制,它作为底层网络后端的包装器。事件系统让为连接添加处理函数变得非常简便,同时降低了底层 I/O 复杂性。这是 libevent 系统的核心。
libevent 库的其他组件提供其他功能,包括缓冲的事件系统(用于缓冲发送到客户端/从客户端接收的数据)以及 HTTP、DNS 和 RPC 系统的核心实现。
另外,libevent库非常轻量级,这让我们学习它的源码难度低了不少。关于源码分析具体可参考:
如果要生成libevent库的文档,可参考博文使用Doxygen生成libevent document(2.0.15)-- CHM格式。
回显服务端示例
简易流程
创建 libevent 服务器的基本方法是,注册当发生某一操作(比如接受来自客户端的连接)时应该执行的函数,然后调用主事件循环event_base_dispatch()
。执行过程的控制由 libevent系统处理。注册事件和将调用的函数之后,事件系统开始自治;在应用程序运行时,可以在事件队列中添加(注册)或删除(取消注册)事件。事件注册非常方便,可以通过它添加新事件以处理新打开的连接,从而构建灵活的网络处理系统。
例如,可以打开一个监听套接字,然后注册一个回调函数,每当需要调用accept()函数以打开新连接时调用这个回调函数,这样就创建了一个网络服务器。下边所示的代码片段说明了这个基本过程:
int main(int argc, char **argv)
{
/* Declare a socket file descriptor. */
evutil_socket_t listenfd; /* Setup listening socket */ /* Make the listen socket reuseable and non-blocking. */
evutil_make_listen_socket_reuseable(listenfd);
evutil_make_socket_nonblocking(listenfd); /* Declare an event_base to host events. */
struct event_base *base = event_base_new(); /* Register listen event. */
struct event *listen_event;
listen_event = event_new(base, listenfd, EV_READ | EV_PERSIST, do_accetp, (void *)base);
event_add(listen_event, NULL); /* Start the event loop. */
event_base_dispatch(base); /* End. */
close(listenfd);
return ;
}
下边详细介绍上边程序中用到的libevent中的API:
1)evutil_socket_t 定义于Util.h头文件中,用于跨平台表示socket的ID(在Linux下表示的是其文件描述符),如下所示:
/**
* A type wide enough to hold the output of "socket()" or "accept()". On
* Windows, this is an intptr_t; elsewhere, it is an int. */
#ifdef WIN32
#define evutil_socket_t intptr_t
#else
#define evutil_socket_t int
#endif
2)evutil_make_listen_socket_reuseable 函数声明于Util.h,实现于Evutil.c,用于跨平台将socket设置为可重用(实际上是将端口设为可重用,具体可参照博文Linux 套接字编程中的 5 个隐患中的第3个隐患),具体定义如下:
int
evutil_make_listen_socket_reuseable(evutil_socket_t sock)
{
#ifndef WIN32
int one = ;
/* REUSEADDR on Unix means, "don't hang on to this address after the
* listener is closed." On Windows, though, it means "don't keep other
* processes from binding to this address while we're using it. */
return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one,
(ev_socklen_t)sizeof(one));
#else
return ;
#endif
}
同样,evutil_make_socket_nonblocking函数也声明于Util.h,实现于Evutil.c,用于跨平台将socket设置为非阻塞,具体定义如下:
int
evutil_make_socket_nonblocking(evutil_socket_t fd)
{
#ifdef WIN32
{
u_long nonblocking = ;
if (ioctlsocket(fd, FIONBIO, &nonblocking) == SOCKET_ERROR) {
event_sock_warn(fd, "fcntl(%d, F_GETFL)", (int)fd);
return -;
}
}
#else
{
int flags;
if ((flags = fcntl(fd, F_GETFL, NULL)) < ) {
event_warn("fcntl(%d, F_GETFL)", fd);
return -;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -) {
event_warn("fcntl(%d, F_SETFL)", fd);
return -;
}
}
#endif
return ;
}
3)event_base结构体定义在event_internal.h中,它记录了所有的等待和已激活的事件,并当有事件被激活时通知调用者。默认地,我们用event_base_new函数就可以新建一个event_base对象。event_base_new函数的定义如下:
struct event_base *
event_base_new(void)
{
struct event_base *base = NULL;
struct event_config *cfg = event_config_new();
if (cfg) {
base = event_base_new_with_config(cfg);
event_config_free(cfg);
}
return base;
}
也就是说实际上该函数调用了event_base_new_with_config来创建event_base对象,所以我们也可以利用event_config_new和event_base_new_with_config定制event_base对象。
4)event结构体定义在event_struct.h文件中,主要记录事件的相关属性。event_new函数用于创建一个event对象,具体定义如下:
struct event *
event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg)
{
struct event *ev;
ev = mm_malloc(sizeof(struct event));
if (ev == NULL)
return (NULL);
if (event_assign(ev, base, fd, events, cb, arg) < ) {
mm_free(ev);
return (NULL);
} return (ev);
}
// Parameters:
// base the event base to which the event should be attached.
// fd the file descriptor or signal to be monitored, or -1.
// events desired events to monitor: bitfield of EV_READ, EV_WRITE, EV_SIGNAL, EV_PERSIST, EV_ET.
// callback callback function to be invoked when the event occurs
// callback_arg an argument to be passed to the callback function
// Returns:
// a newly allocated struct event that must later be freed with event_free().
在上边程序中,cb是回调函数,其原型如下:
/**
A callback function for an event. It receives three arguments: @param fd An fd or signal
@param events One or more EV_* flags
@param arg A user-supplied argument. @see event_new()
*/
typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
5)event_base_dispatch函数开启事件轮询(event_base_loop提供同样功能,不过更为灵活,实际event_base_dispatch只是event_base_loop的特例),定义如下:
int
event_base_dispatch(struct event_base *event_base)
{
return (event_base_loop(event_base, ));
}
实际例子
一个完整的服务器端的程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h> #include <event2/event.h>
#include <event2/bufferevent.h> #define SERV_PORT 9877
#define LISTEN_BACKLOG 32
#define MAX_LINE 1024 void do_accetp(evutil_socket_t listenfd, 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 argc, int **argv)
{
evutil_socket_t listenfd;
if((listenfd = socket(AF_INET, SOCK_STREAM, )) < )
{
perror("socket\n");
return ;
} struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < )
{
perror("bind\n");
return ;
}
if(listen(listenfd, LISTEN_BACKLOG) < )
{
perror("listen\n");
return ;
} printf("Listening...\n"); evutil_make_listen_socket_reuseable(listenfd);
evutil_make_socket_nonblocking(listenfd); struct event_base *base = event_base_new();
if(base == NULL)
{
perror("event_base\n");
return ;
}
const char *eventMechanism = event_base_get_method(base);
printf("Event mechanism used is %s\n", eventMechanism); struct event *listen_event;
listen_event = event_new(base, listenfd, EV_READ | EV_PERSIST, do_accetp, (void *)base);
event_add(listen_event, NULL);
event_base_dispatch(base); if(close(listenfd) < )
{
perror("close\n");
return ;
}
printf("The End\n");
return ;
} void do_accetp(evutil_socket_t listenfd, short event, void *arg)
{
struct event_base *base = (struct event_base *)arg;
evutil_socket_t fd;
struct sockaddr_in cliaddr;
socklen_t clilen;
fd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);
if(fd < )
{
perror("accept\n");
return;
}
if(fd > FD_SETSIZE)
{
perror("fd > FD_SETSIZE");
if(close(fd) < )
{
perror("close\n");
return;
}
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)
{
char line[MAX_LINE + ];
int n;
evutil_socket_t fd = bufferevent_getfd(bev); while((n = bufferevent_read(bev, line, MAX_LINE)) > )
{
line[n] = '\0';
printf("fd = %u, read line: %s", fd, line);
bufferevent_write(bev, line, 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("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);
} void write_cb(struct bufferevent *bev, void *arg)
{
// leave blank
}
注意:在Linux下编译时需要加libevent静态库event,即gcc ... -levent。
上边程序中用到的bufferevent值得再说明一下。bufferevent由一个底层的传输端口(如套接字)、一个读取缓冲区和一个写入缓冲区组成。与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调。详细可参考博文libevent参考手册第六章:bufferevent:概念和入门。
利用bufferevent的简易流程如下:
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);
bufferevent_setcb使得我们可以定制我们自己的回调函数,这里我们只用到了读和错误回调函数。最后,我们要调用bufferevent_enable来使得bufferevent启动。
客户端示例
客户端用到的libevent的API跟服务端的基本一样。具体程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h> #include <event2/event.h>
#include <event2/bufferevent.h> #define SERV_PORT 9877
#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 < )
{
perror("usage: echocli <IPadress>");
return ;
} evutil_socket_t sockfd;
if((sockfd = socket(AF_INET, SOCK_STREAM, )) < )
{
perror("socket\n");
return ;
} 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[], &servaddr.sin_addr) < )
{
perror("inet_ntop\n");
return ;
}
if(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < )
{
perror("connect\n");
return ;
}
evutil_make_socket_nonblocking(sockfd); printf("Connect to server sucessfully!\n"); struct event_base *base = event_base_new();
if(base == NULL)
{
perror("event_base\n");
return ;
}
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 ;
} void cmd_msg_cb(int fd, short event, void *arg)
{
char msg[MAX_LINE];
int nread = read(fd, msg, sizeof(msg));
if(nread < )
{
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 + ];
int n;
evutil_socket_t fd = bufferevent_getfd(bev); while((n = bufferevent_read(bev, line, MAX_LINE)) > )
{
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);
}
参考资料
A tiny introduction to asynchronous IO
libevent参考手册第六章:bufferevent:概念和入门
轻量级网络库libevent初探的更多相关文章
- 轻量级网络库libevent概况
Libevent is a library for writing fast portable nonblocking IO. libevent是一个为编写快速可移植的非阻塞IO程序而设计的. lib ...
- [原]网络库libevent在Visual Studio中的使用方法
libevent是一个事件触发的网络库,适用于windows.linux.bsd等多种平台,内部使用select.epoll.kqueue等系统调用管理事件机制.著名分布式缓存软件memcached也 ...
- 网络库libevent、libev、libuv对比
Libevent.libev.libuv三个网络库,都是c语言实现的异步事件库Asynchronousevent library). 异步事件库本质上是提供异步事件通知(Asynchronous Ev ...
- [开源] gnet: 一个轻量级且高性能的 Golang 网络库
Github 主页 https://github.com/panjf2000/gnet 欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦. 简介 gnet 是一个基于 Ev ...
- Mudo C++网络库第十一章学习笔记
反思C++面向对象与虚函数 C++语言学习可以看<C++ Primer>这本书; 在C++中进行面向对象编程会遇到其他语言中不存在的问题, 其本质原因是C++ class是值语义, 而非对 ...
- libevent网络库
1.概述 libevent是一个C语言编写的.轻量级开源高性能事件通知库.作为底层网络库,已经被广泛应用(如:memcached.Vomit.Nylon.Netchat等).主要有以下几个亮点: 事件 ...
- 开源网络库ACE、Boost的ASIO、libevent、libev、ZeroMQ
开源C/C++网络库:ACE C++语言 跨平台Boost的ASIO C++语言 跨平台libevent C语言 主要支持linux,新版增加了对windows的IOC ...
- Windows下libevent C++封装类实现(为什么要使用封装好的网络库?)
题记 windows平台下对于服务器高并发的网络模型选型中,使用libevent是个不错的选择. 本文的背景基于:国内博客对于libevent大多介绍linux实现,大多是c语言的实现,Windows ...
- 以libevent网络库为引:网络通信和多线程
1. windows下编译及使用libevent http://www.cnblogs.com/luxiaoxun/p/3603399.html 2. <<libevent学习资料&g ...
随机推荐
- C++语言编译系统提供的内部数据类型的自动隐式转换
C++语言编译系统提供的内部数据类型的自动隐式转换规则如下: 程序在执行算术运算时,低类型自动隐式转换为高类型. 在函数调用时,将实参值赋给形参,系统隐式的将实参转换为形参的类型,并赋值给形参. 函数 ...
- MPAndroidChart——饼图
MPAndroidChart--饼图 MPAndroidChart是安卓下的一个开源图形库,很多效果,简单看几个效果图 Github地址:https://github.com/PhilJay/MPAn ...
- 值集&快速编码(Lookup_code)
--值集 SELECT ffv.flex_value, ffv.description FROM fnd_flex_values_vl ffv, fnd_flex_value_sets ffs ...
- 【移动开发】EditText输入字数限制总结(包括中文输入内存溢出的解决方法)
限定EditText输入个数的解决方案很多,但是一般主要考虑两点,也就是处理两件事:(1)不同语言字符(英文.中文等)处理方式(2)输入字符达到数目后,是否仍然允许用户输入 第一点,涉及的东东其实蛮多 ...
- iOS让软键盘消失的简单方法
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 一些文本输入控件等待输入时会弹出软键盘,我们可以设置这些控件的 ...
- 随机采样和随机模拟:吉布斯采样Gibbs Sampling实现高斯分布参数推断
http://blog.csdn.net/pipisorry/article/details/51539739 吉布斯采样的实现问题 本文主要说明如何通过吉布斯采样来采样截断多维高斯分布的参数(已知一 ...
- Python爬虫! 单爬,批量爬,这都不是事!
昨天做了一个煎蛋网妹子图的爬虫,个人感觉效果不错.但是每次都得重复的敲辣么多的代码(相比于Java或者其他语言的爬虫实现,Python的代码量可谓是相当的少了),就封装了一下!可以实现对批量网址以及单 ...
- 学习TensorFlow,多层卷积神经网络
一.网络结构 二.代码 from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_ ...
- UNIX网络编程——基本TCP套接字编程
一.基于TCP协议的网络程序 下图是基于TCP协议的客户端/服务器程序的一般流程: 服务器调用socket().bind().listen()完成初始化后,调用accept()阻塞等待,处于监听端口的 ...
- Android数据库框架——ORMLite轻量级的对象关系映射(ORM)Java包
Android数据库框架--ORMLite轻量级的对象关系映射(ORM)Java包 事实上,我想写数据库的念头已经很久了,在之前写了一个答题系统的小项目那只是初步的带了一下数据库,数据库是比较强大的, ...