libevent框架的使用

目录

一、概述

1. LAMP - web服务器套件

2. libevent 框架

3. 使用步骤

二、libevent的安装

三、libevent主要API(通用)

1. 连接监听对象(struct evconnlistener)的创建、启用和释放

2. 事件集

3. 事件循环

三、事件(不带buffer缓冲)

1. 创建事件

2. 释放事件

3. 添加事件

4. 删除事件

5. 附录

四、事件(带buffer缓冲)

1.创建

2. 释放

3. 设置和获取回调函数

4. bufferevent 的启用和禁用

5. 读/写数据

6. 将数据写入缓冲区/从缓冲区读入数据

7. 启动connect()

8. 解析主机名并连接

9. 返回 (3.解析主机名并连接) 函数执行时的错误码

10. 设置和获取 event_base

11. 设置/获取优先级

12. 附录

五、示例代码

1. 回声服务器的实现

2. 实现简单的服务器和客户端通信


一、概述

1. LAMP - web服务器套件

LAMP:它是一个缩写,指一组通常一起使用来运行动态网站或者服务器的自由软件。

LAMP展开
缩写展开说明
LLinux操作系统
AApache网页服务器
MMySQL数据库管理系统(或数据库服务器)
PPHPPerl或Python - 脚本语言

C10K:并发能力突破不了1万连接。

2. libevent 框架

  • libevent是一个轻量级的开源高性能的事件触发的网络库,适用于Windows、Linux、bsd等多种平台;内部使用select、epoll、kqueue等系统调用管理事件机制。
  • 它被众多的开源项目使用,例如大名鼎鼎的memcached等。
  • 特点如下:

1. 事件驱动,高性能

2. 轻量级,专注于网络(相对于ACE)

3. 开放源代码,代码相当精炼、易读

4. 跨平台,支持Windows、Linux、BSD和Mac OS

5. 支持多种I/O多路复用技术(epoll、poll、dev/poll、select和kqueue等),在不同的操作系统下,做了多路复用模型的抽象,可以选择使用不同的模型,通过事件函数提供服务

6. 支持I/O、定时器和信号等事件

  • 采用Reactor设计模式

libevent是一个典型的reactor设计模式的实现。

普通的函数调用机制:程序调用某个函数,函数执行,程序等待,函数将结果返回给调用程序(如果含有函数返回值的话),也就是顺序执行的。

Reactor模式的基本流程:应用程序需要提供相应的接口并且注册到reactor反应器上,如果相应的事件发生的话,那么reactor将自动调用相应的注册的接口函数(类似于回调函数)通知你,所以libevent是事件触发的网络库。

  • libevent的功能
  1. Libevent提供了事件通知,io缓存事件,定时器,超时,异步解析dns,事件驱动的http server以及一个rpc框架。
  2. 事件通知:当文件描述符可读可写时将执行回调函数。
  3. IO缓存:缓存事件提供了输入输出缓存,能自动的读入和写入,用户不必直接操作io。
  4. 定时器:libevent提供了定时器的机制,能够在一定的时间间隔之后调用回调函数。
  5. 信号:触发信号,执行回调。
  6. 异步的dns解析:libevent提供了异步解析dns服务器的dns解析函数集。
  7. 事件驱动的http服务器:libevent提供了一个简单的,可集成到应用程序中的HTTP服务器。
  8. RPC客户端服务器框架:libevent为创建RPC服务器和客户端创建了一个RPC框架,能自动的封装和解封数据结构。

3. 使用步骤

  1. 创建 socket
  2. 创建事件集 event_base
  3. 创建event(socket, EV_READ, callback1) / event(socket, EV_WRITE, callback2)
  4. 把event添加到事件集event_base

  5. event_base_dispatch(evnet_base); 或 event_base_loop();

注意:程序的最后调用event_base_dispatch(base);实现事件的循环处理

二、libevent的安装

libevent官网:libevent – an event notification library

安装步骤如下:

在Linux系统shell下,切换至超级用户

  1. 下载源码包(可根据实际需要版本更改地址):wget https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
  2. 解压: tar zxvf libevent-2.1.12-stable.tar-1.gz
  3. 进入解压后的目录:cd libevent-2.1.12-stable
  4. 配置安装路径:./configure --disable-openssl
  5. 编译:make
  6. 安装:make install
  7. 测试是否安装成功(成功会显示如下图片):ls -la /usr/local/include | grep event

注意:如果编译后的程序提示找不到libvent的so,则创建库的链接和缓存文件 ldconfig(具体操作见:【Linux】error while loading shared libraries: libevent-2.1.so.6 的解决办法|动态库.so找不到的解决办法)

编译时带上 -levent 选项即可使用libevent

三、libevent主要API(通用)

1. 连接监听对象(struct evconnlistener)的创建、启用和释放

1. 创建并监听 evconnlistener

/***********************************************************************************
 * 函数;evconnlistener_new()
 * 功能:分配一个新的evConnlistener对象以侦听给定地址上的传入TCP连接。
 * 参数:
        base        - 要与监听器关联的事件基数。
        cb          - 新连接到达时要调用的回调。如果回调为空,则在设置回调之前,监听器将被视为禁用。
        ptr         - 用户提供的指向回调的指针(通常传base)。
        flags       - 任意数量的 LEV_OPT_*标志。
        backlog     - 传递给listen()调用以确定可接受的连接backlog的长度。要获得合理的默认值,请设置为-1。如果套接字已在侦听,则设置为0。
        fd          - 要监听的文件描述符。它必须是非阻塞文件描述符,并且应该已经绑定到适当的端口和地址。
 * 返回:struct evconnlistener 结构体
************************************************************************************/
struct evconnlistener *evconnlistener_new(struct event_base *base, evconnlistener_cb cb, void *ptr,
                                            unsigned flags, int backlog, evutil_socket_t fd);

2. 创建监听并绑定 evconnlistener

/***********************************************************************************
 * 函数;evconnlistener_new_bind()
 * 功能:分配一个新的evConnlistener对象以侦听给定地址上的传入TCP连接。
 * 参数:
        base        - 要与监听器关联的事件基数。
        cb          - 新连接到达时要调用的回调。如果回调为空,则在设置回调之前,监听器将被视为禁用。
        ptr         - 用户提供的指向回调的指针(通常传base)。
        flags       - 任意数量的 LEV_OPT_*标志。
        backlog     - 传递给listen()调用以确定可接受的连接backlog的长度。要获得合理的默认值,请设置为-1。
        addr        - 要侦听连接的地址。
        socklen     - 地址的长度。

 * 返回:struct evconnlistener 结构体
************************************************************************************/
struct evconnlistener *evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, 
                                        unsigned flags, int backlog, const struct sockaddr *sa, int socklen);

3. 禁用并取消分配 evconnlistener

void evconnlistener_free(struct evconnlistener *lev);

4. 重新启用已禁用的 evconnlistener

int evconnlistener_enable(struct evconnlistener *lev);

5. 停止监听 evconnlistener 上的连接

int evconnlistener_disable(struct evconnlistener *lev);

 7. 获取 evconnlistener 的关联 event_base

struct event_base *evconnlistener_get_base(struct evconnlistener *lev);

8. 获取 evconnlistner 正在监听的套接字

/*************************************************************************************
 * 函数:evconnlistener_get_fd()
 * 功能:获取 evconnlistner 正在监听的套接字
 * 参数:
        lev        - 需要获取的 evconnlistener 结构体变量
 * 返回:
        监听的套接字
**************************************************************************************/
evutil_socket_t evconnlistener_get_fd(struct evconnlistener *lev);

9. 将侦听器(struct evconnlistener)上的回调更改为cb,并将其user_data更改为arg

/*************************************************************************************
 * 函数:evconnlistener_set_cb()
 * 功能:将侦听器(struct evconnlistener)上的回调更改为cb,并将其user_data更改为arg
 * 参数:
        lev        - 待设置回调的 evconnlistener
        errorcb    - 回调函数
        arg        - 回调函数参数
 * 返回:无
**************************************************************************************/
void evconnlistener_set_cb(struct evconnlistener *lev, evconnlistener_cb cb, void *arg);

10. 设置evconnlistener的错误回调

/*************************************************************************************
 * 函数:evconnlistener_set_error_cb()
 * 功能:设置evconnlistener的错误回调。
 * 参数:
        lev        - 待设置错误回调的 evconnlistener
        errorcb    - 错误回调函数
 * 返回:无
**************************************************************************************/
void evconnlistener_set_error_cb(struct evconnlistener *lev, evconnlistener_errorcb errorcb);

11. 使用到的结构体及相关定义

/**************************************************************************************
 * 定义:(*evconnlistener_cb)
 * 功能:监听程序有新连接时调用的回调。
 * 参数:
        listener    - evconnlistener
        fd          - 新的文件描述符
        addr        - 连接的源地址
        socklen     - 地址长度
        user_arg    - 传递给 evconnlistener_new() 的指针
***************************************************************************************/
typedef void (*evconnlistener_cb)(struct evconnlistener *listener, evutil_socket_t fd,
                                struct sockaddr *addr, int socklen, void *user_arg);



/**************************************************************************************
 * 定义:(*evconnlistener_errorcb)
 * 功能:当侦听器遇到不可重试的错误时调用的回调。
 * 参数:
        listener    - evconnlistener
        user_arg    - 传递给 evconnlistener_new() 的指针
 * 返回:
***************************************************************************************/
typedef void (*evconnlistener_errorcb)(struct evconnlistener *listener, void *user_arg);


// 函数参数中flags的定义

/* FLAG:指示在将传入套接字传递给回调之前不应使其非阻塞。 */
#define LEV_OPT_LEAVE_SOCKETS_BLOCKING	(1u<<0)

