libevent使用

libevent就是一个基于事件通知机制的库,支持/dev/poll、kqueue、event ports、select、poll和epoll事件机制,也因此它是一个跨操作系统的库(支持Linux、*BSD、Mac OS X、Solaris、Windows等)。
libevent 库实际上是使用对于每个平台最高效的异步IO解决方案,在其实现外加上一个包装器,降低了底层IO的复杂性。

环境:ubuntu 18.04

1. 编译libevent

拉取源码:

git clone https://github.com/libevent/libevent.git

进入到工程目录下,cmake编译:

./autogen.sh
./configure [–disable-shared –enable-static]	#默认静态库和动态库都会编译
make
sudo make install

这样会把使用libevent所需的库文件和头文件安装到系统环境中。

2. 使用

2.1 关键API及变量

2.1.1 evutil_socket_t

用于跨平台表示socket的句柄:

#ifdef WIN32
#define evutil_socket_t intptr_t
#else
#define evutil_socket_t int
#endif

2.1.2 evutil_make_listen_socket_reuseable

用于跨平台将socket设置为可重用,实际上是将端口设为可重用.

2.1.3 evutil_make_socket_nonblocking

用于跨平台将socket设置为非阻塞.

2.1.4 event_base

记录了所有的等待和已激活的事件,并当有事件被激活时通知调用者。
使用event_base_new来创建一个event_base对象;使用event_base_dispatch来开启这个base对象的事件轮询;使用event_base_free来释放这个对象。

2.1.5 event

主要记录事件的相关属性。
event_new函数用于创建一个event对象;event_add、event_del两个函数分别是使event生效和失效.
event_assign可以给指定event对象的每一个成员赋予一个指定的值。

2.1.6 bufferevent

libevent的bufferevent在event的基础上自己维护了读写缓冲区。
在使用时利用bufferevent_socket_new在创建一个bufferevent对象的同时会关联对象的socketfd;
使用bufferevent_setcb设置读写回调以及错误回调函数;
bufferevent_enable/bufferevent_disable使之生效或失效;
生效之后读写都可以直接使用bufferevent_read/bufferevent_write来进行。
最后需要记得使用bufferevent_free对不使用的bufferevent对象占用的套接字和缓冲区进行释放。

2.2 Demo

这里使用libevent在Linux上实现一个简单的echo服务器:
服务端:

#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <event.h>
#include <event2/bufferevent.h>

#include <arpa/inet.h>

#define PORT 9999

//回调函数申明
void accept_cb(int fd, short events, void* arg);
void read_cb(struct bufferevent* bev, void* arg);
void error_cb(struct bufferevent* bev, short event, void* arg);

int init_tcp_server(int port);

int main(int argc, void* args[]){
    int fd = init_tcp_server(PORT);
    if(fd < 0) {
        return -1;
    }

    //初始化event
    struct event_base* base = event_base_new();

    //添加请求连接事件,并讲event_base对象作为参数传递到回调函数中,以方便进行事件的更改
    struct event* ev_listen = event_new(base, fd, EV_READ|EV_PERSIST, accept_cb, base);
    event_add(ev_listen, NULL);
    
    //进入event处理
    event_base_dispatch(base);
    //释放event
    event_base_free(base);

    return 0;
}


int init_tcp_server(int port) {
    evutil_socket_t listener = socket(PF_INET, SOCK_STREAM, 0);

    if(listener == -1)
    {
        printf("socket() error: %s\n", strerror(errno));
        return -1;
    }

    //设置绑定地址
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = 0;
    sin.sin_port = htons(port);

    //允许地址复用, 需要用在bind之前
    if(evutil_make_listen_socket_reuseable(listener) == -1) {
        printf("evutil_make_listen_socket_reuseable() error: %s\n", strerror(errno));
        evutil_closesocket(listener);
        return -1;
    }

    //允许端口复用
    if(evutil_make_listen_socket_reuseable_port(listener) == -1) {
        printf("evutil_make_listen_socket_reuseable_port() error: %s\n", strerror(errno));
        evutil_closesocket(listener);
        return -1;
    }

    if(bind(listener, (struct sockaddr*)&sin, sizeof(sin)) == -1)
    {
        printf("bind() error: %s\n", strerror(errno));
        evutil_closesocket(listener);
        return -1;
    }

    if(listen(listener, 10) == -1) {
        printf("listen() error: %s\n", strerror(errno));
        evutil_closesocket(listener);
        return -1;
    }

    //设置socket为非阻塞
    if(evutil_make_socket_nonblocking(listener) == -1) {
        printf("evutil_make_socket_nonblocking() error: %s\n", strerror(errno));
        evutil_closesocket(listener);
        return -1;
    }

    //初始化成功
    //printf("Init socket : %d\n", listener);
    return listener;
}

