libevent

文章目录

  • 前言
  • 概述
    • 基本函数介绍
      • event_init
      • event_set
      • event_dispatch
      • event_base_set
      • event_add
      • event_base_loop
      • event_base_dispatch
    • 重要结构体
    • 实现一个简单的server
    • 使用bufferevent
    • bufferevent高级


前言


概述

官网:https://libevent.org/
使用文档:https://www.monkey.org/~provos/libevent/doxygen-2.0.1/index.html
libevent和libiop,redis等一样都是采用事件回调机制,这种模式被称作Reactor模式。Reactor模式:https://www.jianshu.com/p/458e4b276607

#include
#include
#include

内部使用select、epoll、kqueue等系统调用管理事件机制。

编译库代码,编译脚本会判断OS支持哪种类型的事件机制(select、epoll或kqueue),然后条件编译相应代码,供上层使用的接口仍然是保持统一的。

libevent支持用户使用三种类型的事件,分别是网络IO、定时器、信号三种。定时器的数据结构使用最小堆(Min Heap),以提高效率。网络IO和信号的数据结构采用了双向链表(TAILQ)。在实现上主要有3种链表:EVLIST_INSERTED, EVLIST_ACTIVE, EVLIST_TIMEOUT,一个ev在这3种链表之间被插入或删除,处于EVLIST_ACTIVE链表中的ev最后将会被调度执行。

基本函数介绍

event_init

初始化libevent库:
调用event_base_new,初始化struct event_base对象。
event_base_new里做了如下工作:
1、 申请内存
2、 初始化定时器堆和事件队列
3、 为event_base对象选择底层事件函数封装对象。根据编译选项,初始化eventops全局对象。该对象存放指向底层select/pool/epoll等功能的封装函数。
4、 初始化活动队列。

event_set

初始化struct event结构。可以用event_add把该事件结构增加到事件循环,用event_del从事件循环中删除。支持的事件类型可以是下面组合:
EV_TIMEOUT:超时;
EV_READ:只要网络缓冲中还有数据,回调函数就会被触发;
EV_WRITE:只要塞给网络缓冲的数据被写完,回调函数就会被触发;
EV_SIGNAL:POSIX信号量;
EV_PERSIST:不指定这个属性,回调函数被触发后事件会被删除;
EV_ET:Edge-Trigger边缘触发
1、 把参数中指定初始化的事件对象的ev_base指向全局的current_base。
2、 赋值回调函数、描述符、监视事件等变量。

event_dispatch

event_base_set

修改structevent事件结构所属的event_base为指定的event_base。Libevnet内置一个全局的event_base结构。多个线程应用中,如果多个线程都需要一个libevent事件循环,需要调用event_base_set修改事件结构基于的event_base。

event_add

增加事件到事件监控中。
1、 对于读、写、信号事件,调用封装的add函数,调用底层select/pool/epoll相关函数,增加到操作系统事件监控里。对于epoll,调用的是epoll_add函数。Epoll_add函数调用epoll_ctl添加事件监控,libevent使用水平触发方式。把监听时间加入到event_base的事件队列中。
2、 对应定时器事件,加入到event_base的定时器最小堆里。
3、 对信号事件,调用evsignal_add,加入事件处理队列中。

event_base_loop

事件循环。调用底层的select、poll或epoll等,如监听事件发生,调用事件结构中指定的回调函数。
1、 计算最近的超时时间:定时器最小堆按照超时时间排序,取最小的超时时间;如已有活动事件或指定不阻塞,超时时间为0。
2、 调用dispatch。对epoll,对应epoll_dispatch函数。该函数调用epoll_wait监控指定事件。
3、 把到了超时时间的时间加入到活动事件队列。从超时时间最小堆中依次取最小超时时间和当前时间比较,对小于/等于当前时间的事件,加入到活动事件队列。
4、 循环调用活动事件队列中所有事件的回调函数。

event_base_dispatch

重要结构体

struct eventop {

       constchar *name;

       void*(*init)(struct event_base *);

       int(*add)(void *, struct event *);

       int(*del)(void*, struct event *);

       int(*dispatch)(struct event_base *, void *, struct timeval *);

       void(*dealloc)(struct event_base *, void *);

       /*set if we need to reinitialize the event base */