/* FLAG:指示释放侦听器应关闭底层套接字。 */
#define LEV_OPT_CLOSE_ON_FREE		(1u<<1)

/* FLAG:指示如果可能,我们应该设置 close-on-exec 标志 */
#define LEV_OPT_CLOSE_ON_EXEC		(1u<<2)

/* FLAG:指示我们应该禁用此套接字关闭和可以在同一端口上再次侦听之间的超时(如果有的话)。 */
#define LEV_OPT_REUSEABLE		(1u<<3)

/* FLAG:指示侦听器应该被锁定,以便可以安全地同时从多个线程使用。 */
#define LEV_OPT_THREADSAFE		(1u<<4)

/* FLAG:指示应在禁用状态下创建监听程序。稍后使用evConnlistener_enable()启用它。 */
#define LEV_OPT_DISABLED		(1u<<5)

/* FLAG:指示监听程序应将Accept()推迟到数据可用(如果可能)。在不支持此功能的平台上被忽略。
 *
 * 此选项可提高客户端在连接后立即传输的协议的性能。
 * 如果您的协议不是从客户端传输数据开始的,请不要使用此选项,因为在这种情况下,
 * 此选项有时会导致内核永远不会告诉您有关连接的信息。
 *
 * 该选项只支持evConnlistener_new_bind():不能与evConnlistener_new_fd()一起使用,
 * 因为需要在实际绑定之前告知监听器使用该选项。
 */
#define LEV_OPT_DEFERRED_ACCEPT		(1u<<6)

/* FLAG:表示如果多个服务器(进程或线程)都设置了该选项,则我们要求允许它们绑定到同一端口。
 * 
 * SO_REUSEPORT是大多数人期望的SO_REUSEADDR,但是SO_REUSEPORT并不意味着SO_REUSEADDR。
 *
 * 这仅在Linux和内核3.9+上可用。
 */
#define LEV_OPT_REUSEABLE_PORT		(1u<<7)

/* FLAG:表示监听程序只想在IPv6套接字中工作。
 *
 * 根据RFC3493和大多数Linux发行版,默认值是在IPv4映射模式下工作。
 * 如果需要在相同的IP地址上绑定相同的端口,但IPv4和IPv6的处理程序不同,
 * 则需要设置IPv6_V6ONLY套接字选项,以确保代码按预期工作,
 * 而不受系统中的bindv6only sysctl设置的影响。
 *
 * Windows也支持此套接字选项。
 */
#define LEV_OPT_BIND_IPV6ONLY		(1u<<8)


// 工作函数,因为listener可以用于不同的协议
struct evconnlistener_ops {
	int (*enable)(struct evconnlistener *);
	int (*disable)(struct evconnlistener *);
	void (*destroy)(struct evconnlistener *);
	void (*shutdown)(struct evconnlistener *);
	evutil_socket_t (*getfd)(struct evconnlistener *);
	struct event_base *(*getbase)(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;                          // 引用计数
	int accept4_flags;
	unsigned enabled : 1;                  // 位域为1.即只需一个比特位来存储这个成员 
};

struct evconnlistener_event {
	struct evconnlistener base;
	struct event listener;                 // //内部event,插入到event_base,完成监听
};

#ifdef _WIN32
struct evconnlistener_iocp {
	struct evconnlistener base;
	evutil_socket_t fd;
	struct event_base *event_base;
	struct event_iocp_port *port;
	short n_accepting;
	unsigned shutting_down : 1;
	unsigned event_added : 1;
	struct accepting_socket **accepting;
};
#endif

#define LOCK(listener) EVLOCK_LOCK((listener)->lock, 0)
#define UNLOCK(listener) EVLOCK_UNLOCK((listener)->lock, 0)


2. 事件集

1. 事件集的创建


/* 创建默认 event_base */

/***************************************************************************************
 * 函数:struct event_base *event_base_new(void);
 * 功能:创建并返回一个新的 EVENT_BASE 结构体 以与 libevent 的其余部分一起使用。
 * 参数:无
 * 返回:成功返回新的EVENT_BASE,失败返回NULL。
 * 来自:event2/event.h
****************************************************************************************/
struct event_base *event_base_new(void);


/* 通过event_config_new()函数 和 event_base_new_with_config() 函数创建自定义的 event_base */

/***************************************************************************************
 * 函数:struct event_config *event_config_new(void);
 * 功能:分配新的事件配置对象。
 * 参数:无
 * 返回:可用于存储配置的EVENT_CONFIG对象,如果遇到错误,则返回NULL。
 * 说明:事件配置对象可用于更改事件库的行为。
 * 来自:event2/event.h
****************************************************************************************/
struct event_config *event_config_new(void);


/***************************************************************************************
 * 函数:struct event_base *event_base_new_with_config(const struct event_config *);
 * 功能:初始化事件接口。
 * 参数:
        cfg     - 事件配置对象。
 * 返回:返回可用于注册事件的初始化EVENT_BASE,如果请求的EVENT_CONFIG无法创建事件库,则返回NULL。
 * 说明:事件配置对象可用于更改事件库的行为。
 * 来自:event2/event.h
****************************************************************************************/
struct event_base *event_base_new_with_config(const struct event_config *cfg);


/* 附:struct event_config 及相关结构 */
struct event_config {
	TAILQ_HEAD(event_configq, event_config_entry) entries;

	int n_cpus_hint;
	struct timeval max_dispatch_interval;
	int max_dispatch_callbacks;
	int limit_callbacks_after_prio;
	enum event_method_feature require_features;
	enum event_base_config_flag flags;
};

/**
    用于描述EVENT_BASE(必须)提供哪些功能的标志。

    由于操作系统的限制,并不是每个Libeent后端都支持所有可能的功能。可以将此类型与
    EVENT_CONFIG_REQUIRED_FEATURES()一起使用,以告知Libeent仅在 EVENT_BASE 实现给定
    功能时才继续,并且可以从EVENT_BASE_GET_FEATURES()接收此类型以查看哪些功能可用。
*/
enum event_method_feature {
    /** 需要允许使用 EV_ET的边缘触发事件的事件方法。 */
    EV_FEATURE_ET = 0x01,

    /** 需要一个事件方法,其中触发多个事件中的一个事件是[近似]O(1)运算。
        这不包括(例如)SELECT和POLL,对于等于可能事件总数的N,它们大约是O(N)。 */
    EV_FEATURE_O1 = 0x02,

    /** 需要允许文件描述符和套接字的事件方法。 */
    EV_FEATURE_FDS = 0x04,

    /** 需要一个事件方法,该方法允许您使用EV_CLOSED来检测连接关闭,而无需读取所有挂起的数据。
        支持EV_CLOSED的方法可能无法在所有内核版本上提供支持。
     **/
    EV_FEATURE_EARLY_CLOSE = 0x08
};

/**
    传递给EVENT_CONFIG_SET_FLAG()的标志。
    这些标志更改分配的EVENT_BASE的行为。

    查看:event_config_set_flag(), event_base_new_with_config(), event_method_feature
 */
enum event_base_config_flag {

	/** 即使我们设置了锁定,也不要为事件库分配锁。
	    设置此选项将使从多个线程并发调用基函数变得不安全且不起作用。
	*/
	EVENT_BASE_FLAG_NOLOCK = 0x01,

	/**配置 EVENT_BASE 时,不要检查 EVENT_* 环境变量 */
	EVENT_BASE_FLAG_IGNORE_ENV = 0x02,

	/** 仅限Windows:启动时启用IOCP调度程序。

        如果设置了此标志,则Bufferevent_socket_new()和
        evconn_listener_new()将使用IOCP支持的实现,而不是Windows上通常基于选择的实现。
	 */
	EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,

	/** 不是在每次事件循环准备好运行超时回调时检查当前时间,而是在每次超时回调之后进行检查。
	 */
	EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,

	/** 如果我们使用的是EPOLL后端,该标志表示可以安全地使用Libeventt的内部更改
        列表代码批量添加和删除,以便尽可能少地执行syscall。
        设置此标志可以使您的代码运行得更快,但它可能会触发Linux错误:
        如果您有任何由dup()或其变体克隆的FD,则使用此标志是不安全的。
        这样做会产生奇怪且难以诊断的错误。

        也可以通过设置EVENT_EPOLL_USE_CHANGELIST环境变量来激活此标志。

        如果您最终使用的后端不是EPOLL,则此标志无效。
	 */
	EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,

