当前位置: 首页 > news >正文

libevent入门教程:Echo Server based on libevent

    花了两天的时间在libevent上,想总结下,就以写简单tutorial的方式吧,貌似没有一篇简单的说明,让人马上就能上手用的。

首先给出官方文档吧: http://libevent.org ,首页有个Programming with Libevent,里面是一节一节的介绍libevent,但是感觉信息量太大了,而且还是英文的-。-(当然,如果想好好用libevent,看看还是很有必要的),还有个Reference,大致就是对各个版本的libevent使用doxgen生成的文档,用来查函数原型和基本用法什么的。

下面假定已经学习过基本的socket编程(socket,bind,listen,accept,connect,recv,send,close),并且对异步/callback有基本认识。

基本的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其他事件。

 


 
  1. //创建一个event_base 
  2. struct event_base *base = event_base_new(); 
  3. assert(base != NULL); 

   event_base内部有一个循环,循环阻塞在epoll/kqueue等系统调用上,直到有一个/一些事件发生,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上。每个事件对应一个struct event,可以是监听一个fd或者POSIX信号量之类(这里只讲fd了,其他的看manual吧)。struct event使用event_new来创建和绑定,使用event_add来启用:

 


 
  1. //创建并绑定一个event 
  2. struct event *listen_event; 
  3. //参数:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数 
  4. listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base); 
  5. //参数:event,超时时间(struct timeval *类型的,NULL表示无超时设置) 
  6. event_add(listen_event, NULL); 

 注:libevent支持的事件及属性包括(使用bitfield实现,所以要用 | 来让它们合体)
    (a) EV_TIMEOUT: 超时
    (b) EV_READ: 只要网络缓冲中还有数据,回调函数就会被触发
    (c) EV_WRITE: 只要塞给网络缓冲的数据被写完,回调函数就会被触发
    (d) EV_SIGNAL: POSIX信号量,参考manual吧
    (e) EV_PERSIST: 不指定这个属性的话,回调函数被触发后事件会被删除
    (f) EV_ET: Edge-Trigger边缘触发,参考EPOLL_ET

  然后需要启动event_base的循环,这样才能开始处理发生的事件。循环的启动使用event_base_dispatch,循环将一直持续,直到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数。

 


 
  1. //启动事件循环 
  2. event_base_dispatch(base); 

   接下来关注下绑定到event的回调函数callback_func:传递给它的是一个socket fd、一个event类型及属性bit_field、以及传递给event_new的最后一个参数(去上面几行回顾一下,把event_base给传进来了,实际上更多地是分配一个结构体,把相关的数据都撂进去,然后丢给event_new,在这里就能取得到了)。其原型是:

 


 
  1. typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg) 

