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

基本的socket编程是阻塞/同步的,每个操作除非已经完成或者出错才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没法支撑大量的请求。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信号量之类的。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吧

(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_nnblocking)

2. 创建一个event_base

3. 创建一个event, 将该socket托管给event_base,指定要监听的事件类型,并绑定上相应的回调函数(及需要给它的参数)。对于listener socket来说,只需要监听EV_READ|EV_PERSIST

4. 启动该事件

5. 进入事件循环

6. (异步)当有client发起请求的时候,调用该回调函数,进行处理。

问题:为什么不在listen完马上调用accept,获得客户端连接以后再丢给event_base呢?这个问题不知道YFS就这么干的,不过这个要另起一个线程。

回调函数要做什么事情呢?当然是处理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,设置回调函数和参数

  event_write, 绑上sockfd的EV_WRITE|EV_PERSIST, 设置回调函数和参数

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等相关数据

  1. void read_cb(struct bufferevent *bev, void *arg) {
  2. char line[];
  3. int n;
  4. evutil_socket_t fd = bufferevent_getfd(bev);
  5. while (n = bufferevent_read(bev, line, ), n > )
  6. bufferevent_write(bev, line, n);
  7. }
  8.  
  9. void error_cb(struct bufferevent *bev, short event, void *arg) {
  10. bufferevent_free(bev);
  11. }
  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. {
  19. int ret;
  20. evutil_socket_t listener;
  21. listener = socket(AF_INET, SOCK_STREAM, );
  22. assert(listener > );
  23. evutil_make_listen_socket_reuseable(listener);
  24.  
  25. struct sockaddr_in sin;
  26. sin.sin_family = AF_INET;
  27. sin.sin_addr.s_addr = ;
  28. sin.sin_port = htons(LISTEN_PORT);
  29.  
  30. if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < ) {
  31. perror("bind");
  32. return ;
  33. }
  34.  
  35. if (listen(listener, LISTEN_BACKLOG) < ) {
  36. perror("listen");
  37. return ;
  38. }
  39.  
  40. printf ("Listening...\n");
  41.  
  42. evutil_make_socket_nonblocking(listener);
  43.  
  44. struct event_base *base = event_base_new();
  45. assert(base != NULL);
  46. struct event *listen_event;
  47. listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
  48. event_add(listen_event, NULL);
  49. event_base_dispatch(base);
  50.  
  51. printf("The End.");
  52. return ;
  53. }
  54.  
  55. void do_accept(evutil_socket_t listener, short event, void *arg)
  56. {
  57. struct event_base *base = (struct event_base *)arg;
  58. evutil_socket_t fd;
  59. struct sockaddr_in sin;
  60. socklen_t slen = sizeof(sin);
  61. fd = accept(listener, (struct sockaddr *)&sin, &slen);
  62. if (fd < ) {
  63. perror("accept");
  64. return;
  65. }
  66. if (fd > FD_SETSIZE) { //这个if是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改
  67. perror("fd > FD_SETSIZE\n");
  68. return;
  69. }
  70.  
  71. printf("ACCEPT: fd = %u\n", fd);
  72.  
  73. struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
  74. bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
  75. bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST);
  76. }
  77.  
  78. void read_cb(struct bufferevent *bev, void *arg)
  79. {
  80. #define MAX_LINE 256
  81. char line[MAX_LINE+];
  82. int n;
  83. evutil_socket_t fd = bufferevent_getfd(bev);
  84.  
  85. while (n = bufferevent_read(bev, line, MAX_LINE), n > ) {
  86. line[n] = '\0';
  87. printf("fd=%u, read line: %s\n", fd, line);
  88.  
  89. bufferevent_write(bev, line, n);
  90. }
  91. }
  92.  
  93. void write_cb(struct bufferevent *bev, void *arg) {}
  94.  
  95. void error_cb(struct bufferevent *bev, short event, void *arg)
  96. {
  97. evutil_socket_t fd = bufferevent_getfd(bev);
  98. printf("fd = %u, ", fd);
  99. if (event & BEV_EVENT_TIMEOUT) {
  100. printf("Timed out\n"); //if bufferevent_set_timeouts() called
  101. }
  102. else if (event & BEV_EVENT_EOF) {
  103. printf("connection closed\n");
  104. }
  105. else if (event & BEV_EVENT_ERROR) {
  106. printf("some other error\n");
  107. }
  108. bufferevent_free(bev);
  109. }