	/** 通常,Libeent使用我们拥有的最快的单调计时器来实现其时间和超时代码。
        但是,如果设置了此标志,我们将使用效率较低、精度较高的计时器(假设存在一个计时器)。
	 */
	EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
};


2. 重新初始化结构体 event_base

/********************************************************************************************
 * 函数:int event_reinit(struct event_base *base);
 * 功能:重新初始化 event_base 结构体
 * 参数:
        base    - 需要重新初始化的事件库。
 * 返回:
        成功则返回0,如果某些事件无法重新添加,则返回-1。
 * 说明:有些事件机制不能跨分叉存活。需要使用 EVENT_REINIT() 函数重新初始化事件库(event2/event.h)。
*********************************************************************************************/
int event_reinit(struct event_base *base);

3. 释放事件集

/*************************************************************************************
 * 函数:void event_base_free(struct event_base *eb);
 * 功能:释放与EVENT_BASE关联的所有内存,然后释放该基址。
 * 参数:eb    - 要释放的EVENT_BASE
 * 返回:无
 * 说明:
 *     请注意,该函数不会关闭任何FD,也不会释放作为回调参数传递给EVENT_NEW的任何内存。
 *     如果有任何挂起的终结器回调,此函数将调用它们。
**************************************************************************************/
void event_base_free(struct event_base *eb);



// 作为 event_base_free(),但不运行终结器。
void event_base_free_nofinalize(struct event_base *);

3. 事件循环

/*************************************************************************************
 * 函数:int event_base_loop(struct event_base *, int);
 * 功能:等待事件变为活动状态,然后运行其回调。
 * 参数:
        eb    - EVENT_BASE_NEW()或EVENT_BASE_NEW_WITH_CONFIG()返回的EVENT_BASE结构。
        flags - EVLOOP_ONCE|EVLOOP_NONBLOCK的任意组合。
 * 返回:
        如果成功,则返回0;如果发生错误,则返回-1;
        如果因为没有事件处于挂起或活动状态而退出,则返回1。
**************************************************************************************/
int event_base_loop(struct event_base *eb, int flags);



/*************************************************************************************
 * 函数:int event_base_dispatch(struct event_base *);
 * 功能:事件调度循环。
 * 参数:
        base    - EVENT_BASE_NEW()返回的EVENT_BASE结构 或 EVENT_BASE_NEW_WITH_CONFIG()。
 * 返回:
        如果成功,则返回0;如果发生错误,则返回-1;
        如果因为没有事件处于挂起或活动状态而退出,则返回1。
**************************************************************************************/
int event_base_dispatch(struct event_base *base);


// 提示:struct event_base 结构的定义及相关结构在:
// 三、libevent主要API(不带buffer缓冲) -> 1. 事件 -> 5.附录内

三、事件(不带buffer缓冲)

1. 创建事件

/*************************************************************************************
 * 函数:struct event *event_new(struct event_base *, evutil_socket_t, short, event_callback_fn, void *);
 * 功能:分配新的事件结构,准备添加。
 * 参数:
        base            - 事件应该附加到的事件库。
        fd              - 要监控的文件描述符或信号,或-1。
        events          - 需要监视的事件(EV_READ, EV_WRITE, EV_SIGNAL, EV_PERSIST, EV_ET)
        callback        - 事件发生时调用的回调函数。
        callback_arg    - 要传递给回调函数的参数。
 * 返回:
        返回新分配的struct事件,该事件必须在以后使用。如果发生错误,则返回EVENT_FREE()或NULL。
 * 说明:
        函数event_new()返回一个新事件,可在将来调用event_add()和event_del()时使用。
**************************************************************************************/
struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, event_callback_fn cb, void *callback_arg);


// 给已经创建的事件赋值(event.h)
int event_assign(struct event *, struct event_base *, evutil_socket_t, short, event_callback_fn, void *);

2. 释放事件


/************************************************************************************
 * 函数:void event_free(struct event *);
 * 功能:释放由EVENT_NEW()返回的结构 event*。
 * 参数:
        _ev    - 待释放的 event 结构体
 * 返回:无
 * 说明:如果事件处于挂起或活动状态,则此函数首先将其设置为非挂起和非活动状态。
 ************************************************************************************/
void event_free(struct event *_ev);

3. 添加事件

/*************************************************************************************
 * 函数:int event_add(struct event *ev, const struct timeval *timeout);
 * 功能:将事件添加到挂起事件集中。
 * 参数:
        ev        - 通过EVENT_ASSIGN()或EVENT_NEW()初始化的事件结构。
        timeout   - 等待事件的最长时间,或为NULL,表示永远等待。
 * 返回:如果成功则返回0,如果出错则返回-1。
 * 说明:
        函数event_add()用于在EVENT_ASSIGN()或EVENT_NEW()指定的条件发生时,或在超时中指定的时间已过时调度事件'ev'的执行。如果超时为NULL,则不会发生超时,只有在发生匹配事件时才会调用该函数。Ev参数中的事件必须已由EVENT_ASSIGN()或EVENT_NEW()初始化,并且在不再挂起之前不能用于调用EVENT_ASSIGN()。
        如果ev参数中的事件已经有计划的超时,如果tv非空,则调用event_add()将用新的超时替换旧的超时。
**************************************************************************************/
int event_add(struct event *ev, const struct timeval *timeout);

4. 删除事件


/*************************************************************************************
 * 函数:int event_del(struct event *);
 * 功能:从所监视的事件集中删除事件。
 * 参数:
        ev        - 要从工作集中删除的事件结构。
 * 返回:如果成功则返回0,如果出错则返回-1。
 * 说明:函数event_del()将取消参数ev中的事件。如果事件已经执行或从未添加,则调用将不起作用。
**************************************************************************************/
int event_del(struct event *ev);

5. 附录

附1: struct event_base 的定义

struct event_base {
	/* 描述此EVENT_BASE后端的函数指针和其他数据。 */
	const struct eventop *evsel;
	/* 指向后端特定数据的指针。 */
	void *evbase;

	/* 下次派单时要告知后端的更改列表。仅供O(1)后端使用。 */
	struct event_changelist changelist;

	/* 用于描述此EVENT_BASE用于信号的后端的函数指针 */
	const struct eventop *evsigsel;
	/* 用于实现公共信号处理程序代码的数据。 */
	struct evsig_info sig;

	/* 虚拟事件数量 */
	int virtual_event_count;
	/* 最大活动虚拟事件数 */
	int virtual_event_count_max;
	/* 添加到此EVENT_BASE的事件总数 */
	int event_count;
	/* 添加到此EVENT_BASE的最大事件总数 */
	int event_count_max;
	/* 此EVENT_BASE中活动的事件总数 */
	int event_count_active;
	/* 此EVENT_BASE中活动的最大事件总数 */
	int event_count_active_max;

	/* 设置在处理完事件后是否应该终止循环。 */
	int event_gotterm;
	/* 设置是否应立即终止循环 */
	int event_break;
	/* 设置是否应立即启动循环的新实例。 */
	int event_continue;

	/* 事件的当前运行优先级 */
	int event_running_priority;

	/* 设置是否运行EVENT_BASE_LOOP函数,以防止重入调用。 */
	int running_loop;

	/* 设置为我们在循环中“激活”的DEFERED_CB的数量。这是一种防止饥饿的方法;
     * 只使用 event_config_set_max_dispatch_interval的max_callback功能会更聪明 */
	int n_deferreds_queued;

	/* 活动事件管理。 */
	/* 活动EVENT_CALLBACKS(已触发且需要调用其回调的队列)的非激活队列队列数组。
     * 优先级较低的数字更为重要,而优先级较高的数字则会延迟。*/
	struct evcallback_list *activequeues;
	/* Actiequeues数组的长度 */
	int nactivequeues;
	/* 下一次处理事件时应激活的EVENT_CALLBACKS列表,但这次不会。 */
	struct evcallback_list active_later_queue;

	/* 通用超时逻辑 */

	/* 我们知道的所有常见超时值的COMMON_TIMEOUT_LIST*数组。 */
	struct common_timeout_list **common_timeout_queues;
	/* COMMON_TIMEOUT_QUEUES中使用的条目数。 */
	int n_common_timeouts;
	/* COMMON_TIMEOUT_QUEUES的总大小。 */
	int n_common_timeouts_allocated;

	/* 从文件描述符到已启用(已添加)事件的映射。 */
	struct event_io_map io;

	/* 从信号号映射到启用(添加)的事件。 */
	struct event_signal_map sigmap;

	/* 具有超时的事件的优先级队列。 */
	struct min_heap timeheap;

	/** 存储时间:用于避免过于频繁地调用gettimeofday/lock_gettime。 */
	struct timeval tv_cache;

	struct evutil_monotonic_timer monotonic_timer;

	/* 内部时间(可能来自lock_gettime)和gettimeofday之间的差异。 */
	struct timeval tv_clock_diff;
	/* 第二,我们在单调时间内最后更新了TV_CLOCK_DIFF。 */
	time_t last_updated_clock_diff;

#ifndef EVENT__DISABLE_THREAD_SUPPORT
	/* 线程支持 */
	/* 当前为此基础运行EVENT_LOOP的线程 */
	unsigned long th_owner_id;
	/* 用于防止对此EVENT_BASE的冲突访问的锁 */
	void *th_base_lock;
	/* 当我们处理完有服务员的事件时发出信号的情况。 */
	void *current_event_cond;
	/* CURRENT_EVENT_COND上阻塞的线程数。 */
	int current_event_waiters;
#endif
	/* 当前正在执行其回调的事件 */
	struct event_callback *current_event;

#ifdef _WIN32
	/* IOCP支持结构(如果启用了IOCP)。 */
	struct event_iocp_port *iocp;
#endif

	/* 用来配置此基础的标志 */
	enum event_base_config_flag flags;

	struct timeval max_dispatch_time;
	int max_dispatch_callbacks;
	int limit_callbacks_after_prio;

	/* 通知主线程唤醒中断等。 */
	/* 如果 base 已经有一个挂起的通知,并且我们不需要再添加任何通知,则为True。 */
	int is_notify_pending;
	/* 一些th_tify函数用来唤醒主线程的套接字对。 */
	evutil_socket_t th_notify_fd[2];
	/* TH_NOTIFY函数用来唤醒主线程的事件。 */
	struct event th_notify;
	/* 用于从另一个线程唤醒主线程的函数。 */
	int (*th_notify_fn)(struct event_base *base);

	/* 为弱随机数生成器保存种子。一些后端利用这一点在套接字之间产生公平性。
       受th_base_lock保护。 */
	struct evutil_weakrand_state weakrand_seed;

	/* 尚未触发的EVENT_ONCE列表。 */
	LIST_HEAD(once_event_list, event_once) once_events;

};

 附2: struct event_base 中 struct eventop 的定义及使用到的相关结构

