libevent学习使用1

基本使用方法

libevent是一个使用事件驱动模型的网络网络库,网络开发,可以通过使用这个库,非常简单、清晰的代码做出一个支持I/O复用的程序。工作中需要使用到此库,所以记录一下学习进度。

基本使用可以参考源码的sample/目录下的使用示例,根据示例名称,我首先看一下 hello-workd.c 这个程序:代码不少,但是单个函数拆分来看,还是分清晰的。

首先是main 函数中:

    //......
    base = event_base_new();
    //......
    listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
            LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
            (struct sockaddr*)&sin, sizeof(sin));
    //......
    /// 注册了一个信号事件,该事件处理函数 signal_cb   处理的Ctrl+C信号
        signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
        if (!signal_event || event_add(signal_event, NULL)<0) {
            fprintf(stderr, "Could not create/add a signal event!\n");
            return 1;
        }
    /// 开启时间轮询
    event_base_dispatch(base);
    /// 停止程序使用资源
    evconnlistener_free(listener);
    event_free(signal_event);
    event_base_free(base);

首先调用 event_base_new 创建了一个 event_base 结构体,也不知道是干嘛的,但是并没有给他提供任何网络相关的参数,暂时先不管它。
之后调用了 evconnlistener_new_bind 函数,给他传入了网络的sockaddr结构信息,结合文件名看来就是在这里开始创建套接字和监听了。进入头文件listener.h 中也能看到关于该函数的介绍:

/**
   Allocate a new evconnlistener object to listen for incoming TCP connections
   on a given address.

   @param base The event base to associate the listener with. event 会和 event_base 关联
   @param cb A callback to be invoked when a new connection arrives. If the
      callback is NULL, the listener will be treated as disabled until the
      callback is set. 链接来的socket的处理函数
   @param ptr A user-supplied pointer to give to the callback. 参数指针
   @param flags Any number of LEV_OPT_* flags   
   @param backlog Passed to the listen() call to determine the length of the
      acceptable connection backlog.  Set to -1 for a reasonable default.
   @param sa The address to listen for connections on. 
   @param socklen The length of the address.
 */

可以看出 listener_cb 是处理链接客户端的socketLEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE可以根据listen.h中找到解释,设置了socket地址的可重用和关闭时释放, sa 是地址信息。

然后在 listener_cb 中,应该是处理链接的socket的数据的函数了,

bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
        if (!bev) {
            fprintf(stderr, "Error constructing bufferevent!");
            event_base_loopbreak(base);
            return;
        }
        ///设置bufferevent的读写事件处理函数
        bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
        /// 使能写事件
        bufferevent_enable(bev, EV_WRITE);
        /// 失能读事件
        bufferevent_disable(bev, EV_READ);
        ///向bufferevent写入数据
    bufferevent_write(bev, MESSAGE, strlen(MESSAGE));

如上面代码,根据头文件中的描述这个bufferevent结构的bev变量是和套接字fd关联的,然后使用bufferevent_setcb 有绑定了两个回调函数,写函数和异常处理函数。并enable了写,diable读,并向bev中写入了"Hello, World!\n"
进入两个回调函数中看一下,第一个

    static void
    conn_writecb(struct bufferevent *bev, void *user_data)
    {
        struct evbuffer *output = bufferevent_get_output(bev);
        if (evbuffer_get_length(output) == 0) {
            printf("flushed answer\n");
            bufferevent_free(bev);
        }
}

这里有些看不明白的是,在之前的 listener_cb 当中已经调用了一次bufferevent_write 写了数据,这里的写回调是有什么用处呢,
函数里面通过获取event_bufferoutputbuffer并判断其长度,如果为0,就释放资源,根据前面建立连接时的flag参数可以知道这里释放了资源就相当于关闭链接了。那就是给客户端写了一个hello world就退出链接了。。。(后来想到,这里的conn_writecb是为了保证outputt的数据已经写完了,再关闭链接的用途)。

    static void
    conn_eventcb(struct bufferevent *bev, short events, void *user_data)
    {
        if (events & BEV_EVENT_EOF) {   /// 写到结束即socket收到FIN后返回0
            printf("Connection closed.\n");
        } else if (events & BEV_EVENT_ERROR) {  /// socket出错
            printf("Got an error on the connection: %s\n",
                strerror(errno));/*XXX win32*/
        }
        /* None of the other events can happen here, since we haven't enabled
         * timeouts */
        bufferevent_free(bev);
    }

conn_eventcb应该就是异常处理和结束处理的函数,没什么看的了。

看了代码之后,根据之气那的分析看一下实际执行是不是这样子了:

我们先执行一下编译的可执行程序,执行后在新窗口执行telnet 127.0.01 9995 后可以看到输出如下:

Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Hello, World!
Connection closed by foreign host.

执行 hello-word的窗口显示如下:

root@DESKTOP-RMCD5EP:/mnt/d/code/libevent/build/bin# ./hello-world
flushed answer

可以看到hello-world 执行的流程是:hello-world程序监听9995端口,telnet 链接上之后,hello-world 程序直接显示了 “flushed answer” 并给客户端的链接套接字写入了 “Hello, World!"(不知道先后顺序),之后把客户端链接套接字关闭了。导致telnet 程序退出了。

除了 socket 处理,后面还有一个信号事件处理,基本上也是一样的流程,但是信号处理中先是使用 evsignal_new 函数新建一个事件,并同时将事件处理的信号类型以及函数指针传入,得到一个 event 结构,之后需要调用 evnet_add ,这个函数将没有找到详细的说明,不过看起来像是把新建的 event 加入到唯一的 event_base 中。需要深入分析。之后等到信号传入时, hello-world 程序就会自动调用自定义的信号处理函数了。

总结一下

使用libevent,首先初始化一个event_base结构体,然后创建event结构,创建tcp服务器使用evconnlistener_new_bind函数,他返回一个evnet结构,之后将处理客户端链接的处理函数指针传入evconnlistener_new_bind,这个函数帮我们完成了从创建socketconnect的处理,我们只需要将处理客户但链接的函数指针传入就可以了。之后调用event_base_dispatch 函数 libevnet就自动开始监听了。

再处理客户端链接的时候,libevent提供了bufferevent,我们可以将一个客户端链接socket绑定bufferevent,这个bufferevent替我们做了读写socket获取传输内容的操作。当接收时调用bufferevent_read函数,当发送时直接调用 bufferevent_write 函数往befferevnet中写就可以了。这样一个简单的服务器就完成了。

你可能感兴趣的