记录学习过程|

梨子Li

园龄:1年9个月粉丝:3关注:0

【libevent】libevent简介

1、Libevent

1.1 简介

Libevent 是一个用C语言编写的、轻量级的开源高性能事件驱动网络库。

基本的socket编程是阻塞/同步的,每个操作除非已经完成或者出错才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没法支撑大量的请求,于是各系统分别提出了基于异步/callback的系统调用,例如Linux的epoll,BSD的kqueue,Windows的IOCP。由于在内核层面做了支持,所以可以用O(1)的效率查找到active的fd。

libevent就是对这些高效IO的封装,提供统一的API,简化开发。

1.2 特点

libevent是一个用于开发可扩展性网络服务器的基于事件驱动(event-driven)模型的网络库。Libevent有几个显著的特点:
(1)事件驱动(event-driven),高性能;
(2)轻量级,专注于网络,不如 ACE 那么臃肿庞大;
(3)跨平台,支持 Windows、Linux、*BSD和 Mac Os;
(4)支持多种I/O多路复用技术,epoll、poll、dev/poll、select和kqueue等;
(5)支持I/O,定时器和信号等事件;
(6)采用Reactor模式;

libevent实现高并发的关键在于其基于事件驱动的异步模型。主要实现方式包括:

  1. 事件驱动: libevent使用事件循环机制,通过监听和响应事件来实现非阻塞IO操作。当有事件发生时,libevent会调用相应的回调函数进行处理,从而避免了传统同步阻塞IO模型中的线程阻塞和资源浪费。
  2. 多路复用: libevent利用操作系统提供的多路复用机制(如epoll、kqueue、select等),将多个IO操作事件集中管理,实现在单线程或少量线程上处理大量并发连接的能力。这样可以避免每个连接都需要一个线程的资源消耗,从而提高了系统的并发性能。
  3. 缓冲区管理: libevent的bufferevent模块提供了高效的缓冲区管理,可以在网络IO中缓冲数据,以减少系统调用次数和数据拷贝,从而提升性能。
  4. 非阻塞IO: libevent使用非阻塞IO操作,通过设置套接字为非阻塞模式,使得IO操作不会阻塞整个进程或线程,提高了处理并发连接的效率。

1.3 下载安装

官网:http://libevent.org/

这里下载的是libevent-2.1.11-stable.tar.gz

tar -zxvf libevent-2.1.11-stable.tar.gz
cd libevent-2.1.11-stable/
./configure --prefix=`pwd`/install #检查安装环境,指定安装路径,生成makefile
make
make install

1.4 测试

在sample文件下有libevent提供的测试程序

#服务器端
cd libevent-2.1.11-stable/sample
./hello-world
#客户端(另开一个窗口)
netcat 127.0.0.1 9995 #收到hello world

2、事件驱动

2.1 简介

事件驱动(Event-Driven)是一种编程范式,系统中的各种操作是通过事件的发生来驱动的。事件驱动编程模型通过事件监听器(Listener)和事件处理器(Handler)来响应和处理各种事件。

事件驱动模型的核心是通过事件(Event)来触发操作。事件可以是用户的操作(如鼠标点击、键盘输入),也可以是系统内部的状态变化(如定时器事件、网络数据到达)。事件驱动编程通过事件循环(Event Loop)不断地等待和处理事件,从而实现系统的响应和交互。

2.2 组成部分

  1. 事件:事件是系统中的一种信号,表示某种操作或状态的发生。事件可以携带额外的信息,如鼠标位置、键盘按键值、网络数据等。
  2. 事件源(Event Source):事件源是事件的产生者,可以是用户输入设备、网络连接、定时器等。
  3. 事件监听器(Event Listener):事件监听器是一个回调函数或方法,用于监听和捕获特定类型的事件。当事件发生时,事件监听器会被触发并调用相应的事件处理器。
  4. 事件处理器(Event Handler):事件处理器是实际处理事件的逻辑,包含具体的业务操作。
  5. 事件循环(Event Loop):事件循环是事件驱动模型的核心,负责不断地等待和分发事件。事件循环通常是一个无限循环,通过检查事件队列或事件多路复用器来获取事件,并调用相应的事件处理器。

