libevent应用探究

1.libevent安装

      作为一个轻量级服务端库,libevent应用场景主要在linux环境(也支持windows环境)。可以在官网下载所需版本的libevent: 在官网下载http://libevent.org,之后做完基本配置、编译、安装即可:

配置:./configure --prefix=/usr

编译:make

安装:make install

     我在安装过程中碰到的问题是,如果安装的版本较低时,在包含了envent.h的模块进行编译时会提示各种类型不识别导致无法编译,从网上的信息看应该是需要另外安装libevent-devel包,之后重新下载较新版本安装后(libevent-2.0.21-stable),应用模块编译正常。

2.基础事件处理流程

事件类型

处理流程

同步IO

通过一个简单的使用libevent的服务端例子,梳理了下调用流程:

1)        event_init()取得libevent实例;

2)        event_set先设置监听事件对象,包括将监听事件绑定服务端套接字、设置事件类型、和监听触发回调函数及其参数;

3)        event_base_set将监听事件绑定libevent实例;

4)        event_add将监听事件加入监听列表;

5)        event_base_dispatch启动事件循环。

注意的问题:

1)      event_set的事件参数,要注意其生命期,如果在函数内设置事件,则事件往往是内存堆中申请的,否则函数退出后事件对象就回收了,会出问题;

2)      EV_PERSIST参数的使用很关键,默认是非持续性事件,在非持续性事件中,触发一次后,事件就退出事件监听队列;

3)      回调函数的参数,是void *类型,如果有多个参数,则需要以封装成结构对象后传入地址指针的形式传递。

定时器

定时器的应用与上面同步IO测试基本使用方式相当,区别在于:

1)        定时器使用中调用的是evtimer_set(&ev, timer_cb, NULL);类似于调用上面的event_set(&ev, -1, 0, timer_cb, NULL);

2)        event_add添加定时器事件时,必须设置一个非空的时间,表示多久后触发。

信号

libevent对信号的捕捉,使用方式与同步IO测试完全一样,唯一的区别是绑定的是信号而不是套接字。这样,当该系统发出该信号时,就会触发回调函数。

3.例子分析

     libevent源码的sample目录下,有一些使用libevent接口的例子。

hello-world.c>

     编译后直接运行,然后找一台网络可通的机器telnet运行hello-world程序的机器,测试的现象是:

     1)telnet客户端连接很快断开,hello-world服务程序打印"flushed answer"。

     从代码中可以看出是由于conn_writecb回调里面evbuffer_get_length判断bufferevent的output为空,所以将连接断开。如果去掉该判断,会发现telnet客户端能正常打印"Hello World!",连接也会保持。

     从该现象看,表明conn_writecb是在bufferevent_write写入发送信息且信息已经从bufferevent的outbuf走出后,因此回调里面会发现buf数据已经没有了。

     另外,自己加打印测试看了下,在listener_cb走完前,写到bufferevent的outbuf的数据是不会发出去的,所以注意这个顺序:listener_cb -> 发送outbuf数据 -> conn_writecb。

     2)当服务端触发"ctrl + c"后,会出现打印"Caught an interrupt signal; exiting cleanly in two seconds."

     程序中有对SIGINT信号进行监控,libevent支持对信号的捕获处理,并将其整合到基本IO流程中,手动按下"ctrl + c"即触发了SIGINT信号,所以signal_cb被调用并给出上面的打印。

     本例中采用bufferevent的异步IO方式,从中可看出bufferevent基本调用流程:

     1)调用bufferevent_socket_new获取bufferevent实例,参数中需要提供event_base、连接套接字;

     2)调用bufferevent_setcb设置bufferevent实例的可读、可写、出错的回调;

     3)调用bufferevent_enable设置bufferevent实例的读写使能情况,之后就可以通过bufferevent_write和bufferevent_read进行异步数据处理了。

     另外,可以注意到本里中通过evconnlistener_new_bind方法,取代了上面同步IO测试的2-4步流程,且封装了IP:端口绑定相关操作,即通过这一个方法调用,实现了一系列过程。

time-test.c>

     定时器测试很简单,基本流程与上面定时器测试中相当,区别是例子中用的event_assign将定时器事件绑定libevent实例、设置标记、绑定回调函数等。需要注意的问题是:

     event_assign中设置的标记为EV_PERSIST和0的区别:EV_PERSIST表示事件被重复监测、0表示事件被处理后就被清离监测列表。而当设置为0时,event_base_dispatch在定时回调函数执行一次后就退出,这样程序也就退出了。这里也要注意event_base_dispatch和event_base_loop的区别,可以在libevent源码中看到:

int event_base_dispatch(struct event_base *event_base)

{

      return (event_base_loop(event_base, 0));

}

event_base_loop第二个参数为0表示循环在处理完一次监测回调事件后不会退出。

signal-test.c与time-test.c流程几乎一样。

