黑马程序员C语言教程:libevent
- 格式:docx
- 大小:51.40 KB
- 文档页数:10
libevent使用介绍在linux平台上使用c开发网络程序的同志们一般情况下都对鼎鼎大名的libevent非常的熟悉了。
但是一些新进入此领域的new new people们对此都是一头雾水。
原本的迷茫再加上开源软件一贯的“帮助文件”缺失作风,让我们这些新手们显的非常的无助。
幸好有一些热心的朋友们帮忙,才化险为夷啊!前几天一直在开发一个locker server,虽然公司现有的locker server能很好的运转,但是毕竟是net的,通用性不广,当我们要在linux上开发多集群系统的时候现有的locker server 就未免显得有点捉襟见肘了。
正是在开发locker server的过程中使用到了libevent。
总体上,libevent是很好用的。
一两个函数就能搞定你复杂的网络通讯工作了。
当然了,这句话得用在你使用的是“单线程”的情况下。
虽然在linux系统中,进程的资源和window系统中进程的资源相比轻量级很多,代价也相当的没有那么昂贵,所以很多的软件都是使用“多进程”方式实现的,比如大名鼎鼎的apache。
但是在我们的系统中,我们使用了“单进程多线程”的方式,这样,我们就能在单机上启动多个进程,以达到“伪分布式”的效果来达到测试的目的。
那么这个时候就要注意libevent的使用了,因为对于event_base 来说,不是线程安全的。
也就是说多线程不能share同一个event_base,就算是加锁操作也不行。
那么这个时候就只能采取“单线程单event_base”的策略了。
我的做法是做一个task pool(任务对象池),每个任务会被一个thread执行,当然了,thread肯定也是从thread pool拿出来的,而在task pool 初始化的时候,我就给每个task中的event_base初始化了对象,这样,万事大吉了。
这个地方注意了以后,就开始说网络通讯了。
在使用libevent的时候,触发事件是在接收到网络连接(或者timeout事件超时)的时候。
libevent evthread_use_pthreads -回复libevent是一个开源的事件驱动网络库,它提供了一个高效的跨平台的事件驱动模型,用于构建可扩展的网络应用程序。
evthread_use_pthreads 是libevent库中的一个函数,它用于设置使用pthread线程库作为底层的多线程实现。
本文将深入探讨libevent库及其事件驱动模型以及evthread_use_pthreads函数的作用和用法。
一、libevent库和事件驱动模型简介libevent是一个轻量级、高性能、事件驱动的网络库,最初由Nick Mathewson于2000年开发,并在2002年发布。
它的设计目标是提供一个统一的、跨平台的接口,用于处理网络事件和I/O操作,以支持高并发的网络应用程序。
libevent库可以在多个操作系统上运行,包括Windows、Linux、BSD等。
libevent采用了事件驱动的编程模型,通过使用异步的、非阻塞的I/O操作和事件回调机制来处理网络事件。
在传统的阻塞式I/O模型中,每个连接都需要一个独立的线程或进程来处理,当同时有大量的连接时,会消耗大量的系统资源。
而在事件驱动模型中,通过一个主循环不断监听和分发事件,将连接的处理交给事件回调函数,可以大大提高系统的吞吐量和性能。
二、libevent库的特性和用途1. 高性能:libevent采用非阻塞I/O和事件回调机制,能够处理大量的并发连接,实现高性能的网络应用程序。
2. 跨平台:libevent可以在多个操作系统上运行,包括Windows、Linux、BSD、macOS等。
3. 支持多种网络协议:libevent支持多种常用的网络协议,如TCP、UDP、HTTP等,能够满足不同类型的网络应用需求。
4. 支持定时器:libevent提供了定时器功能,可以方便地实现定时任务的调度和执行。
5. 可扩展性:libevent的设计模式允许用户自定义事件源和事件处理器,以满足复杂应用的需求。
黑马程序员C语言教程:深入浅出-服务器高并发库libevent5篇范文第一篇:黑马程序员C语言教程:深入浅出-服务器高并发库libevent标题:深入浅出-服务器高并发库libevent(二)上一章,我们简单介绍了libevent的环境的安装,和简单的事例。
现在先不要着急分析他的代码,在这里我首先要介绍一个专业名词“Reactor 模式”。
2.1 Reactor的事件处理机制我们应该很清楚函数的调用机制。
1.程序调用函数 2.函数执行3.程序等待函数将结果和控制权返回给程序4.程序继续处理和执行Reactor 被翻译成反应堆,或者反应器。
Re-actor 发音。
他是一种事件驱动机制。
和普通函数调用的不同之处在于,应用程序不是主动的调用某刻API完成处理,而是恰恰相反,reactor逆置了事件的处理流程,应用程序需要提供相应的接口注册到reacotr上。
如果相应的事件发生。
Reacotr将主动调用应用程序注册的接口,这些接口就是我们常常说的“回调函数”。
我们使用libevent框架也就是想利用这个框架去注册相应的事件和回调函数。
当这些事件发生时,libevent会调用这些注册好的回调函数处理相应的事件(I/O读写、定时和信号)通过reactor调用函数,不是你主动去调用函数,而是等着系统调用。
一句话:“不用打电话给我们,我么会打电话通知你”。
举个例子,你去应聘某xx公司,面试结束后。
“普通函数调用机制”公司的HR比较懒,不会记你的联系方式,那咋办,你只能面试完自己打电话问结果。
有没有被录取啊,还是被拒绝了。
“Reacotr”公司的HR就记下了你的联系方式,结果出来后HR会主动打电话通知你。
有没有被录取啊,还是悲剧了。
你不用自己打电话去问,实际上你也不能,你没有HR的联系方式。
2.2 Reactor模式的优点Reactor模式是编写高性能网络服务器的必备技术之一,它具有如下的优点:1)响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;2)编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;3)可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;4)可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;2.3 Reactor模式的必备条件1)事件源Linux上是文件描述符,Windows上就是Socket或者Handle 了,这里统一称为“句柄集”;程序在指定的句柄上注册关心的事件,比如I/O事件。
libevent源码分析event_base_dispatch,event_base_lo。
接⼝:event_base_dispatch/**Event dispatching loop 事件分配循环This loop will run the event base until either there are no more pending oractive, or until something calls event_base_loopbreak() orevent_base_loopexit().这个循环将会运⾏event base,知道没有等待的或者活动的事件,或者其它的调⽤了event_base_loopbreak()或event_base_loopexit().@param base the event_base structure returned by event_base_new() orevent_base_new_with_config() event_base_new() 或者 event_base_new_with_config() 返回的event_base对象@return 0 if successful, -1 if an error occurred, or 1 if we exited becauseno events were pending or active. 成功返回0,错误返回-1,或者1(当没有等待的或者活动事件时退出,会返回1)@see event_base_loop()*/EVENT2_EXPORT_SYMBOLint event_base_dispatch(struct event_base *base);intevent_base_dispatch(struct event_base *event_base){return (event_base_loop(event_base, 0));}跟踪event_base_loop接⼝/**Wait for events to become active, and run their callbacks.等待events 变成活动的,并运⾏对应的回调函数。
libevent编译
LibEVENT是一款跨平台的基于BSD下的高性能异步事件驱动库,用于解决异步编程难题。
它能够有效地执行异步I/O或时间事件,可极大提高程序的性能,使之变得优雅。
libevent既可以单独使用,也可以与应用程序(比如web服务器)进行集成,可是以模块的方式实现,做到最大的性能。
LibEvent的编译方法简单易行,有基于Linux、Windows、Mac、BSD等不同操作系统的编译工具。
编译分为configure-make-install三步:
首先,确定libevent的源码包路径,使用linux shell命令进行当前路径切换,cmd进行切换;
其次,执行configure脚本,这是一个自动构建libevent库所需的步骤,命令参数格式如./configure,此步负责生成makefile文件;
最后,执行make命令,此步负责根据makefile文件来编译库文件,编译完成后,安装对应的执行文件。
LibEvent的编译相对来说容易,但需要搭配系统环境,无法满足针对每一种操作系统进行定制。
因此,有时候我们可能需要重新编译libevent,以满足自己应用系统的需求,才能达到最佳性能。
Libevent (单线程)C语言200客户端CPU 88.1%Libevent 4+1线程(event loop per thread) C语言200客户端TPS如下图CPU占用如下图400客户端1000客户端EPOLL(多worker线程,多个线程共享1个epoll,snapshot) C语言CPU 135.6%400客户端1000客户端Mina2(JAVA) 200客户端CPU 254.3%400客户端1000客户端数据汇总:由以上两图可以得出以下结论:1. 从TPS(每秒处理的请求数据上来看),三者没有本质差异,基于JA V A语言的MINA2框架在TPS上绝对不输于C语言EPOLL和LIBEVENT。
2. EPOLL无论在TPS或者CPU占用率方面都占优势。
这是预料之中,毕竟LIBEVENT也好,MINA2也好,在LINUX上面,最终使用也是EPOLL机制,所以只要不犯低级错误,原生的EPOLL在性能和CPU消耗方面必然会占优势。
3. LIBEVENT和MINA2都在EPOLL基础上做了大量的封装工作,比如LIBEVENT的buffer event,Mina2的codec,不可避免造成额外的CPU开销。
其中基于java语言的mina2在CPU 上开销明显大于C语言的其它方案。
甚至有接近2倍的差异。
4. 我们在开发功能复杂的服务器应用之时,一些基本的工作必须要做,比如buffer,比如codec,这些工作我们自己做,或者交由libevent或者mina2来做,始终都要去做,是避免不了的,而且我们自己去实现,本身效率如何也未知。
所以我们才会选择使用更上层的封闭,一方面减少工作量,另一方面,避免无关的错误发生。
从这一点出发,在做技术选型之时,综合取舍,业务简单但对性能极端要求的场景,可以直接用C语言和EPOLL,业务复杂度高工程较大的需求,可以选用JA V A语言和MINA2。
Libevent安装与使⽤(⼀)What the lowest level of the Libevent API does: Provides a consistent interface to various select() replacements, using the most efficient version available on the computer where it’s running.Libevent安装1. 在官⽹上下载对应版本的包2. tar -zxvf /your path/libevent-xxxx-stable.tar.gz解压3. cd libevent-xxxx-stable4. ./configure5. make && make install6. 在/usr/local/lib⽬录下将动态库的符号连接复制到/usr/lib/(这是为了防⽌在系统找不到库⽂件)headers:event2/event.hevent2/bufferevent.h/**************************************************/evutil_socket_t //socket ⽂件描述符类型int evutil_make_socket_nonblocking(evutil_socket_t sock);It sets O_NONBLOCK on Unixand FIONBIO on Windows.int evutil_make_listen_socket_reuseable(evutil_socket_t sock);It sets SO_REUSEADDR on Unix and does nothing on Windows. /**************************************************/struct event_base *event_base_new(void); The event_base_new() function allocates and returns a new event base with the default settings. It examines the environment variables and returns a pointer to a new event_base. If there is an error, it returns NULL.When choosing among methods, it picks the fastest method that the OS supports.function is declared in <event2/event.h>./**************************************************/void event_base_free(struct event_base *base); When you are finished with an event_base, you can deallocate it with event_base_free().Note that this function does not deallocate any of the events that are currently associated with the event_base, or close any oftheir sockets, or free any of their pointers./**************************************************/#define EVLOOP_ONCE 0x01#define EVLOOP_NONBLOCK 0x02#define EVLOOP_NO_EXIT_ON_EMPTY 0x04int event_base_loop(struct event_base *base, int flags); By default, the event_base_loop() function runs an event_base until there are no more events registered in it. To run the loop,it repeatedly checks whether any of the registered events has triggered (for example, if a read event’s file descriptor is ready to read, or if a timeout event’s timeout is ready to expire). Once this happens, it marks all triggered events as "active", and starts torun them.If EVLOOP_ONCE is set, then the loop will wait until some events become active, then run active events until there are no more to run, then return. IfEVLOOP_NONBLOCK is set, then the loop will not wait for events to trigger: it will only check whether any events are ready to trigger immediately, and run their callbacks if so. Ordinarily, the loop will exit as soon as it has no pending or active events.You can override this behavior by passing the EVLOOP_NO_EXIT_ON_EMPTY flag---for example, if you’re going to be adding events from some other thread. If youdo set EVLOOP_NO_EXIT_ON_EMPTY, the loop will keep running until somebody calls event_base_loopbreak(), or callsevent_base_loopexit(), or an error occurs.When it is done, event_base_loop() returns 0 if it exited normally, -1 if it exited because of some unhandled error in the backend,and 1 if it exited because there were no more pending or active events.伪代码:while (any events are registered with the loop,or EVLOOP_NO_EXIT_ON_EMPTY was set) {if (EVLOOP_NONBLOCK was set, or any events are already active)If any registered events have triggered, mark them active.elseWait until at least one event has triggered, and mark it active.for (p = 0; p < n_priorities; ++p) {if (any event with priority of p is active) {Run all active events with priority of p.break; /* Do not run any events of a less important priority */}}if (EVLOOP_ONCE was set or EVLOOP_NONBLOCK was set)break;}/**************************************************/int event_base_dispatch(struct event_base *base); The event_base_dispatch() call is the same as event_base_loop(), with no flags set. Thus, it keeps running until there are no more registered events or until event_base_loopbreak() or event_base_loopexit() is called./**************************************************/If you want an active event loop to stop running before all events are removed from it, you have two slightly different functionsyou can call.int event_base_loopexit(struct event_base *base, const struct timeval *tv);int event_base_loopbreak(struct event_base *base); The event_base_loopexit() function tells an event_base to stop looping after a given time has elapsed. If the tv argument is NULL, the event_base stops looping without a delay. If the event_base is currently running callbacks for any active events, it will continue running them, and not exit until they have all been run.The event_base_loopbreak() function tells the event_base to exit its loop immediately. It differs from event_base_loopexit(base, NULL) in that if the event_base is currently running callbacks for any active events, it will exit immediately after finishing the one it’s currently processingNote also that event_base_loopexit(base,NULL) and event_base_loopbreak(base) act differently when no event loop is running: loopexit schedules the next instance of the event loop to stop right after the next round of callbacks are run (as if it had been invoked with EVLOOP_ONCE) whereas loopbreak only stops a currently running loop, and has no effect if the event loop isn’t running.Both of these methods return 0 on success and -1 on failure./**************************************************/#define EV_TIMEOUT 0x01 //This flag indicates an event that becomes active after a timeout elapses.#define EV_READ 0x02#define EV_WRITE 0x04#define EV_SIGNAL 0x08 //Used to implement signal detection. See "Constructing signal events"#define EV_PERSIST 0x10 //Indicates that the event is persistent. See "About Event Persistence" below#define EV_ET 0x20 //Indicates that the event should be edge-triggered, if the underlying event_base backend supports edge-triggered events. //This affects the semantics of EV_READ and EV_WRITEtypedef void (*event_callback_fn)(evutil_socket_t, short, void *);struct event *event_new(struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb, void *arg);The event_new() function tries to allocate and construct a new event for use with base. The what argument is a set of the flags listed above. (Their semantics are described below.) If fd is nonnegative, it is the file that we’ll observe for read or write events. When the event is active, Libevent will invoke the provided cb function, passing it as arguments: the file descriptor fd, a bitfield of all the events that triggered, and the value that was passed in for arg when the function was constructed.On an internal error, or invalid arguments, event_new() will return NULL.All new events are initialized and non-pending. To make an event pending, call event_add() (documented below).To deallocate an event, call event_free(). It is safe to call event_free() on an event that is pending or active: doing so makes the event non-pending and inactive before deallocating it.About Event PersistenceBy default, whenever a pending event becomes active (because its fd is ready to read or write, or because its timeout expires), it becomes non-pending right before its callback is executed. Thus, if you want to make the event pending again, you can call event_add() on it again from inside the callback function.If the EV_PERSIST flag is set on an event, however, the event is persistent. This means that event remains pending even when its callback is activated. If you want to make it non-pending from within its callback, you can call event_del() on it/**************************************************/int event_assign(struct event *event, struct event_base *base, evutil_socket_t fd, short what, void (*callback)(evutil_socket_t, short, void *), void *arg);All the arguments of event_assign() are as for event_new(), except for the event argument, which must point to an uninitialized event. It returns 0 on success, and -1 on an internal error or bad arguments.⽰例:struct event *ev = event_new(NULL, -1, 0, NULL, NULL);event_assign(ev, base, sockfd, EV_READ | EV_PERSIST,socket_read_cb, (void*)ev);/**************************************************/int event_add(struct event *ev, const struct timeval *tv);Calling event_add on a non-pending event makes it pending in its configured base. The function returns 0 on success, and -1 on failure. If tv is NULL, the event is added with no timeout. Otherwise, tv is the size of the timeout in seconds and microseconds.If you call event_add() on an event that is already pending, it will leave it pending, and reschedule it with the provided timeout. If the event is already pending, and you re-add it with the timeout NULL, event_add() will have no effect.Do not set tv to the time at which you want the timeout to run. If you say "tv->tv_sec = time(NULL)+10;" on 1 January 2010,your timeout will wait 40 years, not 10 seconds.正确⽤法:struct timeval ten_sec;ten__sec = 10;ten__usec = 0;event_add(ev, &ten_sec)/**************************************************/int event_del(struct event *ev);Calling event_del on an initialized event makes it non-pending and non-active. If the event was not pending or active, there is no effect. The return value is 0 on success, -1 on failure.If you delete an event after it becomes active but before its callback has a chance to execute, the callback will not be executed. /**************************************************/void *event_self_cbarg();The event_self_cbarg() function returns a "magic" pointer which, when passed as an event callback argument, tells event_new() to create an event receiving itself as its callback argument./**************************************************/#ifdef WIN32int evthread_use_windows_threads(void);#define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED#endif#ifdef _EVENT_HAVE_PTHREADSint evthread_use_pthreads(void);#define EVTHREAD_USE_PTHREADS_IMPLEMENTED#endifIf you are using the pthreads library, or the native Windows threading code, you’re in luck. There are pre-defined functions that will set Libevent up to use the right pthreads or Windows functions for you.Both functions return 0 on success, and -1 on failure./**************************************************/enum event_method_feature{EV_FEATURE_ET = 0x01, //要求⽀持边沿触发的后端EV_FEATURE_O1 = 0x02, //要求添加、删除单个事件,或者确定哪个事件激活的操作是O(1)复杂度的后端EV_FEATURE_FDS = 0x04, //要求⽀持任意⽂件描述符,⽽不仅仅是套接字的后端};int event_config_require_features(struct event_config *cfg,enum event_method_feature feature);//让 libevent不使⽤不能提供所有指定特征的后端int evutil_make_listen_socket_reuseable(evutil_socket_t sock);This function makes sure that the address used by a listener socket will be available to another socket immediately after the socket is closed. (It sets SO_REUSEADDR on Unix and does nothing on Windows. You don’t want to use SO_REUSEADDR on Windows; it means something different there.)/**************************************************/tips: ⾮阻塞IO:如果输⼊操作不能被满⾜(TCP没有⼀个字节到达,UDP没有⼀个数据报可读)则输⼊操作返回-1 并设置EWOULDBLCOK 错误;如果TCP连接被关闭,则输⼊操作返回0;如果UDP收到长度为0的数据报,则输⼊操作返回0; 对于TCP输出操作,如果发送缓冲区没有空间,则返回-1 并设置EWOULDBLCOK错误;如果发动缓冲区有不⾜的空间,则返回内核能够复制到该缓冲区的字节数,即不⾜字节数。
一、概述1.1 libevent是什么1.2 cmake是什么二、libevent的编译2.1 下载libevent源码2.2 解压源码2.3 创建build目录2.4 使用cmake编译libevent2.4.1 cmake命令的基本语法2.4.2 cmake的常用参数2.4.3 选择编译选项2.4.4 开始编译三、常见问题及解决方案3.1 编译过程中遇到的常见问题3.1.1 找不到依赖库3.1.2 编译错误3.2 解决方案3.2.1 安装依赖库3.2.2 检查编译选项3.2.3 查看编译日志四、接口和应用一、概述1.1 libevent是什么在计算机科学领域,libevent是一个开源的事件通知库,它提供了一个简单的、高效的跨评台事件通知接口,用于实现事件驱动的编程。
它常用于网络应用程序的开发,如服务器端开发,网络爬虫等。
1.2 cmake是什么cmake是一个跨评台的、开源的构建系统,它通过一个描述项目的配置文件CMakeLists.txt来管理整个项目的编译过程。
它可以生成各种不同的构建文件,如Makefile、Visual Studio项目文件等。
二、libevent的编译2.1 下载libevent源码我们需要从libevent全球信息站或者其他途径上下载libevent的源码压缩包,通常为.tar.gz或.zip格式。
2.2 解压源码下载完源码之后,我们需要解压缩文件到我们工作目录中。
解压命令如下:tar -zxvf libevent-x.x.x.tar.gz2.3 创建build目录接下来,我们需要在libevent源码目录下创建一个build目录,用于存放编译生成的文件。
可以使用以下命令创建build目录:mkdir buildcd build2.4 使用cmake编译libevent在build目录中,我们使用cmake命令来配置和编译libevent。
下面是一些常用的cmake命令及参数。
Libevent源码分析—event_add()接下来就是将已经初始化的event注册到libevent的事件链表上,通过event_add()来实现,源码位于event.c中。
event_add()这个函数主要完成了下⾯⼏件事:1.将event注册到event_base的I/O多路复⽤要监听的事件中2.将event注册到event_base的已注册事件链表中3.如果传⼊了超时时间,则删除旧的超时时间,重新设置,并将event添加到event_base的⼩根堆中;如果没有传⼊超时时间,则不会添加到⼩根堆中。
只有步骤1成功,才会执⾏步骤2和3;否则什么都没做,直接返回,保证不会改变event的状态。
从中还可以看到,将event添加到已注册事件链表、添加到⼩根堆、从活跃事件链表移除、从⼩根堆中移除,都是通过两个函数完成的:event_queue_insert()、event_queue_remove()intevent_add(struct event *ev, const struct timeval *tv){struct event_base *base = ev->ev_base; //event所属的event_baseconst struct eventop *evsel = base->evsel; //event_base的I/O多路复⽤机制void *evbase = base->evbase; //event_base的I/O多路复⽤机制int res = 0;//DEBUG log.hevent_debug(("event_add: event: %p, %s%s%scall %p",ev,ev->ev_events & EV_READ ? "EV_READ " : "",ev->ev_events & EV_WRITE ? "EV_WRITE " : "",tv ? "EV_TIMEOUT " : "",ev->ev_callback));assert(!(ev->ev_flags & ~EVLIST_ALL));/** prepare for timeout insertion further below, if we get a* failure on any step, we should not change any state.*///如果传⼊了超时时间并且event不再time⼩根堆上,则在⼩根堆上预留⼀个位置//以保证如果后⾯有步骤失败,不会改变初始状态,保证是个原⼦操作if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {if (min_heap_reserve(&base->timeheap, //min_heap.h1 + min_heap_size(&base->timeheap)) == -1)return (-1); /* ENOMEM == errno */}//如果event不在已注册链表或活跃链表中,//则调⽤evsel->add()注册event事件到I/O多路复⽤监听的事件上if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {res = evsel->add(evbase, ev); //将event注册到监听事件上//注册监听事件成功,则将event注册到已注册事件链表上if (res != -1)event_queue_insert(base, ev, EVLIST_INSERTED); //插⼊}/** we should change the timout state only if the previous event* addition succeeded.*///前⾯操作都成功情况下,才能执⾏下⾯步骤//改变超时状态if (res != -1 && tv != NULL) {struct timeval now;/** we already reserved memory above for the case where we* are not replacing an exisiting timeout.*///EVLIST_TIMEOUT表明event已在定时器堆中//则删除旧的定时器if (ev->ev_flags & EVLIST_TIMEOUT)event_queue_remove(base, ev, EVLIST_TIMEOUT); //移除/* Check if it is active due to a timeout. Rescheduling* this timeout before the callback can be executed* removes it from the active list. *///如果事件是由于超时⽽变成活跃事件//则从活跃事件链表中删除if ((ev->ev_flags & EVLIST_ACTIVE) &&(ev->ev_res & EV_TIMEOUT)) {/* See if we are just active executing thisif (ev->ev_ncalls && ev->ev_pncalls) {/* Abort loop */*ev->ev_pncalls = 0; //调⽤次数清0}//从活跃事件链表移除event_queue_remove(base, ev, EVLIST_ACTIVE); //移除}gettime(base, &now);evutil_timeradd(&now, tv, &ev->ev_timeout); //为event添加超时时间event_debug(("event_add: timeout in %ld seconds, call %p",tv->tv_sec, ev->ev_callback));//将event插⼊到⼩根堆中event_queue_insert(base, ev, EVLIST_TIMEOUT); //插⼊}return (res);}event_queue_insert()该函数根据不同的输⼊队列,即不同的事件,在不同的队列中插⼊,并增加相应的事件计数,更新event状态;EVLIST_INSERTED:在已注册事件链表event_base.eventqueue插⼊EVLIST_ACTIVE:根据event优先级,在活跃事件链表event_base.activequeues[event.ev_pri]插⼊EVLIST_TIMEOUT:在⼩根堆event_base.timeheap中插⼊voidevent_queue_insert(struct event_base *base, struct event *ev, int queue){//如果event已经在活跃链表中,则返回;否则,出错if (ev->ev_flags & queue) {/* Double insertion is possible for active events */if (queue & EVLIST_ACTIVE)return;event_errx(1, "%s: %p(fd %d) already on queue %x", __func__,ev, ev->ev_fd, queue);}if (~ev->ev_flags & EVLIST_INTERNAL)base->event_count++; //增加注册事件数ev->ev_flags |= queue; //改变event状态switch (queue) { //根据不同的输⼊参数队列,选择在不同的事件集合中插⼊case EVLIST_INSERTED: //I/O或Signal事件TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next); //在已注册事件链表插⼊break;case EVLIST_ACTIVE: //活跃事件base->event_count_active++; //增加活跃事件数TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri], //在活跃事件链表插⼊ev,ev_active_next);break;case EVLIST_TIMEOUT: { //定时器事件min_heap_push(&base->timeheap, ev); //在⼩根堆插⼊break;}default:event_errx(1, "%s: unknown queue %x", __func__, queue);}}event_queue_remove()和event_queue_insert()相对应,这个函数主要根据不同的输⼊参数,从不同的事件集合中删除事件。
标题:深入浅出-服务器高并发库libevent (一)1安装libevent是一个开源的高并发服务器开发包,官方地址/ libevent目前有两个版本一个是1.4系列版本,一个是2.0系列版本。
我们可以在官方网站上看到类似有个stable表示稳定版本。
libevent-1.4.15-stable.tar.gz对于初学者学习,建议从1.4版本学起。
在安装libevent之前先判断本电脑是否已经安装了通过指令ls -al /usr/lib|grep libevent如果没有任何信息则表示没有安装,有的话如果发现libevent是1.3以下版本,则可以同过执行rpm -e libevent —nodeps 进行卸载。
如果是其他操作系统使用其他对应卸载指令即可。
对于下好的tar包,通过tar -zxvf libevent-release-1.4.15-stable.tar.gz指令解压。
然后执行./configure命令,但是有的包可能没有configure文件,却存在一个autogen.sh 脚本,运行这个脚本。
(如果运行不起来请安装autoconf包)然后./configure–prefix=/usrmakesudo make install安装完之后执行ls -al /usr/lib/|grep libevent如果发现有libevent文件库存在就代表安装完毕。
2 简单的libevent服务器我们通过连接libevent库来进行管理libevent库,所以在使用gcc或者g++编译的时候最后需要加上-levent下面是一个简单的libevent服务器。
#include <stdio.h>#include <string.h>#include <iostream>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <netdb.h>#include <unistd.h>#include <event.h>using namespace std;#define SERVER_ADDR "127.0.0.1"#define SERVER_PORT 8888// 事件basestruct event_base* base;// 读事件回调函数void onRead(int iCliFd, short iEvent, void *arg) {int iLen;char buf[1500];iLen = recv(iCliFd, buf, 1500, 0);if (iLen <= 0) {cout << "Client Close" << endl;// 连接结束(=0)或连接错误(<0),将事件删除并释放内存空间 struct event *pEvRead = (struct event*)arg; event_del(pEvRead);delete pEvRead;close(iCliFd);return;}buf[iLen] = 0;cout << "Client Info:" << buf << endl;struct bufferevent* buf_ev;buf_ev = bufferevent_new(iCliFd, NULL, NULL, NULL, NULL);buf_ev->wm_read.high = 4096;char MESSAGE[]="welcome to server..";bufferevent_write(buf_ev, MESSAGE, strlen(MESSAGE));}// 连接请求事件回调函数void onAccept(int iSvrFd, short iEvent, void *arg){int iCliFd;struct sockaddr_in sCliAddr;socklen_t iSinSize = sizeof(sCliAddr);iCliFd = accept(iSvrFd, (struct sockaddr*)&sCliAddr,&iSinSize);// 连接注册为新事件 (EV_PERSIST为事件触发后不默认删除)struct event *pEvRead = new event;event_set(pEvRead, iCliFd, EV_READ|EV_PERSIST, onRead, pEvRead);event_base_set(base, pEvRead);event_add(pEvRead, NULL);struct bufferevent* buf_ev;buf_ev = bufferevent_new(iCliFd, NULL, NULL, NULL, NULL); buf_ev->wm_read.high = 4096;char MESSAGE[]="welcome to server..";bufferevent_write(buf_ev, MESSAGE, strlen(MESSAGE));cout<<"a client connect:"<<iCliFd<<endl;}int main(){int iSvrFd;struct sockaddr_in sSvrAddr;memset(&sSvrAddr, 0, sizeof(sSvrAddr));sSvrAddr.sin_family = AF_INET;sSvrAddr.sin_addr.s_addr = inet_addr(SERVER_ADDR); sSvrAddr.sin_port = htons(SERVER_PORT);// 创建tcpSocket(iSvrFd),监听本机8888端口iSvrFd = socket(AF_INET, SOCK_STREAM, 0);bind(iSvrFd, (struct sockaddr*)&sSvrAddr,sizeof(sSvrAddr));listen(iSvrFd, 10);// 初始化basebase = (struct event_base*)event_init();struct event evListen;// 设置事件event_set(&evListen, iSvrFd, EV_READ|EV_PERSIST, onAccept, NULL);// 设置为base事件event_base_set(base, &evListen);// 添加事件event_add(&evListen, NULL);// 事件循环event_base_dispatch(base);return 0;}通过编译指令g++ server.cpp -o server -Wall -g -I ./ -levent得到可执行程序./server启动。
如果能够编译成功并且能够正常启动,说明你操作系统的libevent安装时没问题的。
然后可以通过命令nc 127.0.0.1 8888来进行测试。
或者编写如下客户端代码:/******* 客户端程序 client.c ************/#include <stdlib.h>#include <stdio.h>#include <errno.h>#include <string.h>#include <unistd.h>#include <netdb.h>#include <sys/socket.h>#include <netinet/in.h>#include <sys/types.h>#include <arpa/inet.h>#include<sys/time.h>#include<event.h>#define SERVER_ADDR "127.0.0.1"#define SERVER_PORT 8888int main(int argc, char *argv[]){int sockfd;char buffer[1024];struct sockaddr_in server_addr;struct hostent *host;int portnumber,nbytes;if((host=gethostbyname(SERVER_ADDR))==NULL) {fprintf(stderr,"Gethostname error\n"); exit(1);}if((portnumber=SERVER_PORT)<0){fprintf(stderr,"Usage:%s hostnameportnumber\a\n",argv[0]);exit(1);}/* 客户程序开始建立 sockfd描述符 */if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){fprintf(stderr,"SocketError:%s\a\n",strerror(errno));exit(1);}/* 客户程序填充服务端的资料 */bzero(&server_addr,sizeof(server_addr));server_addr.sin_family=AF_INET;server_addr.sin_port=htons(portnumber);server_addr.sin_addr=*((struct in_addr *)host->h_addr);/* 客户程序发起连接请求 */if(connect(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr))==-1){fprintf(stderr,"ConnectError:%s\a\n",strerror(errno));exit(1);}while(true){char MESSAGE[]="hello server..\n";//bufferevent_write(buf_ev,MESSAGE,strlen(MESSAGE));//if(-1 == (::send(sockfd,MESSAGE,strlen(MESSAGE),0))) {printf("the net has a error occured..");break;}if((nbytes = read(sockfd,buffer,1024))==-1){fprintf(stderr,"readerror:%s\n",strerror(errno));exit(1);}buffer[nbytes]='\0';printf("I have received:%s\n",buffer);memset(buffer,0,1024);sleep(2);}/* 结束通讯 */close(sockfd);exit(0);}通过编译指令g++ client.cpp -o client -Wall -g -I ./ -levent生成客户端程序client 进行测试。