       intneed_reinit;

};
struct event_base {

       conststruct eventop *evsel;   //指向编译时选择的一个select/pool/epoll/kqueue接口封装对象。

       void*evbase;

       intevent_count;            /* counts numberof total events */

       intevent_count_active;  /* counts number ofactive events */

 

       intevent_gotterm;         /* Set to terminateloop */

       intevent_break;            /* Set toterminate loop immediately */

 

       /*active event management */

       structevent_list **activequeues; //活动事件队列

       intnactivequeues;

 

       /*signal handling info */

       structevsignal_info sig;

 

       structevent_list eventqueue;  //监听事件队列

       structtimeval event_tv;

 

       structmin_heap timeheap; //定时器时间堆

 

       structtimeval tv_cache;

};
struct event {

       TAILQ_ENTRY(event) ev_next;

       TAILQ_ENTRY(event) ev_active_next;

       TAILQ_ENTRY(event) ev_signal_next;

       unsignedint min_heap_idx;   /* for managingtimeouts */

 

       structevent_base *ev_base;  //事件输入的evnet_base

 

       intev_fd;

       shortev_events;

       shortev_ncalls;

       short*ev_pncalls;   /* Allows deletes incallback */

 

       structtimeval ev_timeout;

 

       intev_pri;             /* smaller numbers arehigher priority */

 

       void(*ev_callback)(int, short, void *arg);  //回调函数

       void*ev_arg;

 

       intev_res;             /* result passed toevent callback */

       intev_flags;

};

实现一个简单的server

server端代码:

/**
You need libevent2 to compile this piece of code
Please see: http://libevent.org/
Or you can simply run this command to install on Mac: brew install libevent
Cmd to compile this piece of code: g++ LibeventQuickStartServer.c  -o  LibeventQuickStartServer /usr/local/lib/libevent.a
**/
#include  
#include  
#include  
  
#include  
#include

void accept_cb(int fd, short events, void* arg);
void socket_read_cb(int fd, short events, void* arg);

int tcp_server_init(int port, int listen_num);

int main(int argc, char const *argv[])
{
    /* code */
    int listener = tcp_server_init(9999, 10);
    if (listener == -1)
    {
        perror("tcp_server_init error");
        return -1;
    }

    struct event_base* base = event_base_new();

    // 监听客户端请求链接事件
    struct event* ev_listen = event_new(base, listener, EV_READ | EV_PERSIST, accept_cb, base);

    event_add(ev_listen, NULL);

    event_base_dispatch(base);

    return 0;
}

void accept_cb(int fd, short events, void* arg)
{
    evutil_socket_t sockfd;

    struct sockaddr_in client;
    socklen_t len = sizeof(client);

    sockfd = ::accept(fd, (struct sockaddr*)&client, &len);
    evutil_make_socket_nonblocking(sockfd);

    printf("accept a client %d\n", sockfd);

    struct event_base* base = (event_base*)arg;

    //动态创建一个event结构体,并将其作为回调参数传递给
    struct event* ev = event_new(NULL, -1, 0, NULL, NULL);
    event_assign(ev, base, sockfd, EV_READ | EV_PERSIST, socket_read_cb, (void*)ev);

    event_add(ev, NULL);
}


void socket_read_cb(int fd, short events, void* arg)
{
    char msg[4096];
    struct event* ev = (struct event*)arg;
    int len = read(fd, msg, sizeof(msg) - 1);

    if(len <= 0)
    {
        printf("some error happen when read\n");
        event_free(ev);
        close(fd);
        return;
    }

    msg[len] = '\0';
    printf("recv the client msg : %s\n", msg);

    char reply_msg[4096] = "I have received the msg: ";
    strcat(reply_msg + strlen(reply_msg), msg);

    write(fd, reply_msg, strlen(reply_msg));
}

