5

libevent学习使用1

 2 years ago
source link: https://segmentfault.com/a/1190000041140431
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

基本使用方法

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中写就可以了。这样一个简单的服务器就完成了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK