• 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
    ", 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
    ", 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
    ", strerror(errno));
            evutil_closesocket(listener);
            return -1;
        }
    
        if(bind(listener, (struct sockaddr*)&sin, sizeof(sin)) == -1)
        {
            printf("bind() error: %s
    ", strerror(errno));
            evutil_closesocket(listener);
            return -1;
        }
    
        if(listen(listener, 10) == -1) {
            printf("listen() error: %s
    ", strerror(errno));
            evutil_closesocket(listener);
            return -1;
        }
    
        //设置socket为非阻塞
        if(evutil_make_socket_nonblocking(listener) == -1) {
            printf("evutil_make_socket_nonblocking() error: %s
    ", strerror(errno));
            evutil_closesocket(listener);
            return -1;
        }
    
        //初始化成功
        //printf("Init socket : %d
    ", 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
    ", strerror(errno));
            return;
        } else {
            printf("Client connected, fd: %d, addr: %s:%d
    ", 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] = '';
    
        printf("Recv client msg: %s
    ", 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
    ");
        } else if(event&BEV_EVENT_ERROR) {
            printf("some other error
    ");
        }
    
        //释放资源,将自动释放套接字和读写缓冲区
        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
    ");
        } else {
            printf("Success to connect server
    ");
        }
    
        //配置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
    ");
    
        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
    ", server_ip);
            return -1;
        }
    
        sockfd = socket(PF_INET, SOCK_STREAM, 0);
        if(sockfd < 0) {
            printf("socket() error: %s
    ", strerror(errno));
            return -1;
        }
    
        if(connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
            printf("connect() error: %s
    ", strerror(errno));
            evutil_closesocket(sockfd);
            return -1;
        }
    
        //设置非阻塞
        if(evutil_make_socket_nonblocking(sockfd) == -1) {
            printf("evutil_make_socket_nonblocking() error: %s
    ", 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
    ");
        } else if(event & BEV_EVENT_ERROR) {
            printf("Other error
    ");
        }
    
        //释放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] = ''; 
        printf("recv msg from server: %s
    ", 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

  • 相关阅读:
    HTML from表单的name属性
    Vue基础语法数据绑定、事件处理和扩展组件
    Vue 系列 之 Vue1.x
    SQLServer XML类型
    基于gitlab + jenkins + harbor + k8s 搭建部署微服务环境
    NuGet微软官方中国国内镜像
    4层架构六边型架构(也就是洋葱架构)
    锁优化建议
    一款开源的跨平台实时web应用框架——DotNetify
    使用 .NET 升级助手将NET Core 3.1项目升级为.NET 6
  • 原文地址:https://www.cnblogs.com/xl2432/p/12206020.html
Copyright © 2020-2023  润新知