typedef struct sockaddr SA;  
int tcp_server_init(int port, int listen_num)  
{  
    int errno_save;  
    evutil_socket_t listener;  
  
    listener = ::socket(AF_INET, SOCK_STREAM, 0);  
    if( listener == -1 )  
        return -1;  
  
    //允许多次绑定同一个地址。要用在socket和bind之间  
    evutil_make_listen_socket_reuseable(listener);  
  
    struct sockaddr_in sin;  
    sin.sin_family = AF_INET;  
    sin.sin_addr.s_addr = 0;  
    sin.sin_port = htons(port);  
  
    if( ::bind(listener, (SA*)&sin, sizeof(sin)) < 0 )  
        goto error;  
  
    if( ::listen(listener, listen_num) < 0)  
        goto error;  
  
  
    //跨平台统一接口,将套接字设置为非阻塞状态  
    evutil_make_socket_nonblocking(listener);  
  
    return listener;  
  
    error:  
        errno_save = errno;  
        evutil_closesocket(listener);  
        errno = errno_save;  
  
        return -1;  
}  

client端代码

/**
You need libevent2 to compile this piece of code
Please see: http://libevent.org/
Or you can simply run this command to install on Mac: brew install libevent
Cmd to compile this piece of code: g++ LibeventQuickStartClient.c -o LibeventQuickStartClient /usr/local/lib/libevent.a
**/
#include  
#include  
#include  
#include  
#include  
#include  
  
#include  
#include  
#include  
  
#include  
#include  
  
  
  
  
int tcp_connect_server(const char* server_ip, int port);  
  
  
void cmd_msg_cb(int fd, short events, void* arg);  
void socket_read_cb(int fd, short events, void *arg);  
  
int main(int argc, char** argv)  
{  
    if( argc < 3 )  
    {  
        printf("please input 2 parameter\n");  
        return -1;  
    }  
  
  
    //两个参数依次是服务器端的IP地址、端口号  
    int sockfd = tcp_connect_server(argv[1], atoi(argv[2]));  
    if( sockfd == -1)  
    {  
        perror("tcp_connect error ");  
        return -1;  
    }  
  
    printf("connect to server successful\n");  
  
    struct event_base* base = event_base_new();  
  
    struct event *ev_sockfd = event_new(base, sockfd,  
                                        EV_READ | EV_PERSIST,  
                                        socket_read_cb, NULL);  
    event_add(ev_sockfd, NULL);  
  
    //监听终端输入事件  
    struct event* ev_cmd = event_new(base, STDIN_FILENO,  
                                      EV_READ | EV_PERSIST, cmd_msg_cb,  
                                      (void*)&sockfd);  
  
  
    event_add(ev_cmd, NULL);  
  
    event_base_dispatch(base);  
  
    printf("finished \n");  
    return 0;  
}  
  
  
  
  
  
  
void cmd_msg_cb(int fd, short events, void* arg)  
{  
    char msg[1024];  
  
    int ret = read(fd, msg, sizeof(msg));  
    if( ret <= 0 )  
    {  
        perror("read fail ");  
        exit(1);  
    }  
  
    int sockfd = *((int*)arg);  
  
    //把终端的消息发送给服务器端  
    //为了简单起见,不考虑写一半数据的情况  
    write(sockfd, msg, ret);  
}  
  
  
void socket_read_cb(int fd, short events, void *arg)  
{  
    char msg[1024];  
  
    //为了简单起见,不考虑读一半数据的情况  
    int len = read(fd, msg, sizeof(msg)-1);  
    if( len <= 0 )  
    {  
        perror("read fail ");  
        exit(1);  
    }  
  
    msg[len] = '\0';  
  
    printf("recv %s from server\n", msg);  
}  
  
  
  
typedef struct sockaddr SA;  
int tcp_connect_server(const char* server_ip, int port)  
{  
    int sockfd, status, save_errno;  
    struct sockaddr_in server_addr;  
  
    memset(&server_addr, 0, sizeof(server_addr) );  
  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_port = htons(port);  
    status = inet_aton(server_ip, &server_addr.sin_addr);  
  
    if( status == 0 ) //the server_ip is not valid value  
    {  
        errno = EINVAL;  
        return -1;  
    }  
  
    sockfd = ::socket(PF_INET, SOCK_STREAM, 0);  
    if( sockfd == -1 )  
        return sockfd;  
  
  
    status = ::connect(sockfd, (SA*)&server_addr, sizeof(server_addr) );  
  
    if( status == -1 )  
    {  
        save_errno = errno;  
        ::close(sockfd);  
        errno = save_errno; //the close may be error  
        return -1;  
    }  
  
    evutil_make_socket_nonblocking(sockfd);  
  
    return sockfd;  
}  