// 附: struct event_base 中 struct eventop 的定义及使用到的相关结构
struct eventop {
	/* 此后端的名称。 */
	const char *name;
	/* 函数来设置EVENT_BASE以使用此后端。它应该创建一个新结构,
     * 保存运行后端所需的任何信息,并将其返回。
     * 返回的指针将由event_init存储到event_base.evbase字段中。
     * 失败时,此函数应返回NULL。 */
	void *(*init)(struct event_base *);
	/* 启用对给定FD或信号的读/写。
     * ‘events’将是我们试图启用的事件:
     *    EV_READ、EV_WRITE、EV_SIGNAL和EV_ET中的一个或多个。
     * 'old'将是以前在此FD上启用的那些事件。
     * ‘fdinfo’将是通过evmap与FD相关联的结构;
     * 其大小由下面的fdinfo字段定义。第一次添加FD时,它将被设置为0。
     * 函数成功时应返回0,错误时应返回-1。 */
	int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
	/** 作为“添加”,除了“事件”包含我们想要禁用的事件。 */
	int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
	/* 函数来实现事件循环的核心。
     * 它必须查看哪些添加的事件已就绪,并导致为每个活动事件调用EVENT_ACTIVE(通常通过event_io_active等)。
     * 它应该在成功时返回0,在错误时返回-1。
     */
	int (*dispatch)(struct event_base *, struct timeval *);
	/* 函数来清理和释放EVENT_BASE中的数据。 */
	void (*dealloc)(struct event_base *);
	/* FLAG:如果我们在分叉后需要重新初始化事件库,则设置。 */
	int need_reinit;
	/** Bit-array 该后端可以提供的受支持的EVENT_METHOD_FEATURES功能。 */
	enum event_method_feature features;
	/* 我们应该为具有一个或多个活动事件的每个FD记录的额外信息的长度。
     * 此信息被记录为每个FD的evmap条目的一部分,并作为参数传递给上面的add和del函数。
	 */
	size_t fdinfo_len;
};

// struct eventop 中 enum event_method_feature 的定义

enum event_method_feature {
    /* 需要允许使用ev_et的边缘触发事件的事件方法。 */
    EV_FEATURE_ET = 0x01,
    /* 需要一个事件方法,其中触发多个事件中的一个事件是[近似]O(1)运算。
     * 这不包括(例如)SELECT和POLL,它们对于N大约是O(N) 等于可能发生的事件的总数。 */
    EV_FEATURE_O1 = 0x02,
    /* 需要允许文件描述符和套接字的事件方法。 */
    EV_FEATURE_FDS = 0x04,
    /* 需要一个事件方法,该方法允许您使用EV_CLOSED来检测连接关闭,
     * 而无需读取所有挂起的数据。
     * 
     * 支持EV_CLOSED的方法可能无法在所有内核版本上提供支持。
     **/
    EV_FEATURE_EARLY_CLOSE = 0x08
};

附3:struct event_base 中 struct event_changelist 的定义及使用到的相关结构

/* 自上次调用Eventop.Dispatch以来的“更改”列表。仅在后端使用变更集时维护。 */
struct event_changelist {
	struct event_change *changes;
	int n_changes;
	int changes_size;
};



/* 在更改列表中设置数据字段。 */
void event_changelist_init_(struct event_changelist *changelist);

/* 删除更改列表中的所有更改,并在基础中的事件映射中进行相应更改。
 * 此函数通常在更改列表中进行所有更改后立即使用。 */
void event_changelist_remove_all_(struct event_changelist *changelist, struct event_base *base);

/* 释放更改列表中保存的所有内存。 */
void event_changelist_freemem_(struct event_changelist *changelist);

/* Eventop_add的实现,它将事件排队到更改列表中。 */
int event_changelist_add_(struct event_base *base, evutil_socket_t fd, short old, short events, void *p);
/* Eventop_del的实现,它将事件排队到更改列表中。 */
int event_changelist_del_(struct event_base *base, evutil_socket_t fd, short old, short events, void *p);


// 附 struct event_change 的定义 (changelist-internal.h)

/** 表示一个 */
struct event_change {
	/* 要更改其事件的FD或信号 */
	evutil_socket_t fd;
	/* 在进行任何这些更改之前在FD上启用的事件。可能包括EV_READ或EV_WRITE。 */
	short old_events;

	/* 我们希望在这个FD上进行读写的更改。如果这是一个信号,
     * 则READ_CHANGE设置EV_CHANGE_SIGNAL,而WRITE_CHANGE未使用。 */
	ev_uint8_t read_change;
	ev_uint8_t write_change;
	ev_uint8_t close_change;
};

/* READ_CHANGE和WRITE_CHANGE的标志。 */
/* 如果设置,则添加事件。 */
#define EV_CHANGE_ADD     0x01
/* 如果已设置,请删除该事件。独占EV_CHANGE_ADD */
#define EV_CHANGE_DEL     0x02
/* 如果设置,则此事件引用信号,而不是FD。 */
#define EV_CHANGE_SIGNAL  EV_SIGNAL
/* 为持久性事件设置。当前未使用。 */
#define EV_CHANGE_PERSIST EV_PERSIST
/* 用于添加边缘触发事件的设置。 */
#define EV_CHANGE_ET      EV_ET

/* 后端在让Changelist处理其添加和删除功能时应使用的fdinfo_size的值。 */
#define EVENT_CHANGELIST_FDINFO_SIZE sizeof(int)



附4:struct event_base 中 struct evsig_info 的定义及使用到的相关结构(evsignal-internal.h)


typedef void (*ev_sighandler_t)(int);

/* Signal.c中默认信号处理实现的数据结构 */
struct evsig_info {
	/* 事件监视EV_SIGNAL_Pair[1] */
	struct event ev_signal;
	/* SocketPair用于从信号处理程序发送通知 */
	evutil_socket_t ev_signal_pair[2];
	/* 如果我们已经添加了EV_SIGNAL事件,则为真。 */
	int ev_signal_added;
	/* 计算我们目前正在监视的信号数量。 */
	int ev_n_signals_added;

	/* 以前的信号处理程序对象的数组,然后Libevent开始处理它们。用于还原旧的信号处理程序。 */
#ifdef EVENT__HAVE_SIGACTION
	struct sigaction **sh_old;
#else
	ev_sighandler_t **sh_old;
#endif
	/* sh_old的大小。 */
	int sh_old_max;
};

附5:struct event_base 中 struct evcallback_list 的定义及相关结构


// struct event_callback 中 evcb_pri列所用到的宏(event_struct.h)
// 此处注释参考文章:https://blog.csdn.net/beitiandijun/article/details/72772909
#define EVLIST_TIMEOUT	    0x01    // event在time堆中,min_heap
#define EVLIST_INSERTED	    0x02    // event在已注册事件链表中,event_base的queue中
#define EVLIST_SIGNAL	    0x04    // 未见使用
#define EVLIST_ACTIVE	    0x08    // event在激活链表中,event_base的active_queue中
#define EVLIST_INTERNAL	    0x10    // 内部使用标记
#define EVLIST_ACTIVE_LATER 0x20    // event在下一次激活链表中
#define EVLIST_FINALIZING   0x40    // event已被初始化
#define EVLIST_INIT	        0x80    // 主要用于判断事件状态的合法性


// struct event_callback 中 evcb_closure 列所用到的宏(event-internal.h)
#define EV_CLOSURE_EVENT                0    // 定期活动。使用evcb_callback回调
#define EV_CLOSURE_EVENT_SIGNAL         1    // 一个信号事件。使用evcb_callback回调
#define EV_CLOSURE_EVENT_PERSIST        2    // 持久的无信号(non-signal)事件。使用evcb_callback回调
#define EV_CLOSURE_CB_SELF              3    // 一个简单的回调。使用evcb_selfcb回调。
#define EV_CLOSURE_CB_FINALIZE          4    // 最后的回调。使用evcb_cbfinalize回调。
#define EV_CLOSURE_EVENT_FINALIZE       5    // 最后的事件。使用evcb_evfinalize回调。
#define EV_CLOSURE_EVENT_FINALIZE_FREE  6    // 一个最终的事件,应该在之后释放。使用evcb_evfinalize回调。


// TAILQ_HEAD宏在event_struct.h
#define TAILQ_HEAD(name, type)						            \
struct name {								                    \
	struct type *tqh_first;	/* first element */			        \
	struct type **tqh_last;	/* addr of last next element */		\
}

// 调用TAILQ_HEAD宏生成 struct evcallback_list
TAILQ_HEAD(evcallback_list, event_callback);

// struct event_callback
struct event_callback {
	TAILQ_ENTRY(event_callback) evcb_active_next;
	short evcb_flags;
	ev_uint8_t evcb_pri;	/* 数字越小,优先级越高 */
	ev_uint8_t evcb_closure;
	/* 允许我们针对不同类型的活动采用 */
        union {
		    void (*evcb_callback)(evutil_socket_t, short, void *);
		    void (*evcb_selfcb)(struct event_callback *, void *);
		    void (*evcb_evfinalize)(struct event *, void *);
		    void (*evcb_cbfinalize)(struct event_callback *, void *);
	    } evcb_cb_union;
	void *evcb_arg;
};


 附6:struct event_base 中 struct common_timeout_list 的定义及相关结构

/* 等待给定“common”超时值的事件列表。通常,等待超时的事件在小堆上等待。然而,有时排队可能会更快。*/
struct common_timeout_list {
	/* 当前在队列中等待的事件列表。 */
	struct event_list events;
	/* 'Magic‘timeval用于指示此队列中事件的持续时间。 */
	struct timeval duration;
	/* 每当队列中的一个事件准备好激活时触发的事件 */
	struct event timeout_event;
	/* 此超时列表所属的 EVENT_BASE */
	struct event_base *base;
};

