Libevent 学习三:上下文属性配置和源码分析

Libevent 上下文属性配置和源码分析

Libevent 创建上下文方法,默认 event_base_new()

struct event_base *event_base_new(void);

也可以根据配置创建上下文

struct event_base *event_base_new_with_config(const struct event_config *);

此函数在 libevent 2.X 版本中才有。可以使用如下函数创建和销毁一个配置项:

// 创建和释放配置项,用完后必须释放
struct event_config *event_config_new(void);
void event_config_free(struct event_config *cfg);

在使用 event_config_new 创建完 event_base 上下文后,必须使用 event_config_free 进行释放,否则会有内存泄漏。

获取和设置网络模式

// 1.显示支持的网络模式
const char **event_get_supported_methods(void);

// 2.获取当前的网络模型
const char *event_base_get_method(const struct event_base *);

// 3.设置网络模型,使用select。Windows上无效
int event_config_avoid_method(struct event_config *cfg, const char *method);

Libevent 中支持的网络模型选项位于 event.c 中,代码如下:

/* Array of backends in order of preference. */
static const struct eventop *eventops[] = {
#ifdef EVENT__HAVE_EVENT_PORTS
	&evportops,
#endif
#ifdef EVENT__HAVE_WORKING_KQUEUE
	&kqops,
#endif
#ifdef EVENT__HAVE_EPOLL
	&epollops,
#endif
#ifdef EVENT__HAVE_DEVPOLL
	&devpollops,
#endif
#ifdef EVENT__HAVE_POLL
	&pollops,
#endif
#ifdef EVENT__HAVE_SELECT
	&selectops,
#endif
#ifdef _WIN32
	&win32ops,
#endif
	NULL
};

核心思想是优先支持效率高的网络模型,运行时按顺序选择,前面不支持才选后面。即有 epoll 支持 epoll,没有 epoll,选择使用 poll/select。

设置标志位

int event_config_set_flag(struct event_config *cfg, int flag);

flag 可用参数:

  • EVENT_BASE_FLAG_NOLOCK 不要为 event_base 分配锁。设置这个选项可以为 event_base 节省一点用于锁定和解锁的时间,但会让多个线程不安全;
  • EVENT_BASE_FLAG_IGNORE_ENV 作为后端程序时,不要检测 EVENT_* 环境变量。使用这个标志需要三思:可能受环境变量影响程序调试;
  • EVENT_BASE_FLAG_STARTUP_IOCP 仅用于 Windows,启用任何必须的 IOCP 分发逻辑必须要初始化 IOCP 多线程,即调用 evthread_use_windows_threads 和 event_config_set_num_cpus_hint 函数;
  • EVENT_BASE_FLAG_NO_CACHE_TIME 不在事件循环执行超时回调时检测当前时间,而是在每次超时回调后检测,设置后时间会更精确。但目前我还并不明白具体用途,一般情况下也用不到;
  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST epoll下有效,防止同一个 fd 多次激发事件,fd 如果做复制会有 bug;
  • EVENT_BASE_FLAG_PRECISE_TIMER 默认使用系统最快的计时机制,如果系统有较慢但更精确的计时,则采用。但更精确的计时机制一般会伴随更复杂的调用,可能会有效率问题。

这些参数在 Linux 编程中并不常用,但在 Windows 编程中,EVENT_BASE_FLAG_STARTUP_IOCP 标志经常会用到,因为 Windows 只支持 IOCP 模式。

设置特征参数

int event_config_require_features(struct event_config *cfg, int feature);

feature 指定特征方法,可用参数:

  • EV_FEATURE_ET 使用边沿触发,支队 epoll 有效;
  • EV_FEATURE_O1 要求添加、删除单个事件,或者确定哪个事件激活的操作是 O(1) 的复杂度;
  • EV_FEATURE_FDS 要求支持任意文件描述符,而不仅仅是套接字;
  • EV_FEATURE_EARLY_CLOSE 检测连接关闭事件。允许使用 EV_CLOSED 检测连接关闭,但不确定在所有内核版本上都支持,需要进行检测判断。

注意,这些特征不一定能全部支持,具体支持哪一种,可以写代码查询,后面会有示例源码。

Libevent 源码中会执行各种特征的匹配,代码如下

...
for (i = 0; eventops[i] && !base->evbase; i++) {
	if (cfg != NULL) {
		/* determine if this backend should be avoided */
		if (event_config_is_avoided_method(cfg, eventops[i]->name))
			continue;
		if ((eventops[i]->features & cfg->require_features)
			!= cfg->require_features)
			continue;
	}

	/* also obey the environment variables */
	if (should_check_environment &&
		event_is_method_disabled(eventops[i]->name))
		continue;

	base->evsel = eventops[i];

	base->evbase = base->evsel->init(base);
}
......

Libevent 上下文配置源码实例