使用bufferevent

设置scokfd为nonblocking;
使用bufferevent_socket_new创建一个struct bufferevent* bev,关联上面的sockfd,并托管给event_base;
使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void*)arg);
使用buffevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)来启动read/write事件

server端代码

#include  
#include  
#include  
#include  
#include
void accept_cb(int fd, short events, void* arg);
void socket_read_cb(int fd, short events, void* arg);
int tcp_server_init(int port, int listen_num);

int main(int argc, char const *argv[])
{
    /* code */
    int listener = tcp_server_init(9999, 10);
    if (listener == -1)
    {
        perror("tcp_server_init error");
        return -1;
    }
    struct event_base* base = event_base_new();
    // 监听客户端请求链接事件
    struct event* ev_listen = event_new(base, listener, EV_READ | EV_PERSIST, accept_cb, base);
   event_add(ev_listen, NULL);
    event_base_dispatch(base);
    return 0;
}

void accept_cb(int fd, short events, void* arg)
{
    evutil_socket_t sockfd;
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    sockfd = ::accept(fd, (struct sockaddr*)&client, &len);
    evutil_make_socket_nonblocking(sockfd);
    printf("accept a client %d\n", sockfd);
    struct event_base* base = (event_base*)arg;
    //动态创建一个event结构体,并将其作为回调参数传递给
    struct event* ev = event_new(NULL, -1, 0, NULL, NULL);
    event_assign(ev, base, sockfd, EV_READ | EV_PERSIST, socket_read_cb, (void*)ev);
    event_add(ev, NULL);
}
void socket_read_cb(int fd, short events, void* arg)
{
    char msg[4096];
    struct event* ev = (struct event*)arg;
    int len = read(fd, msg, sizeof(msg) - 1);
    if(len <= 0)
    {
        printf("some error happen when read\n");
        event_free(ev);
        close(fd);
        return;
    }
    msg[len] = '\0';
    printf("recv the client msg : %s\n", msg);
    char reply_msg[4096] = "I have received the msg: ";
    strcat(reply_msg + strlen(reply_msg), msg);
    write(fd, reply_msg, strlen(reply_msg));
}

typedef struct sockaddr SA;  
int tcp_server_init(int port, int listen_num)  
{  
    int errno_save;  
    evutil_socket_t listener;   
    listener = ::socket(AF_INET, SOCK_STREAM, 0);  
    if( listener == -1 )  
        return -1;    
    //允许多次绑定同一个地址。要用在socket和bind之间     evutil_make_listen_socket_reuseable(listener);  
    struct sockaddr_in sin;  
    sin.sin_family = AF_INET;  
    sin.sin_addr.s_addr = 0;  
    sin.sin_port = htons(port);  
    if( ::bind(listener, (SA*)&sin, sizeof(sin)) < 0 )  
        goto error;  
    if( ::listen(listener, listen_num) < 0)  
        goto error;   
    //跨平台统一接口,将套接字设置为非阻塞状态  
    evutil_make_socket_nonblocking(listener);  
    return listener;  
   error:  
        errno_save = errno;  
        evutil_closesocket(listener);  
        errno = errno_save;   
        return -1;  
}  

client端代码

#include  
#include  
#include  
#include  
#include  
#include  
  
#include  
#include  
#include  
  
#include  
#include  
  
int tcp_connect_server(const char* server_ip, int port);  
void cmd_msg_cb(int fd, short events, void* arg);  
void socket_read_cb(int fd, short events, void *arg);  
int main(int argc, char** argv)  
{  
    if( argc < 3 )  
    {  
        printf("please input 2 parameter\n");  
        return -1;  
    }  
    //两个参数依次是服务器端的IP地址、端口号  
    int sockfd = tcp_connect_server(argv[1], atoi(argv[2]));  
    if( sockfd == -1)  
    {  
        perror("tcp_connect error ");  
        return -1;  
    }  
    printf("connect to server successful\n");  
    struct event_base* base = event_base_new();  
    struct event *ev_sockfd = event_new(base, sockfd,  EV_READ | EV_PERSIST, socket_read_cb, NULL);  
    event_add(ev_sockfd, NULL);  
  
    //监听终端输入事件  
    struct event* ev_cmd = event_new(base, STDIN_FILENO,  	//#include  系统级的API,是一个文件句柄
                                      EV_READ | EV_PERSIST, cmd_msg_cb,  (void*)&sockfd);  
    event_add(ev_cmd, NULL);   
    event_base_dispatch(base);   
    printf("finished \n");  
    return 0;  
}  
void cmd_msg_cb(int fd, short events, void* arg)  
{  
    char msg[1024];   
    int ret = read(fd, msg, sizeof(msg));  
    if( ret <= 0 )  
    {  
        perror("read fail ");  
        exit(1);  
    }  
    int sockfd = *((int*)arg);  
    //把终端的消息发送给服务器端  
    //为了简单起见,不考虑写一半数据的情况  
    write(sockfd, msg, ret);  
}  