event-test.c>

     这个例子中监测事件绑定的是文件句柄,代码中区分windows和linux不同平台做了差异化处理,linux下主要是用有名管道作为测试对象。编译程序,运行后,另启一个shell会话输入"echo aaa >> event.fifo",这时,例程会打印出监测到管道的输入信息。

     整个流程除了对文件句柄的处理外,其它与之前的例子没有太大区别。

le-proxy.c>

     本例是一个网络代理模块,监听客户端的连接,对每一个客户端连接对应建立一个与目标服务器的连接,然后中转客户端到目标服务器的数据,同时也能将目标服务器数据回传给对应客户端,下面是一个简图:

 

     b_in和b_out是一组对应的bufferevent,另外从main中可以看出,通过进程参数-s可以控制b_out是否选择采用openssl加密。

     本例的精彩之处全在readcb回调,b_in和b_out这对bufferevent在运作时,并不区分in/out,只管将自己收到的数据给另一方,并由另一方发送出去。同时,做了流量控制处理,即如果另一方发送缓存里数据太多,则自己先停止向自己的读缓存接数据,待另一方发送之后则放开自己的读缓存,这里用到了bufferevent_setwatermark高低水位协助控制。

Http-server.c>

     这是一个使用libevent http接口的简单http服务器例子,编译运行后,通过指定一个路径作为进程参数,之后用客户端浏览器访问服务进程的http服务端口,浏览器上就会显示指定路径的目录列表。而如果路径后面接上html文件的相对路径,则会将该网页内容返回。

     源码自带的例子一般都是相关接口的标准使用方式,照此可以得出http服务器基本使用流程:

  • event_base_new建立libevent实例;
  • evhttp_new生成一个http服务实例,需要提供libevent实例参数;
  • evhttp_set_cb或evhttp_set_gencb设置http服务收到请求后的处理回调,前者是针对uri解析,后者是一个通用的回调;
  • evhttp_bind_socket_with_handle设置IP端口绑定;
  • 之后在event_base_dispatch中就可以开始事件处理循环了。

 

      libevent同步IO建立服务端的经典例子:

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <event.h>
#include <stdlib.h>
#include <unistd.h>

#define PORT        8000
#define BACKLOG     5
#define MEM_SIZE    1024
#define EV_FREE(EV_PTR) if(NULL != EV_PTR) \
    { \
    free(EV_PTR); \
    EV_PTR = NULL; \
    }

struct event_base* base;
struct sockEvent
{
    struct event* readEvent;
    struct event* writeEvent;
    char* buffer;
};

void releaseSockEvent(struct sockEvent* ev)//delete from base and free it
{
    event_del(ev->readEvent);
    event_del(ev->writeEvent);
    EV_FREE(ev->readEvent);
    EV_FREE(ev->writeEvent);
    EV_FREE(ev->buffer);
    EV_FREE(ev);
}

void handleWrite(int sock, short event, void* arg)
{
    char* buffer = (char*)arg;
    send(sock, buffer, strlen(buffer), 0);
}

void handldRead(int sock, short event, void* arg)
{
    struct event* writeEvent;
    int size;
    struct sockEvent* ev = (struct sockEvent*)arg;
    bzero(ev->buffer, MEM_SIZE);
    size = recv(sock, ev->buffer, MEM_SIZE, 0);
    printf("receive data:%s, size:%d\n", ev->buffer, size);
    if (size == 0) //client has send FIN
    {
        releaseSockEvent(ev);
        close(sock);
        return;
    }
    //add event to base to send the received data
    event_set(ev->writeEvent, sock, EV_WRITE, handleWrite, ev->buffer);
    event_base_set(base, ev->writeEvent);
    event_add(ev->writeEvent, NULL);
}

void handleAccept(int sock, short event, void* arg)//when new connection coming, calling this func
{
    struct sockaddr_in cli_addr;
    int newfd;
    socklen_t sinSize;
    struct sockEvent* ev = (struct sockEvent*)malloc(sizeof(struct sockEvent));
    ev->readEvent = (struct event*)malloc(sizeof(struct event));
    ev->writeEvent = (struct event*)malloc(sizeof(struct event));
    ev->buffer = (char*)malloc(MEM_SIZE);
    sinSize = sizeof(struct sockaddr_in);
    newfd = accept(sock, (struct sockaddr*)&cli_addr, &sinSize);
    //set the new coming connection event
    event_set(ev->readEvent, newfd, EV_READ|EV_PERSIST, handldRead, ev);
    event_base_set(base, ev->readEvent);
    event_add(ev->readEvent, NULL);
}

int main(int argc, char* argv[])
{
    struct sockaddr_in serverAddr;
    int sock;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    int on = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int));
    //memset(&serverAddr, 0, sizeof(serverAddr));
    bzero(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(PORT);
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    bind(sock, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr));
    listen(sock, BACKLOG);

    struct event listenEvent;
    base = event_init();//Create new EventBase
    event_set(&listenEvent, sock, EV_READ|EV_PERSIST, handleAccept, NULL);//conbine listenEvent and  it's callback function
    event_base_set(base, &listenEvent);
    event_add(&listenEvent, NULL);
    event_base_dispatch(base);//start base

    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/PattonCCNU/p/6439942.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值