对于一个服务器而言,上面的流程大概是这样组合的:
    1. listener = socket(),bind(),listen(),设置nonblocking(POSIX系统中可使用fcntl设置,windows不需要设置,实际上libevent提供了统一的包装evutil_make_socket_nonblocking)
    2. 创建一个event_base
    3. 创建一个event,将该socket托管给event_base,指定要监听的事件类型,并绑定上相应的回调函数(及需要给它的参数)。对于listener socket来说,只需要监听EV_READ|EV_PERSIST
    4. 启用该事件
    5. 进入事件循环
    ---------------
    6. (异步) 当有client发起请求的时候,调用该回调函数,进行处理。

    问题:为什么不在listen完马上调用accept,获得客户端连接以后再丢给event_base呢?这个问题先想想噢。

    回调函数要做什么事情呢?当然是处理client的请求了。首先要accept,获得一个可以与client通信的sockfd,然后……调用recv/send吗?错!大错特错!如果直接调用recv/send的话,这个线程就阻塞在这个地方了,如果这个客户端非常的阴险(比如一直不发消息,或者网络不好,老是丢包),libevent就只能等它,没法处理其他的请求了——所以应该创建一个新的event来托管这个sockfd。

    在老版本libevent上的实现,比较罗嗦[如果不想详细了解的话,看下一部分]。
    对于服务器希望先从client获取数据的情况,大致流程是这样的:
    1. 将这个sockfd设置为nonblocking
    2. 创建2个event:
        event_read,绑上sockfd的EV_READ|EV_PERSIST,设置回调函数和参数(后面提到的struct)
        event_write,绑上sockfd的EV_WRITE|EV_PERSIST,设置回调函数和参数(后面提到的struct)
    3. 启用event_read事件
    ------
    4. (异步) 等待event_read事件的发生, 调用相应的回调函数。这里麻烦来了:回调函数用recv读入的数据,不能直接用send丢给sockfd了事——因为sockfd是nonblocking的,丢给它的话,不能保证正确(为什么呢?)。所以需要一个自己管理的缓存用来保存读入的数据中(在accept以后就创建一个struct,作为第2步回调函数的arg传进来),在合适的时间(比如遇到换行符)启用event_write事件【event_add(event_write, NULL)】,等待EV_WRITE事件的触发
    ------
    5. (异步) 当event_write事件的回调函数被调用的时候,往sockfd写入数据,然后删除event_write事件【event_del(event_write)】,等待event_read事件的下一次执行。
    以上步骤比较晦涩,具体代码可参考官方文档里面的【Example: A low-level ROT13 server with Libevent】


    由于需要自己管理缓冲区,且过程晦涩难懂,并且不兼容于Windows的IOCP,所以libevent2开始,提供了bufferevent这个神器,用来提供更加优雅、易用的API。struct bufferevent内建了两个event(read/write)和对应的缓冲区【struct evbuffer *input, *output】,并提供相应的函数用来操作缓冲区(或者直接操作bufferevent)。每当有数据被读入input的时候,read_cb函数被调用;每当output被输出完的时候,write_cb被调用;在网络IO操作出现错误的情况(连接中断、超时、其他错误),error_cb被调用。于是上一部分的步骤被简化为:
    1. 设置sockfd为nonblocking
    2. 使用bufferevent_socket_new创建一个struct bufferevent *bev,关联该sockfd,托管给event_base
    3. 使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void *)arg)将EV_READ/EV_WRITE对应的函数
    4. 使用bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)来启用read/write事件
    ------
    5. (异步)
        在read_cb里面从input读取数据,处理完毕后塞到output里(会被自动写入到sockfd)
        在write_cb里面(需要做什么吗?对于一个echo server来说,read_cb就足够了)
        在error_cb里面处理遇到的错误
    *. 可以使用bufferevent_set_timeouts(bev, struct timeval *READ, struct timeval *WRITE)来设置读写超时, 在error_cb里面处理超时。
    *. read_cb和write_cb的原型是
        void read_or_write_callback(struct bufferevent *bev, void *arg)
      error_cb的原型是
        void error_cb(struct bufferevent *bev, short error, void *arg) //这个是event的标准回调函数原型
      可以从bev中用libevent的API提取出event_base、sockfd、input/output等相关数据,详情RTFM~
    

    于是代码简化到只需要几行的read_cb和error_cb函数即可:


 
  1. void read_cb(struct bufferevent *bev, void *arg) { 
  2.     char line[256]; 
  3.     int n; 
  4.     evutil_socket_t fd = bufferevent_getfd(bev); 
  5.     while (n = bufferevent_read(bev, line, 256), n > 0) 
  6.         bufferevent_write(bev, line, n); 
  7.  
  8. void error_cb(struct bufferevent *bev, short event, void *arg) { 
  9.     bufferevent_free(bev); 

 于是一个支持大并发量的echo server就成型了!下面附上无注释的echo server源码,110行,多抄几遍,就能完全弄懂啦!更复杂的例子参见官方文档里面的【Example: A simpler ROT13 server with Libevent】

 


 
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <errno.h> 
  4. #include <assert.h> 
  5.  
  6. #include <event2/event.h> 
  7. #include <event2/bufferevent.h> 
  8.  
  9. #define LISTEN_PORT 9999 
  10. #define LISTEN_BACKLOG 32 
  11.  
  12. void do_accept(evutil_socket_t listener, short event, void *arg); 
  13. void read_cb(struct bufferevent *bev, void *arg); 
  14. void error_cb(struct bufferevent *bev, short event, void *arg); 
  15. void write_cb(struct bufferevent *bev, void *arg); 
  16.  
  17. int main(int argc, char *argv[]) 
  18.     int ret; 
  19.     evutil_socket_t listener; 
  20.     listener = socket(AF_INET, SOCK_STREAM, 0); 
  21.     assert(listener > 0); 
  22.     evutil_make_listen_socket_reuseable(listener); 
  23.  
  24.     struct sockaddr_in sin; 
  25.     sin.sin_family = AF_INET; 
  26.     sin.sin_addr.s_addr = 0; 
  27.     sin.sin_port = htons(LISTEN_PORT); 
  28.  
  29.     if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) { 
  30.         perror("bind"); 
  31.         return 1; 
  32.     } 
  33.  
  34.     if (listen(listener, LISTEN_BACKLOG) < 0) { 
  35.         perror("listen"); 
  36.         return 1; 
  37.     } 
  38.  
  39.     printf ("Listening...\n"); 
  40.  
  41.     evutil_make_socket_nonblocking(listener); 
  42.  
  43.     struct event_base *base = event_base_new(); 
  44.     assert(base != NULL); 
  45.     struct event *listen_event; 
  46.     listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base); 
  47.     event_add(listen_event, NULL); 
  48.     event_base_dispatch(base); 
  49.  
  50.     printf("The End."); 
  51.     return 0; 
  52.  
  53. void do_accept(evutil_socket_t listener, short event, void *arg) 
  54.     struct event_base *base = (struct event_base *)arg; 
  55.     evutil_socket_t fd; 
  56.     struct sockaddr_in sin; 
  57.     socklen_t slen; 
  58.     fd = accept(listener, (struct sockaddr *)&sin, &slen); 
  59.     if (fd < 0) { 
  60.         perror("accept"); 
  61.         return
  62.     } 
  63.     if (fd > FD_SETSIZE) { 
  64.         perror("fd > FD_SETSIZE\n"); 
  65.         return
  66.     } 
  67.  
  68.     printf("ACCEPT: fd = %u\n", fd); 
  69.  
  70.     struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); 
  71.     bufferevent_setcb(bev, read_cb, NULL, error_cb, arg); 
  72.     bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST); 
  73.  
  74. void read_cb(struct bufferevent *bev, void *arg) 
  75. #define MAX_LINE    256 
  76.     char line[MAX_LINE+1]; 
  77.     int n; 
  78.     evutil_socket_t fd = bufferevent_getfd(bev); 
  79.  
  80.     while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) { 
  81.         line[n] = '\0'
  82.         printf("fd=%u, read line: %s\n", fd, line); 
  83.  
  84.         bufferevent_write(bev, line, n); 
  85.     } 
  86.  
  87. void write_cb(struct bufferevent *bev, void *arg) {} 
  88.  
  89. void error_cb(struct bufferevent *bev, short event, void *arg) 
  90.     evutil_socket_t fd = bufferevent_getfd(bev); 
  91.     printf("fd = %u, ", fd); 
  92.     if (event & BEV_EVENT_TIMEOUT) { 
  93.         printf("Timed out\n"); //if bufferevent_set_timeouts() called 
  94.     } 
  95.     else if (event & BEV_EVENT_EOF) { 
  96.         printf("connection closed\n"); 
  97.     } 
  98.     else if (event & BEV_EVENT_ERROR) { 
  99.         printf("some other error\n"); 
  100.     } 
  101.     bufferevent_free(bev); 

