高性能C++网络库libtnet实现:IOLoop
IOLoop
libtnet采用的是prefork + event loop的架构方式,prefork就是server在启动的时候预先fork多个子进程同时工作,而event loop则是基于epoll的事件处理机制。
在最新的linux系统中,提供了timerfd,eventfd,signalfd,加上原先的socket,大部分功能都可以抽象成io事件来处理了。而在libtnet中,这一切的基础就是IOLoop。
类似于tornado,libtnet的IOLoop也提供了相似的接口。其中最核心的就是以下三个:
typedef std::function<void (IOLoop*, int)> IOHandler_t;
int addHandler(int fd, int events, const IOHandler_t& handler);
int updateHandler(int fd, int events);
int removeHandler(int fd);
对于任意的IO,我们可以注册感兴趣的事件(TNET_READ和TNET_WRITE),并绑定一个对应的callback回调。
callback的回调采用的是std::function的方式,这也就意味着,你可以在外部通过std::bind绑定任意不同的实现,再加上shared_ptr技术模拟闭包。
假设现在我们需要创建了一个socket对象,并将其添加到IOLoop中,我们可以这么做:
std::shared_ptr<Connection> conn = std::make_shared<Connection>(socketfd);
ioloop->addHandler(socketfd, TNET_READ, std::bind(&Connection::onHandler, conn, _1, _2));
这样,当该socket有读事件发生的时候,对应的onHandler就会被调用。在这里,我是用了shared_ptr技术,主要是为了方便进行对象生命周期的管理。
在上面的例子中,因为std::bind的时候引用了conn,只要不将socketfd进行removeHandler,conn对象就会一直存在。所以libtnet在IOLoop内部,自行维护了conn对象的生命周期。外面不需要在将其保存到另一个地方(如果真保存了该shared_ptr的conn,反而会引起内存泄露)。在libtnet的基础模块中,我都使用的是weak_ptr来保存相关对象,每次使用都通过lock来判定是否该对象存活。
在IOLoop内部,我使用一个vector来存放注册的handler,vector的索引就是io的fd。这样,我们通过io的fd就可以非常快速的查找到对应的handler了。为什么可以这样设计,是因为在linux系统中,进程中新建文件的file descriptor都是系统当前最小的可用整数。譬如,我创建了一个socket,fd为10,然后我关闭了该socket,再次新建一个socket,这时候新的socket的fd仍然为最小可用的整数,也就是10。
EPoll
提到linux下面的高性能网络编程,epoll是一个铁定绕不开的话题,关于epoll的使用,网上有太多的讲解,这里就不展开了。
libtnet在Poller中集成了epoll,参考了libev的实现。epoll有两种工作模式,水平触发和边沿触发,各有利弊。libtnet使用的是水平触发方式,主要原因在于水平触发方式在有消息但是没处理的时候会一直通知你处理,实现起来不容易出错,也比较简单。
fork and epoll_create
这里顺便记录一下我在实现prefork模型的时候遇到的一个坑。这个问题就是epoll fd应该在fork之前还是之后创建?
大家都知道,linux fork的时候采用COW(copy on write)方式复制父进程的内容,然后我想当然的以为各个子进程会拥有独立的epoll内核空间,于是在fork之前创建了epoll fd。但是后面我却惊奇的发现一个子进程对epoll的操作竟然会影响另一个子进程。也就是说,各个子进程共享了父进程的epoll内核空间。
所以,epoll fd的创建应该在fork之后,各个子进程独立创建。
Example
Timer
IOLoop提供了一个简单的runAfter函数,用以实现定时器功能,使用非常简单:
void func(IOLoop* loop)
{
    cout << "hello world" << endl;
    loop->stop();
}
IOLoop loop;
loop.runAfter(10 * 1000, std::bind(&func, &loop));
loop.start();
loop启动十秒之后,会打印hello world,然后整个loop退出。更多定制化的timer使用,可以使用libtnet提供的Timer class。
Callback
libtnet是一个单线程单ioloop的模型,但是不排除仍然会有其他线程希望与IOLoop进行通信,所以IOLoop提供了addCallback功能,这是libtnet唯一一个线程安全的函数。因为加入callback是一个很快速的操作,IOLoop使用了spinlock。在IOLoop每次循环的末尾,会将全部的callback取出,依次执行。
void callback(IOLoop* loop)
{
    cout << "tell to exit" << endl;
    loop->stop();
}
IOLoop loop;
loop.addCallback(std::bind(&func, &loop));
loop.start();
最后,再次列出libtnet的地址https://github.com/siddontang/libtnet,欢迎围观。
												
											高性能C++网络库libtnet实现:IOLoop的更多相关文章
- 高性能C++网络库libtnet实现:Connection
		