// desc: libevent demo simple config
// file: demo02_config.cpp

#include <iostream>
#include <string.h>
#ifndef _WIN32
#include <signal.h>
#endif // !_WIN32

#include "event.h"
#include "event2/listener.h"
#include "event2/thread.h"

#define SERVER_PORT 8000

// 接收连接的回调函数
void listen_cb(struct evconnlistener *e,
    evutil_socket_t s, struct sockaddr *addr, int socklen, void *arg)
{
    std::cout << "listen_cb" << std::endl;
}

int main(int argc, char* argv[])
{
#ifdef _WIN32
    // 初始化socket库
    WSADATA wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);
#else
    // 忽略管道信号,因为发送数据给已关闭的socket会生成SIGPIPE信号,导致进程退出
    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
        return -1;
#endif

    std::cout << "libevent config test" << std::endl;

    // 创建配置上下文
    event_config *cfg = event_config_new();
    // 显示支持的网络模式
    const char **methods = event_get_supported_methods();
    std::cout << "event_get_supported_methods: \n";
    for (int i = 0; methods[i]; i++)
        std::cout << "\t" << methods[i] << std::endl;

    // 设置特征
    // 设置边沿触发
    // event_config_require_features(cfg, EV_FEATURE_ET);
    // Windows中EV_FEATURE_FDS无效;Linux中设置了EV_FEATURE_FDS,其他特征就无法设置
    // event_config_require_features(cfg, EV_FEATURE_FDS);

    // 设置网络模型,使用select。Windows上无效
    // event_config_avoid_method(cfg, "epoll");
    // event_config_avoid_method(cfg, "poll");

#ifdef _WIN32
    // Windows中支持IOCP(线程池)
    event_config_set_flag(cfg, EVENT_BASE_FLAG_STARTUP_IOCP);
    // 初始化IOCP线程,必须要有
    evthread_use_windows_threads();
    // 设置CPU数量
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    std::cout << "num of processors: " << si.dwNumberOfProcessors << std::endl;
    event_config_set_num_cpus_hint(cfg, si.dwNumberOfProcessors);
#endif

    // 使用配置初始化libevent上下文
    event_base *base = event_base_new_with_config(cfg);
    event_config_free(cfg);
    if (!base) {
        std::cout << "event_base_new_with_config failed" << std::endl;
        return -1;
    }
    std::cout << "event_base_new_with_config success" << std::endl;

    // 获取当前网络模型
    std::cout << "current method: " << event_base_get_method(base) << std::endl;

    // 确认特征是否生效
    int flag = event_base_get_features(base);
    if (flag & EV_FEATURE_ET)
        std::cout << "EV_FEATURE_ET events is supported" << std::endl;
    else
        std::cout << "EV_FEATURE_ET events is not supported" << std::endl;
    if (flag & EV_FEATURE_O1)
        std::cout << "EV_FEATURE_O1 events is supported" << std::endl;
    else
        std::cout << "EV_FEATURE_O1 events is not supported" << std::endl;
    if (flag & EV_FEATURE_FDS)
        std::cout << "EV_FEATURE_FDS events is supported" << std::endl;
    else
        std::cout << "EV_FEATURE_FDS events is not supported" << std::endl;
    if (flag & EV_FEATURE_EARLY_CLOSE)
        std::cout << "EV_FEATURE_EARLY_CLOSE events is supported" << std::endl;
    else
        std::cout << "EV_FEATURE_EARLY_CLOSE events is not supported" << std::endl;

    // 监听端口,包含 socket/bind/listen
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERVER_PORT);

    evconnlistener *ev = evconnlistener_new_bind(
        base,                                       // libevent上下文
        listen_cb,                                  // 接收连接的回调函数
        base,                                       // 回调函数获取的参数
        LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,  // 设置地址重用和关闭同时关闭socket
        10,                                         // 连接队列大小,对应listen函数
        (struct sockaddr*)&sin, sizeof(sin));       // 绑定socket地址和端口
    if (!ev) {
        std::cout << "evconnlistener_new_bind failed" << std::endl;
        return -1;
    }

    // 事件分发处理
    event_base_dispatch(base);
    // 释放资源
    evconnlistener_free(ev);
    event_base_free(base);

#ifdef _WIN32
    WSACleanup();
#endif
    return 0;
}

这里是一个完整的示例,演示了在 Windows 上使用 IOCP 的实例,其中部分代码因为系统不支持,所以做了注释,但代码中演示了所有配置项的设置和获取方法。

Libevent 设置 IOCP 要调用 evthread_use_windows_threads ,这是让 IOCP 支持多线程。另外,还可以调用 event_config_set_num_cpus_hint 设置 CPU 使用数量,这仅仅是一个设置,实际使用可能少一些。默认情况下,线程数与 CPU 数量一致,调用上面两个函数设置后线程数为20,如下图:

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值