转载请注明出自 http://www.felix021.com/blog/read.php?2068 ,如是转载文则注明原出处,谢谢:)

相关文章:

  • 初尝量子计算开放果实
  • Logger之Logger.getLogger(CLass)
  • 分享:Meteor 0.5.3 发布,Web 应用构建平台
  • Redhat6.5搭建ftp服务
  • cisco 端口映射
  • 编译型语言,解释型语言,混合型语言,静态类型语言、动态类型语言、动态语言、静态语言、强类型语言、弱类型语言...
  • 好文转载—六步创建一个安全的密码
  • gf框架之分页模块(五) - 自定义分页
  • SQL Server 2005 日常运维检查操作手册
  • FPGA设计中遇到的奇葩问题之“芯片也要看出身”(一)
  • UI自动化测试篇 :webdriver+ant+jenkins自动化测试实践
  • 再谈DOMContentLoaded与渲染阻塞—分析html页面事件与资源加载
  • mysql alter 曾删改查
  • jsonp
  • find_first_zore_bit-一个位图的实现
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • JS笔记四:作用域、变量(函数)提升
  • markdown编辑器简评
  • mongo索引构建
  • PHP 7 修改了什么呢 -- 2
  • Redis字符串类型内部编码剖析
  • 表单中readonly的input等标签,禁止光标进入(focus)的几种方式
  • 开源SQL-on-Hadoop系统一览
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 深度学习中的信息论知识详解
  • 微信小程序开发问题汇总
  • 我有几个粽子,和一个故事
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • 移动互联网+智能运营体系搭建=你家有金矿啊!
  • ​Linux·i2c驱动架构​
  • ​力扣解法汇总946-验证栈序列
  • ​什么是bug?bug的源头在哪里?
  • # Panda3d 碰撞检测系统介绍
  • #define用法
  • #stm32驱动外设模块总结w5500模块
  • $.proxy和$.extend
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (3)nginx 配置(nginx.conf)
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (数据结构)顺序表的定义
  • (原)Matlab的svmtrain和svmclassify
  • (转)Linux下编译安装log4cxx
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • .bashrc在哪里,alias妙用
  • .MSSQLSERVER 导入导出 命令集--堪称经典,值得借鉴!
  • .net core 6 redis操作类
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .NET Core WebAPI中使用swagger版本控制,添加注释
  • .NET Core 实现 Redis 批量查询指定格式的Key
  • .NET Standard 支持的 .NET Framework 和 .NET Core
  • .net wcf memory gates checking failed
  • .net 简单实现MD5
  • .NET 应用启用与禁用自动生成绑定重定向 (bindingRedirect),解决不同版本 dll 的依赖问题