void socket_read_cb(int fd, short events, void *arg)  
{  
    char msg[1024];  
  
    //为了简单起见,不考虑读一半数据的情况  
    int len = read(fd, msg, sizeof(msg)-1);  
    if( len <= 0 )  
    {  
        perror("read fail ");  
        exit(1);  
    }    
    msg[len] = '\0';   
    printf("recv %s from server\n", msg);  
}   
typedef struct sockaddr SA;  
int tcp_connect_server(const char* server_ip, int port)  
{  
    int sockfd, status, save_errno;  
    struct sockaddr_in server_addr;    
    memset(&server_addr, 0, sizeof(server_addr) );    
    server_addr.sin_family = AF_INET;  
    server_addr.sin_port = htons(port);  
    status = inet_aton(server_ip, &server_addr.sin_addr);  
  
    if( status == 0 ) //the server_ip is not valid value  
    {  
        errno = EINVAL;  
        return -1;  
    }    
    sockfd = ::socket(PF_INET, SOCK_STREAM, 0);  
    if( sockfd == -1 )  
        return sockfd;    
    status = ::connect(sockfd, (SA*)&server_addr, sizeof(server_addr) );   
    if( status == -1 )  
    {  
        save_errno = errno;  
        ::close(sockfd);  
        errno = save_errno; //the close may be error  
        return -1;  
    }    
    evutil_make_socket_nonblocking(sockfd);  	//将其设置为非阻塞式IO 
    return sockfd;  
}  

bufferevent高级

server端(编译有错误)

#include  
#include  
#include  
  
#include  
#include  
  
#include  
#include  
#include  
#include  
  
  
void listener_cb(evconnlistener *listener, evutil_socket_t fd,  
                 struct sockaddr *sock, int socklen, void *arg);  
  
void socket_read_cb(bufferevent *bev, void *arg);  
void socket_event_cb(bufferevent *bev, short events, void *arg);  
  
int main()  
{  
    //evthread_use_pthreads();//enable threads  
  
    struct sockaddr_in sin;  
    memset(&sin, 0, sizeof(struct sockaddr_in));  
    sin.sin_family = AF_INET;  
    sin.sin_port = htons(9999);  
  
    event_base *base = event_base_new();  
    evconnlistener *listener  
            = evconnlistener_new_bind(base, listener_cb, base,  
                                      LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE,  
                                      10, (struct sockaddr*)&sin,  
                                      sizeof(struct sockaddr_in));  
  
    event_base_dispatch(base);  
  
    evconnlistener_free(listener);  
    event_base_free(base);  
  
    return 0;  
}  
  
  
//一个新客户端连接上服务器了  
//当此函数被调用时,libevent已经帮我们accept了这个客户端。该客户端的
//文件描述符为fd
void listener_cb(evconnlistener *listener, evutil_socket_t fd,  
                 struct sockaddr *sock, int socklen, void *arg)  
{  
    printf("accept a client %d\n", fd);  
  
    event_base *base = (event_base*)arg;  
  
    //为这个客户端分配一个bufferevent  
    bufferevent *bev =  bufferevent_socket_new(base, fd,  
                                               BEV_OPT_CLOSE_ON_FREE);  
  