2.3 工作流程

  1. 事件注册:应用程序将事件和对应的事件监听器注册到事件源。
  2. 事件产生:当事件源产生事件时,事件被放入事件队列中等待处理
  3. 事件分发:事件循环不断地从事件队列中取出事件,将事件分发给相应的事件监听器。
  4. 事件处理:事件监听器调用事件处理器,执行相应的业务逻辑。
  5. 事件循环继续:事件处理完成后,事件循环继续等待和处理下一个事件。

3、Reactor模式

3.1 简介

Reactor模式是一种高效的事件驱动设计模式,广泛应用于高性能服务器和网络应用中。它通过将事件的检测和分发与实际的事件处理逻辑分离,提供了一种高效的多任务并发处理方法。

3.2 事件处理机制

普通函数调用的机制:

  1. 函数调用:调用一个函数,传递必要的参数。
  2. 执行函数:程序执行进入函数体,按顺序执行函数内的代码。
  3. 返回结果:函数执行完毕后返回结果,程序继续执行调用处之后的代码。
  4. 阻塞执行:在函数执行期间,调用线程会被阻塞,直到函数返回结果。

而事件驱动机制不同,程序不是主动的调用某个API完成处理,恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”

3.3 组成部分

描述符(handle):由操作系统提供,用于识别每一个事件,如Socket描述符、文件描述符等。在Linux中,它用一个整数来表示。事件可以来自外部,如来自客户端的连接请求、数据等。事件也可以来自内部,如定时器事件。

事件多路复用器(Event Demultiplexer):使用操作系统提供的多路复用机制(如select、poll、epoll、kqueue)来监听多个事件源。程序首先将其关心的句柄(事件源)及其事件注册到event demultiplexer上; 当有事件到达时,event demultiplexer会通知程序处理事件。

反应器 Reactor:事件管理的接口,内部使用event demultiplexer注册、注销事件,并运行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数处理事件。(libevent中的event_base结构体)

事件处理器(Event Handler):提供了一组接口(回调函数),每个接口对应了一种类型的事件,供Reactor在相应的事件发生时调用,执行相应的事件处理。

3.4 工作流程

4、基本使用

4.1 事件处理流程

  1. 应用程序准备并初始化event,设置事件类型和回调函数
  2. 向libevent添加事件event。对于定时事件,libevent使用一个小根堆管理,key为超时时间;对于Signal和I/O事件,libevent将其放入到等待链表(wait list)中,这是一个双向链表
  3. 程序调用event_base_dispatch()系列函数进入无限循环,等待事件

5、源码分析

5.1 event_base

libevent默认情况下是单线程的(可以配置成多线程),每个线程有且只有一个event_base,对应一个struct event_base结构体(以及附于其上的事件管理器),用来托管给它的一系列event,可以和操作系统的进程管理类比,当一个事件发生后,event_base会在合适的时间(不一定是立即)去调用绑定在这个事件上的函数,直到这个函数执行完,再返回其他事件。

event_base结构体就是Reactor框架中的反应器Reactor。

结构体声明位于event-internal.h文件中。

//创建一个event_base
struct event_base *base = event_base_new();
assert(base != NULL);

event_base内部有一个循环,循环阻塞在epoll/kqueue等系统调用上,直到有事件发生,然后去处理这些事件。

启动event_base的循环,这样才能开始处理发生的事件。循环的启动使用event_base_dispatch,循环将一直持续,直到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数。

//启动事件循环
event_base_dispatch(base);

使用完 event_base 之后,使用 event_base_free()进行释放。

void event_base_free(struct event_base *base);

5.2 事件event

event是整个libevent的核心。event就是Reactor框架中的事件处理程序组件。它提供了函数接口,供Reactor在事件发生时调用,以执行相应的事件处理。

