libevent示例

libevent示例

基本概念

Libevent是基于 Reactor 模式的网络库,在 Reactor 模式中,通常都有一个事件循环(Event Loop),在 Libevent 中,这个事件循环就是event_base结构体:

struct event_base *event_base_new(void);         // 创建事件循环
void event_base_free(struct event_base *base);   // 销毁事件循环
int event_base_dispatch(struct event_base *base);// 运行事件循环

通常来说,事件循环主要有两个作用:

  • 用来管理事件,比如说添加我们感兴趣的事件,修改事件或删除事件。
  • 用来轮询它管理的所有事件,如果发现有事件活跃 (avtive),就调用相应的回调函数去处理事件。
    Libevent 使用event结构体来代表事件,可以使用event_new()创建一个事件:
struct event *event_new(struct event_base *base, // 事件循环
                 evutil_socket_t fd,      // 文件描述符
                 short what,              // 事件类型
                 event_callback_fn cb,    // 回调函数
                 void *arg);              // 传递给回调函数的参数

创建一个事件之后,要怎么把它加入到事件循环呢?可以使用event_add()函数:

int event_add(struct event *ev,             // 事件
              const struct timeval *tv);    // 超时时间

默认情况下,当一个事件变得活跃时,Libevent 会执行这个事件的回调函数,但同时也会将这个事件从事件循环中移除,例如,下面的程序,定时器只会触发一次:

#include <event2/event.h>
#include <iostream>
#include <string>

void timer_cb(evutil_socket_t fd, short what, void *arg)
{
    auto str = static_cast<std::string *>(arg);
    std::cout << *str << std::endl;
}

int main()
{
    std::string str = "Hello, World!";
    auto *base = event_base_new();

    struct timeval five_seconds = {1, 0};
    //auto *ev = event_new(base, -1, EV_TIMEOUT, timer_cb, (void *)&str);
    //EV_PERSIST可以控制时间不被移除
    auto *ev = event_new(base, -1, EV_TIMEOUT|EV_PERSIST, timer_cb, (void *)&str);

    event_add(ev, &five_seconds);
    event_base_dispatch(base);

    event_free(ev);
    event_base_free(base);

    return 0;
}

TCP Server

Libevent 使用evconnlistener结构来表示 TCP Server,创建 TCP Server 的做法很简单:

struct evconnlistener *evconnlistener_new_bind(
    struct event_base *base,        // 事件循环
    evconnlistener_cb cb, // 回调函数,当 accept() 成功时会被调用
    void *arg,                      // 传递给回调函数的参数
    unsigned flags,                 // 选项
    int backlog,                    // tcp backlog 参数
    const struct sockaddr *sa,      // 地址
    int socklen
);
void evconnlistener_free(struct evconnlistener *lev);

调用evconnlistener_new_bind()函数之后,listening socket 会自动被设置成非阻塞的。我们还通过flags参数设置一些有用的选项,例如:

  • LEV_OPT_CLOSE_ON_FREE表示当调用evconnlistener_free()时,相应的 listening socket 也会被close()掉。
  • LEV_OPT_REUSEABLE表示会自动对 listening socket 设置SO_REUSEADDR这个 TCP 选项。
#include <event2/listener.h>
#include <arpa/inet.h>
#include <string.h>
#include <iostream>

void accept_conn_cb(struct evconnlistener *listener, evutil_socket_t fd,
                    struct sockaddr *address, int socklen, void *arg)
{
    char addr[INET_ADDRSTRLEN];
    auto *sin = reinterpret_cast<sockaddr_in *>(address);
    inet_ntop(AF_INET, &sin->sin_addr, addr, INET_ADDRSTRLEN);

    std::cout << "Accept TCP connection from: " << addr << std::endl;
}

void accept_error_cb(struct evconnlistener *listener, void *arg)
{
    auto *base = evconnlistener_get_base(listener);

    // 跨平台的错误处理
    int err = EVUTIL_SOCKET_ERROR();
    std::cerr << "Got an error on the listener: "
              << evutil_socket_error_to_string(err)
              << std::endl;
    
    event_base_loopexit(base, NULL);
}

int main()
{
    short port = 8000;
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    sin.sin_port = htons(port);
    
    auto *base = event_base_new();
    auto *listener = evconnlistener_new_bind(
        base, accept_conn_cb, nullptr,
        LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
        reinterpret_cast<struct sockaddr *>(&sin), sizeof(sin)
    );

    if (listener == nullptr) {
        std::cerr << "Couldn't create listener" << std::endl;
        return 1;
    }

    evconnlistener_set_error_cb(listener, accept_error_cb);
    event_base_dispatch(base);
    
    return 0;
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容