Connection libtnet只支持IPv4 TCP Connection,之所以这么做都是为了使得实现尽可能的简单.我们主要在Connection类中封装了对tcp连接的操作. Connect ...
 - 高性能C++网络库libtnet实现:http
		
HTTP libtnet提供了简单的http支持,使用也很简单. 一个简单的http server: void onHandler(const HttpConnectionPtr_t& con ...
 - 高性能C++网络库libtnet实践:comet单机百万连接挂载测试
		
最近在用go语言做一个挂载大量长连接的推送服务器,虽然已经完成,但是内存占用情况让我不怎么满意,于是考虑使用libtnet来重新实现一个.后续我会使用comet来表明推送服务器. 对于comet来说, ...
 - 发布一个参考tornado的高性能c++网络库:libtnet
		
libtnet是一个用c++编写的高性能网络库,它在设计上面主要参考tornado,为服务端网络编程提供简洁而高效的接口,非常易于使用. Echo Server void onConnEvent(co ...
 - 专注于HTTP的高性能高易用性网络库:Fslib.network库
		
博客列表页:http://blog.fishlee.net/tag/fslib-network/ 原创FSLib.Network库(目前专注于HTTP的高性能高易用性网络库) FSLib.Networ ...
 - [开源] gnet: 一个轻量级且高性能的 Golang 网络库
		
Github 主页 https://github.com/panjf2000/gnet 欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦. 简介 gnet 是一个基于 Ev ...
 - Cowboy 开源 WebSocket 网络库
		
Cowboy.WebSockets 是一个托管在 GitHub 上的基于 .NET/C# 实现的开源 WebSocket 网络库,其完整的实现了 RFC 6455 (The WebSocket Pro ...
 - 开源免费的C/C++网络库(c/c++ sockets library)
		
(1)ACE 庞大.复杂,适合大型项目.开源.免费,不依赖第三方库,支持跨平台. http://www.cs.wustl.edu/~schmidt/ACE.html (2)Asio Asio基于Boo ...
 - poco网络库分析,教你如何学习使用开源库
		
Poco::Net库中有 FTPClient HTML HTTP HTTPClient HTTPServer ICMP Logging Mail Messages NetCore NTP OAuth ...
 
随机推荐
- Java并发编程(一)-为什么要并发
			
并发所带来的好处 1. 并发在某些情况(并不是所有情况)下可以带来性能上的提升 1) 提升对CPU的使用效率 提升多核CPU的利用率:一般来说一台主机上的会有多个CPU核心,我们可以创建多个线程,理论 ...
 - CRM客户关系管理系统(五)
			
第五章.分页功能开发 5.1.修改BaseKingAdmin和完善前段页面显示 现在访问没有注册的model会报错,因为基类中没有写list_display和list_filter. 在基类中设置一个 ...
 - 代理delegate
			
1>代理的用处是什么? 监听那些不能通过addTarget监听的事件 主要用开负责在两个对象之间,发生某些事件时,传递或发送消息 当我们需要 监听某些事件时,但苹果没有提供相关监听方法(addt ...
 - Spring异常之版本错误
			
今天开始一个新项目的开发,结果使用maven刚刚部署完环境,一启动立马报错 java.lang.NoSuchMethodError: org.springframework.util.ClassUti ...
 - Node.js HTTP
			
稳定性: 3 - 稳定 使用 HTTP 服务器或客户端功能必须调用 require('http'). Node 里的 HTTP 接口支持协议里原本比较难用的特性.特别是很大的或块编码的消息.这些接口不 ...
 - MySQL 数学函数
			
MySQL 数学函数 所有的数学函数在发生错误的情况下,均返回 NULL. -元减.改变参数的符号 mysql> SELECT - 2; -> -2 注意,如果这个操作符被用于一个 BIG ...
 - ngx.re.match
			
ngx.re.match syntax: captures, err = ngx.re.match(subject, regex, options?, ctx?, res_table?) contex ...
 - WCF Restful调用跨域解决方案
			
目前很多项目中CRM更多扮演一个纯后台管理系统,用户更多的操作是在移动端执行,不管是安卓还是IOS甚至是H5.这里以H5为例,CRM提供数据接口,移动web端来调用接口进行数据处理,这里就会涉及到一个 ...
 - 【SSH系列】深入浅出SpringMvc+入门Demo
			
Spring MVC框架是有一个MVC框架,通过实现Model-View-Controller模式来很好地将数据.业务与展现进行分离.从这样一个角度来说,Spring MVC和Struts.Strut ...
 - 【SSH系列】Hibernate映射 -- 一对多关联映射
			
映射原理 一对多关联映射和多对一关联映射的映射原理是一样一样的,所以说嘛,知识都是相通的,一通百通,为什么说一对多关联映射和多对一关联映射是一样的呢?因为她们都是在多的一端加入一个 ...