5.2.1. 事件类型

libevent 支持以下几种主要的事件类型:

  • I/O 事件:用于监视文件描述符(如套接字)的可读、可写状态。
  • 定时事件:在指定的时间间隔之后触发。
  • 信号事件:用于处理 UNIX 信号。

调用 libevent 函数设置事件并且关联到 event_base 之后,事件进入“已初始化(initialized)”状态。此时可以将事件添加到 event_base 中,这使之进入“未决(pending)”状态。

在未决状态下,如果触发事件的条件发生,则事件进入“激活(active)”状态,事件回调函数将被执行。如果配置为“持久的(persistent)”,事件将保持为未决状态。否则,执行完回调后,事件不再是未决的。删除操作可以让未决事件成为非未决(已初始化)的 ; 添加操作可以让非未决事件再次成为未决的。

5.2.2 创建event

每个事件对应一个struct event,struct event使用event_new来创建和绑定,使用event_add来启用:

//创建并绑定一个event
struct event *event;
//参数:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数
event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base);
//参数:event,超时时间(struct timeval *类型的,NULL表示无超时设置)
event_add(event, NULL);

libevent支持的事件及属性包括(使用bitfield实现,所以要用 | 来让它们合体)
(1) EV_TIMEOUT: 超时
(2) EV_READ: 只要网络缓冲中还有数据,回调函数就会被触发
(3) EV_WRITE: 只要塞给网络缓冲的数据被写完,回调函数就会被触发
(4) EV_SIGNAL: POSIX信号量
(5) EV_PERSIST: 不指定这个属性的话,回调函数被触发后事件会被删除
(6) EV_ET: Edge-Trigger边缘触发,参考EPOLL_ET

5.2.3 管理event

每次当有事件event转变为就绪状态时,libevent就会把它移入到active event list[priority]中,其中priority是event的优先级。

libevent会根据自己的调度策略选择就绪事件,调用其callback()函数执行事件处理;并根据就绪的句柄和事件类型填充callback函数的参数

5.2.4 检查事件状态

/*确定给定的事件是否是未决的或者激活的。如果是,而且 what 参 数设置了 EV_READ、EV_WRITE、EV_SIGNAL 或者 EV_TIMEOUT 等标志,则函数会返回事件当前为之未决或者激活的所有标志 。如果提供了 tv_out 参数,并且 what 参数中设置了 EV_TIMEOUT 标志,而事件当前正因超时事件而未决或者激活,则 tv_out 会返回事件的超时值。*/
int event_pending(const struct event *ev, short what, struct timeval *tv_out);
//事件配置的文件描述符
evutil_socket_t event_get_fd(const struct event *ev);
//事件配置的 event_base
struct event_base *event_get_base(const struct event *ev);
//返回事件的标志(EV_READ、EV_WRITE 等)
short event_get_events(const struct event *ev);
//返回事件的回调函数及其参数指针
event_callback_fn event_get_callback(const struct event *ev);
void *event_get_callback_arg(const struct event *ev);

5.2.5 一次触发事件

如果不需要多次添加一个事件,或者要在添加后立即删除事件,而事件又不需要是持久的 , 则可以使用 event_base_once()。

int event_base_once(struct event_base *, evutil_socket_t, short, void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);
  • 除了不支持 EV_SIGNAL 或者 EV_PERSIST 之外,这个函数的接口与 event_new()相同。
  • 安排的事件将以默认的优先级加入到 event_base并执行。回调被执行后,libevent内部将 会释放 event 结构。
  • 不能删除或者手动激活使用 event_base_once ()插入的事件,如果要取消事件, 应该使用 event_new()或者 event_assign()。

5.2.6 事件状态的转换

5.3 事件主循环

在libevent中,事件主循环的作用就是执行一个循环,在循环中监听事件以及超时的事件并且将这些激活的事件进行处理。libevent提供了对用户开放了两种执行事件主循环的函数:

int event_base_dispatch(struct event_base *);
int event_base_loop(struct event_base *, int);

在event_base_dispatch函数中,实际上调用的是event_base_loop(event_base, 0)。

event_base_loop()函数的定义:

int event_base_loop(struct event_base *base, int flags){
const struct eventop *evsel = base->evsel;
void *evbase = base->evbase;
struct timeval tv;
struct timeval *tv_p;
int res, done;
// 清空时间缓存
base->tv_cache.tv_sec = 0;
// evsignal_base是全局变量,在处理signal时,用于指名signal所属的event_base实例
if (base->sig.ev_signal_added)
evsignal_base = base;
done = 0;
while (!done) { // 事件主循环
// 查看是否需要跳出循环,程序可以调用event_loopexit_cb()设置event_gotterm标记
// 调用event_base_loopbreak()设置event_break标记
if (base->event_gotterm) {
base->event_gotterm = 0;
break;
}
if (base->event_break) {
base->event_break = 0;
break;
}
// 校正系统时间,如果系统使用的是非MONOTONIC时间,用户可能会向后调整了系统时间
// 在timeout_correct函数里,比较last wait time和当前时间,如果当前时间< last wait time
// 表明时间有问题,这是需要更新timer_heap中所有定时事件的超时时间。
timeout_correct(base, &tv);
// 根据timer heap中事件的最小超时时间,计算系统I/O demultiplexer的最大等待时间
tv_p = &tv;
if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {
timeout_next(base, &tv_p);
} else {
// 依然有未处理的就绪时间,就让I/O demultiplexer立即返回,不必等待
// 下面会提到,在libevent中,低优先级的就绪事件可能不能立即被处理
evutil_timerclear(&tv);
}
// 如果当前没有注册事件,就退出
if (!event_haveevents(base)) {
event_debug(("%s: no events registered.", __func__));
return (1);
}
// 更新last wait time,并清空time cache
gettime(base, &base->event_tv);
base->tv_cache.tv_sec = 0;
// 调用系统I/O demultiplexer等待就绪I/O events,可能是epoll_wait,或者select等;
// 在evsel->dispatch()中,会把就绪signal event、I/O event插入到激活链表中
res = evsel->dispatch(base, evbase, tv_p);
if (res == -1)
return (-1);
// 将time cache赋值为当前系统时间
gettime(base, &base->tv_cache);
// 检查heap中的timer events,将就绪的timer event从heap上删除,并插入到激活链表中
timeout_process(base);
// 调用event_process_active()处理激活链表中的就绪event,调用其回调函数执行事件处理
// 该函数会寻找最高优先级(priority值越小优先级越高)的激活事件链表,
// 然后处理链表中的所有就绪事件;
// 因此低优先级的就绪事件可能得不到及时处理;
if (base->event_count_active) {
event_process_active(base);
if (!base->event_count_active && (flags & EVLOOP_ONCE))
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
}
// 循环结束,清空时间缓存
base->tv_cache.tv_sec = 0;
event_debug(("%s: asked to terminate loop.", __func__));
return (0);
}

5.4 bufferevent

5.4.1 简介

bufferevent专门为封装成带有缓冲区的socket套接字。一个bufferevent包含了一个底层传输的fd(通常为socket),一个输入buffer和一个输出buffer。每个 bufferevent 有两个与数据相关的回调:读取回调函数和写入回调函数。 bufferevent中回调函数触发的条件与普通的event回调函数触发的条件不同( 普通的事件在底层传输端口已经就绪,可读或可写时就会执行回调函数,而 bufferevent 是在读取或写入了一定量的数据后才会调用回调函数)。

struct bufferevent结构体定义:

//bufferevent_struct.h文件
struct bufferevent {
struct event_base *ev_base; //执行ev_base,最后需要将socket fd添加进ev_base。
//操作结构体,成员有一些函数指针。类似struct eventop结构体
const struct bufferevent_ops *be_ops;
struct event ev_read;//读事件event
struct event ev_write;//写事件event
struct evbuffer *input;//读缓冲区
struct evbuffer *output; //写缓冲区
struct event_watermark wm_read;//读水位
struct event_watermark wm_write;//写水位
bufferevent_data_cb readcb;//可读时的回调函数指针
bufferevent_data_cb writecb;//可写时的回调函数指针
bufferevent_event_cb errorcb;//错误发生时的回调函数指针
void *cbarg;//回调函数的参数
struct timeval timeout_read;//读事件event的超时值
struct timeval timeout_write;//写事件event的超时值
/** Events that are currently enabled: currently EV_READ and EV_WRITE
are supported. */
short enabled;
};

5.4.2 水位线

我们刚刚说bufferevent两个数据相关的回调:一个读取回调和一个写入回调,bufferevent 是在读取或写入了一定量的数据后才会调用回调函数。通过调整bufferevent的读取和写入“水位(watermarks)”可以覆盖这些函数的默认行为。

每个bufferevent有四个水位:

读取低水位:读取操作使得输入缓冲区的数据量在此级别或者更高时,读取回调将被调用。默认值为0,所以每个读取操作都会导致读取回调被调用。

读取高水位:输入缓冲区中的数据量达到此级别后,bufferevent将停止读取,直到输入缓冲区中足够量的数据被抽取,使得数据量低于此级别。默认值是无限,所以永远不会因为输入缓冲区的大小而停止读取。

写入低水位:写入操作使得输出缓冲区的数据量达到或者低于此级别时,写入回调将被调用。默认值是0,所以只有输出缓冲区空的时候才会调用写入回调。

写入高水位:bufferevent没有直接使用这个水位。它在bufferevent用作另外一个bufferevent的底层传输端口时有特殊意义。

5.4.3 evbuffer

bufferevent采用evbuffer作为输入输出缓存。evbuffer像是一个字节队列,在队列的末尾写入数据,在队列的头部读取数据。evbuffer具体实现则是一个链表,链表中的每个节点都是一块连续的内存块,往evbuffer写数据时(调用evbuffer_add/evbuffer_add_printf等函数),evbuffer内部动态创建链表节点,并紧凑的写入数据(一个节点写满后,再写另外一个节点);从evbuffer中删除数据时(调用evbuffer_remove/evbuffer_drain),从链表头部节点开始读取,当一个节点的数据被全部读取后删除该节点,如果未读取完,则用标示记录数据已读取(删除)的位置。对于这种头部有数据被标示为读取(删除)的节点,再次写入数据时,可能会进行调整,即将数据部分整体往前拷贝移动,然后再继续写入数据。

关于evbuffer和bufferevent的详细介绍可以参考:http://t.csdnimg.cn/rit2P

5.4.4 bufferevent 选项标志

  • BEV_OPT_CLOSE_ON_FREE :释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字,释放底层 bufferevent 等。
  • BEV_OPT_THREADSAFE :自动为 bufferevent 分配锁,这样就可以安全地在多个线程中使用 bufferevent。
  • BEV_OPT_DEFER_CALLBACKS :设置这个标志时, bufferevent 延迟所有回调,如上所述。
  • BEV_OPT_UNLOCK_CALLBACKS :默认情况下,如果设置 bufferevent 为线程安全 的,则 bufferevent 会在调用用户提供的回调时进行锁定。设置这个选项会让 libevent 在执行回调的时候不进行锁定。

5.4.5 使用

5.4.5.4 创建bufferevent

struct bufferevent *bufferevent_socket_new(
struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options);

5.4.5.5 连接

int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen);