// struct common_timeout_list 中 events列的定义(event_struct.h)
// struct event 结构体见 [子标题3: 创建事件] 
TAILQ_HEAD (event_list, event);

// struct timeval 结构体见 [附10:struct event_base 中 struct  timeval 的定义及相关结构]

附7:struct event_base 中 struct  event_io_map 的定义及相关结构


#define HT_HEAD(name, type)                                             \
  struct name {                                                         \
    /* 哈希表本身。 */                                                   \
    struct type **hth_table;                                            \
    /* 哈希表有多长? */                                                 \
    unsigned hth_table_length;                                          \
    /* 该表包含多少个元素? */                                             \
    unsigned hth_n_entries;                                             \
    /* 在调整表大小之前,我们允许在表中包含多少元素?*/                     \
    unsigned hth_load_limit;                                            \
    /* 素数表中hth_table_length的位置。 */             \
    int hth_prime_idx;                                                  \
  }


HT_HEAD(event_io_map, event_map_entry);

struct event_io_map
{
    /* 哈希表,连续地址分配 */
    struct event_map_entry **hth_table;
    /* 哈希表的长度 */
    unsigned hth_table_length;
    /* 哈希的元素个数 */
    unsigned hth_n_entries;
    /* 哈希表扩容阈值,当哈希表中元素数目达到这个值就需要进行扩容 */
    unsigned hth_load_limit;
    /* 哈希表的长度所对应素数数组中的索引 */
    int hth_prime_idx;
};

附8:struct event_base 中 struct  event_signal_map 的定义及相关结构

/* 用于将信号编号映射到事件列表。如果未定义EVMAP_USE_HT,
 * 则此结构还用作event_io_map,它将FD映射到事件列表。
 */

struct event_signal_map {
	/* Evmap_io*或evmap_ignal*数组;空条目设置为NULL。 */
	void **entries;
	/* 条目中可用条目的数量 */
	int nentries;
};

附9:struct event_base 中 struct  min_heap 的定义及相关结构(minheap-internal.h)

typedef struct min_heap {
	struct event** p;
	unsigned n, a;
} min_heap_t;

附10:struct event_base 中 struct  timeval 的定义及相关结构(time.h)

struct timeval {
	long tv_sec;
	long tv_usec;
};

附11:struct event_base 中 struct  evutil_monotonic_timer 的定义及相关结构(time-internal.h)


struct evutil_monotonic_timer {

#ifdef HAVE_MACH_MONOTONIC
	struct mach_timebase_info mach_timebase_units;
#endif

#ifdef HAVE_POSIX_MONOTONIC
	int monotonic_clock;
#endif

#ifdef HAVE_WIN32_MONOTONIC
	ev_GetTickCount_func GetTickCount64_fn;
	ev_GetTickCount_func GetTickCount_fn;
	ev_uint64_t last_tick_count;
	ev_uint64_t adjust_tick_count;

	ev_uint64_t first_tick;
	ev_uint64_t first_counter;
	double usec_per_count;
	int use_performance_counter;
#endif

	struct timeval adjust_monotonic_clock;
	struct timeval last_time;
};

附12:struct event_base 中 struct event_iocp_port 的定义及相关结构(iocp-internal.h)

/* 仅供内部使用。存储Windows IO完成端口以及相关数据。 */
struct event_iocp_port {
	/* 端口本身 */
	HANDLE port;
	/* 用来覆盖内部结构的锁。 */
	CRITICAL_SECTION lock;
	/* 端口上曾经打开的线程数。 */
	short n_threads;
	/* 如果我们要关闭此端口上的所有线程,则为真 */
	short shutdown;
	/* 此端口上的线程检查关闭和其他条件的频率 */
	long ms;
	/* 正在等待事件的线程。 */
	HANDLE *threads;
	/* 此端口上当前打开的线程数。 */
	short n_live_threads;
	/* 当我们完成关闭时发出信号的信号灯。 */
	HANDLE *shutdownSemaphore;
};

附13:struct event_base 中 enum event_base_config_flag 的定义及相关结构(util-internal.h)

enum event_base_config_flag {
    EVENT_BASE_FLAG_NOLOCK = 0x01,                  // event_base不使用lock初始化
    EVENT_BASE_FLAG_IGNORE_ENV = 0x02,              // 挑选backend方法时,不检查EVENT_xxx标志。这个功能慎用
    EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,            // 仅用于Windows。起码我不关心
    EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,           // 不检查timeout,代之以为每个event loop都准备调用timeout方法。这会导致CPU使用率偏高
    EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,    // 告诉libevent如果使用epoll的话,可以使用基于“changlist”的backend。
    EVENT_BASE_FLAG_PRECISE_TIMER = 0x20            // 使用更加精确的定时机制
};

附14:struct event_base 中 struct evutil_weakrand_state 的定义及相关结构(util-internal.h)

/* 结构来保存弱随机数生成器的状态。*/
struct evutil_weakrand_state {
	ev_uint32_t seed;
};

附15:struct event 结构体

#define TAILQ_ENTRY(type)						\
struct {								\
	struct type *tqe_next;	/* next element */			\
	struct type **tqe_prev;	/* address of previous next element */	\
}

#define LIST_ENTRY(type)						\
struct {								\
	struct type *le_next;	/* next element */			\
	struct type **le_prev;	/* address of previous next element */	\
}

struct timeval {
	long tv_sec;
	long tv_usec;
};

/**
 * 结构来表示单个事件。
 *
 * 事件可以有其表示的一些基本条件:套接字变为可读或可写(或两者兼有),或者引发信号。
 * (表示没有底层条件的事件仍然有用:您可以使用一个事件来实现计时器,或者在线程之间进行通信。)
 *
 * 通常,您可以使用EVENT_NEW()创建事件,然后使用EVENT_ADD()使它们挂起。
 * 当EVENT_BASE运行时,它将运行条件被触发的事件的回调。
 * 当您不再需要该事件时,使用EVENT_FREE()将其释放。
 *
 * 更深入地了解:
 * 事件可能是"pending"(我们正在关注其条件)、"active"(其条件已触发且其回调即将运行)、两者都不是。事件通过EVENT_ASSIGN()或EVENT_NEW()产生,然后既不活动也不挂起。
 *
 * 要使事件挂起,请将其传递给event_add()。执行此操作时,您还可以为事件设置超时。
 *
 * 在EVENT_BASE_LOOP()调用期间,当事件的条件已触发或已超时时,事件将变为活动状态。
 * 您还可以使用EVENT_ACTIVE()手动激活事件。EVEN_BASE循环将运行活动事件的回调;
 * 执行此操作后,它会将它们标记为不再活动。
 *
 * 通过将事件传递给event_del(),可以使事件成为非挂起事件。这也会使事件处于非活动状态。
 *
 * 事件可以是"persistent"或"non-persistent"。
 * 一旦触发非持久性事件,它就会变为非挂起状态:因此,每次调用event_add()时,它最多只能运行一次。
 * 持久事件即使在激活时仍保持挂起状态:您需要手动对其执行event_del()操作,才能使其成为非挂起状态。
 * 当具有超时的持久事件变为活动状态时,其超时将被重置:这意味着您可以使用持久事件来实现周期性超时。
 * 
 * 这应该被视为不透明的结构;您永远不应该直接读写它的任何字段。
 * 为了向后兼容旧代码,它在event2/event_struct.h头文件中定义;
 * 包括此头文件可能会使您的代码与Libeent的其他版本不兼容。
 */
struct event {
	struct event_callback ev_evcallback;

	/* 用于管理超时 */
	union {
		TAILQ_ENTRY(event) ev_next_with_common_timeout;
		int min_heap_idx;
	} ev_timeout_pos;
	evutil_socket_t ev_fd;

	struct event_base *ev_base;

	union {
		/* 用于io事件 */
		struct {
			LIST_ENTRY (event) ev_io_next;
			struct timeval ev_timeout;
		} ev_io;

		/* 由信号事件使用 */
		struct {
			LIST_ENTRY (event) ev_signal_next;
			short ev_ncalls;
			/* 允许在回调中删除 */
			short *ev_pncalls;
		} ev_signal;
	} ev_;

	short ev_events;
	short ev_res;		/* 结果传递给事件回调 */
	struct timeval ev_timeout;
};

四、事件(带buffer缓冲)

1.创建

/***************************************************************************************
 * 函数:bufferevent_socket_new()
 * 功能:在现有套接字上创建新套接字BufferEvent(bufferevent.h)。
 * 参数:
        base    - 与新BufferEvent关联的 enevt_base。
        fd      - 从中读取和写入数据的文件描述符。此文件描述符不允许为管道。
                  将FD设置为-1是安全的,只要您稍后使用Bufferevent_setfd或
                  Bufferevent_socket_connect()设置它。
        options - 零个或多个BEV_OPT_*标志
 * 返回:返回指向新分配的BufferEvent结构的指针,如果发生错误,则返回NULL
****************************************************************************************/
struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options);

2. 释放


/**************************************************************************************
 * 函数:void bufferevent_free(struct bufferevent *bufev);
 * 功能:取消分配与 bufferevent 结构相关联的存储。
 * 参数:
        bufev       - 要释放的 bufferevent 结构。
 * 返回:无
 * 说明:如果有挂起的数据要写入BufferEvent,那么在BufferEvent被释放之前,它可能不会被刷新。
***************************************************************************************/
void bufferevent_free(struct bufferevent *bufev);

