zoukankan      html  css  js  c++  java
  • 网络编程之libevent

    安装libevent

    官方仓库 下载源码,按照Cmake方式安装,可能会缺少一些依赖包

    sudo apt install libssl-dev
    sudo apt install libmbedtls-dev libmbedtls10

    编译程序的时候发现还是找不到,将libevent目录下的include文件复制到/usr/include 仍然不行

    直接 sudo apt-get install libevent-dev 哈哈哈

    通过gdb core dump方法查看程序异常时的堆栈信息

    当发生"内存越界"等错误时,会产生"Segmention fault",并生成Core文件

    Core文件默认是没有打开的,可以通过 ulimit -a 查看,用 ulimit -c unlimited 设置(只在当前窗口有效)

    再执行 gdb program_name core 就可以查看出错时的堆栈信息啦

    参考通过gdb core dump方法查看程序异常时的堆栈信息

    初识libevent

    libevent提供的simple有点劝退,这里用《Linux高性能服务器编程》中Libevent源码分析里面的demo

    #include <sys/signal.h>
    #include <event.h>
    
    void signal_cb( int fd, short event, void* argc )
    {
        struct event_base* base =(struct event_base*)argc;
        struct timeval delay = { 2, 0 };
        printf( "Caught an interrupt signal; exiting cleanly in two seconds...\n" );
        event_base_loopexit( base, &delay );
    }  
    
    void timeout_cb( int fd, short event, void* argc )
    {
        printf( "timeout\n" );
    }
    
    int main()  
    {  
        struct event_base* base = event_init();
    
        struct event* signal_event = evsignal_new( base, SIGINT, signal_cb, base ); // 事件处理器
        event_add( signal_event, NULL ); // 添加到事件队列
    
        struct timeval tv = { 1, 0 };
        struct event* timeout_event = evtimer_new( base, timeout_cb, NULL );
        event_add( timeout_event, &tv );
    
        event_base_dispatch( base );  // 执行事件循环
    
        event_free( timeout_event );
        event_free( signal_event );
        event_base_free( base );
    }  
    

    Output:

    gcc -o libevent_test libevent_test.c -levent
    ./libevent_test
    timeout
    ^CCaught an interrupt signal; exiting cleanly in two seconds...
    

    Demo简单,但是却描述了LibEvent库的主要逻辑

    libevent开发流程

    零、socket编程

    基本的socket编程是阻塞/同步的,每个操作除非已经完成或者出错才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没法支撑大量的请求(所谓c10k problem?),例如内存:默认情况下每个线程需要占用2~8M的栈空间。posix定义了可以使用异步的select系统调用,但是因为其采用了轮询的方式来判断某个fd是否变成active,效率不高[O(n)],连接数一多,也还是撑不住。于是各系统分别提出了基于异步/callback的系统调用,例如Linux的epoll,BSD的kqueue,Windows的IOCP。由于在内核层面做了支持,所以可以用O(1)的效率查找到active的fd。基本上,libevent就是对这些高效IO的封装,提供统一的API,简化开发

    libevent大概是这样的:
    默认情况下是单线程的(可以配置成多线程,如果有需要的话),每个线程有且只有一个event_base,对应一个struct event_base结构体(以及附于其上的事件管理器),用来schedule托管给它的一系列event,可以和操作系统的进程管理类比,当然,要更简单一点。当一个事件发生后,event_base会在合适的时间(不一定是立即)去调用绑定在这个事件上的函数(传入一些预定义的参数,以及在绑定时指定的一个参数),直到这个函数执行完,再返回schedule其他事件。

    一、创建event_base对象

    调用event_init函数创建event_base对象。一个event_base相当于一个rector实例.

    struct event_base* base = event_init();
    

    二、创建具体的事件处理器

    创建具体的事件处理器,并设置从属的Rector实例,evsignal_newevtimer_new分别用于创建信号事件处理器和定时事件处理器。

    struct event* signal_event = evsignal_new( base, SIGINT, signal_cb, base );//创建信号事件处理器并设置从属的Rector实例
    struct event* timeout_event = evtimer_new( base, timeout_cb, NULL );//创建定时事件处理器并设置从属的Rector实例
    

    这两个函数有一个统一入口函数event_new:

    struct event* event_new(struct event_base* base,evutil_socket_t  fd,short what,event_callback_fn  cb,void* arg);
    

    参数1:base 指定新创建的事件处理器从属的 Rector,
    参数2:fd指定与该事件处理器关联的句柄
    参数3:events需要监控的事件

    EV_TIMEOUT      0x01	定时事件
    EV_READ			0x02	可读事件
    EV_WRITE		0x04	可写事件
    EV_SIGNAL		0x08	信号事件
    EV_PERSIST		0x10	永久事件
    /*边沿触发事件,需要I/O复用系统调用支持*/
    EV_ET			0x20
    

    参数4:callback指定目标事件的回调函数
    参数5:callback_arg 传递给回调函数的参数
    函数返回值:成功返回一个event类型对象,

    三、将事件处理器添加到注册事件队列

    调用event_add函数将事件处理器添加到注册事件队列中去

    四、执行事件循环

    调用event_base_dispatch函数执行事件循环

    五、释放资源

    事件循环之后free掉资源

    libevent里面的一些函数

    设置端口重用

    evutil_make_listen_socket_reuseable(server_socketfd); 
    

    设置无阻赛 实体在evutil.c中,是对fcntl操作

    evutil_make_socket_nonblocking(server_socketfd); 
    
    

    返回一个字符串,标识内核事件机制(kqueue的,epoll的,等等)

    const char *x =  event_base_get_method(base); //查看用了哪个IO多路复用模型,linux一下用epoll 
    
    

    程序进入无限循环,等待就绪事件并执行事件处理

    int event_base_dispatch(struct event_base *);
    

    属性获取示例

    点击查看代码
    #include<stdio.h>
    #include<event2/event.h>
    
    int main() {
    
        // libevent version
        printf("LIBEVENT_VERSION_NUMBER:%d\n", LIBEVENT_VERSION_NUMBER);
        printf("version: %s\n", event_get_version());
        struct event_base *base;
        base = event_base_new();
        // return current the method of multi-thread io
        const char *x =  event_base_get_method(base);
        printf("METHOD:%s\n", x);
    
        // return base support bit mask
        int t = event_base_get_features(base);
        if(t & EV_FEATURE_ET) {  // 支持边沿触发的后端
            printf("EV_FEATURE_ET\n");
        } 
        if(t & EV_FEATURE_O1) {  // 添加、删除单个事件,或者确定哪个事件激活的操作是 O(1)复杂度的后端
            printf("EV_FEATURE_O1\n");
        }
        if(t & EV_FEATURE_FDS) {  // /要求支持任意文件描述符,而不仅仅是套接字的后端
            printf("EV_FEATURE_FDS\n");
        }
     
    
        int y = event_base_dispatch(base);
        event_base_free(base);
        return 1;
    }
    

    Output:

    点击查看代码
    LIBEVENT_VERSION_NUMBER:33622016
    version: 2.1.8-stable
    METHOD:epoll
    EV_FEATURE_ET
    EV_FEATURE_O1
    

    事件循环 event_loop

    一旦创建好事件根基event_base,并且在根基上安插好事件之后,需要对事件循环监控(换句话说就是等待事件的到来,触发事件的回调函数),有两种方式可以达到上面描述的功能,即:event_base_dispatch和event_base_loop

    int event_base_dispatch(struct event_base *);	//程序进入无限循环,等待就绪事件并执行事件处理
    int event_base_loop(struct event_base *base, int flags);	//
    

    参数flags

    • EVLOOP_ONCE:相当于epoll_wait阻塞方式&&只调用一次 ⇒ 当没有事件到来时,程序将一直阻塞在event_base_loop函数;直到有任意一个事件到来时,程序才不会阻塞在event_base_loop,将会继续向下执行。
    • EVLOOP_NONBLOCK:相当于epoll_wait非阻塞方式&&只调用一次 ⇒ 即使没有事件到来,程序也不会阻塞在event_base_loop
    • EVLOOP_NO_EXIT_ON_EMPTY:等价于event_base_dispatch ⇒ 将一直循环监控事件 ⇒ 直到没有已经注册的事件 || 调用了event_base_loopbreak()或 event_base_loopexit()为止

    事件循环的退出的情况
    引起循环退出的情况:

    • event_base中没有事件了
    • 调用event_base_loopbreak 事件循环会停止 (立即停止)
    • 调用event_base_loopexit (等待所有事件结束后停止)
    • 程序错误

    libevent简易聊天室

    #include <stdio.h>  
    #include <stdlib.h>  
    #include <unistd.h>  
    #include <sys/types.h>      
    #include <sys/socket.h>      
    #include <netinet/in.h>      
    #include <arpa/inet.h>     
    #include <string.h>  
    #include <fcntl.h>   
      
    #include <event2/event.h>  
    #include <event2/bufferevent.h> 
    
    int cli_socket[1024]; 
    int cli_index = 0;
    
    //读取客户端  
    void do_read(evutil_socket_t fd, short event, void *arg) {  
        //继续等待接收数据    
        char buf[1024];  //数据传送的缓冲区      
        int len;    
        if ((len = recv(fd, buf, 1024, 0)) > 0)  {    
            buf[len] = '\0';      
            printf("%s", buf); 
    		for(int index = 0;index<cli_index;index++){
    			if (send(cli_socket[index], buf, len, 0) < 0) {    //将接受到的数据写回每一个客户端  
    				perror("write");      
    			}
    		}
        }
    	if(len == 0)
    	{
    		printf("fd:%d close\n",fd);
    		close(fd);
    	}
    	if(len <0)
    	{
    		perror("recv");     
    	}
    } 
    
    //回调函数,用于监听连接进来的客户端socket  
    void do_accept(evutil_socket_t fd, short event, void *arg) {  
        int client_socketfd;//客户端套接字      
        struct sockaddr_in client_addr; //客户端网络地址结构体     
        int in_size = sizeof(struct sockaddr_in);    
        //客户端socket    
        client_socketfd = accept(fd, (struct sockaddr *) &client_addr, &in_size); //等待接受请求,这边是阻塞式的    
        if (client_socketfd < 0) {    
            puts("accpet error");    
            exit(1);  
        } 
    	cli_socket[cli_index++] = client_socketfd;
    	printf("Connect from %s:%u ...!\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port)); 
        //类型转换  
        struct event_base *base_ev = (struct event_base *) arg;  
      
        //socket发送欢迎信息    
        char * msg = "Welcome to Libevent socket\n";    
        int size = send(client_socketfd, msg, strlen(msg), 0);    
      
        //创建一个事件,这个事件主要用于监听和读取客户端传递过来的数据  
        //持久类型,并且将base_ev传递到do_read回调函数中去  
        struct event *ev;  
        ev = event_new(base_ev, client_socketfd, EV_TIMEOUT|EV_READ|EV_PERSIST, do_read, base_ev);  
        event_add(ev, NULL);  
    } 
    
    //入口主函数  
    int main() {  
      
        int server_socketfd; //服务端socket    
        struct sockaddr_in server_addr;   //服务器网络地址结构体      
        memset(&server_addr,0,sizeof(server_addr)); //数据初始化--清零      
        server_addr.sin_family = AF_INET; //设置为IP通信      
        server_addr.sin_addr.s_addr = INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上      
        server_addr.sin_port = htons(5555); //服务器端口号      
        
        //创建服务端套接字    
        server_socketfd = socket(PF_INET,SOCK_STREAM,0);    
        if (server_socketfd < 0) {    
            puts("socket error");    
            return 0;    
        }    
      
        evutil_make_listen_socket_reuseable(server_socketfd); //设置端口重用  
        evutil_make_socket_nonblocking(server_socketfd); //设置无阻赛  
        
        //绑定IP    
        if (bind(server_socketfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr))<0) {    
            puts("bind error");    
            return 0;    
        }  
    
        //监听,监听队列长度 5    
        listen(server_socketfd, 10);    
        printf("listen port:%d\n",5555);
        //创建event_base 事件的集合,多线程的话 每个线程都要初始化一个event_base  
        struct event_base *base_ev;  
        base_ev = event_base_new();   
        const char *x =  event_base_get_method(base_ev); //获取IO多路复用的模型,linux一般为epoll  
        printf("METHOD:%s\n", x);  
      
        //创建一个事件,类型为持久性EV_PERSIST,回调函数为do_accept(主要用于监听连接进来的客户端)  
        //将base_ev传递到do_accept中的arg参数  
        struct event *ev;  
        ev = event_new(base_ev, server_socketfd, EV_TIMEOUT|EV_READ|EV_PERSIST, do_accept, base_ev);  
      
        //注册事件,使事件处于 pending的等待状态  
        event_add(ev, NULL);  
      
        //事件循环  
        event_base_dispatch(base_ev);  
      
        //销毁event_base  
        event_base_free(base_ev);    
        return 1;  
    }
    
    
    
    gcc simple_chat_room.c -o simple_chat_room -levent
    ./simple_chat_room
    listen port:5555
    METHOD:epoll
    

    效果:一个客户端的消息会发送到其他所有客户端

    参考链接

    1. 基于Libevent的简易聊天室设计----从0开始
    2. libevent入门教程:Echo Server based on libevent
    个性签名:时间会解决一切
  • 相关阅读:
    面向对象分析与设计
    数据摘要pandas
    面向对象(简介)
    SQL触发器、事物
    SQL——查询考试
    SQL存储过程、视图
    SQL变量、运算符、分支、循环语句
    SQL连接查询
    SQL主外键和子查询
    SQL各种语句、函数
  • 原文地址:https://www.cnblogs.com/lfri/p/15703235.html
Copyright © 2011-2022 走看看