address 和 addrlen 参数跟标准调用 connect()的参数相同。如果还没有为 bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的。

如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知 libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。

5.5 evbuffer

5.5.1 简介

Libevent提供了evbuffer用于处理缓冲网络IO的缓冲部分,为后续bufferevent的工作做准备。

缓冲区由evbuffer和evbuffer_chain组成,其中evbuffer_chain是真正存储数据的一块内存,通过链表将一个一个的evbuffer_chain连接在一起,组成内存池,可以存放很多的缓冲数据。。而通过evbuffer结构体就可以管理这个内存池链表了。

struct evbuffer结构体定义如下:

//evbuffer-internal.h文件
struct evbuffer_chain;
struct evbuffer {
struct evbuffer_chain *first;
struct evbuffer_chain *last;
//这是一个二级指针。使用*last_with_datap时,指向的是链表中最后一个有数据的evbuffer_chain。
//所以last_with_datap存储的是倒数第二个evbuffer_chain的next成员地址。
//一开始buffer->last_with_datap = &buffer->first;此时first为NULL。所以当链表没有节点时
//*last_with_datap为NULL。当只有一个节点时*last_with_datap就是first。
struct evbuffer_chain **last_with_datap;
size_t total_len;//链表中所有chain的总字节数
...
};
struct evbuffer_chain {
struct evbuffer_chain *next;
size_t buffer_len;//buffer的大小
//错开不使用的空间。该成员的值一般等于0
ev_off_t misalign;
//evbuffer_chain已存数据的字节数
//所以要从buffer + misalign + off的位置开始写入数据
size_t off;
...
unsigned char *buffer;
};