3. 设置和获取回调函数

/**************************************************************************************
 * 函数:bufferevent_setcb()
 * 功能:设置 bufferevent 的回调。
 * 参数:
        bufev       - 要更改其回调的BufferEvent对象。
        readcb      - 回调在存在要读取的数据时调用,如果为NULL则说明不需要回调。
        writecb     - 回调在文件描述符准备好写入时调用,如果为NULL则说明不需要回调。
        eventcb     - 在文件描述符上有事件时调用的回调。
        cbarg       - 将提供给每个回调(readcb、writecb和errorcb)的参数。
 * 返回:无
***************************************************************************************/
void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb,
        bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg);


/**************************************************************************************
 * 函数:bufferevent_getcb()
 * 功能:获取(检索) bufferevent 的回调。
 * 参数:
        bufev       - 要更改其回调的BufferEvent对象。
        readptr     - 如果readcb_ptr 非空,则*readcb_ptr 设置为BufferEvent的当前读取回调。
        writeptr    - 如果writecb_ptr非空,则*writecb_ptr设置为BufferEvent的当前写入回调。
        eventptr    - 如果eventcb_ptr非空,则*eventcb_ptr设置为BufferEvent的当前事件回调。
        cbarg_ptr   - 如果cbarg_ptr  非空,则*cbarg_ptr  设置为BufferEvent的当前回调参数。
 * 返回:无
***************************************************************************************/
void bufferevent_getcb(struct bufferevent *bufev, bufferevent_data_cb *readcb_ptr,
bufferevent_data_cb *writecb_ptr, bufferevent_event_cb *eventcb_ptr, void **cbarg_ptr);

4. bufferevent 的启用和禁用

/**************************************************************************************
 * 函数:int bufferevent_enable(struct bufferevent *bufev, short event);
 * 作用:启用 bufferevent
 * 参数:
        bufev   - 要启用的 bufferevent
        event   - EV_READ|EV_WRITE 的任意组合。
 * 返回:如果成功,则返回0;如果发生错误,则返回-1
***************************************************************************************/
int bufferevent_enable(struct bufferevent *bufev, short event);

/**************************************************************************************
 * 函数:int bufferevent_disable(struct bufferevent *bufev, short event);
 * 作用:禁用 bufferevent
 * 参数:
        bufev   - 要关闭的 bufferevent
        event   - EV_READ|EV_WRITE 的任意组合。
 * 返回:如果成功,则返回0;如果发生错误,则返回-1
***************************************************************************************/
int bufferevent_disable(struct bufferevent *bufev, short event);

5. 读/写数据

/**************************************************************************************
 * 函数:int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
 * 功能:将数据写入 bufferevent 缓冲区(将数据写入文件描述符。数据被附加到输出缓冲区,并在可供写入时自动写入描述符)(bufferevent.h)。
 * 参数:
        bufev       - 要写入的 bufferevent。
        data        - 指向要写入的数据的指针。
        size        - 数据的长度,以字节为单位。
 * 返回:如果成功则返回0,如果出错则返回-1
***************************************************************************************/
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);



/**************************************************************************************
 * 函数:int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
 * 功能:从 bufferevent 缓冲区读取数据(从输入缓冲区读取数据)。
 * 参数:
        bufev       - 要从中读取的的 bufferevent。
        data        - 指向将存储数据的缓冲区的数据指针。
        size        - 数据缓冲区的大小,以字节为单位。
 * 返回:返回读取的数据量,单位为字节。
***************************************************************************************/
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);

6. 将数据写入缓冲区/从缓冲区读入数据

/*************************************************************************************
 * 函数:int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf);
 * 功能:将数据从事件缓冲区写入缓冲区事件缓冲区。因此,evbuffer正在被排出。
 * 参数:
        bufev   - 要写入的 bufferevent。
        buf     - 要写入的 evbuffer。
 * 返回:如果成功则返回0,如果出错则返回-1。
**************************************************************************************/
int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf);


/*************************************************************************************
 * 函数:int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf);
 * 功能:将数据从BufferEvent缓冲区读取到evbuffer。这避免了内存复制。
 * 参数:
        bufev   - 要从中读取的 bufferevent。
        buf     - 要添加数据的 evbuffer。
 * 返回:如果成功则返回0,如果出错则返回-1。
**************************************************************************************/
int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf);

7. 启动connect()

/*************************************************************************************
 * 函数:bufferevent_socket_connect()
 * 功能:使用基于套接字的BufferEvent启动connect()尝试(bufferevent.h)。
 * 参数:
        bufev       - 使用BufferEVENT_SOCKET_NEW()分配的现有BufferEvent。
        addr        - 我们应该连接到的地址。
        socklen     - 地址长度。
 * 返回:成功时返回0,失败时返回-1。
 * 说明:
        如果BufferEvent尚未设置套接字,我们将在这里分配一个新套接字,并在开始之前使其成为非阻塞的。
        如果没有提供地址,我们假设套接字已经在连接,并配置BufferEvent,以便在连接完成时会产生BEV_EVENT_CONNECTED事件。
**************************************************************************************/
int bufferevent_socket_connect(struct bufferevent *bufev, const struct sockaddr *addr, int socklen);

8. 解析主机名并连接

/*************************************************************************************
 * 函数:bufferevent_socket_connect_hostname
 * 功能:解析主机名‘hostname’并使用 bufferevent_socket_connect() 连接到它(bufferevent.h)。
 * 参数:
        bufev       - 使用BufferEVENT_SOCKET_NEW()分配的现有BufferEvent。
        base        - (可选)一个用于异步解析主机名的evdns_base。对于阻塞解析,可以设置为NULL。
        family      - 要将地址解析到的首选地址系列,如果没有首选项,则为AF_UNSPEC。仅支持AF_INET、AF_INET6和AF_UNSPEC。
        hostname    - 要解析的主机名;有关可识别格式的说明,请参阅说明。
        port        - 解析地址上要连接的端口。
 * 返回:如果成功则返回0,如果失败则返回-1。
 * 说明:
        可识别的主机名格式为:
            www.example.com(主机名)。
            1.2.3.4(IPv6地址)。
            ::1(IPv6地址)。
            [::1]([ipv6address])。
        性能说明:如果不提供evdns_base,则此函数在等待DNS响应时可能会阻塞。这可能不是你想要的。
**************************************************************************************/
int bufferevent_socket_connect_hostname(struct bufferevent *bufev, struct evdns_base *base, int family, const char *hostname, int port);

9. 返回 (3.解析主机名并连接) 函数执行时的错误码

/****************************************************************************************
 * 函数:int bufferevent_socket_get_dns_error(struct bufferevent *bev);
 * 功能:返回 bufferevent_socket_connect_hostname() 上一次失败的
            DNS查找尝试的错误代码(bufferevent.h)。
 * 参数:
        bev     - BufferEvent对象。
 * 返回:DNS 错误码
 * 说明:参考( evutil_gai_strerror() )
*****************************************************************************************/
int bufferevent_socket_get_dns_error(struct bufferevent *bev);

10. 设置和获取 event_base

/***************************************************************************************
 * 函数:int bufferevent_base_set(struct event_base *base, struct bufferevent *bufev);
 * 功能:将 bufferevent 分配给特定的 event_base(bufferevent.h)。
 * 参数:
        base    - 由event_init()返回的event_base。
        bufev   - bufferevent_new()或 bufferevent_socket_new()返回的 bufferevent 结构。
 * 返回:如果成功则返回0,如果出错则返回-1。
 * 说明:只有套接字缓冲区事件(socket bufferevents)支持此函数。
****************************************************************************************/
int bufferevent_base_set(struct event_base *base, struct bufferevent *bufev);

/**************************************************************************************
 * 函数:struct event_base *bufferevent_get_base(struct bufferevent *bev);
 * 功能:获取 bufferevent使用的 event_base
 * 参数:
        bev     - 需要获取 event_base 的 bufferevent
 * 返回:event_base 结构体
***************************************************************************************/
struct event_base *bufferevent_get_base(struct bufferevent *bev);

11. 设置/获取优先级

/*************************************************************************************
 * 函数:int bufferevent_priority_set(struct bufferevent *bufev, int pri);
 * 功能:设置 bufferevent 优先级
 * 参数:
        bufev       - bufferevent 结构体
        pri         - 要分配的优先级
 * 返回:如果成功则返回0,如果出错则返回-1
 * 说明:仅套接字缓冲区事件(socket bufferevent)支持。
**************************************************************************************/
int bufferevent_priority_set(struct bufferevent *bufev, int pri);

/*************************************************************************************
 * 函数:int bufferevent_get_priority(const struct bufferevent *bufev);
 * 功能:获取 bufferevent 优先级
 * 参数:
        bufev       - bufferevent 结构体
 * 返回:返回BufferEvent的优先级。
 * 说明:仅套接字缓冲区事件(socket bufferevent)支持。
**************************************************************************************/
int bufferevent_get_priority(const struct bufferevent *bufev);

12. 附录

附1:bufferevent 结构体的定义

/**************************************************************************************
 * bufferevent 的共享实现。
 * 公开此类型只是因为它在以前的版本中公开,有些人的代码可能依赖于操作它。否则,您真的不应该依赖这个结构的布局、大小或内容:它非常不稳定,并且在将来的代码版本中会发生变化。
***************************************************************************************/
struct bufferevent {
	/* 为其创建此 bufferevent 的事件基数。 */
	struct event_base *ev_base;
	/* 指向函数指针表的指针,用于设置此 bufferevent 的行为方式。 */
	const struct bufferevent_ops *be_ops;

