libevent介绍
libevent是一款事件驱动的网络开发包 由于采用 c 语言开发 体积小巧,跨平台,速度极快。
通常我们在建立服务器的处理模型的时候,主要是下面集中模型;
(1) a new Connection 进来,用 fork() 产生一个 Process 处理。
(2) a new Connection 进来,用 pthread_create() 产生一个 Thread 处理。
(3) a new Connection 进来,丢入 Event-based Array,由 Main Process 以 Nonblocking 的方式处理所有的 I/O。
这三种方法当然也都有各自的缺点:
用 fork() 的问题在于每一个 Connection 进来时的成本太高,如果同时接入的并发连接数太多容易进程数量很多,进程之间的切换开销会很大,同时对于老的内核(Linux)会产生雪崩效应。
用 Multi-thread 的问题在于 Thread-safe 与 Deadlock 问题难以解决,另外有 Memory-leak 的问题要处理,这个问题对于很多程序员来说无异于恶梦,尤其是对于连续服务器的服务器程序更是不可以接受。 如果才用 Event-based 的方式在于实做上不好写,尤其是要注意到事件产生时必须 Nonblocking,于是会需要实做 Buffering 的问题,而 Multi-thread 所会遇到的 Memory-leak 问题在这边会更严重。而在多 CPU 的系统上没有办法使用到所有的 CPU resource。
针对上面存在的问题,通常采用的方法有: 以 Poll 的方式解决:当一个 Process 处理完一个 Connection 后,不直接死掉,而继续回到 accept() 的状态继续处理,但这样会遇到 Memory-leak 的问题,于是采用这种方式的人通常会再加上「处理过 N 个 Connection 后死掉,由 Parent Process 再 fork() 一只新的」。最有名的例子是 Apache 1.3服务器,大家可以参考其源代码的实现。 hread-safe 的问题可以寻找其他 Thread-safe Library 直接使用。Memory-leak 的问题可以试着透过 Garbage Collection Library 分析出来。Apache 2.0 的 Thread MPM 就是使用这个模式。
然而,目前高效率的 Server 都偏好采用 Event-based,一方面是没有 Create Process/Thread 所造成的 Overhead,另外一方面是不需要透过 Shared Memory 或是 Mutex 在不同的 Process/Thread 之间交换资料。然而,Event-based 在实做上的几个复杂的地方在于:
select() 与 poll() 的效率过慢,造成每次要判断「有哪些 Event 发生」这件事情的成本很高,这在 BSD 支援 kqueue()、Linux 支援 epoll()、Solaris 支援 /dev/poll 后就解决了,在Windows平台上通过完成端口的方式解决了.但这两组 Function 都不是 Standard,于是在不同的平台上就必须再改一次。
对于非阻塞的IO模型, 因为 Nonblocking,所以在 write() 或是 send() 时满了需要自己 Buffering。 因为 Nonblocking,所以不能使用 fgets() 或是其他类似的 function,于是需要自己刻一个 Nonblocking 的 fgets()。但是使用者所丢过来的资料又不能保证在一次 read() 或 recv() 就有一行,于是要自己做 Buffering。实际上这三件事情在 libevent 都有 Library 处理掉了.
libevent 是一个事件触发的网络库,适用于windows、linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机 制。著名的用于apache的php缓存库memcached据说也是libevent based,而且libevent在使用上可以做到跨平台,如果你将要开发的应用程序需要支持以上所列出的平台中的两个以上,那么强烈建议你采用这个库,即使你的应用程序只需要支持一个平台,选择libevent也是有好处的,因为它可以根据编译/运行环境切换底层的事件驱动机制,这既能充分发挥系统的性能,又增加了软件的可移植性。它封装并且隔离了事件驱动的底层机制,除了一般的文件描述符读写操作外,它还提供有读写超时、定时器和信号回调,另外,它还允许为事件设定不同的优先级,当前版本的libevent还提供dns和http协议的异步封装,这一切都让这个库尤其适合于事件驱动应用程序的开发。
下面介绍libevent实现的框架
原文请参考:libevent官方网址: http://www.monkey.org/~provos/libevent/
比较好的文档:
http://unx.ca/log/category/libevent/
http://tb.blog.csdn.net/TrackBack.aspx?PostId=1808095
libenvent库的代码结构可以大概分成几个模块:
事件处理框架
事件引擎模块
Buffer管理模块
信号处理模块
1. 事件处理框架
1.1 event_init() 初始化
首先要隆重介绍event_base对象:
struct event_base {
const struct eventop *evsel;
void *evbase;
int event_count; /* counts number of total events */
int event_count_active; /* counts number of active events */
int event_gotterm; /* Set to terminate loop */
/* active event management */
struct event_list **activequeues;
int nactivequeues;
struct event_list eventqueue;
struct timeval event_tv;
RB_HEAD(event_tree, event) timetree;
};
event_base对象整合了事件处理的一些全局变量, 角色是event对象的"总管家", 他包括了事件引擎函数对象(evsel, evbase), 当前入列事件列表(event_count, event_count_active, eventqueue), 全局终止信号(event_gotterm), 活跃事件列表(avtivequeues), 事件队列树(timetree)...初始化时创建event_base对象, 选择 当前OS支持的事件引擎(epoll, poll, select...)并初始化, 创建全局信号队列(signalqueue), 活跃队列的内存分配( 根据设置的priority个数,默认为1).
1.2 event_set() 事件定义
event_set来设置event对象,包括所有者event_base对象, fd, 事件(EV_READ| EV_WRITE), 回掉函数和参数,事件优先级是当前event_base的中间级别(current_base->nactivequeues/2). event对象的定义见下:
struct event {
TAILQ_ENTRY (event) ev_next;
TAILQ_ENTRY (event) ev_active_next;
TAILQ_ENTRY (event) ev_signal_next;
RB_ENTRY (event) ev_timeout_node;
struct event_base *ev_base;
int ev_fd;
short ev_events;
short ev_ncalls;
short *ev_pncalls; /* Allows deletes in callback */
struct timeval ev_timeout;
int ev_pri; /* smaller numbers are higher priority */
void (*ev_callback)(int, short, void *arg);
void *ev_arg;
int ev_res; /* result passed to event callback */
int ev_flags;
};
1.3 event_add() 事件添加:
int event_add(struct event *ev, struct timeval *tv)
这个接口有两个参数, 第一个是要添加的事件, 第二个参数作为事件的超时值(timer). 如果该值非NULL, 在添加本事件的同时添加超时事件(EV_TIMEOUT)到时间队列树(timetree), 根据事件类型处理如下:
EV_READ => EVLIST_INSERTED => eventqueue
EV_WRITE => EVLIST_INSERTED => eventqueue
EV_TIMEOUT => EVLIST_TIMEOUT => timetree
EV_SIGNAL => EVLIST_SIGNAL => signalqueue
1.4 event_base_loop() 事件处理主循环
这里是事件的主循环,只要flags不是设置为EVLOOP_NONBLOCK, 该函数就会一直循环监听事件/处理事件.
每次循环过程中, 都会处理当前触发(活跃)事件:
(a). 检测当前是否有信号处理(gotterm, gotsig), 这些都是全局参数,不适合多线程
(b). 时间更新,找到离当前最近的时间事件, 得到相对超时事件tv
(c). 调用事件引擎的dispatch wait事件触发, 超时值为tv, 触发事件添加到activequeues
(d). 处理活跃事件, 调用caller的callbacks (event_process_acitve)
2. 事件引擎模块 :
Linux下有多种I/O复用机制, .来处理多路事件监听, 常见的有epoll, poll, select, 按照优先级排下来为:
evport
kqueue
epoll
devpoll
rtsig
poll
select
在event_init()选择事件引擎时,按照优先级从上向下检测, 如果检测成功,当前引擎被选中.每个引擎需要定义几个处理函数,以epoll为例:
struct eventop epollops = {
"epoll",
epoll_init,
epoll_add,
epoll_del,
epoll_recalc,
epoll_dispatch,
epoll_dealloc
};
3. Buffer管理模块:
libevent定义了自己的buffer管理机制evbuffer, 支持多种类型数据的read/write功能, 包括不定长字符串,buffer中内存采用预分配/按需分配结合的方式, 可以比较方便的管理多个数据结构映射到内存buffer.
需要拉出来介绍的是evbuffer_expand()函数, 当内部内存不够时,需要expand, 这里采用预分配的方式,如果需要长度<256字节,预分配256字节, 同时内存成倍增长,一直到大于需要的长度.
4. 信号处理模块
信号处理单独提出来,主要是libevent的信号处理比较轻巧, 从而很好融合到event机制.
singal模块初始化(evsignal_init)时, 创建了UNIX域socket ( pipe)作为内部消息传递桥梁:
if (socketpair(AF_UNIX, SOCK_STREAM, 0, ev_signal_pair) == -1)
event_err(1, "%s: socketpair", __func__);
FD_CLOSEONEXEC(ev_signal_pair[0]);
FD_CLOSEONEXEC(ev_signal_pair[1]);
fcntl(ev_signal_pair[0], F_SETFL, O_NONBLOCK);
event_set(&ev_signal, ev_signal_pair[1], EV_READ,
evsignal_cb, &ev_signal);
ev_signal.ev_flags |= EVLIST_INTERNAL;
evsignal_add(), 添加信号事件, 关联信号处理方法(sigaction)
实际运行过程中,如果某singal发生, 对应的信号处理方法被调用, write a character to pipe
同时pipe的另一端被激活, 添加信号到singalqueue, 在事件循环中evsignal_process处理信号callbacks.
libevent库的具体使用方法
直接写一个很简单的 Time Server 来当作例子:当你连上去以后 Server 端直接提供时间,然后结束连线。event_init() 表示初始化 libevent 所使用到的变数。event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev) 把 s 这个 File Description 放入 ev (第一个参数与第二个参数),并且告知当事件 (第三个参数的 EV_READ) 发生时要呼叫 connection_accept() (第四个参数),呼叫时要把 ev 当作参数丢进去 (第五个参数)。其中的 EV_PERSIST 表示当呼叫进去的时候不要把这个 event 拿掉 (继续保留在 Event Queue 里面),这点可以跟 connection_accept() 内在注册 connection_time() 的代码做比较。而 event_add(&ev, NULL) 就是把 ev 注册到 event queue 里面,第二个参数指定的是 Timeout 时间,设定成 NULL 表示忽略这项设定。
注:这段代码来自于网络,虽然很粗糙,但是对libevent的使用方法已经说明的很清楚了.
附源码:使用方法
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <event.h>
#include <stdio.h>
#include <time.h>
void connection_time(int fd, short event, struct event *arg)
{
char buf[32];
struct tm t;
time_t now;
time(&now);
localtime_r(&now, &t);
asctime_r(&t, buf);
write(fd, buf, strlen(buf));
shutdown(fd, SHUT_RDWR);
free(arg);
}
void connection_accept(int fd, short event, void *arg)
{
/* for debugging */
fprintf(stderr, "%s(): fd = %d, event = %d.\n", __func__, fd, event);
/* Accept a new connection. */
struct sockaddr_in s_in;
socklen_t len = sizeof(s_in);
int ns = accept(fd, (struct sockaddr *) &s_in, &len);
if (ns < 0) {
perror("accept");
return;
}
/* Install time server. */
struct event *ev = malloc(sizeof(struct event));
event_set(ev, ns, EV_WRITE, (void *) connection_time, ev);
event_add(ev, NULL);
}
int main(void)
{
/* Request socket. */
int s = socket(PF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("socket");
exit(1);
}
/* bind() */
struct sockaddr_in s_in;
bzero(&s_in, sizeof(s_in));
s_in.sin_family = AF_INET;
s_in.sin_port = htons(7000);
s_in.sin_addr.s_addr = INADDR_ANY;
if (bind(s, (struct sockaddr *) &s_in, sizeof(s_in)) < 0) {
perror("bind");
exit(1);
}
/* listen() */
if (listen(s, 5) < 0) {
perror("listen");
exit(1);
}
/* Initial libevent. */
event_init();
/* Create event. */
struct event ev;
event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev);
/* Add event. */
event_add(&ev, NULL);
event_dispatch();
return 0;
}
在写 Nonblocking Network Program 通常要处理 Buffering 的问题,但并不好写,主要是因为 read() 或 recv() 不保证可以一次读到一行的份量进来。
在 libevent 里面提供相当不错的 Buffer Library 可以用,完整的说明在 man event 的时候可以看到,最常用的应该就是以 evbuffer_add()、evbuffer_readline() 这两个 Function,其他的知道存在就可以了,需要的时候再去看详细的用法。
下面直接提供 libevent-buff.c 当作范例,编译后看执行结果,再回头来看 source code 应该就有感觉了:
#include <sys/time.h>
#include <event.h>
#include <stdio.h>
void printbuf(struct evbuffer *evbuf)
{
for (;;) {
char *buf = evbuffer_readline(evbuf);
printf("* buf = %p, the string = \"\e[1;33m%s\e[m\"\n", buf, buf);
if (buf == NULL)
break;
free(buf);
}
}
int main(void)
{
struct evbuffer *evbuf;
evbuf = evbuffer_new();
if (evbuf == NULL) {
fprintf(stderr, "%s(): evbuffer_new() failed.\n", __func__);
exit(1);
}
/* Add "gslin" into buffer. */
u_char *buf1 = "gslin";
printf("* Add \"\e[1;33m%s\e[m\".\n", buf1);
evbuffer_add(evbuf, buf1, strlen(buf1));
printbuf(evbuf);
u_char *buf2 = " is reading.\nAnd he is at home.\nLast.";
printf("* Add \"\e[1;33m%s\e[m\".\n", buf2);
evbuffer_add(evbuf, buf2, strlen(buf2));
printbuf(evbuf);
evbuffer_free(evbuf);
}
最后的 event_dispatch() 表示进入 event loop,当 Queue 里面的任何一个 File Description 发生事件的时候就会进入 callback function 执行。
相关文章:

蓝色起源载人火箭7月首飞,贝索斯即将实现儿时愿望
整理 | 寇雪芹出品 | AI 科技大本营(ID:rgznai100)头图 | 下载于ICphoto美国当地时间6月7日早,亚马逊创始人、世界首富贝索斯(Jeff Bezos)在社交媒体上发帖表示,自己将在7月20日乘坐蓝色起源(Bl…

使用jquery.more.js来实现点击底部更多后, 底部加载出新的数据
<div class"bus-nav-bar ft12"><div class"navt bor-r-c pos-rel {if $int 0}fwbold{/if}"><a href"portal.php?modmerchant&actionvoucherlist&int0">全部订单</a><em class"pos-abs"></…

ios开发学习-手势交互(Gesture)效果源码分享
qianqianlianmengios开发学习-手势交互(Gesture)效果源码分享 All Around Pull View 介绍:实现视图四个方向(上下左右)都能够拖动更新(pull to refresh)。 编译测试,测试环境…

通过C#实现集合类纵览.NET Collections及相关技术
概述:在真正的对象化开发项目中,我们通常会将常用的业务实体抽象为特定的类,如Employee、Customer、Contact等,而多数的类之间会存在着相应的关联或依存关系,如Employee和Customer通过Contact而产生关联、Contact是依赖…

TIOBE 6 月榜单: Python 有望超越 C 语言成为第一名
整理 | 苏宓出品 | CSDN(ID:CSDNnews)头图 | 下载于ICphotoTIOBE 官方最新发布了 6 月的编程语言榜单,这个月榜单中又有怎样的发展趋势?Python 有望成为第一名在本月榜单中,位居第二名的 Python 与第一名 C…

使用dom4j解析XML例子
包括三个文件:studentInfo.xml(待解析的xml文件), Dom4jReadExmple.java(解析的主要类), TestDom4jReadExmple.java(测试解析的结果) 代码运行前需先导入dom4j架包。 studentInfo.xml文件(该文件放在本项目目录下)内容如下: <?…

mkdir、rmdir命令、head、tail命令
mkdir-p 递归创建目录11里面都是空目录rmdir删除空目录 -p 当子目录被删除后使它也成为空目录的话,则一并删除步骤:先删除11/22/33 发现22目录空了,因为33删了,于是再删22,空了再删11head默认是前10行 –n指定几行tai…

Linux Find 命令精通指南
作者:Sheryl Calish Linux find 命令是所有 Linux 命令中最有用的一个,同时也是最混乱的一个。它很难,因为它的语法与其他 Linux 命令的标准语法不同。但是,它很强大,因为它允许您按文件名、文件类型、用户甚至是时间戳…

【安全运维】 linux 系统账户,网络,简易安全加固方案(第一部分),经测试可行...
前言讲到linux系统账户的管理以及安全,就必须涉及 /etc/passwd /etc/shadow 这2个文件这里以截图中文字说明的方式,来分析这2个文件的内容,并且给出一些实用的安全加固方案注意,本文会持续更新,后续加入的内容都以直…

不用深度学习,怎么提取图像特征?
来源 | 小白学视觉头图 | 下载于ICphoto图像分类是数据科学中最热门的领域之一,在本文中,我们将分享一些将图像转换为特征向量的技术,可以在每个分类模型中使用。VATboxVATbox,作为n一个我们所暗示的,涉及增值税问题&a…

课程第五天内容《基础交换 五》
2019独角兽企业重金招聘Python工程师标准>>> 以太网/LAN(local area network )的相关概念: 问题: 信号传输距离有限; 解决方案: 中继器 - 放大电信号,延长信息的传输距离࿱…

C#综合揭秘——Entity Framework 并发处理详解
引言 在软件开发过程中,并发控制是确保及时纠正由并发操作导致的错误的一种机制。从 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NET Entity Framework,.NET 都为并发控制提供好良好的支持方案。 并发处理方式一般分为乐观必并发与悲观必并发两种࿰…

@2021高考生,用 Python 分析专业“钱景”
来源 | 关于数据分析于可视化头图 | 下载于ICphoto2021年的高考在昨日拉开帷幕,十年的寒窗苦读,终于到了最后见分晓的时候了。在这么一场关键的考试当中,除了考试前努力奋斗,考场上认真答题,考后的志愿填报也是极其的重…

Linux下C语言的fgets与fputs
使用的是 CentOS gcc编译下面程序 显示warning: the gets function is dangerous and should not be used.问题出在程序中使用了 gets Linux 下gcc编译器不支持这个函数,解决办法是使用 fgets fgets()函数的基本用法为: fgets(char * s,int size,FILE * …

linux发行版的用户交互
1 cli,即command line interface 纯命令行的交互方式,该命令行界面是由shell提供的。 linux内核本身也自带了一个console,即linux console,它是基于frame buffer的。 cli的界面都是基于ncurses库开发的。 2 GUI,graphi…

C#中在应用程序和DLL使用消息
在C#中采用的是事件驱动方式,但在我们使用的过程中,有时候通过调用系统原有的消息,处理起来会比较简单一些,特别是在处理与DLL文件的交互时,的确是非常的方便。 在C#中使用自定义消息 在C#中使用自定义消息非常简单&…

倪光南院士:openEuler与全球开发者共同推动计算产业发展
今日,以“创造最好的OS,成就更好的我们”为主题的 openEuler Developer Day 2021在北京成功举行。本次大会由openEuler社区发起,产业组织专家、学者、企业领袖和开发者们围绕多样性计算、云原生全栈、全场景协同等技术方向共同探讨和创新。大…

jhello框架-ajax
2019独角兽企业重金招聘Python工程师标准>>> 现在的web开发没有ajax都没法活,所以在jhello中实现了一个简单的ajax交互,使用json作为载体。 在上篇《交互》中讲到一种交互的方式是把数据放入Model中,通过ModelAndView类返回到视图…

发微信模版消息换行用\n
发微信模版消息换行用\n发微信模版消息换行用\n发微信模版消息换行用\n发微信模版消息换行用\n发微信模版消息换行用\n转载于:https://blog.51cto.com/xuqin/1974131

二次元会让人脸识别失效吗?
来源 | PyTorch 开发者社区责编 | 寇雪芹头图 | 下载于ICphoto人脸识别也遇到坑了,识别得了三次元,却对二次元无效。迪士尼的技术团队,正在开发这一算法,以帮助动画制作者进行后期搜索。团队利用 PyTorch,效率得到很大…

基于第四层交换技术的负载均衡
摘 要 本文介绍了第四层交换技术的概念,技术原理以及如何使用第四层交换技术实现远程教育系统中的应用服务器负载均衡。 作者: 凌仲权,现就读于西安电子科技大学,2001级硕士研究生。主要的科研方向为计算机网络技术研究以及远程教…

開始Unity3D的学习之旅
前言:这个系列的文章纯属对自己学习的整理,非高手之作。但确实的记载了我作为一个没接触过3D游戏编程的大学生的心路历程。争取每周整理一次吧。之所以会開始学Unity3D,最基本的原因是由于在快放暑假的时候,我找了一家做iPhone游戏…

linux 定时任务crond
1.定时任务crond 1.1 crond是什么 crond是linux系统中用来定期执行命令或指定程序任务的一种服务或软件。特殊需求:(秒级别)crond服务就无法搞定了,一般工作中写脚本守护进程执行。 为什么要使用crond定时任务: linux系…

用C#去除代码的SourceSafe管理
经常看一些的程序,有些一个解决方案带有多个项目,由于代码比较多,多人开发,所以好多vs.net下的工程是用source safe进行版本控制的。而用source safe进行版本控制需要局域网路径共享,因此好多项目换一台机器打开会出现…

用jarsigner对android apk进行签名
以前对apk重新打包签名都是用的apktool里面的Auto-sign 工具,后来发现有时候利用该工具对一些apk签名会失败,所以后来就使用 jarsigner这个工具,现记录一下这个工具的使用方法1 首先要生成一个自己的keyD:\>keytool -genkey -alias myKey…

利用java反射机制 读取配置文件 实现动态类载入以及动态类型转换
作者:54dabang 在spring的学习过程之中,我们能够看出通过配置文件来动态管理bean对象的优点(松耦合 能够让零散部分组成一个总体,而这些总体并不在意之间彼此的细节,从而达到了真正的物理上的疏散耦合,而非…

C#操作消息队列
public class QueueManage { /// /// 发送对象到队列中 /// /// 队列名称,因为队列名称在一个应用中应该不改变的,所以大家最好写在配置文件中 /// 要发出去的对象 public static void SendQueue(string QueuePath,MyBase.SmsQueue sq) { Syste…

2021《程序员》数字科技企业研发实力榜TOP50
互联网的盛行带来了众多数字科技企业的崛起,但如何客观地衡量每家企业的技术实力?研发投入、研发人才的数量和人才密度是关键指标。2021年5月,《程序员》根据相关企业披露的财报数据及市场调研数据,整理发布“2021数字科技企业研发实力榜TOP5…

eclipse中java项目转换为web项目
2019独角兽企业重金招聘Python工程师标准>>> 经常在eclipse中导入web项目时,出现转不了项目类型的问题,导入后就是一个java项目,有过很多次经历,今天也有同事遇到类似问题,就把这个解决方法记下来吧&#x…