// 处理客户端连接请求事件的回调
void accept_cb(int fd, short events, void* arg) {
    evutil_socket_t sockfd;

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

    sockfd = accept(fd, (struct sockaddr*)&clientAddr, &len);
    if(sockfd < 0) {
        printf("accept() error: %s\n", strerror(errno));
        return;
    } else {
        printf("Client connected, fd: %d, addr: %s:%d\n", sockfd, inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
    }

    struct event_base* base = (struct event_base*)arg;
    struct bufferevent* bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);
    //设置读回调、写回调和错误时回调
    bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);

    bufferevent_enable(bev, EV_READ|EV_PERSIST);
}

//接收数据时的回调
void read_cb(struct bufferevent* bev, void* arg) {
    char msg[4096];

    //读取数据
    size_t len = bufferevent_read(bev, msg, sizeof(msg)-1);
    msg[len] = '\0';

    printf("Recv client msg: %s\n", msg);

    //将信息回传给客户端
    bufferevent_write(bev, msg, strlen(msg));
}

//处理错误消息的回调
void error_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");
    }

    //释放资源,将自动释放套接字和读写缓冲区
    bufferevent_free(bev);
}

客户端:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>

#include <event.h>
#include <event2/bufferevent.h>

int tcp_connect_server(const char* server_ip, const int port);

//回调申明
void error_cb(struct bufferevent* bev, short event, void* arg);
void input_cb(int fd, short events, void* arg);
void read_msg_cb(struct bufferevent* bev, void* arg);

int main(int argc, char* argv[]){
    if(argc != 3) {
        printf("Usage: libevent_client ip port");
        return -1;
    }

    //两个参数分别作为服务器的ip和端口号
    int sockfd = tcp_connect_server(argv[1], atoi(argv[2]));
    if(sockfd < 0) {
        printf("tcp_connect_server failed\n");
    } else {
        printf("Success to connect server\n");
    }

    //配置event
    struct event_base* base = event_base_new();
    struct bufferevent* bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);

    //添加终端输入事件
    struct event* event_input = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, input_cb, (void*)bev);
    event_add(event_input, NULL);

    bufferevent_setcb(bev, read_msg_cb, NULL, error_cb, (void*)event_input);
    bufferevent_enable(bev, EV_READ|EV_PERSIST);

    event_base_dispatch(base);

    printf("CLose client\n");

    return 0;
}

int tcp_connect_server(const char* server_ip, const int port) {
    evutil_socket_t sockfd;
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    if(inet_aton(server_ip, &server_addr.sin_addr) <= 0) {
        printf("error: Invalid server ip: %s\n", server_ip);
        return -1;
    }

    sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if(sockfd < 0) {
        printf("socket() error: %s\n", strerror(errno));
        return -1;
    }

    if(connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        printf("connect() error: %s\n", strerror(errno));
        evutil_closesocket(sockfd);
        return -1;
    }

    //设置非阻塞
    if(evutil_make_socket_nonblocking(sockfd) == -1) {
        printf("evutil_make_socket_nonblocking() error: %s\n", strerror(errno));
        evutil_closesocket(sockfd);
        return -1;
    }

    return sockfd;
}

void error_cb(struct bufferevent* bev, short event, void* arg) {
    if(event & BEV_EVENT_EOF) {
        printf("Connection closed\n");
    } else if(event & BEV_EVENT_ERROR) {
        printf("Other error\n");
    }

    //释放socket和读写缓冲区
    bufferevent_free(bev);

    //删除event
    struct event* event_input = (struct event*)arg;
    event_free(event_input);
}

void input_cb(int fd, short events, void* arg) {
    char msg[4096];
    memset(msg, 0, sizeof(msg));

    int len = read(fd, msg, sizeof(msg));
    if(len <= 0 ) {
        perror("read input failed: ");
        return;
    }

    struct bufferevent* bev = (struct bufferevent*)arg;

    //发送消息给服务器
    bufferevent_write(bev, msg, strlen(msg)+1);
}

void read_msg_cb(struct bufferevent* bev, void* arg) {
    char msg[4096];
    memset(msg, 0, sizeof(msg));

    size_t len = bufferevent_read(bev, msg, sizeof(msg));
    msg[len] = '\0'; 
    printf("recv msg from server: %s\n", msg);
}

CMakeLists.txt:

cmake_minimum_required (VERSION 2.8)
project(libeventDemo)

# 生成服务端
add_executable(libevent_server src/server.c)
# 添加链接库
target_link_libraries(libevent_server event)

# 生成客户端
add_executable(libevent_client src/client.c)
# 添加链接库
target_link_libraries(libevent_client event)

项目地址:https://github.com/243286065/CppToolsDemo/tree/master/libevent

posted @ 2020-01-17 15:26  星星,风,阳光  阅读(2408)  评论(0编辑  收藏  举报