5.5.2 使用

5.5.2.1 创建和释放evbuffer

struct evbuffer *evbuffer_new(void);
void evbuffer_free(struct evbuffer *buf);

5.5.2.2 线程安全

int evbuffer_enable_locking(struct evbuffer *buf, void *lock);
void evbuffer_lock(struct evbuffer *buf);
void evbuffer_unlock(struct evbuffer *buf);

在多个线程中同时访问 evbuffer 是不安全的。如果需要这样的访问,可以调 用 evbuffer_enable_locking() 。如果 lock 参数为 NULL , libevent 会使用evthread_set_lock_creation_callback 提供的锁创建函数创建一个锁 。否则,libevent 将 lock 参数用作锁。

evbuffer_lock()和 evbuffer_unlock()函数分别请求和释放 evbuffer 上的锁。可以使用这两个 函数让一系列操作是原子的。

5.5.2.3 添加数据

//添加 data 处的 datalen 字节到 buf 的末尾,成功时返回0,失败时返回-1。
int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);
//添加格式化的数据到 buf 末尾。
//格式参数和其他参数的处理分别与 C 库函数 printf 和 vprintf 相同。函数返回添加的字节数。
int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)
int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap);

5.6 监听器evconnlisterner

5.6.1 简介

基于event和event_base已经可以写一个CS模型了。但是对于服务器端来说,仍然需要用户自行调用socket、bind、listen、accept等步骤。这个过程有点繁琐,并且一些细节可能考虑不全,为此Libevent提供了econnlistener 机制,简化了整个监听的流程,用户仅仅需要在对应回调函数里面处理已完成连接的套接字即可。

struct evconnlistener结构体定义如下:

struct evconnlistener {
const struct evconnlistener_ops *ops; //操作函数
void *lock; //锁变量,用于线程安全
evconnlistener_cb cb; //用户的回调函数
evconnlistener_errorcb errorcb; //发生错误时的回调函数
void *user_data; //回调函数的参数,当回调函数执行时候,通过形参传入回调函数内部
unsigned flags; //属性标志 ,例如socket套接字属性,可以是阻塞,非阻塞,reuse等。
short refcnt; //引用计数
unsigned enabled : 1; //位域为1.即只需一个比特位来存储这个成员
};
struct evconnlistener_event {
struct evconnlistener base;
struct event listener; //内部event,插入到event_base,完成监听
};

5.6.2 创建和释放

5.6.2.1 函数原型

struct evconnlistener *evconnlistener_new(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd);
struct evconnlistener *evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen);
//释放
void evconnlistener_free(struct evconnlistener *lev);

