libevent 构造httpServer

来源:互联网 发布:乐视手机数据恢复 编辑:程序博客网 时间:2024/06/11 01:59

        前一阵老板要求搭一个web server backend的框架,要支持高并发。 刚开始并没有什么思路,网上想找现成的,于是输入关键 httpserver+高并发,返回出来最多的结果有两个 reactor和actor。 其中reactor模型就是libevent,好吧,研究一下,网上的文档很多很多,入门还是比较容易的。

        首先,了解到libevent 是事件驱动机制(区别于loop driven);其次,libevent集成了I/O,Timer,Signal事件(集成的方法很多文章都介绍的很细);然后,跨平台Linux和windows都支持,对于不同的平台I/O封装的底层都是不一样的(select, epoll,等);最后最重要的就是:Libevent本身是单线程的,单线程的,单线程的;那么单线程的怎么会实现高并发呢?因为传统的思路,高并发都是伴随着异步IO,如 boost::asio,linux的aio;原来Libevent封装了bufferevent,bufferevent封装了windows的IOCP来实现完成句柄的调用(linux下好像真的没有实现异步IO)。所以说,Bufferevent有两套调用机制,同步和异步的;如果配置成异步的,初始化时会启用线程池,所有的Bufferevent回调函数的上下文都是线程池里的线程,并且由Bufferevent去保证其线程安全。如果是同步的,回调函数的上下文都是Libevent主线程。

坦率的说,同步和异步这两个概念和阻塞和非阻塞有点混淆,这里我谈谈自己对同步异步的理解,欢迎同学们指正。

同步调用,当调用返回时该请求的调用者已经有成功或失败明确的结果;异步调用,当调用返回时该请求的调用者并没有成功或失败的明确结果。linux下读操作肯定是同步的,写操作很有可能是异步的(因为可能返回的时候仅仅写到cache里)。那么对于同步I/O,发出命令的是主线程,回调的上下文也是主线程,主线程有明确结果;异步I/O,发出命令的是主线程,回调的上下文是工作线程(主线程没有明确结果),一个fd出现在多个工作线程中怎么保证安全?Bufferevent封装了,他去保证,用户看不到fd,用户不用担心。再多说一句,同理,asio中的io_service也是线程安全的,socket不是的,所以一个对象只能在一个线程中出现。有点扯远了,回到libevent。


Libevent封装了httpev去构建httpServer,而httpev封装了Bufferevent,所以libevent开放给用户去构建httpServer的接口很简单,只要输入http path和回调函数即可,别的都不用管。在我的项目里libevent IO没有用到异步的,因为我的回调函数里面干的事情比较简单,仅仅把收到的req消息放到Actor高并发队列里,同时这个消息本身的传递也是转移语意(move semantics)的。Actor的高并发模型,我另外会有一篇文章来细细探讨,里面也会有很多奇技淫巧。

evhttp_new_object会创建一个evhttp的对象,这个数据结构很庞大,主要是记录了一些Listener的sockets以及回调函数,live的connection列表;evhttp_bind_socket 会把 port+address作为入参绑定一个socket并且listen;调用evconnlistener_new 构造listener对象,把需要监听的fd交给event base,同时注册一个accept相关的回调函数accept_socket_cb;

在event_base_loop里面会有个while loop,loop里会select event_base里有的fd,一旦被触发,会激活base event的read事件,accept_socket_cb被调用;

接下来就是evhttp_get_request被调用,创建一个evcon和http req结构并把两者关联起来( 这时候,Bufferevent就登场了,他会挂在evcon下面,并且bufferevent_setfd把相应fd给base event,并注册read和write事件),在http req上设定回调函数evhttp_handle_request,最后evhttp_start_read_也会被调用;在evhttp_start_read_里面主要是设定了bufferevent的相关回调函数;所以,以后一旦有该connection的数据过来,select会触发read事件,bufferevent被触发调用回调函数获取可读数据(这里evhttp_handle_request最终会被调用)。而在evhttp_handle_request里面会调用我们最开始初始化是设定的回调函数去处理http请求。

我们知道 http reqest和response一般是成对出现的,那么response是如何回复给客户端的呢? 前面提到过,在我的项目里和libevent交互的是一个actor model。所以,我的libevent 构建了一个socketpair(本质上是pipe通信,类似ZeroMQ里的主线程和IO线程的通信方式)去接受actor的消息,actor会把消息放在一个队列,通过往Socketpair发送一个字节去触发libevent获取消息,在这里消息队列肯定是互斥的,但是因为SocketPair里发的消息只有一个字节,所以actor多个并发线程与libevent之间的SocketPair可以无锁使用(socket对象本身是线程非安全的,但是因为发的数据只有一个字节,所以可以这么使用)。

libevent里面会调用evhttp_send_reply做回复,在该函数里面最后还是enable了bufferevent的EV_WRITE,只要相应的socket可写,bufferevent_writecb就会被调用把数据发送出去,当数据发送完毕,EV_WRITE会被disable; 同时evhttp_send_done作为回调也会被调用做一些收尾工作,如删除http req和connection。

其实bufferevent还有一个很有用的接口就是可以控制高低水位去决定是否即使发送或接收缓存在bufferevent的数据,不过我的项目里没有用到。


       

0 0
原创粉丝点击