慢慢一点一点看看Boost,这段时间就Asio库吧。
据说这货和libevent的效率差不多,但是Boost的平台兼容性,你懂得。还有它帮忙干掉了很多线程安全和线程分发的事情。
Boost.Asio 依赖项:
- Boost.System (所以它必须链接boost_system)
- [可选] 如果使用read_until() or async_read_until() 函数,则依赖Boost.Regex(boost_regex)
- [可选] SSL功能依赖OpenSSL
先来个简单的,系统信号量 Signal控制:
使用ASIO操作信号量有一个注意事项,不允许再使用其他库或工具管理信号量(如signal() 或 sigaction()函数)
#include
#include
#include
#include
#include
#include
#include
#include
#include
void signal_handler(
boost::asio::signal_set& stSigs,
const boost::system::error_code& error,
int signal_number)
{
if (error)
{
std::cout<< error.message()<< std::endl;
}
std::cout<< "Catch a signal: "<< signal_number<< std::endl;
stSigs.async_wait(boost::bind(signal_handler, boost::ref(stSigs), _1, _2));
}
void signal_handler2(
boost::asio::signal_set& stSigs,
const boost::system::error_code& error,
int signal_number)
{
if (error)
{
std::cout<< error.message()<< std::endl;
}
std::cout<< "Catch a signal - handle 2: "<< signal_number<< std::endl;
stSigs.async_wait(boost::bind(signal_handler2, boost::ref(stSigs), _1, _2));
}
int main(int argc, char* argv[]) {
boost::asio::io_service stMainService;
boost::asio::signal_set stSigs(stMainService);
stSigs.add(SIGINT);
stSigs.add(SIGTERM);
stSigs.async_wait(boost::bind(signal_handler, boost::ref(stSigs), _1, _2));
stSigs.async_wait(boost::bind(signal_handler2, boost::ref(stSigs), _1, _2));
stMainService.run();
return 0;
}
So easy吧,可以一次注册多个handler。
需要注意的是每次触发signal的handler后,handler就被取消了,需要重新注册一次。否则下一次就不会跳到这个handler了
第二个尝试,网络IO:
按照文档描述,除非使用宏来禁止功能,网络IO在不同的环境下采用了不同的实现方式:
- Windows: IOCP
- Linux: epoll
- Mac OS X, FreeBSD, NetBSD, OpenBSD: kqueue
- Solaris: /dev/poll
Boost.Asio的接口是仿IOCP的异步IO的形式的(参见:http://www.cnblogs.com/hello-leo/archive/2011/04/12/leo.html),所以,其他环境下都是模拟的。
#include
#include
#include
#include
#include
#include
#include
#include
这个搞起来相当费劲,经常需要跳转到Boost的源码中,查看一些回调函数的定义式。
write和write_some函数在completion_condition返回0时才发送,否则将数据加入到发送窗口,并且没有发生数据拷贝,也就是说,如果是异步操作,开发者必须保证发送时数据有效。(这类函数默认的completion_condition是仿函数transfer_all,返回default_max_transfer_size: 65536)
同样,read和read_some也是这样。
Send和receive函数才是立即执行的(不推荐使用)。
另外,streambuf流用于管理发送或接收缓冲,但是在发送或接收完后,要执行consume函数移出或commit移入缓冲区,否则数据不会被销毁。
UDP和TCP的类似,我就不再多写一个demo了。
以上sample的client和server的读数据采用了两种不同的方式
有一点比较爽,在多线程条件下 io_service的run函数是线程安全的,也就是说,多个线程调用同一个run的时候,就自动被加入工作线程池,在消息到来的时候io_service会找到一个可用的线程进行处理。
注:以上代码Visual Studio中需要包含Boost的include目录和lib目录;GCC或Clang中需要加编译选项-I[BOOST_PREFIX目录]/include –L[BOOST_PREFIX目录]/lib -lboost_random -lboost_thread -lboost_system -lstdc++ -lrt -pthread -D_POSIX_MT_, 如果BOOST不在系统动态库查找目录中且同时存在Boost的动态和静态库则加上 -static 选项
另外,这个demo代码包含一些功能检测的代码。
小试牛的第三刀,定时器Timer:
还是喜欢上代码,so…
#include
#include
#include
#include
#include
#include
#include
#include
话说Boost.Asio每次异步wait回调之后还要重新wait一下挺麻烦的
额外功能:
设备文件支持
boost::asio::serial_port 可以打开一个Unix设备文件,并作为输入输出流,然后可以用自由函数boost::asio::read(),boost::asio::async_read(),boost::asio::write(),boost::asio::async_write(),boost::asio::read_until() 和 boost::asio::async_read_until()操作这些文件
在Windows上,需要系统支持I/O completion port时才能使用,可以通过BOOST_ASIO_HAS_SERIAL_PORTS 这个宏来检测是否可用这个功能(如果定义了则可用)。
Unix系统功能
第一项是Unix Domain Sockets
这种socket可以通过
local::datagram_protocol::socket socket1(my_io_service);
local::datagram_protocol::socket socket2(my_io_service);
local::connect_pair(socket1, socket2);
//或者
//服务端
local::stream_protocol::endpoint ep("/tmp/foobar");
local::stream_protocol::acceptor acceptor(my_io_service, ep);
local::stream_protocol::socket socket(my_io_service);
acceptor.accept(socket);
//客户端
local::stream_protocol::endpoint ep("/tmp/foobar");
local::stream_protocol::socket socket(my_io_service);
socket.connect(ep);
来使用,其他的部分和普通的Socket一样
第二项是指向流的文件描述符
posix::stream_descriptor in(my_io_service, ::dup(STDIN_FILENO));
posix::stream_descriptor out(my_io_service, ::dup(STDOUT_FILENO));
创建出的descriptor可以用asio的自由函数的读写函数操作
第三项是fork支持通过notify_fork函数来重建内部描述符
SSL支持
这部分依赖OpenSSL,简单的说,就是在socket外面包了一层,然后操作带ssl的socket。
大概就是
typedef ssl::stream ssl_socket;
然后用ssl_socket代替tcp::socket
简要性能测试
- 测试机器: CPU Intel(R) Xeon(R) CPU X3440 @ 2.53GHz × 8 , 内存 8096MB
- 测试环境: Linux 2.6.32.43, GCC 4.8.0, 编译优化配置 -O2 -g -ggdb
- 程序配置: 16 工作进程,1主进程,主线程参与逻辑
- 测试结果: 5000-8000连接,每秒收到约320K个报文,7MB流量,每秒发送约320K个报文,12MB流量, CPU 负载: 180%(5000连接) – 195% (8000连接)
结论: 不知道为什么,压力再也上不去了, 我是把输出重定向到文件的,所以开始以为是磁盘IO爆了,写出日志次数太多,但是gperftools显示磁盘IO占用并不高,性能分布还是比较平均的。但是基本上就在16万个报文了(每个包有一次发送长度的包[4字节]和一次数据的send[不定长])
测试代码地址: https://gist.github.com/owent/5660983
profile性能分析报告: http://api.owent.net/resource/doc/link/test.asio.pdf
主要就是这个样子,一些更底层的东西比如srand可以以后再看( * ^ _ ^ * )