    bufferevent_setcb(bev, socket_read_cb, NULL, socket_event_cb, NULL);  
    bufferevent_enable(bev, EV_READ | EV_PERSIST);  
}  
  
  
void socket_read_cb(bufferevent *bev, void *arg)  
{  
    char msg[4096];  
  
    size_t len = bufferevent_read(bev, msg, sizeof(msg)-1 );  
  
    msg[len] = '\0';  
    printf("server read the data %s\n", msg);  
  
    char reply[] = "I has read your data";  
    bufferevent_write(bev, reply, strlen(reply) );  
}  
  
  
void socket_event_cb(bufferevent *bev, short events, void *arg)  
{  
    if (events & BEV_EVENT_EOF)  
        printf("connection closed\n");  
    else if (events & BEV_EVENT_ERROR)  
        printf("some other error\n");  
  
    //这将自动close套接字和free读写缓冲区  
    bufferevent_free(bev);  
}  
#include
#include
#include
#include
#include
#include
 
#include
#include
#include
 
#include
#include
#include
#include
 
 
 
 
int tcp_connect_server(const char* server_ip, int port);
 
 
void cmd_msg_cb(int fd, short events, void* arg);
void server_msg_cb(struct bufferevent* bev, void* arg);
void event_cb(struct bufferevent *bev, short event, void *arg);
 
int main(int argc, char** argv)
{
    if( argc < 3 )
    {
        //两个参数依次是服务器端的IP地址、端口号
        printf("please input 2 parameter\n");
        return -1;
    }
 
    struct event_base *base = event_base_new();
 
    struct bufferevent* bev = bufferevent_socket_new(base, -1,
                                                     BEV_OPT_CLOSE_ON_FREE);
 
    //监听终端输入事件
    struct event* ev_cmd = event_new(base, STDIN_FILENO,
                                     EV_READ | EV_PERSIST,
                                     cmd_msg_cb, (void*)bev);
 
 
    event_add(ev_cmd, NULL);
 
    struct sockaddr_in server_addr;
 
    memset(&server_addr, 0, sizeof(server_addr) );
 
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1], &server_addr.sin_addr);
 
    bufferevent_socket_connect(bev, (struct sockaddr *)&server_addr,
                               sizeof(server_addr));
 
 
    bufferevent_setcb(bev, server_msg_cb, NULL, event_cb, (void*)ev_cmd);
    bufferevent_enable(bev, EV_READ | EV_PERSIST);
 
 
 
    event_base_dispatch(base);
 
    printf("finished \n");
    return 0;
}
 
 
 
 
 
void cmd_msg_cb(int fd, short events, void* arg)
{
    char msg[1024];
 
    int ret = read(fd, msg, sizeof(msg));
    if( ret < 0 )
    {
        perror("read fail ");
        exit(1);
    }
 
    struct bufferevent* bev = (struct bufferevent*)arg;
 
    //把终端的消息发送给服务器端
    bufferevent_write(bev, msg, ret);
}
 
 
void server_msg_cb(struct bufferevent* bev, void* arg)
{
    char msg[1024];
 
    size_t len = bufferevent_read(bev, msg, sizeof(msg));
    msg[len] = '\0';
 
    printf("recv %s from server\n", msg);
}
 
 
void event_cb(struct bufferevent *bev, short event, void *arg)
{
 
    if (event & BEV_EVENT_EOF)
        printf("connection closed\n");
    else if (event & BEV_EVENT_ERROR)
        printf("some other error\n");
    else if( event & BEV_EVENT_CONNECTED)
    {
        printf("the client has connected to server\n");
        return ;
    }
 
    //这将自动close套接字和free读写缓冲区
    bufferevent_free(bev);
 
    struct event *ev = (struct event*)arg;
    event_free(ev);
}

代码中新出现的函数:
evutil_make_socket_nonblocking //跨平台统一接口,将套接字设置为非阻塞状态
evutil_socket_t 结构体
evutil_make_listen_socket_reuseable//在socket和listen之间调用,允许多次绑定同一个地址
evutil_closesocket
event_new //动态创建一个event结构体
event_assign//将动态创建的结构体作为event的回调参数
event_free

bufferevent 结构体
bufferevent_socket_new
bufferevent_write
bufferevent_setcb//当socket关闭时会用到回调参数
bufferevent_enable//设置属性
bufferevent_read
bufferevent_free

参考文章:
http://blog.csdn.net/luotuo44/article/details/39670221
https://blog.csdn.net/luotuo44/category_2435521.html
https://blog.csdn.net/windeal3203/category_6489079.html

你可能感兴趣的:(RPC框架)