博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
libevent入门教程:Echo Server based on libevent - Blog of Felix021 - 日,泯然众人矣。
阅读量:5942 次
发布时间:2019-06-19

本文共 8101 字,大约阅读时间需要 27 分钟。

libevent入门教程:Echo Server based on libevent

转载请注明出自  ,如是转载文则注明原出处,谢谢:)

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


首先给出官方文档吧: ,首页有个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的 epollBSDkqueueWindowsIOCP。由于在内核层面做了支持,所以可以用 `O(1)的效率查找到active的fd。基本上,libevent就是对这些高效 IO 的封装,提供统一的API,简化开发。


libevent简介:

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

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

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

struct event *listen_event;


:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数

listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, ( void *)base);


:event,超时时间( struct timeval *类型的, NULL 表示无超时设置)

event_add(listen_event, NULL );

注:libevent支持的事件及属性包括(使用bitfield实现,所以要用 | 来让它们合体)

  • EV_TIMEOUT: 超时

  • EV_READ: 只要网络缓冲中还有数据,回调函数就会被触发

  • EV_WRITE: 只要塞给网络缓冲的数据被写完,回调函数就会被触发

  • EV_SIGNAL: POSIX 信号量,参考manual吧

  • EV_PERSIST: 不指定这个属性的话,回调函数被触发后事件会被删除

  • EV_ET: Edge-Trigger边缘触发,参考EPOLL_ET

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

event_base_dispatch(base);

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

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. 进入事件循环


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

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

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

magichan 注释: 这段话应该是说 accpet 获得一个 socket 时,不会放在这个回调函数本身

处理这个套接字发过来的数据,而是把该套接字绑定到一个的新 event 中,再放回事件循环之中。
如果该 socket 有数据发送过来,那么就触发新的 event 处理数据。


libevent 1.× 版本套接字发送和接受 解决的方案

在老版本libevent上的实现,比较罗嗦[如果不想详细了解的话,看下一部分]。

对于服务器希望先从client获取数据的情况,大致流程是这样的:

  1. 将这个sockfd设置为 nonblocking

  2. 创建2个event:

event_read,绑上sockfd的 EV_READ|EV_PERSIST,设置回调函数和参数(后面提到的struct)

event_write,绑上sockfd的 EV_WRITE|EV_PERSIST,设置回调函数和参数(后面提到的struct)

  1. 启用 event_read 事件


  1. (异步) 等待 event_read 事件的发生, 调用相应的回调函数。这里麻烦来了:回调函数用recv读入的数据,不能直接用 send 丢给 sockfd 了事——因为 sockfd 是 nonblocking 的,丢给它的话,不能保证正确(为什么呢?)。所以需要一个自己管理的缓存用来保存读入的数据中(在accept以后就创建一个struct,作为第2步回调函数的arg传进来),在合适的时间(比如遇到换行符)启用 event_write 事件【event_add(event_write, NULL)】,等待 EV_WRITE 事件的触发


  1. (异步) 当 event_write 事件的回调函数被调用的时候,往 sockfd 写入数据,然后删除 event_write 事件【event_del(event_write)】,等待event_read事件的下一次执行。

以上步骤比较晦涩,具体代码可参考里面的【Example: A low-level ROT13 server with Libevent】

libevent 2.× 版本套接字发送和接受 解决的方案

由于需要自己管理缓冲区,且过程晦涩难懂,并且不兼容于Windows的IOCP,所以libevent2开始,提供了bufferevent这个神器,用来提供更加优雅、易用的API。struct bufferevent 内建了两个 event(read/write) 和对应的缓冲区【struct evbuffer *input, *output】,并提供相应的函数用来操作缓冲区(或者直接操作bufferevent)。每当有数据被读入input的时候,read_cb函数被调用;每当utput被输出完的时候,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 事件


  1. (异步)

  • 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_cbwrite_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函数即可:

void read_cb( struct bufferevent *bev, void *arg) {     char line[256];     int n;    evutil_socket_t fd = bufferevent_getfd(bev);     while (n = bufferevent_read(bev, line, 256), n > 0)    bufferevent_write(bev, line, n);    }        void error_cb( struct bufferevent *bev, short event, void *arg) {    bufferevent_free(bev);    }

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

# include 
# include
# include
# include
# include
# include
# define LISTEN_PORT 9999 # define LISTEN_BACKLOG 32 void do_accept(evutil_socket_t listener, short event, void *arg); void read_cb( struct bufferevent *bev, void *arg); void error_cb( struct bufferevent *bev, short event, void *arg); void write_cb( struct bufferevent *bev, void *arg); int main( int argc, char *argv[]) { int ret; evutil_socket_t listener; listener = socket(AF_INET, SOCK_STREAM, 0); assert(listener > 0); evutil_make_listen_socket_reuseable(listener); struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0; sin.sin_port = htons(LISTEN_PORT); if (bind(listener, ( struct sockaddr *)&sin, sizeof (sin)) < 0) { perror("bind"); return 1; } if (listen(listener, LISTEN_BACKLOG) < 0) { perror("listen"); return 1; } printf ("Listening...\n"); evutil_make_socket_nonblocking(listener); struct event_base *base = event_base_new(); assert(base != NULL ); struct event *listen_event; listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, ( void *)base); event_add(listen_event, NULL ); event_base_dispatch(base); printf ("The End."); return 0; } void do_accept(evutil_socket_t listener, short event, void *arg) { struct event_base *base = ( struct event_base *)arg; evutil_socket_t fd; struct sockaddr_in sin; socklen_t slen = sizeof (sin); fd = accept(listener, ( struct sockaddr *)&sin, &slen); if (fd < 0) { perror("accept"); return ; } if (fd > FD_SETSIZE) { //这个 if 是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改 perror("fd > FD_SETSIZE\n"); return ; } printf ("ACCEPT: fd = %u\n", fd); struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev, read_cb, NULL , error_cb, arg); bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST); } void read_cb( struct bufferevent *bev, void *arg) { # define MAX_LINE 256 char line[MAX_LINE+1]; int n; evutil_socket_t fd = bufferevent_getfd(bev); while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) { line[n] = '\0'; printf ("fd=%u, read line: %s\n", fd, line); bufferevent_write(bev, line, n); } } void write_cb( struct bufferevent *bev, void *arg) {} void error_cb( struct bufferevent *bev, short event, void *arg) { evutil_socket_t fd = bufferevent_getfd(bev); printf ("fd = %u, ", fd); if (event & BEV_EVENT_TIMEOUT) { printf ("Timed out\n"); // if bufferevent_set_timeouts() called } else if (event & BEV_EVENT_EOF) { printf ("connection closed\n"); } else if (event & BEV_EVENT_ERROR) { printf ("some other error\n"); } bufferevent_free(bev); }

--

 

转载于:https://www.cnblogs.com/dilidingzhi/p/4706012.html

你可能感兴趣的文章
Android DrawLayout + ListView 的使用(一)
查看>>
clear session on close of browser jsp
查看>>
asp.net mvc Post上传文件大小限制 (转载)
查看>>
关于吃掉物理的二次聚合无法实现的需要之旁门左道实现法
查看>>
mysql出现unblock with 'mysqladmin flush-hosts'
查看>>
oracle exp/imp命令详解
查看>>
开发安全的 API 所需要核对的清单
查看>>
Mycat源码中的单例模式
查看>>
WPF Dispatcher介绍
查看>>
fiddler展示serverIP方法
查看>>
C语言中的随意跳转
查看>>
WPF中如何将ListViewItem双击事件绑定到Command
查看>>
《聚散两依依》
查看>>
小tips:你不知道的 npm init
查看>>
Mac笔记本中是用Idea开发工具在Java项目中调用python脚本遇到的环境变量问题解决...
查看>>
Jmeter也能IP欺骗!
查看>>
Rust 阴阳谜题,及纯基于代码的分析与化简
查看>>
ASP.NET Core的身份认证框架IdentityServer4(4)- 支持的规范
查看>>
(原創) array可以使用reference方式傳進function嗎? (C/C++)
查看>>
170多个Ionic Framework学习资源(转载)
查看>>