	/* 发生超时或套接字准备读取数据时触发的读取事件。仅由 bufferevent 的某些子类型使用。 */
	struct event ev_read;
	/* 发生超时或套接字准备写入数据时触发的写入事件。仅由 bufferevent 的某些子类型使用。 */
	struct event ev_write;

	/* 输入缓冲器。只允许 bufferevent 向此缓冲区添加数据,但允许用户清空该缓冲区。 */
	struct evbuffer *input;

	/* 输入缓冲器。虽然允许用户添加数据,但只允许 bufferevent 从该缓冲区中排出数据。 */
	struct evbuffer *output;

	struct event_watermark wm_read;
	struct event_watermark wm_write;

	bufferevent_data_cb readcb;
	bufferevent_data_cb writecb;
	/* 这应该称为‘eventcb’,但是重命名会破坏向后兼容性 */
	bufferevent_event_cb errorcb;
	void *cbarg;

	struct timeval timeout_read;
	struct timeval timeout_write;

	/* 当前启用的事件:目前支持EV_READ和EV_WRITE。 */
	short enabled;
};

附2:bufferevent_ops 结构体的定义

/**************************************************************************************
 * bufferevent 的实现表:保存函数指针和其他信息,以使各种 bufferevent 类型工作。
***************************************************************************************/
struct bufferevent_ops {
	/* bufferevent 类型的名称。 */
	const char *type;
	/**********************************************************************************
         在实现类型的多大偏移量处,我们将找到BufferEvent结构?
         示例:如果类型实现为。
                struct bufferevent_x {
                    int extra_data;
	                struct bufferevent bev;
                };
            那么mem_offset应该是offsetof(struct Bufferevent_x,bev)
	***********************************************************************************/
	off_t mem_offset;

	/*  在 bufferevent 上启用一个或多个EV_READ|EV_WRITE。
        不需要调整‘enabled’(启用)字段。成功时返回0,失败时返回-1。
	 */
	int (*enable)(struct bufferevent *, short);

	/*  禁用 bufferevent 上的一个或多个EV_READ|EV_WRITE。
        不需要调整‘enabled’(启用)字段。成功时返回0,失败时返回-1。
	 */
	int (*disable)(struct bufferevent *, short);

	/* 从相关数据结构中分离 bufferevent。一旦其引用计数达到0,就立即调用。 */
	void (*unlink)(struct bufferevent *);

	/* 释放任何存储并释放此实现中使用的任何额外数据或结构。在 bufferevent 完成时调用。*/
	void (*destruct)(struct bufferevent *);

	/* 在 bufferevent 的超时发生更改时调用。*/
	int (*adj_timeouts)(struct bufferevent *);

	/* 调用以刷新数据。 */
	int (*flush)(struct bufferevent *, short, enum bufferevent_flush_mode);

	/* 调用以访问其他字段。 */
	int (*ctrl)(struct bufferevent *, enum bufferevent_ctrl_op, union bufferevent_ctrl_data *);
};

附3:evbuffer 结构体的定义(evbuffer-internal.h)

struct evbuffer {
	/* 此缓冲区的链表中的第一个链。 */
	struct evbuffer_chain *first;
	/* 此缓冲区的链表中的最后一个链。 */
	struct evbuffer_chain *last;

	/* 指向指向‘last_with_data’链的下一个指针的指针。
	 *
	 * 要打开包装,请执行以下操作:
	 *
	 * last_with_data 链是其中包含任何数据的最后一个链。
                            如果缓冲区中的所有链都为空,则它是第一个链。
                            如果缓冲区没有链,则为NULL。
	 *
	 * last_with_datap 指针指向 _whatever 'next' 指针,指向 last_with_data 链。
     * 如果 last_with_data 链是第一个链,或者它是 NULL 则 last_with_datap 指针是&buf->first。
	 */
	struct evbuffer_chain **last_with_datap;

	/* 存储在所有链中的字节总数。*/
	size_t total_len;

	/* 自上次尝试调用回调以来已添加到缓冲区的字节数。 */
	size_t n_add_for_cb;
	/* 自上次尝试调用回调以来已从缓冲区中删除的字节数。 */
	size_t n_del_for_cb;

#ifndef EVENT__DISABLE_THREAD_SUPPORT
	/* 用于协调对此缓冲区的访问的锁。 */
	void *lock;
#endif
	/* 当我们释放此 evbuffer 时,如果我们应该释放 lock 字段,则为 true。 */
	unsigned own_lock : 1;
	/* 如果我们不允许更改缓冲区的前面(排出或预置),则为True。 */
	unsigned freeze_start : 1;
	/* True当且仅当我们不允许更改缓冲区末尾(追加) */
	unsigned freeze_end : 1;
	/* 如果此 evbuffer 的回调不是在缓冲区发生变化时立即调用,而是推迟到 EVENT_BASE 的循环中调用,则为True。
当我们有相互递归的回调时,对于防止巨大的堆栈溢出以及在单个线程中序列化回调非常有用。 */
	unsigned deferred_cbs : 1;
#ifdef _WIN32
	/* 如果此缓冲区是为重叠IO设置的,则为True。 */
	unsigned is_overlapped : 1;
#endif
	/** 零个或多个 EVBUFFER_FLAG_* 位 */
	ev_uint32_t flags;

	/* 用于实现延迟回调。 */
	struct event_base *cb_queue;

	/* 此evbuffer上的引用计数。
       当引用计数达到0时,缓冲区被销毁。
       使用evbuffer_incref和evbuffer_dectf_and_unlock和evbuffer_free进行操作。
     */
	int refcnt;

	/* 一个struct event_callback句柄,用于从事件循环调用此缓冲区的所有回调。 */
	struct event_callback deferred;

	/* 回调函数的双向链接列表 */
	LIST_HEAD(evbuffer_cb_queue, evbuffer_cb_entry) callbacks;

	/* 此 evbuffer 所属的父BufferEvent对象。如果 evbuffer 独立,则为空。 */
	struct bufferevent *parent;
};


/* 事件缓冲区中的单个项。 */
struct evbuffer_chain {
	/* 指向链中的下一个缓冲区 */
	struct evbuffer_chain *next;

	/* 缓冲区字段中可用的总分配。 */
	size_t buffer_len;

	/* 缓冲区开头的未使用空间或发送文件缓冲区的文件偏移量。 */
	ev_misalign_t misalign;

	/* 进入缓冲区的偏移量+开始写入的未对齐位置。换句话说,实际存储在缓冲区中的字节总数。 */
	size_t off;

	/* 设置此链是否需要特殊处理 */
	unsigned flags;
#define EVBUFFER_FILESEGMENT	0x0001  /**< 用于文件段的链 */
#define EVBUFFER_SENDFILE	0x0002	/**< 与sendfile一起使用的链 */
#define EVBUFFER_REFERENCE	0x0004	/**< 带有mem引用的链子 */
#define EVBUFFER_IMMUTABLE	0x0008	/**< read-only 链 */
	/* 在解开链条之前,不能重新分配或释放,也不能移动其内容物的链条。 */
#define EVBUFFER_MEM_PINNED_R	0x0010
#define EVBUFFER_MEM_PINNED_W	0x0020
#define EVBUFFER_MEM_PINNED_ANY (EVBUFFER_MEM_PINNED_R|EVBUFFER_MEM_PINNED_W)
	/* 一条应该被释放的链条,但在它被解开之前不能被释放。 */
#define EVBUFFER_DANGLING	0x0040
	/* 作为另一个链的引用副本的链 */
#define EVBUFFER_MULTICAST	0x0080

	/* 对此链的引用数 */
	int refcnt;

	/* 通常指向属于该缓冲区的读写内存,该缓冲区是作为evbuffer_chain分配的一部分分配的。
       对于mmap,这可以是只读缓冲区,并且将在标志中设置EVBUFFER_IMMOTABLE。
       对于sendfile,它可能指向NULL。
	 */
	unsigned char *buffer;
};

struct event_callback {
	TAILQ_ENTRY(event_callback) evcb_active_next;
	short evcb_flags;
	ev_uint8_t evcb_pri;	/* 数字越小,优先级越高 */
	ev_uint8_t evcb_closure;
	/* allows us to adopt for different types of events */
        union {
		void (*evcb_callback)(evutil_socket_t, short, void *);
		void (*evcb_selfcb)(struct event_callback *, void *);
		void (*evcb_evfinalize)(struct event *, void *);
		void (*evcb_cbfinalize)(struct event_callback *, void *);
	} evcb_cb_union;
	void *evcb_arg;
};


附4:event_watermark 结构体的定义

struct event_watermark {
	size_t low;
	size_t high;
};

附5:bufferevent_options 枚举类型定义

/* 创建BufferEvent时可以指定的选项 */
enum bufferevent_options {
	/* 如果设置,则在释放此BufferEvent时关闭底层文件描述符/bufferevent/任何内容。 */
	BEV_OPT_CLOSE_ON_FREE = (1<<0),

	/* 如果已设置,并且启用了线程处理,则对此 bufferevent 的操作将受锁定保护 */
	BEV_OPT_THREADSAFE = (1<<1),

	/* 如果设置,回调将在事件循环中延迟运行。 */
	BEV_OPT_DEFER_CALLBACKS = (1<<2),

	/* 如果设置,则在不对 bufferevent 持有锁的情况下执行回调。
     * 此选项当前要求还设置BEV_OPT_DEFER_CALLBACKS;Libeent的未来版本可能会删除此要求。*/
	BEV_OPT_UNLOCK_CALLBACKS = (1<<3)
};

附6:函数指针的定义

