前言 经过上一篇文章《boost入门:boost简介》,我们编译了boost库。下面我们学习下Asio。
Asio是不需要编译成lib文件,直接在程序中引入头文件即可。Asio依赖的库比较多,我们可以看下它的依赖:
Boost.System(必须)
Boost.Coroutine(可选),如果用spawn()启动协程就需要它
Boost.Regex(可选),如果使用带参数的read_until()或async_read_until()重载boost::regex
OpenSSL(可选),如果你使用Boost.Asio的SSL支持
Boost.Thread
Boost.Date_Time
Boost.Serialization
是不是很多,上面我们编译整个boost的时候已经生成了绝大多数,只剩下一个OpenSSL。
OpenSSL是不是编译很让人头疼,有个网址可以提供编译好的lib文件和dll文件。我也是偶尔间发现的,请看网址:Win32OpenSSL
上面是OpenSSL64位的安装包,下面是OpenSSL 32位的安装包。带Light是精简版本,建议下载不带Light的版本。下面是我本地电脑的安装目录:
lib文件存放着可链接的lib,我一般用静态链接:
运行示例,测试环境 我们演示两种,一种是自动链接到其他boost库的,比如:Boost.Coroutine ,一种需要链接OpenSSL。
首先我们先链接Boost.Coroutine ,无需再项目上添加链接库,直接将以下代码拷进项目里面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 #include <boost/asio/io_context.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/spawn.hpp> #include <boost/asio/steady_timer.hpp> #include <boost/asio/write.hpp> #include <iostream> #include <memory> using boost::asio::ip::tcp;class session : public std ::enable_shared_from_this<session>{ public : explicit session (boost::asio::io_context& io_context, tcp::socket socket) : socket_(std::move(socket)), timer_(io_context), strand_(io_context.get_executor()) { } void go () { auto self (shared_from_this()) ; boost::asio::spawn(strand_, [this , self](boost::asio::yield_context yield) { try { char data[128 ]; for (;;) { timer_.expires_from_now(std ::chrono::seconds(10 )); std ::size_t n = socket_.async_read_some(boost::asio::buffer(data), yield); boost::asio::async_write(socket_, boost::asio::buffer(data, n), yield); } } catch (std ::exception& e) { socket_.close(); timer_.cancel(); } }); boost::asio::spawn(strand_, [this , self](boost::asio::yield_context yield) { while (socket_.is_open()) { boost::system::error_code ignored_ec; timer_.async_wait(yield[ignored_ec]); if (timer_.expires_from_now() <= std ::chrono::seconds(0 )) socket_.close(); } }); } private : tcp::socket socket_; boost::asio::steady_timer timer_; boost::asio::strand<boost::asio::io_context::executor_type> strand_; }; int main (int argc, char * argv[]) { try { if (argc != 2 ) { std ::cerr << "Usage: echo_server <port>\n" ; return 1 ; } boost::asio::io_context io_context; boost::asio::spawn(io_context, [&](boost::asio::yield_context yield) { tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), std ::atoi(argv[1 ]))); for (;;) { boost::system::error_code ec; tcp::socket socket(io_context); acceptor.async_accept(socket, yield[ec]); if (!ec) { std ::make_shared<session>(io_context, std ::move(socket))->go(); } } }); io_context.run(); } catch (std ::exception& e) { std ::cerr << "Exception: " << e.what() << "\n" ; } return 0 ; }
可以看到编译成功!
我们再尝试链接OpenSSL ,首先在项目中设置openSSL的头文件目录:
设置链接库目录路径:
设置链接库:
然后拷贝代码到cpp文件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 #include <cstdlib> #include <cstring> #include <functional> #include <iostream> #include <boost/asio.hpp> #include <boost/asio/ssl.hpp> using boost::asio::ip::tcp;using std ::placeholders::_1;using std ::placeholders::_2;enum { max_length = 1024 };class client { public : client(boost::asio::io_context& io_context, boost::asio::ssl::context& context, const tcp::resolver::results_type& endpoints) : socket_(io_context, context) { socket_.set_verify_mode(boost::asio::ssl::verify_peer); socket_.set_verify_callback( std ::bind(&client::verify_certificate, this , _1, _2)); connect(endpoints); } private : bool verify_certificate (bool preverified, boost::asio::ssl::verify_context& ctx) { char subject_name[256 ]; X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256 ); std ::cout << "Verifying " << subject_name << "\n" ; return preverified; } void connect (const tcp::resolver::results_type& endpoints) { boost::asio::async_connect(socket_.lowest_layer(), endpoints, [this ](const boost::system::error_code& error, const tcp::endpoint& ) { if (!error) { handshake(); } else { std ::cout << "Connect failed: " << error.message() << "\n" ; } }); } void handshake () { socket_.async_handshake(boost::asio::ssl::stream_base::client, [this ](const boost::system::error_code& error) { if (!error) { send_request(); } else { std ::cout << "Handshake failed: " << error.message() << "\n" ; } }); } void send_request () { std ::cout << "Enter message: " ; std ::cin .getline(request_, max_length); size_t request_length = std ::strlen (request_); boost::asio::async_write(socket_, boost::asio::buffer(request_, request_length), [this ](const boost::system::error_code& error, std ::size_t length) { if (!error) { receive_response(length); } else { std ::cout << "Write failed: " << error.message() << "\n" ; } }); } void receive_response (std ::size_t length) { boost::asio::async_read(socket_, boost::asio::buffer(reply_, length), [this ](const boost::system::error_code& error, std ::size_t length) { if (!error) { std ::cout << "Reply: " ; std ::cout .write(reply_, length); std ::cout << "\n" ; } else { std ::cout << "Read failed: " << error.message() << "\n" ; } }); } boost::asio::ssl::stream<tcp::socket> socket_; char request_[max_length]; char reply_[max_length]; }; int main (int argc, char * argv[]) { try { if (argc != 3 ) { std ::cerr << "Usage: client <host> <port>\n" ; return 1 ; } boost::asio::io_context io_context; tcp::resolver resolver (io_context) ; auto endpoints = resolver.resolve(argv[1 ], argv[2 ]); boost::asio::ssl::context ctx (boost::asio::ssl::context::sslv23) ; ctx.load_verify_file("ca.pem" ); client c (io_context, ctx, endpoints) ; io_context.run(); } catch (std ::exception& e) { std ::cerr << "Exception: " << e.what() << "\n" ; } return 0 ; }
编译该项目,下图显示编译成功:
这样完成了我们对环境的检测,说明Asio的环境没有问题。下面我们就接着去了解Asio库。
Boost.Asio库介绍 官网说的比较抽象:
Most programs interact with the outside world in some way, whether it be via a file, a network, a serial cable, or the console. Sometimes, as is the case with networking, individual I/O operations can take a long time to complete. This poses particular challenges to application development.
Boost.Asio provides the tools to manage these long running operations, without requiring programs to use concurrency models based on threads and explicit locking.
我的理解是Asio是一个 I\O 库,I\O 通常指数据的输入和输出。
网络也可以当做一个输入与输出设备,Asio的重点在于网络,但是也可以用在串行端口、文件描述符等。
Linux提出一切皆文件,那么我们也可以认为一切皆I\O 。(当然没有输入和输出,那就不是了)
Asio支持 I\O 上进行同步和异步操作。
Asio同步的原理 首先看图:
由图可以看出Asio的同步比较简单:
程序调用 I\O 对象 I\O 对象调用 I\O 执行上下文I\O 对象调用操作系统操作系统把结果返回给 I\O 执行上下文 I\O 执行上下文如果有错误信息就会处理错误信息,然后把结果返回给 I\O 对象I\O对象会接收执行结果,如果有错误返回就会接收错误,并且不会有异常抛出 下面用代码介绍下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <boost/asio.hpp> int main () { boost::asio::io_context io; boost::asio::steady_timer t (io, boost::asio::chrono::seconds(5 )) ; t.wait(); std ::cout << "Hello, world!" << std ::endl ; return 0 ; }
Asio异步的原理 还是老样子,看图:
由图可以看出:
程序调用I\O对象
I\O对象调用I\O执行上下文
I\O对象向操作系统发出信号,表示异步连接
操作系统完成该操作后,会把结果放在队列中
程序必须调用检索函数,来触发I\O上下文在队列中拾取结果
I\O执行上下文拾取结果会使操作结果出队,将结果传给回调函数并调用回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <boost/asio.hpp> void print (const boost::system::error_code& ) { std ::cout << "Hello, world!" << std ::endl ; } int main () { boost::asio::io_context io; boost::asio::steady_timer t (io, boost::asio::chrono::seconds(5 )) ; t.async_wait(&print); std ::cout << "wait ..." << std ::endl ; io.run(); std ::cout << "over ..." << std ::endl ; return 0 ; }
如果我想要向回调函数传参呢?可以使用boost :: bind方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> #include <boost/asio.hpp> #include <boost/bind/bind.hpp> void print (const boost::system::error_code& , boost::asio::steady_timer* t, int * count) { if (*count < 5 ) { std ::cout << *count << std ::endl ; ++(*count); t->expires_at(t->expiry() + boost::asio::chrono::seconds(1 )); t->async_wait(boost::bind(print, boost::asio::placeholders::error, t, count)); } } int main () { boost::asio::io_context io; int count = 0 ; boost::asio::steady_timer t (io, boost::asio::chrono::seconds(1 )) ; t.async_wait(boost::bind(print, boost::asio::placeholders::error, &t, &count)); io.run(); std ::cout << "Final count is " << count << std ::endl ; return 0 ; }
结尾 此篇简单介绍了Asio,相信对Asio有了一点印象。接下来让我们继续深入Asio!