两个函数的不同在于如何建立监听套接字。evconnlistener_new()函数假定已经将套接字绑定到要监听的端口,然后通过 fd 传入这个套接字。如果要 libevent 分配和绑定套接字,可以调用 evconnlistener_new_bind() ,传输要绑定到的地址和地址长度。

5.6.2.2 监听器回调

typedef void (*evconnlistener_cb)(struct evconnlistener *listener,
evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr);

接收到新连接会调用提供的回调函数 。

listener 参数是接收连接的连接监听器 。

sock 参数是 新接收的套接字。

addrlen 参数是接收连接的地址和地址长度。

ptr 是调 用 evconnlistener_new() 时用户提供的指针。

6、 示例代码

6.1 客户端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <signal.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/util.h>
#include <arpa/inet.h>
#define IP "127.0.0.1"
#define PORT 8888
void event_cb(struct bufferevent *bev, short events, void *arg)
{
if( events & BEV_EVENT_EOF )
{
printf("Connection closed\n");
}
else if( events & BEV_EVENT_ERROR )
{
printf("Connection error:%s\n", strerror(errno));
}
}
void send_data(evutil_socket_t fd, short events, void *arg)
{
struct bufferevent *bev = (struct bufferevent *)arg;
char buf[32] = "Hello, world!";
printf("%s\n", buf);
bufferevent_write(bev, buf, strlen(buf));
}
int main (int argc, char **argv)
{
struct sockaddr_in addr;
int len = sizeof(addr);
struct event_base *base = NULL;
struct bufferevent *bev = NULL;
struct timeval tv;
struct event *ev = NULL;
base = event_base_new();
if( !base )
{
printf("event_base_new() failure\n");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_aton(IP, &addr.sin_addr);
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
if( !bev )
{
printf("Could not create bufferevent\n");
event_base_free(base);
return -2;
}
bufferevent_setcb(bev, NULL, NULL, event_callback, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
bufferevent_socket_connect(bev, (struct sockaddr *)&addr, len);
tv.tv_sec = 3;
tv.tv_usec = 0;
ev = event_new(base, -1, EV_PERSIST, send_data, bev);
event_add(ev, &tv);
event_base_dispatch(base);
event_free(ev);
bufferevent_free(bev);
event_base_free(base);
return 0;
}

6.2服务器端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <signal.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/util.h>
#include <event2/listener.h>
#include <arpa/inet.h>
#define PORT 8888
void read_cb(struct bufferevent *bev, void *arg)
{
char buf[32];
int len;
while( (len = bufferevent_read(bev, buf, sizeof(buf))) > 0 )
{
buf[len] = '\0';
printf("%s\n", buf);
}
}
void event_cb(struct bufferevent *bev, short events, void *arg)
{
if( events & BEV_EVENT_EOF )
{
printf("Connection closed\n");
}
else if( events & BEV_EVENT_ERROR )
{
printf("Connection error:%s\n", strerror(errno));
}
}
void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int len, void *arg)
{
struct event_base *base = (struct event_base *)arg;
struct bufferevent *bev = NULL;
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if( !bev )
{
printf("bufferevent_socket_new() failure:%s\n", strerror(errno));
close(fd);
return ;
}
bufferevent_setcb(bev, read_cb, NULL, event_callback, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
}
int main (int argc, char **argv)
{
struct sockaddr_in addr;
struct event_base *base = NULL;
struct evconnlistener *listener = NULL;
base = event_base_new();
if( !base )
{
printf("event_base_new() failure\n");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
listener = evconnlistener_new_bind(base, listener_cb, base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1, (struct sockaddr *)&addr, sizeof(addr));
if( !listener )
{
printf("Can't create a listener\n");
return -2;
}
event_base_dispatch(base);
evconnlistener_free(listener);
event_base_free(base);
return 0;
}

本文作者:梨子Li

本文链接:https://www.cnblogs.com/LiBlog--/p/18327543

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   梨子Li  阅读(51)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起