/**************************************************************************************
 * 定义:(*bufferevent_data_cb)
 * 功能:bufferevent 的读取或写入(read or write)回调。
 * 参数:
        bev     - 触发回调的 bufferevent。
        ctx     - 此 bufferevent 的用户指定上下文
 * 返回:
 * 说明:当新数据到达输入缓冲区并且可读数据量超过低水位线(默认为0)时,会触发读回调。
         如果写入缓冲区已耗尽或低于其低水位线,则会触发写入回调。
***************************************************************************************/
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);


/**************************************************************************************
 * 定义:(*bufferevent_event_cb)
 * 功能:bufferevent 的事件/错误(event/error)回调。
 * 参数:
        bev     - 达到错误条件的bufferevent。
        what    - 标志的组合:BEV_EVENT_READING或BEV_EVENT_WRITING,指示在读取或写入路径上是否遇到错误,
                  以及以下标志之一:BEV_EVENT_EOF、BEV_EVENT_ERROR、BEV_EVENT_TIMEOUT、BEV_EVENT_CONNECTED。
        ctx     - 此 bufferevent 的用户指定上下文
 * 返回:
 * 说明:如果遇到EOF条件或其他无法恢复的错误,则触发事件回调。
        对于延迟回调的BufferEvent,这是自上次回调调用以来BufferEvent上发生的所有错误的按位OR。
***************************************************************************************/
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, void *ctx);


五、示例代码

1. 回声服务器的实现

#include <string.h>
#include <stdlib.h>
#include <event.h>
#include <event2/event.h>
#include <event2/listener.h>

#define BUFFER_LENGTH 1024

typedef struct bufferevent bufferevent_t;
typedef struct event_base event_base_t;

typedef struct connect_stat {
	bufferevent_t* bev;
	char send_buffer[BUFFER_LENGTH];
}connect_stat_t;

connect_stat_t* stat_init(bufferevent_t* bev);

void event_listener_cb(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* addr, int socklen, void* user_arg);
void do_echo_request(bufferevent_t* bev, void* arg);
void event_cb(bufferevent_t* bev, short what, void* arg);

int main(int argc, char* argv[]) {
	struct sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(9696);

	event_base_t* base = event_base_new();

	struct evconnlistener* listener = evconnlistener_new_bind(base, event_listener_cb, base,
		LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, 10, (struct sockaddr*)(&sin), sizeof(sin));

	event_base_dispatch(base);

	evconnlistener_free(listener);
	event_base_free(base);

	return 0;
}

connect_stat_t* stat_init(bufferevent_t* bev) {
	connect_stat_t* p = (connect_stat_t*)malloc(sizeof(connect_stat_t));
	memset(p, 0, sizeof(connect_stat_t));

	p->bev = bev;

	return p;
}

void event_listener_cb(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* addr, int socklen, void* user_arg) {
	event_base_t* base = (event_base_t*)user_arg;

	// 为客户端分配bufferevent
	bufferevent_t* bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

	connect_stat_t* stat = stat_init(bev);

	bufferevent_setcb(bev, do_echo_request, NULL, event_cb, stat);
	bufferevent_enable(bev, EV_READ | EV_PERSIST);
}

void do_echo_request(bufferevent_t* bev, void* arg) {
	connect_stat_t* stat = (connect_stat_t*)arg;
	char* msg = stat->send_buffer;

	printf("do echo request!\n");

	size_t len = bufferevent_read(bev, msg, BUFFER_LENGTH - 1);
	msg[len] = '\0';

	printf("recv from client, data: %s\n", msg);

	bufferevent_write(bev, msg, strlen(msg));
}

void event_cb(bufferevent_t* bev, short what, void* arg) {
	connect_stat_t* stat = (connect_stat_t*)arg;

	if (what & BEV_EVENT_EOF) {
		printf("connection closed!\n");
	}
	else if (what & BEV_EVENT_ERROR) {
		printf("some other error!\n");
	}

	// 自动关闭套接字和释放读写缓冲区
	bufferevent_free(bev);
	free(stat);
}




2. 实现简单的服务器和客户端通信

1. 服务器端(server.c)

#include <string.h>
#include <stdlib.h>
#include <event.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>

#define BUFFER_LENGTH 1024

typedef struct event_base		event_base_t;
typedef struct bufferevent		bufferevent_t;
typedef struct evconnlistener	evconnlistener_t;

typedef struct _connect_stat {
	bufferevent_t* bev;
	char buffer[BUFFER_LENGTH];
}connect_stat_t;

connect_stat_t* stat_new(bufferevent_t* bev);

void listener_cb(evconnlistener_t* listener, evutil_socket_t fd, struct sockaddr* addr, int addrlen, void* user_arg);

void do_recv_msg(bufferevent_t* bev, void* user_arg);
void event_cb(bufferevent_t* bev, short what, void* user_arg);

int main(int argc, char* argv[]) {
	struct sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(9696);	// 端口为 9696
	//sin.sin_addr.s_addr = htonl(INADDR_ANY);

	event_base_t* base = event_base_new();

	struct evconnlistener* listener = evconnlistener_new_bind(base, listener_cb, base,
		BEV_OPT_CLOSE_ON_FREE,
		10, (struct sockaddr*)(&sin), sizeof(sin));

	event_base_dispatch(base);

	evconnlistener_free(listener);
	event_base_free(base);

	return 0;
}

connect_stat_t* stat_new(bufferevent_t* bev) {
	connect_stat_t* p = (connect_stat_t*)malloc(sizeof(connect_stat_t));
	memset(p, 0, sizeof(connect_stat_t));
	p->bev = bev;

	return p;
}

void listener_cb(evconnlistener_t* listener, evutil_socket_t fd, struct sockaddr* addr, int addrlen, void* user_arg) {
	event_base_t* base = (event_base_t*)(user_arg);

	// 为客户端分配bufferevent
	bufferevent_t* bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

	connect_stat_t* stat = stat_new(bev);

	bufferevent_setcb(bev, do_recv_msg, NULL, event_cb, stat);
	bufferevent_enable(bev, EV_READ | EV_PERSIST);
}

void do_recv_msg(bufferevent_t* bev, void* user_arg) {
	connect_stat_t* stat = (connect_stat_t*)user_arg;

	char* msg = stat->buffer;

	size_t len = bufferevent_read(bev, msg, BUFFER_LENGTH - 1);
	msg[len] = '\0';

	printf("recv data: %s\n", msg);

	bufferevent_write(bev, msg, strlen(msg));
}

void event_cb(bufferevent_t* bev, short what, void* user_arg) {
	connect_stat_t* stat = (connect_stat_t*)user_arg;

	if (what & BEV_EVENT_EOF) {
		printf("connection closed!\n");
	}
	else if (what & BEV_EVENT_ERROR) {
		printf("some other error!\n");
	}

	// 同时 关闭套接字 和 free 读写缓冲区
	bufferevent_free(bev);

	free(stat);
}

2. 客户端(client.c)

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <event.h>
#include <event2/event.h>

typedef struct sockaddr		sockaddr_t;
typedef struct sockaddr_in	sockaddr_in_t;

int connect_server(const char* server_ip, int port);

void cmd_read_data(int fd, short events, void* arg);
void socket_read_data(int fd, short events, void* arg);

int main(int argc, char* argv[]) {
	if (argc < 3) {
		fprintf(stderr, "please input [ipaddr][ipport]\n");
		return -1;
	}

	int sockfd = connect_server(argv[1], atoi(argv[2]));
	if (sockfd < 0) {
		fprintf(stderr, "connect_server(): failed!\n");
		return -2;
	}

	printf("connect server success!\n");

	struct event_base* base = event_base_new();
	struct event* ev_sockfd = event_new(base, sockfd, EV_READ | EV_PERSIST, socket_read_data, NULL);
	event_add(ev_sockfd, NULL);

	// 监听终端输入事件
	struct event* ev_cmd = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, cmd_read_data, (void*)(&sockfd));
	event_add(ev_cmd, NULL);

	event_base_dispatch(base);

	printf("finished!\n");

	return 0;
}

int connect_server(const char* server_ip, int port) {
	int sockfd, status, save_errno;
	sockaddr_in_t server_addr;

	memset(&server_addr, 0, sizeof(sockaddr_in_t));

	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	status = inet_aton(server_ip, &server_addr.sin_addr);

	if (!status) {
		errno = EINVAL;
		return -1;
	}

	sockfd = socket(PF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) {
		return sockfd;
	}

	status = connect(sockfd, (sockaddr_t*)(&server_addr), sizeof(server_addr));
	if (status < 0) {
		save_errno = errno;
		close(sockfd);
		errno = save_errno;
		return -1;
	}

	// 不用设置非阻塞
	//evutil_make_socket_nonblocking(sockfd);

	return sockfd;
}

void cmd_read_data(int fd, short events, void* arg) {
	char msg[1024];

	int ret = read(fd, msg, sizeof(msg) - 1);
	if (ret < 0) {
		fprintf(stderr, "read error!\n");
		exit(1);
	}

	ret = (msg[ret - 1] == '\n' ? ret - 1 : ret);

	msg[ret] = '\0';

	int sockfd = *((int*)arg);

	// 把终端消息发送给服务器端,客户端忽略性能考虑,直接使用阻塞发送
	printf("send to server: %s\n", msg);
	write(sockfd, msg, ret);
}

void socket_read_data(int fd, short events, void* arg) {
	char msg[1024];

	// 不考虑数据读了一半的情况
	int len = read(fd, msg, sizeof(msg) - 1);
	if (len == 0) {
		fprintf(stderr, "connection close!\n");
		exit(2);
	}
	else if (len < 0) {
		fprintf(stderr, "read failed!\n");
		exit(3);
	}

	msg[len] = '\0';

	printf("recv data: %s\n", msg);
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值