libevent 入门教程:Echo Server based on libevent(转)的更多相关文章

  1. [z]libevent入门教程:Echo Server based on libevent 不指定

    [z]https://www.felix021.com/blog/read.php?2068 花了两天的时间在libevent上,想总结下,就以写简单tutorial的方式吧,貌似没有一篇简单的说明, ...

  2. libevent入门教程

    首先给出官方文档吧: http://libevent.org ,首页有个Programming with Libevent,里面是一节一节的介绍libevent,但是感觉信息量太大了,而且还是英文的- ...

  3. libevent入门介绍

    libevent是之前看到的一个别人推荐的清凉级网络库,我就想了解一下它.今天下载到了一个人写的剖析系列,从结构和源码方面进行了简要分析.只是这个分析文章是2010年的,有点过时了(跟现在的libev ...

  4. 【VS开发】【数据库开发】libevent入门

    花了两天的时间在libevent上,想总结下,就以写简单tutorial的方式吧,貌似没有一篇简单的说明,让人马上就能上手用的.首先给出官方文档吧: http://libevent.org ,首页有个 ...

  5. netty写Echo Server & Client完整步骤教程(图文)

    1.创建Maven工程 1.1 父节点的pom.xml代码(root pom文件) 1 <?xml version="1.0" encoding="UTF-8&qu ...

  6. 无废话ExtJs 入门教程十三[上传图片:File]

    无废话ExtJs 入门教程十三[上传图片:File] extjs技术交流,欢迎加群(201926085) 1.代码如下: 1 <!DOCTYPE html PUBLIC "-//W3C ...

  7. Systemd 入门教程:实战篇

    Systemd 入门教程:实战篇 上一篇文章,介绍了 Systemd 的主要命令,这篇文章主要介绍如何使用 Systemd 来管理我们的服务,以及各项的含义: 一.开机启动 对于那些支持 System ...

  8. nginx入门教程

    nginx入门教程 一.概述    什么是nginx?   Nginx (engine x) 是一款轻量级的Web 服务器 .反向代理服务器及电子邮件(IMAP/POP3)代理服务器.   什么是反向 ...

  9. 【Centos】systemd入门教程

    systemd使用教程 常用指令 运行一个服务: systemctl start <服务名> 关闭一个服务: systemctl stop <服务名> 重启一个服务: syst ...

随机推荐

  1. beansbinding NetBeans IDE 中 Swing数据绑定插件

    http://my.oschina.net/u/127459/blog/111486 https://kenai.com/projects/betterbeansbinding/pages/Home ...

  2. C# Notes

    Token Based Authentication Attributes Tutorial - Conditional - Obsolete Covariance and Contravarianc ...

  3. h5 rem

    <script> (function(){ setRem(); window.addEventListener('orientation' in window?"deviceor ...

  4. 微信小程序——页面中调用组件方法

    我现在有一个弹层的组件(popup),组件里面定义了显示组件(showPopup)和隐藏组件(hidePopup)的方法. 我们如何在调用组件的页面中调用组件里面的方法呢? 在调用组件的页面写如下代码 ...

  5. 【转】Smartphone--Android真机管理平台

    背景 最近在工作中发现几个问题: 作为测试人员,在做产品兼容性测试时,发现手上的测试设备总是不够用,但是可能其他同事的设备在闲置着: 作为Android和H5开发人员,如果测试同事发现一个兼容性的bu ...

  6. C和C++的内存操作小贴士(一):const char*的内存释放问题

    C和C++的内存操作一直是困扰开发人员的老问题,基本概念相信老司机们都很清楚了,在这里就不做过多的描述了,只是把在实际开发中可能遇到的一些小问题的案例列举下,供大家参考.“C和C++的内存操作小贴士” ...

  7. "iostat" On Linux

    CPU是一台电脑的大脑.所有的处理命令都运行在上面.I/O(输入/输出)同样扮演了一个重要角色.硬盘用于提供数据给处理器并保存CPU处理过的数据.一种衡量处理器和I/O利用率的方法是使用iostat命 ...

  8. IntelliJ IDEA 13.1.3 SVN无法正常使用问题

    http://my.oschina.net/luckyi/blog/291007 最新升级IDEA12到13版本,升级后发现IDEA中SVN无法正常使用,但文件夹下能够正常使用. 并且报错:svn: ...

  9. 关于VS2008和VS2013中字体的选择

    我这学期上ASP.NET的课,用C#语言,配合VS2008.自己课余在研究手机游戏的开发,用的是C++语言,配合VS2013.这两种开发环境我自己试过好多字体,后来感觉适合我自己的应该是以下这两种: ...

  10. Pandas的排序和排名(Series, DataFrame) + groupby

    根据条件对数据集排序(sorting)也是一种重要的内置运算.要对行或列索引进行排序(按字典顺序), 可使用sort_index 方法, 它将返回一个已排序的新对象: 而DataFrame, 则可以根 ...