diff --git a/README.md b/README.md index 6bc85ad..d476cc4 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -#实战出精华 +# 实战出精华 *在具体的C++网络编程中提升你的逼格* *John Torjo* --- -####Boost.Asio C++ 网络编程 +#### Boost.Asio C++ 网络编程 Copyright © 2013 Packt Publishing --- -##关于作者 +## 关于作者 做为权威的C++专家,除了偶尔用C#和Java写程序,**John Torjo**把他超过15年编程生涯中的大部分时间都贡献给了C++。 他也很喜欢在C++ Users Journal和其他杂志上写一些编程相关的文章。 @@ -24,7 +24,7 @@ Copyright © 2013 Packt Publishing 把这本书献给我最好的朋友Darius。 --- -##关于评审员 +## 关于评审员 Béla Tibor Bartha 一个使用过多种技术和语言进行开发的专业软件工程师。尽管在过去的4年里,他做的是iOS和OSX应用开发,但是C++陪伴他度过了早期个人游戏项目开发的激情岁月。 @@ -41,18 +41,18 @@ Nicolae Ghimbovschi 我要感谢John让我来评审这本书 --- -##关于译者 +## 关于译者 非主流程序猿mmoaay,技术很烂,喜欢平面设计、鼠绘、交友、运动和翻译,但是确作为一只程序猿混迹在IT行业。热爱开源,技术烂就只好做做设计和翻译的工作。 微博:[http://weibo.com/smmoaay](http://weibo.com/smmoaay) --- -##关于avplayer +## 关于avplayer [http://avplayer.org](http://avplayer.org) 中国第一技术社区。 --- -##目录 +## 目录 --- 前言 @@ -170,7 +170,7 @@ Nicolae Ghimbovschi 索引 --- -##前言 +## 前言 网络编程由来已久,并且是一个极富挑战性的任务。Boost.Asio对网络编程做了一个极好的抽象,从而保证只需要少量的编程就可以实现一个优雅的客户端/服务端软件。在实现的过程中,它能让你体会到极大的乐趣。而且更为有益的是:Boost.Asio包含了一些非网络的特性,用Boost.Asio写出来的代码紧凑、易读,而且如果你按照我在书中所讲的来做,你的代码会无懈可击。 这本书涵盖了什么? @@ -189,17 +189,17 @@ Nicolae Ghimbovschi *第七章:Boost.Asio进阶*包含了一些Boost.Asio进阶问题的处理。虽然在日常编程中不需要深入研究它们,但是了解它们对你有益无害(Boost.Asio高级调试,SSL,Windows特性,POSIX特性等)。 -###读这本书你需要准备什么? +### 读这本书你需要准备什么? 如果要编译Boost.Asio以及运行本书中的例子,你需要一个现代编译器。例如,Visual Studio 2008及其以上版本或者g++ 4.4及其以上版本 -###这本书是为谁准备的? +### 这本书是为谁准备的? 这本书对于那些需要进行网络编程却不想深入研究复杂的原始网络API的开发者来说是一个福音。所有你需要的只是Boost.Asio提供的一套API。作为著名Boost C++库的一部分,你只需要额外添加几个#include文件即可转换到Boost.Asio。 在读这本书之前,你需要熟悉Boost核心库的一些知识,例如Boost智能指针、boost::noncopyable、Boost Functors、Boost Bind、shared_ from_this/enabled_shared_from_this和Boost线程(线程和互斥量)。同时还需要了解Boost的Date/Time。读者还需要知道阻塞的概念以及“非阻塞”操作。 -###约定 +### 约定 本书使用不同样式的文字来区分不同种类的信息。这里给出这些样式的例子以及它们的解释。 @@ -224,7 +224,7 @@ async_write(stream, buffer [, extra options], handler) [*?技巧在这样的一个框里面*] -###读者反馈 +### 读者反馈 我们欢迎来自读者的反馈。告诉我们你对这本书的看法——你喜欢哪部分,不喜欢哪部分。读者的反馈对我们非常重要,它能让我们写出对读者帮助更大的书。 @@ -232,19 +232,19 @@ async_write(stream, buffer [, extra options], handler) 如果你有一个擅长的专题,想撰写一本书或者为某本书做贡献。请阅读我们在[www.packtpub.com/authors](www.packtpub.com/authors)上的作者指引。 -###用户支持 +### 用户支持 现在你已经是Packt书籍的拥有者,我们将告诉你一些事项,让你购买本书得到的收益最大化。 -###下载示例代码 +### 下载示例代码 你可以在[http://www.packtpub.com](http://www.packtpub.com)登录你的帐号,然后下载你所购买的书籍的全部示例代码。同时,你也可以通过访问[http://www.packtpub.com/support](http://www.packtpub.com/support)进行注册,然后这些示例代码文件将直接发送到你的邮箱。 -###纠错 +### 纠错 尽管我们已经尽最大的努力去保证书中内容的准确性,但是错误还是不可避免的。如果你在我们的书籍中发现了错误——也许是文字,也许是代码——如果你能将它们报告给我们,我们将不胜感激。这样的话,你不仅能帮助其他读者,同时也能帮助我们改进这本书的下一个版本。如果你发现任何需要纠正的地方,访问[http://www.packtpub.com/submit-errata](http://www.packtpub.com/submit-errata),选择你的书籍,点击**errata submission form**链接,然后输入详细的纠错信息来将错误报告给我们。一经确定,你的提交就会通过,然后这个纠错就会被上传到我们的网站,或者添加到那本书的纠错信息区域的纠错列表中。所有已发现的纠错都可以访问[http://www.packtpub.com/support](http://www.packtpub.com/support),然后通过选择书名的方式来查看。 -###答疑 +### 答疑 如果你有关于本书任何方面的问题,你可以通过[questions@packtpub.com](questions@packtpub.com)联系我们。我们将尽我们最大的努力进行解答 @@ -280,7 +280,7 @@ Boost.Asio在2003被开发出来,然后于2005年的12月引入到Boost 1.35 * MAC OS X 10.4以上下使用g++ 3.3及以上 它也可能能在诸如AIX 5.3,HP-UX 11i v3,QNX Neutrino 6.3,Solaris下使用Sun Studio 11以上,True64 v5.1,Windows下使用Borland C++ 5.9.2以上等平台上使用。(更多细节请咨询[www.boost.org](www.boost.org)) -###依赖 +### 依赖 Boost.Asio依赖于如下的库: * **Boost.System**:这个库为Boost库提供操作系统支持([http://www.boost.org/doc/libs/1_51_0/doc/html/boost_system/index.html](http://www.boost.org/doc/libs/1_51_0/doc/html/boost_system/index.html)) @@ -288,7 +288,7 @@ Boost.Asio依赖于如下的库: * **Boost.DateTime**:使用这个库(可选的)以便你使用Boost.Asio中的计时器 * **OpenSSL**:使用这个库(可选的)以便你使用Boost.Asio提供的SSL支持。 -###编译Boost.Asio +### 编译Boost.Asio Boost.Asio是一个只需要引入头文件就可以使用的库。然而,考虑到你的编译器和程序的大小,你可以选择用源文件的方式来编译Boost.Asio。如果你想要这么做以减少编译时间,有如下几种方式: 在某个源文件中,添加*#include "boost/asio/impl/src.hpp"*(如果你在使用SSL,添加*#include "boost/asio/ssl/impl/src.hpp"*) @@ -313,7 +313,7 @@ Boost.Asio是一个只需要引入头文件就可以使用的库。然而,考 因为中途做改变会非常困难而且容易出错,所以你在项目初期(最好是一开始)就得决定用同步还是异步的方式实现网络通信。不仅API有极大的不同,你程序的语意也会完全改变(异步网络通信通常比同步网络通信更加难以测试和调试)。你需要考虑是采用阻塞调用和多线程的方式(同步,通常比较简单),或者是更少的线程和事件驱动(异步,通常更复杂)。 下面是一个基础的同步客户端例子: -``` +```cpp using boost::asio; io_service service; ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 2001); @@ -323,7 +323,7 @@ sock.connect(ep); 首先,你的程序至少需要一个*io_service*实例。Boost.Asio使用*io_service*同操作系统的输入/输出服务进行交互。通常一个*io_service*的实例就足够了。然后,创建你想要连接的地址和端口,再建立socket。把socket连接到你创建的地址和端口。 下面是一个简单的使用Boost.Asio的服务端: -``` +```cpp typedef boost::shared_ptr socket_ptr; io_service service; ip::tcp::endpoint ep( ip::tcp::v4(), 2001)); // listen on 2001 @@ -347,7 +347,7 @@ void client_session(socket_ptr sock) { *在client_session*线程中来读取一个客户端的请求,进行解析,然后返回结果。 而创建一个异步的客户端,你需要做如下的事情: -``` +```cpp using boost::asio; io_service service; ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 2001); @@ -367,7 +367,7 @@ void connect_handler(const boost::system::error_code & ec) { 注意:只要还有待处理的异步操作,*servece.run()*循环就会一直运行。在上述例子中,只执行了一个这样的操作,就是socket的*async_connect*。在这之后,*service.run()*就退出了。 每一个异步操作都有一个完成处理程序——一个操作完成之后被调用的函数。 下面的代码是一个基本的异步服务端 -``` +```cpp using boost::asio; typedef boost::shared_ptr socket_ptr; io_service service; @@ -394,7 +394,7 @@ err) { 在使用这个socket之后,你创建了一个新的socket,然后再次调用*start_accept()*,用来创建另外一个“等待客户端连接”的异步操作,从而使*service.run()*循环一直保持忙碌状态。 ### 异常处理VS错误代码 Boost.Asio允许同时使用异常处理或者错误代码,所有的异步函数都有抛出错误和返回错误码两种方式的重载。当函数抛出错误时,它通常抛出*boost::system::system_error*的错误。 -``` +```cpp using boost::asio; ip::tcp::endpoint ep; ip::tcp::socket sock(service); @@ -405,7 +405,7 @@ sock.connect(ep, err); // 第二行 在前面的代码中,*sock.connect(ep)*会抛出错误,*sock.connect(ep, err)*则会返回一个错误码。 看一下下面的代码片段: -``` +```cpp try { sock.connect(ep); } catch(boost::system::system_error e) { @@ -413,7 +413,7 @@ try { } ``` 下面的代码片段和前面的是一样的: -``` +```cpp boost::system::error_code err; sock.connect(ep, err); if ( err) @@ -422,7 +422,7 @@ if ( err) 当使用异步函数时,你可以在你的回调函数里面检查其返回的错误码。异步函数从来不抛出异常,因为这样做毫无意义。那谁会捕获到它呢? 在你的异步函数中,你可以使用异常处理或者错误码(随心所欲),但要保持一致性。同时使用这两种方式会导致问题,大部分时候是崩溃(当你不小心出错,忘记去处理一个抛出来的异常时)。如果你的代码很复杂(调用很多socket读写函数),你最好选择异常处理的方式,把你的读写包含在一个函数*try {} catch*块里面。 -``` +```cpp void client_session(socket_ptr sock) { try { ... @@ -432,14 +432,14 @@ void client_session(socket_ptr sock) { } ``` 如果使用错误码,你可以使用下面的代码片段很好地检测连接是何时关闭的: -``` +```cpp char data[512]; boost::system::error_code error; size_t length = sock.read_some(buffer(data), error); if (error == error::eof) return; // 连接关闭 ``` -Boost.Asio的所有错误码都包含在ˆ的命名空间中(以便你创造一个大型的switch来检查错误的原因)。如果想要了解更多的细节,请参照*boost/asio/error.hpp*头文件 +Boost.Asio的所有错误码都包含在的命名空间boost::asio::error中(以便你创造一个大型的switch来检查错误的原因)。如果想要了解更多的细节,请参照*boost/asio/error.hpp*头文件 ### Boost.Asio中的线程 当说到Boost.Asio的线程时,我们经常在讨论: @@ -452,7 +452,7 @@ Boost.Asio的所有错误码都包含在ˆ的命名空间中(以便你创造 除了网络通信,Boost.Asio还包含了其他的I/O功能。 Boost.Asio支持信号量,比如*SIGTERM*(软件终止)、*SIGINT*(中断信号)、*SIGSEGV*(段错误)等等。 你可以创建一个*signal_set*实例,指定异步等待的信号量,然后当这些信号量产生时,就会调用你的异步处理程序: -``` +```cpp void signal_handler(const boost::system::error_code & err, int signal) { // 纪录日志,然后退出应用 @@ -464,36 +464,36 @@ sig.async_wait(signal_handler); 如果*SIGINT*产生,你就能在你的*signal_handler*回调中捕获到它。 你可以使用Boost.Asio轻松地连接到一个串行端口。在Windows上端口名称是*COM7*,在POSIX平台上是*/dev/ttyS0*。 -``` +```cpp io_service service; serial_port sp(service, "COM7"); ``` 打开端口后,你就可以使用下面的代码设置一些端口选项,比如端口的波特率、奇偶校验和停止位。 -``` +```cpp serial_port::baud_rate rate(9600); sp.set_option(rate); ``` 打开端口后,你可以把这个串行端口看做一个流,然后基于它使用自由函数对串行端口进行读/写操作。比如*async_read(), write, async_write(),* 就像下面的代码片段: -``` +```cpp char data[512]; read(sp, buffer(data, 512)); ``` Boost.Asio也可以连接到Windows的文件,然后同样使用自由函数,比如*read(), asyn_read()*等等,就像下面的代码片段: -``` +```cpp HANDLE h = ::OpenFile(...); windows::stream_handle sh(service, h); char data[512]; read(h, buffer(data, 512)); ``` 对于POXIS文件描述符,比如管道,标准I/O和各种设备(但不包括普通文件)你也可以这样做,就像下面的代码所做的一样: -``` +```cpp posix::stream_descriptor sd_in(service, ::dup(STDIN_FILENO)); char data[512]; read(sd_in, buffer(data, 512)); ``` ### 计时器 一些I/O操作需要一个超时时间。这只能应用在异步操作上(同步意味着阻塞,因此没有超时时间)。例如,下一条信息必须在100毫秒内从你的同伴那传递给你。 -``` +```cpp bool read = false; void deadline_handler(const boost::system::error_code &) { std::cout << (read ? "read successfully" : "read failed") << std::endl; @@ -513,7 +513,7 @@ service.run(); 在上述代码片段中,如果你在超时之前读完了数据,*read*则被设置成true,这样我们的伙伴就及时地通知了我们。否则,当*deadline_handler*被调用时,*read*还是false,也就意味着我们的操作超时了。 Boost.Asio也支持同步计时器,但是它们通常和一个简单的sleep操作是一样的。*boost::this_thread::sleep(500);*这段代码和下面的代码片段完成了同一件事情: -``` +```cpp deadline_timer t(service, boost::posix_time::milliseconds(500)); t.wait(); ``` @@ -524,7 +524,7 @@ t.wait(); 如果你选择用同步的方式来创建你的应用,你则不需要考虑我将在这一节向你展示的东西。 你有多种不同的方式来使用*io_service*。在下面的例子中,我们有3个异步操作,2个socket连接操作和一个计时器等待操作: * 有一个*io_service*实例和一个处理线程的单线程例子: -``` +```cpp io_service service; // 所有socket操作都由service来处理 ip::tcp::socket sock1(service); // all the socket operations are handled by service ip::tcp::socket sock2(service); sock1.asyncconnect( ep, connect_handler); @@ -535,11 +535,11 @@ service.run(); ``` * 有一个io_service实例和多个处理线程的多线程例子: -``` +```cpp io_service service; ip::tcp::socket sock1(service); ip::tcp::socket sock2(service); -sock1.asyncconnect( ep, connect_handler); +sock1.async_connect( ep, connect_handler); sock2.async_connect( ep, connect_handler); deadline_timer t(service, boost::posixtime::seconds(5)); t.async_wait(timeout_handler); @@ -552,11 +552,11 @@ void run_service() ``` * 有多个*io_service*实例和多个处理线程的多线程例子: -``` +```cpp io_service service[2]; ip::tcp::socket sock1(service[0]); ip::tcp::socket sock2(service[1]); -sock1.asyncconnect( ep, connect_handler); +sock1.async_connect( ep, connect_handler); sock2.async_connect( ep, connect_handler); deadline_timer t(service[0], boost::posixtime::seconds(5)); t.async_wait(timeout_handler); @@ -570,7 +570,7 @@ void run_service(int idx) 首先,要注意你不能拥有多个*io_service*实例却只有一个线程。下面的代码片段没有任何意义: -``` +```cpp for ( int i = 0; i < 2; ++i) service[i].run(); ``` @@ -593,7 +593,7 @@ for ( int i = 0; i < 2; ++i) 最后,需要一直记住的是如果没有其他需要监控的操作,*.run()*就会结束,就像下面的代码片段: -``` +```cpp io_service service; tcp::socket sock(service); sock.async_connect( ep, connect_handler); @@ -603,7 +603,7 @@ service.run(); 在上面的例子中,只要sock建立了一个连接,*connect_handler*就会被调用,然后接着*service.run()*就会完成执行。 如果你想要*service.run()*接着执行,你需要分配更多的工作给它。这里有两个方式来完成这个目标。一种方式是在*connect_handler*中启动另外一个异步操作来分配更多的工作。 另一种方式会模拟一些工作给它,用下面的代码片段: -``` +```cpp typedef boost::shared_ptr work_ptr; work_ptr dummy_work(new io_service::work(service)); ``` @@ -620,13 +620,13 @@ Boost.Asio不仅仅可以用来做网络编程。它还有其他更多的特性 下一章我们将深入研究大量Boost.Asio中用来做网络编程的函数和类。同时我们也会学一些异步编程的诀窍。 -##Boost.Asio基本原理 +## Boost.Asio基本原理 这一章涵盖了使用Boost.Asio时必须知道的一些事情。我们也将深入研究比同步编程更复杂、更有乐趣的异步编程。 -###网络API +### 网络API 这一部分包含了当使用Boost.Asio编写网络应用程序时必须知道的事情。 -###Boost.Asio命名空间 +### Boost.Asio命名空间 Boost.Asio的所有内容都包含在boost::asio命名空间或者其子命名空间内。 * *boost::asio*:这是核心类和函数所在的地方。重要的类有io_service和streambuf。类似*read, read_at, read_until*方法,它们的异步方法,它们的写方法和异步写方法等自由函数也在这里。 @@ -637,11 +637,11 @@ Boost.Asio的所有内容都包含在boost::asio命名空间或者其子命名 * *boost::asio::local*:这个命名空间包含了POSIX特性的类 * *boost::asio::windows*:这个命名空间包含了Windows特性的类 -###IP地址 +### IP地址 对于IP地址的处理,Boost.Asio提供了*ip::address , ip::address_v4*和*ip::address_v6*类。 它们提供了相当多的函数。下面列出了最重要的几个: * *ip::address(v4_or_v6_address)*:这个函数把一个v4或者v6的地址转换成*ip::address* -* *ip::address:from_string(str)*:这个函数根据一个IPv4地址(用.隔开的)或者一个IPv6地址(十六进制表示)创建一个地址。 +* *ip::address::from_string(str)*:这个函数根据一个IPv4地址(用.隔开的)或者一个IPv6地址(十六进制表示)创建一个地址。 * *ip::address::to_string()* :这个函数返回这个地址的字符串。 * *ip::address_v4::broadcast([addr, mask])*:这个函数创建了一个广播地址 *ip::address_v4::any()*:这个函数返回一个能表示任意地址的地址。 @@ -649,22 +649,22 @@ Boost.Asio的所有内容都包含在boost::asio命名空间或者其子命名 * *ip::host_name()*:这个函数用string数据类型返回当前的主机名。 大多数情况你会选择用*ip::address::from_string*: -``` +```cpp ip::address addr = ip::address::from_string("127.0.0.1"); ``` 如果你想通过一个主机名进行连接,下面的代码片段是无用的: -``` +```cpp // 抛出异常 ip::address addr = ip::address::from_string("www.yahoo.com"); ``` -###端点 +### 端点 端点是使用某个端口连接到的一个地址。不同类型的socket有它自己的*endpoint*类,比如*ip::tcp::endpoint、ip::udp::endpoint*和*ip::icmp::endpoint* 如果想连接到本机的80端口,你可以这样做: -``` +```cpp ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80); ``` @@ -674,14 +674,14 @@ ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80); * *endpoint(addr, port)*:这个方法创建了一个连接到某个地址和端口的端点。 例子如下: -``` +```cpp ip::tcp::endpoint ep1; ip::tcp::endpoint ep2(ip::tcp::v4(), 80); ip::tcp::endpoint ep3( ip::address::from_string("127.0.0.1), 80); ``` 如果你想连接到一个主机(不是IP地址),你需要这样做: -``` +```cpp // 输出 "87.248.122.122" io_service service; ip::tcp::resolver resolver(service); @@ -694,12 +694,12 @@ std::cout << ep.address().to_string() << std::endl; 你可以用你需要的socket类型来替换tcp。首先,为你想要查询的名字创建一个查询器,然后用resolve()函数解析它。如果成功,它至少会返回一个入口。你可以利用返回的迭代器,使用第一个入口或者遍历整个列表来拿到全部的入口。 给定一个端点,可以获得他的地址,端口和IP协议(v4或者v6): -``` +```cpp std::cout << ep.address().to_string() << ":" << ep.port() << "/" << ep.protocol() << std::endl; ``` -###套接字 +### 套接字 Boost.Asio有三种类型的套接字类:*ip::tcp, ip::udp*和*ip::icmp*。当然它也是可扩展的,你可以创建自己的socket类,尽管这相当复杂。如果你选择这样做,参照一下*boost/asio/ip/tcp.hpp, boost/asio/ip/udp.hpp*和*boost/asio/ip/icmp.hpp*。它们都是含有内部typedef关键字的超小类。 你可以把*ip::tcp, ip::udp, ip::icmp*类当作占位符;它们可以让你便捷地访问其他类/函数,如下所示: @@ -708,7 +708,7 @@ Boost.Asio有三种类型的套接字类:*ip::tcp, ip::udp*和*ip::icmp*。当 * *ip::icmp::socket, ip::icmp::endpoint, ip::icmp::resolver* *socket*类创建一个相应的*socket*。而且总是在构造的时候传入io_service实例: -``` +```cpp io_service service; ip::udp::socket sock(service) sock.set_option(ip::udp::socket::reuse_address(true)); @@ -719,7 +719,7 @@ sock.set_option(ip::udp::socket::reuse_address(true)); * *ip::udp::socket = basic_datagram_socket* * *ip::icmp::socket = basic_raw_socket* -###同步错误码 +### 同步错误码 所有的同步函数都有抛出异常或者返回错误码的重载,比如下面的代码片段: ``` sync_func( arg1, arg2 ... argN); // 抛出异常 @@ -729,12 +729,12 @@ sync_func( arg1 arg2, ..., argN, ec); // 返回错误码 在这一章剩下的部分,你会见到大量的同步函数。简单起见,我省略了有返回错误码的重载,但是不可否认它们确实是存在的。 -###socket成员方法 +### socket成员方法 这些方法被分成了几组。并不是所有的方法都可以在各个类型的套接字里使用。这个部分的结尾将有一个列表来展示各个方法分别属于哪个socket类。 注意所有的异步方法都立刻返回,而它们相对的同步实现需要操作完成之后才能返回。 -###连接相关的函数 +### 连接相关的函数 这些方法是用来连接或绑定socket、断开socket字连接以及查询连接是活动还是非活动的: * *assign(protocol,socket)*:这个函数分配了一个原生的socket给这个socket实例。当处理老(旧)程序时会使用它(也就是说,原生socket已经被建立了) * *open(protocol)*:这个函数用给定的IP协议(v4或者v6)打开一个socket。你主要在UDP/ICMP socket,或者服务端socket上使用。 @@ -747,7 +747,7 @@ sync_func( arg1 arg2, ..., argN, ec); // 返回错误码 * *cancel()*:这个函数取消套接字上所有的异步操作。这个套接字上任何的异步操作都会立即结束,然后返回*error::operation_aborted*错误码。 例子如下: -``` +```cpp ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80); ip::tcp::socket sock(service); sock.open(ip::tcp::v4()); n @@ -759,7 +759,7 @@ sock.close(); ``` -###读写函数 +### 读写函数 这些是在套接字上执行I/O操作的函数。 对于异步函数来说,处理程序的格式*void handler(const boost::system::error_code& e, size_t bytes)*都是一样的 @@ -770,9 +770,9 @@ sock.close(); * *async_send(buffer [, flags], handler)*:这个函数启动了一个异步发送缓冲区数据的操作。 * *async_write_some(buffer, handler)*:这个函数和a*sync_send(buffer, handler)*功能一致。 * *async_send_to(buffer, endpoint, handler)*:这个函数启动了一个异步send缓冲区数据到指定端点的操作。 -* *receive(buffer [, flags])*:这个函数异步地从所给的缓冲区读取数据。在读完所有数据或者错误出现之前,这个函数都是阻塞的。 +* *receive(buffer [, flags])*:这个函数同步地从所给的缓冲区读取数据。在读完所有数据或者错误出现之前,这个函数都是阻塞的。 * *read_some(buffer)*:这个函数的功能和*receive(buffer)*是一致的。 -* * receive_from(buffer, endpoint [, flags])*:这个函数异步地从一个指定的端点获取数据并写入到给定的缓冲区。在读完所有数据或者错误出现之前,这个函数都是阻塞的。 +* * receive_from(buffer, endpoint [, flags])*:这个函数同步地从一个指定的端点获取数据并写入到给定的缓冲区。在读完所有数据或者错误出现之前,这个函数都是阻塞的。 * *send(buffer [, flags])*:这个函数同步地发送缓冲区的数据。在所有数据发送成功或者出现错误之前,这个函数都是阻塞的。 * *write_some(buffer)*:这个函数和*send(buffer)*的功能一致。 * *send_to(buffer, endpoint [, flags])*:这个函数同步地把缓冲区数据发送到一个指定的端点。在所有数据发送成功或者出现错误之前,这个函数都是阻塞的。 @@ -785,7 +785,7 @@ sock.close(); * *ip::socket_type::socket::message_end_of_record*:这个标记指定的数据标识了记录的结束。在Windows下不支持。 你最常用的可能是*message_peek*,使用方法请参照下面的代码片段: -``` +```cpp char buff[1024]; sock.receive(buffer(buff), ip::tcp::socket::message_peek ); memset(buff,1024, 0); @@ -796,7 +796,7 @@ sock.receive(buffer(buff) ); 下面的是一些教你如何同步或异步地从不同类型的套接字上读取数据的例子: * 例1是在一个TCP套接字上进行同步读写: -``` +```cpp ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80); ip::tcp::socket sock(service); sock.connect(ep); @@ -808,7 +808,7 @@ size_t read = sock.read_some(buffer(buff)); * 例2是在一个UDP套接字上进行同步读写: -``` +```cpp ip::udp::socket sock(service); sock.open(ip::udp::v4()); ip::udp::endpoint receiver_ep("87.248.112.181", 80); @@ -822,7 +822,7 @@ sock.receive_from(buffer(buff), sender_ep); *[?注意:就像上述代码片段所展示的那样,使用receive_from从一个UDP套接字读取数据时,你需要构造一个默认的端点]* * 例3是从一个UDP服务套接字中异步读取数据: -``` +```cpp using namespace boost::asio; io_service service; ip::udp::socket sock(service); @@ -842,7 +842,7 @@ int main(int argc, char* argv[]) { service.run(); } ``` -###套接字控制: +### 套接字控制: 这些函数用来处理套接字的高级选项: * *get_io_service()*:这个函数返回构造函数中传入的io_service实例 @@ -853,7 +853,7 @@ int main(int argc, char* argv[]) { 这些是你可以获取/设置的套接字选项: | 名字 | 描述 | 类型 | -| -- | -- | +|-----|-----|------| | broadcast | 如果为true,允许广播消息 | bool | | debug | 如果为true,启用套接字级别的调试 | bool | |do_not_route | 如果为true,则阻止路由选择只使用本地接口 | bool | @@ -868,7 +868,7 @@ int main(int argc, char* argv[]) { | ip::v6_only | 如果为true,则只允许IPv6的连接 | bool | 每个名字代表了一个内部套接字typedef或者类。下面是对它们的使用: -``` +```cpp ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80); ip::tcp::socket sock(service); sock.connect(ep); @@ -889,7 +889,7 @@ sock.set_option(sbs); 就像我之前所说,不是所有的成员方法在所有的套接字类中都可用。我做了一个包含成员函数不同点的列表。如果一个成员函数没有出现在这,说明它在所有的套接字类都是可用的。 | 名字 | TCP | UDP | ICMP | -| -- | -- | -- | -- | +|-----|-----|-----|------| |async_read_some | 是 | - | - | |async_receive_from | - | 是 | 是 | |async_write_some | 是 | - | - | @@ -899,7 +899,7 @@ sock.set_option(sbs); |write_some | 是 | - | - | |send_to | - | 是 | 是 | -###其他方法 +### 其他方法 其他与连接和I/O无关的函数如下: * *local_endpoint()*:这个方法返回套接字本地连接的地址。 * *remote_endpoint()*:这个方法返回套接字连接到的远程地址。 @@ -908,16 +908,16 @@ sock.set_option(sbs); * *native_non_blocking()*:如果套接字是非阻塞的,这个方法返回true,否则返回false。但是,它是基于原生的套接字来调用本地的api。所以通常来说,你不需要调用这个方法(non_blocking()已经缓存了这个结果);你只有在直接调用native_handle()这个方法的时候才需要用到这个方法。 * *at_mark()*:如果套接字要读的是一段OOB数据,这个方法返回true。这个方法你很少会用到。 -###其他需要考虑的事情 +### 其他需要考虑的事情 最后要注意的一点,套接字实例不能被拷贝,因为拷贝构造方法和=操作符是不可访问的。 -``` +```cpp ip::tcp::socket s1(service), s2(service); s1 = s2; // 编译时报错 ip::tcp::socket s3(s1); // 编译时报错 ``` 这是非常有意义的,因为每一个实例都拥有并管理着一个资源(原生套接字本身)。如果我们允许拷贝构造,结果是我们会有两个实例拥有同样的原生套接字;这样我们就需要去处理所有者的问题(让一个实例拥有所有权?或者使用引用计数?还是其他的方法)Boost.Asio选择不允许拷贝(如果你想要创建一个备份,请使用共享指针) -``` +```cpp typedef boost::shared_ptr socket_ptr; socket_ptr sock1(new ip::tcp::socket(service)); socket_ptr sock2(sock1); // ok @@ -925,10 +925,10 @@ socket_ptr sock3; sock3 = sock1; // ok ``` -###套接字缓冲区 +### 套接字缓冲区 当从一个套接字读写内容时,你需要一个缓冲区,用来保存读取和写入的数据。缓冲区内存的有效时间必须比I/O操作的时间要长;你需要保证它们在I/O操作结束之前不被释放。 对于同步操作来说,这很容易;当然,这个缓冲区在receive和send时都存在。 -``` +```cpp char buff[512]; ... sock.receive(buffer(buff)); @@ -937,7 +937,7 @@ sock.send(buffer(buff)); ``` 但是在异步操作时就没这么简单了,看下面的代码片段: -``` +```cpp // 非常差劲的代码 ... void on_read(const boost::system::error_code & err, std::size_t read_bytes) { ... } @@ -958,7 +958,7 @@ void func() { 第一个方法显然不是很好,因为我们都知道全局变量非常不好。此外,如果两个实例使用同一个缓冲区怎么办? 下面是第二种方式的实现: -``` +```cpp void on_read(char * ptr, const boost::system::error_code & err, std::size_t read_bytes) { delete[] ptr; } @@ -968,7 +968,7 @@ sock.async_receive(buffer(buff, 512), boost::bind(on_read,buff,_1,_2)) ``` 或者,如果你想要缓冲区在操作结束后自动超出范围,使用共享指针 -``` +```cpp struct shared_buffer { boost::shared_array buff; int size; @@ -993,13 +993,13 @@ shared_buffer类拥有实质的*shared_array<>*,*shared_array<>*存在的目 第三个选择是使用一个连接对象来管理套接字和其他数据,比如缓冲区,通常来说这是正确的解决方案但是非常复杂。在这一章的末尾我们会对这种方法进行讨论。 ###缓冲区封装函数 纵观所有代码,你会发现:无论什么时候,当我们需要对一个buffer进行读写操作时,代码会把实际的缓冲区对象封装在一个buffer()方法中,然后再把它传递给方法调用: -``` +```cpp char buff[512]; sock.async_receive(buffer(buff), on_read); ``` 基本上我们都会把缓冲区包含在一个类中以便Boost.Asio的方法能遍历这个缓冲区,比方说,使用下面的代码: -``` +```cpp sock.async_receive(some_buffer, on_read); ``` @@ -1015,7 +1015,7 @@ sock.async_receive(some_buffer, on_read); * 一个包含pod数据的std::array 下面的代码都是有效的: -``` +```cpp struct pod_sample { int i; long l; char c; }; ... char b1[512]; @@ -1036,16 +1036,16 @@ sock.async_send(buffer(b7), on_read); 总的来说就是:与其创建你自己的类来处理*ConstBufferSequence*或者*MutableBufferSequence*的需求,不如创建一个能在你需要的时候保留缓冲区,然后返回一个mutable_buffers_1实例的类,而我们早在shared_buffer类中就这样做了。 -###read/write/connect自由函数 +### read/write/connect自由函数 Boost.Asio提供了处理I/O的自由函数,我们分四组来分析它们。 -####connect方法 +#### connect方法 这些方法把套接字连接到一个端点。 -* *connect(socket, begin [, end] [, condition])*:这个方法遍历队列中从start到end的端点来尝试同步连接。begin迭代器是调用*socket_type::resolver::query*的返回结果(你可能需要回顾一下端点这个章节)。特别提示end迭代器是可选的;你可以忽略它。你还可以提供一个condition的方法给每次连接尝试之后调用。用法是*Iterator connect_condition(const boost::system::error_code & err,Iterator next);*。你可以选择返回一个不是*next*的迭代器,这样你就可以跳过一些端点。 +* *connect(socket, begin [, end] [, condition])*:这个方法遍历队列中从begin到end的端点来尝试同步连接。begin迭代器是调用*socket_type::resolver::query*的返回结果(你可能需要回顾一下端点这个章节)。特别提示end迭代器是可选的;你可以忽略它。你还可以提供一个condition的方法给每次连接尝试之后调用。用法是*Iterator connect_condition(const boost::system::error_code & err,Iterator next);*。你可以选择返回一个不是*next*的迭代器,这样你就可以跳过一些端点。 * *async_connect(socket, begin [, end] [, condition], handler)*:这个方法异步地调用连接方法,在结束时,它会调用完成处理方法。用法是*void handler(constboost::system::error_code & err, Iterator iterator);*。传递给处理方法的第二个参数是连接成功端点的迭代器(或者end迭代器)。 它的例子如下: -``` +```cpp using namespace boost::asio::ip; tcp::resolver resolver(service); tcp::resolver::iterator iter = resolver.resolve(tcp::resolver::query("www.yahoo.com","80")); @@ -1062,7 +1062,7 @@ connect(sock, iter); * *async_write(stream, buffer [, completion], handler)*:这个方法异步地向一个流写入数据。参数的意义和*async_read*是一样的。 * *read(stream, buffer [, completion])*:这个方法同步地从一个流中读取数据。参数的意义和*async_read*是一样的。 * *write(stream, buffer [, completion])*: 这个方法同步地向一个流写入数据。参数的意义和*async_read*是一样的。 -``` +```cpp async_read(stream, stream_buffer [, completion], handler) async_write(strean, stream_buffer [, completion], handler) write(stream, stream_buffer [, completion]) @@ -1076,7 +1076,7 @@ read(stream, stream_buffer [, completion]) * 错误发生时 下面的代码会异步地从一个socket中间读取数据直到读取到’\n’: -``` +```cpp io_service service; ip::tcp::socket sock(service); char buff[512]; @@ -1100,7 +1100,7 @@ Boost.Asio也提供了一些简单的完成处理仿函数: 例子如下: -``` +```cpp char buff[512]; void on_read(const boost::system::error_code &, size_t) {} // 读取32个字节 @@ -1109,7 +1109,7 @@ async_read(sock, buffer(buff), transfer_exactly(32), on_read); 上述的4个方法,不使用普通的缓冲区,而使用由Boost.Asio的*std::streambuf*类继承来的*stream_buffer*方法。stl流和流缓冲区非常复杂;下面是例子: -``` +```cpp io_service service; void on_read(streambuf& buf, const boost::system::error_code &, size_t) { std::istream in(&buf); @@ -1132,12 +1132,12 @@ int main(int argc, char* argv[]) { 这些方法在条件满足之前会一直读取: * *async_read_until(stream, stream_buffer, delim, handler)*:这个方法启动一个异步*read*操作。*read*操作会在读取到某个分隔符时结束。分隔符可以是字符,*std::string*或者*boost::regex*。处理方法的格式为:*void handler(const boost::system::error_code & err, size_t bytes);*。 -* *async_read_until(strem, stream_buffer, completion, handler)*:这个方法和之前的方法是一样的,但是没有分隔符,而是一个完成处理方法。完成处理方法的格式为:*pair< iterator,bool > completion(iterator begin, iterator end);*,其中迭代器的类型为*buffers_iterator< streambuf::const_buffers_type >*。你需要记住的是这个迭代器是支持随机访问的。你扫描整个区间(begin,end),然后决定read操作是否应该结束。返回的结果是一个结果对,第一个成员是一个迭代器,它指向最后被这个方法访问的字符;第二个成员指定read操作是否需要结束,需要时返回true,否则返回false。 +* *async_read_until(stream, stream_buffer, completion, handler)*:这个方法和之前的方法是一样的,但是没有分隔符,而是一个完成处理方法。完成处理方法的格式为:*pair< iterator,bool > completion(iterator begin, iterator end);*,其中迭代器的类型为*buffers_iterator< streambuf::const_buffers_type >*。你需要记住的是这个迭代器是支持随机访问的。你扫描整个区间(begin,end),然后决定read操作是否应该结束。返回的结果是一个结果对,第一个成员是一个迭代器,它指向最后被这个方法访问的字符;第二个成员指定read操作是否需要结束,需要时返回true,否则返回false。 * *read_until(stream, stream_buffer, delim)*:这个方法执行一个同步的*read*操作,参数的意义和*async_read_until*一样。 * *read_until(stream, stream_buffer, completion)*:这个方法执行一个同步的read操作,参数的意义和*async_read_until*一样。 下面这个例子在读到一个指定的标点符号之前会一直读取: -``` +```cpp typedef buffers_iterator iterator; std::pair match_punct(iterator begin, iterator end) { while ( begin != end) @@ -1152,7 +1152,7 @@ async_read_until(sock, buf, match_punct, on_read); ``` 如果我们想读到一个空格时就结束,我们需要把最后一行修改为: -``` +```cpp async_read_until(sock, buff, ' ', on_read); ``` #### *_at方法 @@ -1165,7 +1165,7 @@ async_read_until(sock, buff, ' ', on_read); 这些方法不支持套接字。它们用来处理流的随机访问;也就是说,流是可以随机访问的。套接字显然不是这样(套接字是不可回溯的)。 下面这个例子告诉你怎么从一个文件偏移为256的位置读取128个字节: -``` +```cpp io_service service; int main(int argc, char* argv[]) { HANDLE file = ::CreateFile("readme.txt", GENERIC_READ, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0); @@ -1190,7 +1190,7 @@ int main(int argc, char* argv[]) { 假设:你有一个需要处理1000个并发访问的应用,从客户端发给服务端的每个信息都会再返回给客户端,以‘\n’结尾。 同步方式的代码,1个线程: -``` +```cpp using namespace boost::asio; struct client { ip::tcp::socket sock; @@ -1229,7 +1229,7 @@ void on_read_msg(client & c, const std::string & msg) { 这里的瓶颈就是*on_read_msg()*方法;当它执行时,所有进来的消息都在等待。一个良好的*on_read_msg()*方法实现会保证这种情况基本不会发生,但是它还是会发生(有时候向一个套接字写入数据,缓冲区满了时,它会被阻塞) 同步方式的代码,10个线程 -``` +```cpp using namespace boost::asio; struct client {   // ... 和之前一样 @@ -1277,7 +1277,7 @@ void on_read_msg(client & c, const std::string & msg) { 然后,让我们来看异步编程。我们不断地异步读取。当一个客户端请求某些东西时,*on_read*被调用,然后回应,然后等待下一个请求(然后开始另外一个异步的read操作)。 异步方式的代码,10个线程 -``` +```cpp using namespace boost::asio; io_service service; struct client { @@ -1309,12 +1309,12 @@ void on_read(client & c, const error_code & err, size_t read_bytes) { 发现代码变得有多简单了吧?client结构里面只有两个成员,*handle_clients()*仅仅调用了*async_read_until*,然后它创建了10个线程,每个线程都调用*service.run()*。这些线程会处理所有来自客户端的异步read操作,然后分发所有向客户端的异步write操作。另外需要注意的一件事情是:*on_read()*一直在为下一次异步read操作做准备(看最后一行代码)。 -####异步run(), run_one(), poll(), poll_ one() +#### 异步run(), run_one(), poll(), poll_ one() 为了实现监听循环,*io_service*类提供了4个方法,比如:*run(), run_one(), poll()*和*poll_one()*。虽然大多数时候使用*service.run()*就可以,但是你还是需要在这里学习其他方法实现的功能。 ##### 持续运行 再一次说明,如果有等待执行的操作,*run()*会一直执行,直到你手动调用*io_service::stop()*。为了保证*io_service*一直执行,通常你添加一个或者多个异步操作,然后在它们被执行时,你继续一直不停地添加异步操作,比如下面代码: -``` +```cpp using namespace boost::asio; io_service service; ip::tcp::socket sock(service); @@ -1348,21 +1348,21 @@ int main(int argc, char* argv[]) { 7. 当*on_write*结束时,我们有一个等待的操作(read)。 8. 然后一直继续循环下去,直到我们关闭这个应用。 -#####run_one(), poll(), poll_one() 方法 +##### run_one(), poll(), poll_one() 方法 我在之前说过异步方法的handler是在调用了*io_service::run*的线程里被调用的。因为在至少90%~95%的时候,这是你唯一要用到的方法,所以我就把它说得简单了。对于调用了*run_one(), poll()*或者*poll_one()*的线程这一点也是适用的。 -*run_one()*方法最多执行和分发一个异步操作: +*run_one()* 方法最多执行和分发一个异步操作: * 如果没有等待的操作,方法立即返回0 * 如果有等待操作,方法在第一个操作执行之前处于阻塞状态,然后返回1 你可以认为下面两段代码是等效的: -``` +```cpp io_service service; service.run(); // 或者 while ( !service.stopped()) service.run_once(); ``` -你可以使用*run_once()*启动一个异步操作,然后等待它执行完成。 +你可以使用 *run_once()* 启动一个异步操作,然后等待它执行完成。 ``` io_service service; bool write_complete = false; @@ -1375,9 +1375,9 @@ async_write(sock, buffer(data), on_write); do service.run_once() while (!write_complete); ``` -还有一些使用*run_one()*方法的例子,包含在Boost.Asio诸如*blocking_tcp_client.cpp*和*blocking_udp_client.cpp*的文件中。 +还有一些使用 *run_one()* 方法的例子,包含在Boost.Asio诸如 *blocking_tcp_client.cpp* 和 *blocking_udp_client.cpp* 的文件中。 -*poll_one*方法以非阻塞的方式最多运行一个准备好的等待操作: +*poll_one* 方法以非阻塞的方式最多运行一个准备好的等待操作: * 如果至少有一个等待的操作,而且准备好以非阻塞的方式运行,poll_one方法会运行它并且返回1 * 否则,方法立即返回0 @@ -1386,8 +1386,8 @@ do service.run_once() while (!write_complete); * 一个I/O操作完成了(比如*async_read*),然后它的hanlder需要被调用 * 之前被加入*io_services*实例队列中的自定义handler(这会在之后的章节中详解) -你可以使用*poll_one*去保证所有I/O操作的handler完成运行,同时做一些其他的工作 -``` +你可以使用 *poll_one* 去保证所有I/O操作的handler完成运行,同时做一些其他的工作 +```cpp io_service service; while ( true) { // 运行所有完成了IO操作的handler @@ -1396,26 +1396,26 @@ while ( true) { } ``` -*poll()*方法会以非阻塞的方式运行所有等待的操作。下面两段代码是等效的: -``` +*poll()* 方法会以非阻塞的方式运行所有等待的操作。下面两段代码是等效的: +```cpp io_service service; service.poll(); // 或者 while ( service.poll_one()) ; ``` 所有上述方法都会在失败的时候抛出*boost::system::system_error*异常。这是我们所不希望发生的事情;这里抛出的异常通常都是致命的,也许是资源耗尽,或者是你handler的其中一个抛出了异常。另外,每个方法都有一个不抛出异常,而是返回一个*boost::system::error_code*的重载: -``` +```cpp io_service service; boost::system::error_code err = 0; service.run(err); if ( err) std::cout << "Error " << err << std::endl; ``` -####异步工作 +#### 异步工作 异步工作不仅仅指用异步地方式接受客户端到服务端的连接、异步地从一个socket读取或者写入到socket。它包含了所有可以异步执行的操作。 默认情况下,你是不知道每个异步handler的调用顺序的。除了通常的异步调用(来自异步socket的读取/写入/接收)。你可以使用*service.post()*来使你的自定义方法被异步地调用。例如: -``` +```cpp #include #include #include @@ -1442,10 +1442,10 @@ int main(int argc, char* argv[]) { } ``` -在上面的例子中,*service.post(some_function)*添加了一个异步方法调用。 +在上面的例子中,*service.post(some_function)* 添加了一个异步方法调用。 -这个方法在某一个调用了*service.run()*的线程中请求*io_service*实例,然后调用给定的*some_funtion*之后立即返回。在我们的例子中,这个线程是我们之前创建的三个线程中的一个。你不能确定异步方法调用的顺序。你不要期待它们会以我们调用*post()*方法的顺序来调用。下面是运行之前代码可能得到的结果: -``` +这个方法在某一个调用了 *service.run()* 的线程中请求 *io_service* 实例,然后调用给定的 *some_funtion* 之后立即返回。在我们的例子中,这个线程是我们之前创建的三个线程中的一个。你不能确定异步方法调用的顺序。你不要期待它们会以我们调用 *post()* 方法的顺序来调用。下面是运行之前代码可能得到的结果: +```cpp func called, i= 0 func called, i= 2 func called, i= 1 @@ -1458,8 +1458,8 @@ func called, i= 5 func called, i= 9 ``` -有时候你会想让一些异步处理方法顺序执行。比如,你去一个餐馆(*go_to_restaurant*),下单(*order*),然后吃(*eat*)。你需要先去餐馆,然后下单,最后吃。这样的话,你需要用到*io_service::strand*,这个方法会让你的异步方法被顺序调用。看下面的例子: -``` +有时候你会想让一些异步处理方法顺序执行。比如,你去一个餐馆(*go_to_restaurant*),下单(*order*),然后吃(*eat*)。你需要先去餐馆,然后下单,最后吃。这样的话,你需要用到 *io_service::strand*,这个方法会让你的异步方法被顺序调用。看下面的例子: +```cpp using namespace boost::asio; io_service service; void func(int i) { @@ -1484,7 +1484,7 @@ int main(int argc, char* argv[]) } ``` -在上述代码中,我们保证前面的5个线程和后面的5个线程是顺序执行的。*func called, i = 0*在*func called, i = 1*之前被调用,然后调用*func called, i = 2*……同样*func called, i = 5*在*func called, i = 6*之前,*func called, i = 6*在*func called, i = 7*被调用……你需要注意的是尽管方法是顺序调用的,但是不意味着它们都在同一个线程执行。运行这个程序可能得到的一个结果如下: +在上述代码中,我们保证前面的5个线程和后面的5个线程是顺序执行的。*func called, i = 0*在 *func called, i = 1*之前被调用,然后调用*func called, i = 2*……同样*func called, i = 5*在*func called, i = 6*之前,*func called, i = 6*在*func called, i = 7*被调用……你需要注意的是尽管方法是顺序调用的,但是不意味着它们都在同一个线程执行。运行这个程序可能得到的一个结果如下: ``` func called, i= 0/002A60C8 func called, i= 5/002A6138 @@ -1498,14 +1498,14 @@ func called, i= 9/002A6530 func called, i= 4/002A6138 ``` -####异步post() VS dispatch() VS wrap() +#### 异步post() VS dispatch() VS wrap() Boost.Asio提供了三种让你把处理方法添加为异步调用的方式: * *service.post(handler)*:这个方法能确保其在请求*io_service*实例,然后调用指定的处理方法之后立即返回。handler稍后会在某个调用了*service.run()*的线程中被调用。 * *service.dispatch(handler)*:这个方法请求*io_service*实例去调用给定的处理方法,但是另外一点,如果当前的线程调用了*service.run()*,它可以在方法中直接调用handler。 * *service.wrap(handler)*:这个方法创建了一个封装方法,当被调用时它会调用*service.dispatch(handler)*,这个会让人有点困惑,接下来我会简单地解释它是什么意思。 在之前的章节中你会看到关于*service.post()*的一个例子,以及运行这个例子可能得到的一种结果。我们对它做一些修改,然后看看*service.dispatch()*是怎么影响输出的结果的: -``` +```cpp using namespace boost::asio; io_service service; void func(int i) { @@ -1539,7 +1539,7 @@ func called, i= 9 偶数先输出,然后是奇数。这是因为我用*dispatch()*输出偶数,然后用*post()*输出奇数。*dispatch()*会在返回之前调用hanlder,因为当前的线程调用了*service.run()*,而*post()*每次都立即返回了。 现在,让我们讲讲*service.wrap(handler)*。*wrap()*返回了一个仿函数,它可以用来做另外一个方法的参数: -``` +```cpp using namespace boost::asio; io_service service; void dispatched_func_1() { @@ -1570,10 +1570,10 @@ test dispatched 1 dispatched 2 ``` -*io_service::strand *类(用来序列化异步调用)也包含了*poll(), dispatch()*和 *wrap()*等成员函数。它们的作用和*io_service*的*poll(), dispatch()*和*wrap()*是一样的。然而,大多数情况下你只需要把*io_service::strand::wrap()*方法做为*io_service::poll()*或者*io_service::dispatch()*方法的参数即可。 -###保持活动 +*io_service::strand* 类(用来序列化异步调用)也包含了 *poll()*, *dispatch()* 和 *wrap()* 等成员函数。它们的作用和 *io_service* 的 *poll()*, *dispatch()* 和 *wrap()* 是一样的。然而,大多数情况下你只需要把 *io_service::strand::wrap()* 方法做为 *io_service::poll()* 或者 *io_service::dispatch()* 方法的参数即可。 +### 保持活动 假设你需要做下面的操作: -``` +```cpp io_service service; ip::tcp::socket sock(service); char buff[512]; @@ -1581,8 +1581,8 @@ char buff[512]; read(sock, buffer(buff)); ``` -在这个例子中,*sock*和*buff*的存在时间都必须比*read()*调用的时间要长。也就是说,在调用*read()*返回之前,它们都必须有效。这就是你所期望的;你传给一个方法的所有参数在方法内部都必须有效。当我们采用异步方式时,事情会变得比较复杂。 -``` +在这个例子中,*sock* 和 *buff* 的存在时间都必须比 *read()* 调用的时间要长。也就是说,在调用 *read()* 返回之前,它们都必须有效。这就是你所期望的;你传给一个方法的所有参数在方法内部都必须有效。当我们采用异步方式时,事情会变得比较复杂。 +```cpp io_service service; ip::tcp::socket sock(service); char buff[512]; @@ -1591,10 +1591,10 @@ void on_read(const boost::system::error_code &, size_t) {} async_read(sock, buffer(buff), on_read); ``` -在这个例子中,*sock*和*buff*的存在时间都必须比*read()*操作本身时间要长,但是read操作持续的时间我们是不知道的,因为它是异步的。 +在这个例子中,*sock* 和 *buff* 的存在时间都必须比 *read()* 操作本身时间要长,但是read操作持续的时间我们是不知道的,因为它是异步的。 -当使用socket缓冲区的时候,你会有一个*buffer*实例在异步调用时一直存在(使用*boost::shared_array<>*)。在这里,我们可以使用同样的方式,通过创建一个类并在其内部管理socket和它的读写缓冲区。然后,对于所有的异步操作,传递一个包含智能指针的*boost::bind*仿函数给它: -``` +当使用socket缓冲区的时候,你会有一个 *buffer* 实例在异步调用时一直存在(使用 *boost::shared_array<>* )。在这里,我们可以使用同样的方式,通过创建一个类并在其内部管理socket和它的读写缓冲区。然后,对于所有的异步操作,传递一个包含智能指针的 *boost::bind* 仿函数给它: +```cpp using namespace boost::asio; io_service service; struct connection : boost::enable_shared_from_this { @@ -1653,14 +1653,14 @@ int main(int argc, char* argv[]) { connection::ptr(new connection)->start(ep); } ``` -在所有异步调用中,我们传递一个*boost::bind*仿函数当作参数。这个仿函数内部包含了一个智能指针,指向*connection*实例。只要有一个异步操作等待时,Boost.Asio就会保存*boost::bind*仿函数的拷贝,这个拷贝保存了指向连接实例的一个智能指针,从而保证*connection*实例保持活动。问题解决! +在所有异步调用中,我们传递一个 *boost::bind* 仿函数当作参数。这个仿函数内部包含了一个智能指针,指向*connection*实例。只要有一个异步操作等待时,Boost.Asio就会保存*boost::bind*仿函数的拷贝,这个拷贝保存了指向连接实例的一个智能指针,从而保证*connection*实例保持活动。问题解决! 当然,*connection*类仅仅是一个框架类;你需要根据你的需求对它进行调整(它看起来会和当前服务端例子的情况相当不同)。 你需要注意的是创建一个新的连接是相当简单的:*connection::ptr(new connection)- >start(ep)*。这个方法启动了到服务端的(异步)连接。当你需要关闭这个连接时,调用*stop()*。 当实例被启动时(*start()*),它会等待客户端的连接。当连接发生时。*on_connect()*被调用。如果没有错误发生,它启动一个read操作(*do_read()*)。当read操作结束时,你就可以解析这个消息;当然你应用的*on_read()*看起来会各种各样。而当你写回一个消息时,你需要把它拷贝到缓冲区,然后像我在*do_write()*方法中所做的一样将其发送出去,因为这个缓冲区同样需要在这个异步写操作中一直存活。最后需要注意的一点——当写回时,你需要指定写入的数量,否则,整个缓冲区都会被发送出去。 -###总结 +### 总结 网络api实际上要繁杂得多,这个章节只是做为一个参考,当你在实现自己的网络应用时可以回过头来看看。 Boost.Asio实现了端点的概念,你可以认为是IP和端口。如果你不知道准确的IP,你可以使用*resolver*对象将主机名,例如*www.yahoo.com*转换为一个或多个IP地址。 @@ -1675,7 +1675,7 @@ Boost.Asio实现了端点的概念,你可以认为是IP和端口。如果你 下一章会进行实战操作;在实现回显客户端/服务端应用时会有大量的编程实践。 -##回显服务端/客户端 +## 回显服务端/客户端 在这一章,我们将会实现一个小的客户端/服务端应用,这可能会是你写过的最简单的客户端/服务端应用。回显应用就是一个把客户端发过来的任何内容回显给其本身,然后关闭连接的的服务端。这个服务端可以处理任何数量的客户端。每个客户端连接之后发送一个消息,服务端接收到完成消息后把它发送回去。在那之后,服务端关闭连接。 因此,每个回显客户端连接到服务端,发送一个消息,然后读取服务端返回的结果,确保这是它发送给服务端的消息就结束和服务端的会话。 @@ -1685,14 +1685,14 @@ Boost.Asio实现了端点的概念,你可以认为是IP和端口。如果你 ![](http://d.pcs.baidu.com/thumbnail/3ed527792035c0abc0d8e70405180310?fid=3238002958-250528-552015406596888&time=1420768800&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-jmDoVsCZ6Qw1kecSlrmm13%2BuoY0%3D&rt=sh&expires=2h&r=776548562&sharesign=unknown&size=c710_u500&quality=100) 为了节省空间,下面的代码有一些被裁剪掉了。你可以在附加在这本书的代码中看到全部的代码。 -###TCP回显服务端/客户端 +### TCP回显服务端/客户端 对于TCP而言,我们需要一个额外的保证;每一个消息以换行符结束(‘\n’)。编写一个同步回显服务端/客户端非常简单。 我们会展示编码内容,比如同步客户端,同步服务端,异步客户端和异步服务端。 -####TCP同步客户端 +#### TCP同步客户端 在大多数有价值的例子中,客户端通常比服务端编码要简单(因为服务端需要处理多个客户端请求)。 下面的代码展示了不符合这条规则的一个例外: -``` +```cpp size_t read_complete(char * buf, const error_code & err, size_t bytes) { if ( err) return 0; @@ -1701,7 +1701,7 @@ size_t read_complete(char * buf, const error_code & err, size_t bytes) return found ? 0 : 1; } void sync_echo(std::string msg) { - msg += "\n”; + msg += "\n"; ip::tcp::socket sock(service); sock.connect(ep); sock.write_some(buffer(msg)); @@ -1724,11 +1724,11 @@ int main(int argc, char* argv[]) { ``` 核心功能*sync_echo*。它包含了连接到服务端,发送信息然后等待回显的所有逻辑。 -你会发现,在读取时,我使用了自由函数*read()*,因为我想要读’\n’之前的所有内容。*sock.read_some()*方法满足不了这个要求,因为它只会读可用的,而不是全部的消息。 +你会发现,在读取时,我使用了自由函数*read()*,因为我想要读’\n’之前的所有内容。 *sock.read_some()* 方法满足不了这个要求,因为它只会读可用的,而不是全部的消息。 -*read()*方法的第三个参数是完成处理句柄。当读取到完整消息时,它返回0。否则,它会返回我下一步(直到读取结束)能都到的最大的缓冲区大小。在我们的例子中,返回结果始终是1,因为我永远不想读的消息比我们需要的更多。 +*read()* 方法的第三个参数是完成处理句柄。当读取到完整消息时,它返回0。否则,它会返回我下一步(直到读取结束)能都到的最大的缓冲区大小。在我们的例子中,返回结果始终是1,因为我永远不想读的消息比我们需要的更多。 -在*main()*中,我们创建了几个线程;每个线程负责把消息发送到客户端,然后等待操作结束。如果你运行这个程序,你会看到下面的输出: +在 *main()* 中,我们创建了几个线程;每个线程负责把消息发送到客户端,然后等待操作结束。如果你运行这个程序,你会看到下面的输出: ``` server echoed our John says hi: OK server echoed our so does James: OK @@ -1737,9 +1737,10 @@ server echoed our Boost.Asio is Fun!: OK ``` 注意:因为我们是同步的,所以不需要调用*service.run()*。 -####TCP同步服务端 + +#### TCP同步服务端 回显同步服务端的编写非常容易,参考如下的代码片段: -``` +```cpp io_service service; size_t read_complete(char * buff, const error_code & err, size_t bytes) { if ( err) return 0; @@ -1767,12 +1768,13 @@ int main(int argc, char* argv[]) { 服务端的逻辑主要在*handle_connections()*。因为是单线程,我们接受一个客户端请求,读取它发送给我们的消息,然后回显,然后等待下一个连接。可以确定,当两个客户端同时连接时,第二个客户端需要等待服务端处理完第一个客户端的请求。 还是要注意因为我们是同步的,所以不需要调用*service.run()*。 -####TCP异步客户端 + +#### TCP异步客户端 当我们开始异步时,编码会变得稍微有点复杂。我们会构建在**第二章 保持活动**中展示的*connection*类。 -观察这个章节中接下来的代码,你会发现每个异步操作启动了新的异步操作,以保持*service.run()*一直工作。 +观察这个章节中接下来的代码,你会发现每个异步操作启动了新的异步操作,以保持 *service.run()* 一直工作。 首先,核心功能如下: -``` +```cpp #define MEM_FN(x) boost::bind(&self_type::x, shared_from_this()) #define MEM_FN1(x,y) boost::bind(&self_type::x, shared_from_this(),y) #define MEM_FN2(x,y,z) boost::bind(&self_type::x, shared_from_this(),y,z) @@ -1813,7 +1815,7 @@ private: *MEM_FN* *是一个方便使用的宏,它们通过*shared_ptr_from_this()*方法强制使用一个指向* *this *的智能指针。 下面的几行代码和之前的解释非常不同: -``` +```cpp //等同于 "sock_.async_connect(ep, MEM_FN1(on_connect,_1));" sock_.async_connect(ep,boost::bind(&talk_to_svr::on_connect,shared_ptr_from_this(),_1)); sock_.async_connect(ep, boost::bind(&talk_to_svr::on_connect,this,_1)); @@ -1823,7 +1825,7 @@ sock_.async_connect(ep, boost::bind(&talk_to_svr::on_connect,this,_1)); 在接下来的例子中,我们错误地创建了完成处理句柄,当它被调用时,*talk_to_server*实例很可能已经被释放了。 从socket读取或写入时,你使用如下的代码片段: -``` +```cpp void do_read() { async_read(sock_, buffer(read_buffer_), MEM_FN2(read_complete,_1,_2), MEM_FN2(on_read,_1,_2)); } @@ -1839,7 +1841,7 @@ size_t read_complete(const boost::system::error_code & err, size_t bytes) { *do_read()*方法会保证当*on_read()*被调用的时候,我们从服务端读取一行。*do_write()*方法会先把信息拷贝到缓冲区(考虑到当*async_write*发生时msg可能已经超出范围被释放),然后保证实际的写入操作发生时*on_write()*被调用。 然后是最重要的方法,这个方法包含了类的主要逻辑: -``` +```cpp void on_connect(const error_code & err) { if ( !err) do_write(message_ + "\n"); else stop(); @@ -1857,7 +1859,7 @@ void on_write(const error_code & err, size_t bytes) { ``` 当连接成功之后,我们发送消息到服务端,*do_write()*。当write操作结束时,*on_write()*被调用,它初始化了一个*do_read()*方法,当*do_read()*完成时。*on_read()*被调用;这里,我们简单的检查一下返回的信息是否是服务端的回显,然后退出服务。 我们会发送三个消息到服务端让它变得更有趣一点: -``` +```cpp int main(int argc, char* argv[]) { ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001); char* messages[] = { "John says hi", "so does James", "Lucy got home", 0 }; @@ -1875,9 +1877,9 @@ server echoed our so does James: OK server echoed our Lucy just got home: OK ``` -####TCP异步服务端 +#### TCP异步服务端 核心功能和同步服务端的功能类似,如下: -``` +```cpp class talk_to_client : public boost::enable_shared_from_this, boost::noncopyable { typedef talk_to_client self_type; @@ -1911,9 +1913,9 @@ private: ``` 因为我们是非常简单的回显服务,这里不需要*is_started()*方法。对每个客户端,仅仅读取它的消息,回显,然后关闭它。 -*do_read(),do_write()*和*read_complete()*方法和TCP同步服务端的完全一致。 +*do_read(),do_write()* 和 *read_complete()* 方法和TCP同步服务端的完全一致。 主要的逻辑同样是在*on_read()*和*on_write()*方法中: -``` +```cpp void on_read(const error_code & err, size_t bytes) { if ( !err) { std::string msg(read_buffer_, bytes); @@ -1926,7 +1928,7 @@ void on_write(const error_code & err, size_t bytes) { } ``` 对客户端的处理如下: -``` +```cpp ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001)); void handle_accept(talk_to_client::ptr client, const error_code & err) { @@ -1941,14 +1943,14 @@ int main(int argc, char* argv[]) { } ``` 每一次客户端连接到服务时,*handle_accept*被调用,它会异步地从客户端读取,然后同样异步地等待一个新的客户端。 -####代码 +#### 代码 你会在这本书相应的代码中得到所有4个应用(TCP回显同步客户端,TCP回显同步服务端,TCP回显异步客户端,TCP回显异步服务端)。当测试时,你可以使用任意客户端/服务端组合(比如,一个异步客户端和一个同步服务端)。 -###UDP回显服务端/客户端 +### UDP回显服务端/客户端 因为UDP不能保证所有信息都抵达接收者,我们不能保证“信息以回车结尾”。 没收到消息,我们只是回显,但是没有socket去关闭(在服务端),因为我们是UDP。 -####UDP同步回显客户端 +#### UDP同步回显客户端 UDP回显客户端比TCP回显客户端要简单: -``` +```cpp ip::udp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001); void sync_echo(std::string msg) { ip::udp::socket sock(service, ip::udp::endpoint(ip::udp::v4(), 0)); @@ -1971,7 +1973,7 @@ int main(int argc, char* argv[]) { } ``` 所有的逻辑都在*synch_echo()*中;连接到服务端,发送消息,接收服务端的回显,然后关闭连接。 -####UDP同步回显服务端 +#### UDP同步回显服务端 UDP回显服务端会是你写过的最简单的服务端: ``` io_service service; @@ -1991,11 +1993,11 @@ int main(int argc, char* argv[]) { ``` 它非常简单,而且能很好的自释。 我把异步UDP客户端和服务端留给读者当作一个练习。 -###总结 +### 总结 我们已经写了完整的应用,最终让Boost.Asio得以工作。回显应用是开始学习一个库时非常好的工具。你可以经常学习和运行这个章节所展示的代码,这样你就可以非常容易地记住这个库的基础。 在下一章,我们会建立更复杂的客户端/服务端应用,我们要确保避免低级错误,比如内存泄漏,死锁等等。 -##客户端和服务端 +## 客户端和服务端 在这一章节,我们会深入学习怎样使用Boost.Asio建立非凡的客户端和服务端应用。你可以运行并测试它们,而且在理解之后,你可以把它们做为框架来构造自己的应用。 在接下来的例子中: @@ -2012,16 +2014,16 @@ int main(int argc, char* argv[]) { * 每个客户端登录6个用户连接,比如Johon,James,Lucy,Tracy,Frank和Abby * 每个客户端连接随机地ping服务端(随机7秒;这样的话,服务端会时不时关闭一个连接) -###同步客户端/服务端 +### 同步客户端/服务端 首先,我们会实现同步应用。你会发现它的代码很直接而且易读的。而且因为所有的网络调用都是阻塞的,所以它不需要独立的线程。 -####同步客户端 +#### 同步客户端 同步客户端会以你所期望的串行方式运行;连接到服务端,登录服务器,然后执行连接循环,比如休眠一下,发起一个请求,读取服务端返回,然后再休眠一会,然后一直循环下去…… ![](http://d.pcs.baidu.com/thumbnail/102a243f8953a60d8531f3c68699e517?fid=3238002958-250528-439846994747753&time=1420768800&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-bHTbbqvG4PXZKcDNFdOWL6iVPVY%3D&rt=sh&expires=2h&r=382985944&sharesign=unknown&size=c710_u500&quality=100) 因为我们是同步的,所以我们让事情变得简单一点。首先,连接到服务器,然后再循环,如下: -``` +```cpp ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001); void run_client(const std::string & client_name) { talk_to_svr client(client_name); @@ -2035,7 +2037,7 @@ void run_client(const std::string & client_name) { ``` 下面的代码片段展示了talk_to_svr类: -``` +```cpp struct talk_to_svr { talk_to_svr(const std::string & username) : sock_(service), started_(true), username_(username) {} void connect(ip::tcp::endpoint ep) { @@ -2064,7 +2066,7 @@ private: ``` 在这个循环中,我们仅仅填充1个比特,做一个ping操作之后就进入睡眠状态,之后再读取服务端的返回。我们的睡眠是随机的(有时候超过5秒),这样服务端就有可能在某个时间点断开我们的连接: -``` +```cpp void write_request() { write("ping\n"); } @@ -2082,7 +2084,7 @@ void process_msg() { } ``` 对于读取结果,我们使用在之前章节就有说到的*read_complete*来保证我们能读到换行符(’\n’)。这段逻辑在*process_msg()*中,在这里我们读取服务端的返回,然后分发到正确的方法去处理: -``` +```cpp void on_login() { do_ask_clients(); } void on_ping(const std::string & msg) { std::istringstream in(msg); @@ -2105,14 +2107,14 @@ size_t read_complete(const boost::system::error_code & err, size_t bytes) { } ``` -在读取服务端对我们ping操作的返回时,如果得到的消息是*client_list_changed*,我们就需要重新请求客户端列表。 -####同步服务端 +在读取服务端对我们ping操作的返回时,如果得到的消息是 *client_list_changed*,我们就需要重新请求客户端列表。 +#### 同步服务端 同步服务端也是相当简单的。它只需要两个线程,一个负责接收新的客户端连接,另外一个负责处理已经存在的客户端请求。它不能使用单线程,因为等待新的客户端连接是一个阻塞操作,所以我们需要另外一个线程来处理已经存在的客户端请求。 ![](http://d.pcs.baidu.com/thumbnail/ceff46cf09767285fd5fa58be5d5beae?fid=3238002958-250528-625961824567867&time=1420768800&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-jZ%2BsgWwaNZblSuxpNAYBN2BuXP8%3D&rt=sh&expires=2h&r=387068104&sharesign=unknown&size=c710_u500&quality=100) 正常来说服务端都比客户端要难实现。一方面,它要管理所有已经连接的客户端。因为我们是同步的,所以我们需要至少两个线程,一个负责接受新的客户端连接(因为accept()是阻塞的)而另一个负责回复已经存在的客户端。 -``` +```cpp void accept_thread() { ip::tcp::acceptor acceptor(service,ip::tcp::endpoint(ip::tcp::v4(), 8001)); while ( true) { @@ -2144,15 +2146,15 @@ int main(int argc, char* argv[]) { 为了分辨客户端发送过来的请求我们需要保存一个客户端的列表。 每个*talk_to_client*实例都拥有一个socket,socket类是不支持拷贝构造的,所以如果你想要把它们保存在一个*std::vector*对象中,你需要一个指向它的智能指针。这里有两种实现的方式:在*talk_to_client*内部保存一个指向socket的智能指针然后创建一个*talk_to_client*实例的数组,或者让*talk_to_client*实例用变量的方式保存socket,然后创建一个指向*talk_to_client*智能指针的数组。我选择后者,但是你也可以选前面的方式: -``` +```cpp typedef boost::shared_ptr client_ptr; typedef std::vector array; array clients; boost::recursive_mutex cs; // 用线程安全的方式访问客户端数组 ``` -*talk_to_client*的主要代码如下: -``` +*talk_to_client* 的主要代码如下: +```cpp struct talk_to_client : boost::enable_shared_from_this { talk_to_client() { ... } @@ -2189,7 +2191,7 @@ private: 上述代码拥有非常好的自释能力。其中最重要的方法是*read_request()*。它只在存在有效数据的情况才读取,这样的话,服务端永远都不会阻塞: -``` +```cpp void process_request() { bool found_enter = std::find(buff_, buff_ + already_read_, '\n') < buff_ + already_read_; if ( !found_enter) @@ -2226,17 +2228,17 @@ void on_clients() { void write(const std::string & msg){sock_.write_some(buffer(msg)); } ``` -观察*process_request()*。当我们读取到足够多有效的数据时,我们需要知道我们是否已经读取到整个消息(如果*found_enter*为真)。这样做的话,我们可以使我们避免一次读多个消息的可能(’\n’之后的消息也被保存到缓冲区中),然后我们解析读取到的整个消息。剩下的代码都是很容易读懂的。 -###异步客户端/服务端 +观察 *process_request()*。当我们读取到足够多有效的数据时,我们需要知道我们是否已经读取到整个消息(如果 *found_enter* 为真)。这样做的话,我们可以使我们避免一次读多个消息的可能(’\n’之后的消息也被保存到缓冲区中),然后我们解析读取到的整个消息。剩下的代码都是很容易读懂的。 +### 异步客户端/服务端 现在,是比较有趣(也比较难)的异步实现! -当查看示意图时,你需要知道Boost.Asio代表由Boost.Asio执行的一个异步调用。例如*do_read()*,Boost.Asio和*on_read()*代表了从*do_read()*到*on_read()*的逻辑流程,但是你永远不知道什么时候轮到*on_read()*被调用,你只是知道你最终会调用它。 -####异步客户端 +当查看示意图时,你需要知道Boost.Asio代表由Boost.Asio执行的一个异步调用。例如 *do_read()*,Boost.Asio和 *on_read()* 代表了从 *do_read()*到 *on_read()* 的逻辑流程,但是你永远不知道什么时候轮到 *on_read()* 被调用,你只是知道你最终会调用它。 +#### 异步客户端 到这里事情会变得有点复杂,但是仍然是可控的。当然你也会拥有一个不会阻塞的应用。 ![](http://d.pcs.baidu.com/thumbnail/953e9b90f743389e6ea7a425aaeda307?fid=3238002958-250528-223058845569586&time=1420768800&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-56mkJ9mIQ8E81BqrXlmWpBYEqJU%3D&rt=sh&expires=2h&r=552891765&sharesign=unknown&size=c710_u500&quality=100) 下面的代码你应该已经很熟悉: -``` +```cpp #define MEM_FN(x) boost::bind(&self_type::x, shared_from_this()) #define MEM_FN1(x,y) boost::bind(&self_type::x, shared_from_ this(),y) @@ -2281,9 +2283,9 @@ private: }; ``` -你会看到额外还有一个叫*deadline_timer timer_*的方法用来ping服务端;而且ping操作同样是随机的。 +你会看到额外还有一个叫 *deadline_timer timer_* 的方法用来ping服务端;而且ping操作同样是随机的。 下面是类的逻辑: -``` +```cpp void on_connect(const error_code & err) { if ( !err) do_write("login " + username_ + "\n"); else stop(); @@ -2315,8 +2317,8 @@ void on_clients(const std::string & msg) { ``` 在*on_read()*中,首先的两行代码是亮点。在第一行,如果出现错误,我们就停止。而第二行,如果我们已经停止了(之前就停止了或者刚好停止),我们就返回。反之如果所有都是OK,我们就对收到的消息进行处理。 -最后是*do_**方法,实现如下: -``` +最后是 *do_** 方法,实现如下: +```cpp void do_ping() { do_write("ping\n"); } void postpone_ping() { timer_.expires_from_now(boost::posix_time::millisec(rand() % 7000)); @@ -2333,18 +2335,18 @@ void do_write(const std::string & msg) { sock_.async_write_some( buffer(write_buffer_, msg.size()), MEM_FN2(on_write,_1,_2)); ``` 注意每一个*read*操作都会触发一个ping操作 -* 当*read*操作结束时,*on_read()*被调用 -* *on_read()*调用*on_login(),on_ping()*或者*on_clients()* +* 当 *read* 操作结束时,*on_read()* 被调用 +* *on_read()* 调用 *on_login(),on_ping()* 或者 *on_clients()* * 每一个方法要么发出一个ping,要么请求客户端列表 -* 如果我们请求客户端列表,当*read*操作接收到它们时,它会发出一个ping操作。 +* 如果我们请求客户端列表,当 *read* 操作接收到它们时,它会发出一个ping操作。 -####异步服务端 -这个示意图是相当复杂的;从Boost.Asio出来你可以看到4个箭头指向*on_accept,on_read,on_write*和*on_check_ping*。这也就意味着你永远不知道哪个异步调用是下一个完成的调用,但是你可以确定的是它是这4个操作中的一个。 +#### 异步服务端 +这个示意图是相当复杂的;从Boost.Asio出来你可以看到4个箭头指向 *on_accept,on_read,on_write* 和 *on_check_ping*。这也就意味着你永远不知道哪个异步调用是下一个完成的调用,但是你可以确定的是它是这4个操作中的一个。 ![](http://d.pcs.baidu.com/thumbnail/eb7c5e88701b3738d5f57cb774af20f9?fid=3238002958-250528-454635957192459&time=1420768800&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-BELSnAVGnjDaCwLdOtTjcybqk%2BY%3D&rt=sh&expires=2h&r=476530834&sharesign=unknown&size=c710_u500&quality=100) 现在,我们是异步的了;我们可以继续保持单线程。接受客户端连接是最简单的部分,如下所示: -``` +```cpp ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(), 8001)); void handle_accept(talk_to_client::ptr client, const error_code & err) { @@ -2360,14 +2362,14 @@ int main(int argc, char* argv[]) { ``` 上述代码会一直异步地等待一个新的客户端连接(每个新的客户端连接会触发另外一个异步等待操作)。 我们需要监控*client list changed*事件(一个新客户端连接或者一个客户端断开连接),然后当事件发生时通知所有的客户端。因此,我们需要保存一个客户端连接的数组,否则除非你不需要在某一时刻知道所有连接的客户端,你才不需要这样一个数组。 -``` +```cpp class talk_to_client; typedef boost::shared_ptrclient_ptr; typedef std::vector array; array clients; ``` connection类的框架如下: -``` +```cpp class talk_to_client : public boost::enable_shared_from_this , boost::noncopyable { talk_to_client() { ... } public: @@ -2410,8 +2412,8 @@ private: 现在你需要用到之前的代码了;它和我们在客户端应用中所用到的是一样的。我们还有另外一个*stop()*方法,这个方法用来从客户端数组中移除一个客户端连接。 -服务端持续不断地等待异步的*read*操作: -``` +服务端持续不断地等待异步的 *read* 操作: +```cpp void on_read(const error_code & err, size_t bytes) { if ( err) stop(); if ( !started() ) return; @@ -2437,10 +2439,10 @@ void on_clients() { do_write("clients " + msg + "\n"); } ``` -这段代码是简单易懂的;需要注意的一点是:当一个新客户端登录,我们调用*update_clients_changed()*,这个方法为所有客户端将*clients_changed_*标志为*true*。 +这段代码是简单易懂的;需要注意的一点是:当一个新客户端登录,我们调用 *update_clients_changed()*,这个方法为所有客户端将 *clients_changed_* 标志为 *true* 。 服务端每收到一个请求就用相应的方式进行回复,如下所示: -``` +```cpp void do_ping() { do_write("ping\n"); } void do_ask_clients() { do_write("ask_clients\n"); } void on_write(const error_code & err, size_t bytes) { do_read(); } @@ -2457,10 +2459,10 @@ size_t read_complete(const boost::system::error_code & err, size_t bytes) { // ... 就像之前 } ``` -在每个*write*操作的末尾,*on_write()*方法被调用,这个方法会触发另外一个异步读操作,这样的话“等待请求-回复请求”这个循环就会一直执行,直到客户端断开连接或者超时。 +在每个 *write* 操作的末尾,*on_write()* 方法被调用,这个方法会触发另外一个异步读操作,这样的话“等待请求-回复请求”这个循环就会一直执行,直到客户端断开连接或者超时。 在每次读操作开始之前,我们异步等待5秒钟来观察客户端是否超时。如果超时,我们关闭它的连接: -``` +```cpp void on_check_ping() { ptime now = microsec_clock::local_time(); if ( (now - last_ping).total_milliseconds() > 5000) @@ -2475,20 +2477,20 @@ void post_check_ping() { 这就是整个服务端的实现。你可以运行并让它工作起来! 在代码中,我向你们展示了这一章我们学到的东西,为了更容易理解,我把代码稍微精简了下;比如,大部分的控制台输出我都没有展示,尽管在这本书附赠的代码中它们是存在的。我建议你自己运行这些例子,因为从头到尾读一次代码能加强你对本章展示应用的理解。 -###总结 +### 总结 我们已经学到了怎么写一些基础的客户端/服务端应用。我们已经避免了一些诸如内存泄漏和死锁的低级错误。所有的编码都是框架式的,这样你就可以根据你自己的需求对它们进行扩展。 在接下来的章节中,我们会更加深入地了解使用Boost.Asio进行同步编程和异步编程的不同点,同时你也会学会如何嵌入你自己的异步操作。 -##同步VS异步 +## 同步VS异步 Boost.Asio的作者做了一个很惊艳的工作:它可以让你在同步和异步中自由选择,从而更好地适应你的应用。 在之前的章节中,我们已经学习了各种类型应用的框架,比如同步客户端,同步服务端,异步客户端,异步服务端。它们中的每一个都可以作为你应用的基础。如果要更加深入地学习各种类型应用的细节,请继续。 -###混合同步异步编程 +### 混合同步异步编程 Boost.Asio库允许你进行同步和异步的混合编程。我个人认为这是一个坏主意,但是Boost.Asio(就像C++一样)在你需要的时候允许你深入底层。 通常来说,当你写一个异步应用时,你会很容易掉入这个陷阱。比如在响应一个异步*write*操作时,你做了一个同步*read*操作: -``` +```cpp io_service service; ip::tcp::socket sock(service); ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001); @@ -2505,7 +2507,7 @@ async_write(sock, buffer("echo"), on_write); 我唯一能想到的同步和异步同时工作的场景就是同步操作和异步操作是完全隔离的,比如,同步和异步从一个数据库进行读写。 -###从客户端传递信息到服务端VS从服务端传递信息到客户端 +### 从客户端传递信息到服务端VS从服务端传递信息到客户端 成功的客户端/服务端应用一个很重要的部分就是来回传递消息(服务端到客户端和客户端到服务端)。你需要指定用什么来标记一个消息。换句话说,当读取一个输入的消息时,你怎么判断它被完整读取了? 标记消息结尾的方式完全取决于你(标记消息的开始很简单,因为它就是前一个消息之后传递过来的第一个字节),但是要保证消息是简单且连续的。 @@ -2516,7 +2518,7 @@ async_write(sock, buffer("echo"), on_write); * 在消息的头部指定消息的大小 我在整本书中间采用的方式都是“使用’\n’标记消息的结尾”。所以,每次读取一条消息都会如下: -``` +```cpp char buff_[512]; // 同步读取 read(sock_, buffer(buff_), boost::bind(&read_complete, this, _1, _2)); @@ -2532,7 +2534,7 @@ size_t read_complete(const boost::system::error_code & err, size_t bytes) { ``` 我把在消息头部指定消息长度这种方式作为一个练习留给读者;这非常简单。 -###客户端应用中的同步I/O +### 客户端应用中的同步I/O 同步客户端一般都能归类到如下两种情况中的一种: * 它向服务端请求一些东西,读取结果,然后处理它们。然后请求一些其他的东西,然后一直持续下去。事实上,这很像之前章节里说到的同步客户端。 @@ -2542,7 +2544,7 @@ size_t read_complete(const boost::system::error_code & err, size_t bytes) { 两种情况都使用“发送请求-读取结果”的策略。换句话说,一个部分发送一个请求到另外一个部分然后另外一个部分返回结果。这是实现客户端/服务端应用非常简单的一种方式,同时这也是我非常推荐的一种方式。 -你可以创建一个*Mambo Jambo*类型的客户端服务端应用,你可以随心所欲地写它们中间的任何一个部分,但是这会导致一场灾难。(你怎么知道当客户端或者服务端阻塞的时候会发生什么?)。 +你可以创建一个 *Mambo Jambo* 类型的客户端服务端应用,你可以随心所欲地写它们中间的任何一个部分,但是这会导致一场灾难。(你怎么知道当客户端或者服务端阻塞的时候会发生什么?)。 上面的情况看上去会比较相似,但是它们非常不同: * 前者,服务端响应请求(服务端等待来自客户端的请求然后回应)。这是一个请求式连接,客户端从服务端拉取它需要的东西。 @@ -2562,7 +2564,7 @@ size_t read_complete(const boost::system::error_code & err, size_t bytes) { 在一个请求式客户端/服务端应用中,避免上面的情况是非常简单的。你可以通过实现一个ping操作的方式来模拟一个推送式请求,我们假设每5秒钟客户端ping一次服务端。如果没有事情需要通知,服务端返回一个类似*ping ok*的结果,如果有事情需要通知,服务端返回一个*ping [event_name]*。然后客户端就可以初始化一个新的请求去处理这个事件。 复习一下,第一种情况就是之前章节中的同步客户端应用,它的主循环如下: -``` +```cpp void loop() { // 对于我们登录操作的结果 write("login " + username_ + "\n"); @@ -2575,7 +2577,7 @@ void loop() { } ``` 我们对其进行修改以适应第二种情况: -``` +```cpp void loop() { while ( started_) { read_notification(); @@ -2591,20 +2593,20 @@ void process_notification() { // ... 看通知是什么,然后准备回复 } ``` -###服务端应用中的同步I/O +### 服务端应用中的同步I/O 类似客户端,服务端也被分为两种情况用来匹配之前章节中的情况1和情况2。同样,两种情况都采用“发送请求-读取结果”的策略。 ![](http://d.pcs.baidu.com/thumbnail/4aff201a379d1b5f6cf9da5bfce07bdd?fid=3238002958-250528-307717205941237&time=1420768800&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-6Q2ORNCx0wFDvBiqKwzta3be7Ic%3D&rt=sh&expires=2h&r=196560765&sharesign=unknown&size=c710_u500&quality=100) 第一种情况是我们在之前章节实现过的同步服务端。当你是同步时读取一个完整的请求不是很简单,因为你需要避免阻塞(通常来说是能读多少就读多少): -``` +```cpp void read_request() { if ( sock_.available()) } already_read_ += sock_.read_some(buffer(buff_ + already_read_, max_msg - already_read_)); ``` 只要一个消息被完整读到,就对它进行处理然后回复给客户端: -``` +```cpp void process_request() { bool found_enter = std::find(buff_, buff_ + already_read_, '\n') < buff_ + already_read_; if ( !found_enter) @@ -2618,7 +2620,7 @@ void process_request() { } ``` 如果我们想让服务端变成一个推送服务端,我们通过如下的方式修改: -``` +```cpp typedef std::vector array; array clients; array notify; @@ -2637,11 +2639,11 @@ void notify_clients() { } } ``` -*on_new_client()*方法是事件之一,这个事件我们需要通知所有的客户端。*notify_clients*是通知所有对一个事件感兴趣客户端的方法。它发送消息但是不等待每个客户端返回的结果,因为那样的话就会导致阻塞。当客户端返回一个结果时,客户端会告诉我们它为什么回复(然后我们就可以正确地处理它)。 -####同步服务端中的线程 +*on_new_client()* 方法是事件之一,这个事件我们需要通知所有的客户端。*notify_clients* 是通知所有对一个事件感兴趣客户端的方法。它发送消息但是不等待每个客户端返回的结果,因为那样的话就会导致阻塞。当客户端返回一个结果时,客户端会告诉我们它为什么回复(然后我们就可以正确地处理它)。 +#### 同步服务端中的线程 这是一个非常重要的关注点:我们开辟多少线程去处理服务端请求? 对于一个同步服务端,我们至少需要一个处理新连接的线程: -``` +```cpp void accept_thread() { ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001)); while ( true) { @@ -2663,8 +2665,8 @@ void accept_thread() { * 第一种情况,被同一个线程处理的几个客户端同时发送请求,因为一个线程在同一时刻只能处理一个请求。所以这种情况我们什么也不能做。 * 第二种情况,如果我们发现并发请求大于当前线程个数的时候。我们可以简单地创建新线程来处理当前的压力。 -下面的代码片段有点类似之前的*answer_to_client*方法,它向我们展示了第二种方法的实现方式: -``` +下面的代码片段有点类似之前的 *answer_to_client* 方法,它向我们展示了第二种方法的实现方式: +```cpp struct talk_to_client : boost::enable_shared_from_this { ... @@ -2677,7 +2679,7 @@ struct talk_to_client : boost::enable_shared_from_this }; ``` 我们需要对它进行修改使它变成下面代码片段的样子: -``` +```cpp struct talk_to_client : boost::enable_shared_from_this { boost::recursive_mutex cs; @@ -2703,8 +2705,8 @@ struct talk_to_client : boost::enable_shared_from_this } }; ``` -当我们在处理一个客户端请求的时候,它的*in_process*变量被设置成*true*,其他的线程就会忽略这个客户端。额外的福利就是*handle_clients_thread()*方法不需要做任何修改;你可以随心所欲地创建你想要数量的*handle_clients_thread()*方法。 -###客户端应用中的异步I/O +当我们在处理一个客户端请求的时候,它的 *in_process* 变量被设置成 *true*,其他的线程就会忽略这个客户端。额外的福利就是 *handle_clients_thread()* 方法不需要做任何修改;你可以随心所欲地创建你想要数量的*handle_clients_thread()*方法。 +### 客户端应用中的异步I/O 主流程和同步客户端应用有点类似,不同的是Boost.Asio每次都位于async_read和async_write请求中间。 ![](http://d.pcs.baidu.com/thumbnail/4431fc70c5c5a176aab77ed4d987bbee?fid=3238002958-250528-465126390059981&time=1420768800&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-QwJ4QJrMKJk%2Bzc7ffqY4IOqw2Es%3D&rt=sh&expires=2h&r=492020751&sharesign=unknown&size=c710_u500&quality=100) @@ -2712,7 +2714,7 @@ struct talk_to_client : boost::enable_shared_from_this 第一种情况是我在**第四章 客户端和服务端** 中实现过的。你应该还记得在每个异步操作结束的时候,我都启动另外一个异步操作,这样*service.run()*方法才不会结束。 为了适应第二种情况,你需要使用下面的代码片段: -``` +```cpp void on_connect() { do_read(); } @@ -2740,14 +2742,14 @@ void on_clients(const std::string & msg) { ![](http://d.pcs.baidu.com/thumbnail/ee2f4effd44e33822fc13cda59436f5c?fid=3238002958-250528-275385722128526&time=1420772400&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-CZwCGSbdzZ3ZnQj88OTAnBY8Fyc%3D&rt=sh&expires=2h&r=883620133&sharesign=unknown&size=c710_u500&quality=100) -###服务端应用的异步I/O +### 服务端应用的异步I/O 现在要展示的是两个普遍的情况,情况1(拉取)和情况2(推送) ![](http://d.pcs.baidu.com/thumbnail/8c4401c002293f790b56a0810652c29a?fid=3238002958-250528-53644841091289&time=1420772400&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-W8qD0tBIo0TDcNgk0icVoRTIneo%3D&rt=sh&expires=2h&r=395851440&sharesign=unknown&size=c710_u500&quality=100) 第一种情况同样是我在**第4章 客户端和服务端** 中实现的异步服务端。在每一个异步操作最后,我都会启动另外一个异步操作,这样的话service.run()就不会结束。 现在要展示的是被剪裁过的框架代码。下面是talk_to_client类所有的成员: -``` +```cpp void start() { ... do_read(); // first, we wait for client to login @@ -2770,10 +2772,10 @@ void do_write(const std::string & msg) { } void on_write(const error_code & err, size_t bytes) { do_read(); } ``` -简单来说,我们始终等待一个*read*操作,而且只要一发生,我们就处理然后将结果返回给客户端。 +简单来说,我们始终等待一个 *read* 操作,而且只要一发生,我们就处理然后将结果返回给客户端。 我们把上述代码进行修改就可以完成一个推送服务端 -``` +```cpp void start() { ... on_new_client_event(); @@ -2794,7 +2796,7 @@ void do_write(const std::string & msg) { void on_write(const error_code & err, size_t bytes) { do_read(); } ``` 只要有一个事件发生,我们假设*是on_new_client_event*,所有需要被通知到的客户端就都收到一条信息。当它们回复时,我们简单认为他们已经确认收到事件。注意我们永远不会把正在等待的异步操作用尽(所以,*service.run()*不会结束),因为我们一直在等待一个新的客户端: -``` +```cpp ip::tcp::acceptor acc(service, ip::tcp::endpoint(ip::tcp::v4(), 8001)); void handle_accept(talk_to_client::ptr client, const error_code & err) { @@ -2803,7 +2805,7 @@ void handle_accept(talk_to_client::ptr client, const error_code & err) acc.async_accept(new_client->sock(), bind(handle_accept,new_client,_1)); } ``` -####异步服务端中的多线程 +#### 异步服务端中的多线程 我在**第4章 客户端和服务端** 展示的异步服务端是单线程的,所有的事情都发生在main()中: ``` int main() { @@ -2814,7 +2816,7 @@ accept,client,_1)); } ``` 异步的美妙之处就在于可以非常简单地把单线程变为多线程。你可以一直保持单线程直到你的并发客户端超过200。然后,你可以使用如下的代码片段把单线程变成100个线程: -``` +```cpp boost::thread_group threads; void listen_thread() { service.run(); @@ -2834,7 +2836,7 @@ int main(int argc, char* argv[]) { 这也意味着对于一个客户端,只会有一个异步操作在等待。假如在某些情况,一个客户端有两个异步方法在等待,你就需要互斥量了。这是因为两个等待的操作可能正好在同一个时间完成,然后我们就会在两个不同的线程中间同时调用他们的完成处理函数。所以,这里需要线程安全,也就是需要使用互斥量。 在我们的异步服务端中,我们确实同时有两个等待的操作: -``` +```cpp void do_read() { async_read(sock_, buffer(read_buffer_),MEM_FN2(read_complete,_1,_2), MEM_FN2(on_read,_1,_2)); post_check_ping(); @@ -2851,7 +2853,7 @@ void post_check_ping() { 因为我们的例子需要是线程安全的,我已经把*talk_to_client*修改成使用互斥量的了。同时,我们也有一个客户端连接的列表,它也需要自己的互斥量,因为我们有时需要访问它。 避免死锁和内存冲突不是那么容易。下面是我需要对*update_client_changed()*方法进行修改的地方: -``` +```cpp void update_clients_changed() { array copy; { boost::recursive_mutex::scoped_lock lk(clients_cs); copy = clients; } @@ -2860,16 +2862,16 @@ void update_clients_changed() { } ``` 你需要避免的是同时有两个互斥量被锁定(这会导致死锁)。在我们的例子中,我们不想*clients_cs*和一个客户端的*cs_*互斥量同时被锁住 -###异步操作 +### 异步操作 Boost.Asio同样允许你异步地运行你任何一个方法。仅仅需要使用下面的代码片段: -``` +```cpp void my_func() { ... } service.post(my_func); ``` 这样就可以保证*my_func*在调用了*service.run()*方法的某个线程中间被调用。你同样可以异步地调用一个有完成处理handler的方法,方法的handler会在方法结束的时候通知你。伪代码如下: -``` +```cpp void on_complete() { ... } @@ -2880,7 +2882,7 @@ void my_func() { async_call(my_func); ``` 没有现成的*async_call*方法,因此,你需要自己创建。幸运的是,它不是很复杂,参考下面的代码片段: -``` +```cpp struct async_op : boost::enable_shared_from_this, ... { typedef boost::functioncompletion_func; typedef boost::function op_func; @@ -2912,7 +2914,7 @@ private: * 当第一个方法结束时被调用的一个完成处理handler * 会运行完成处理handler的io_service实例。这也是完成时通知你的地方。参考下面的代码: -``` +```cpp struct async_op : boost::enable_shared_from_this, private boost::noncopyable { struct operation { operation(io_service & service, op_func op, completion_func completion) : service(&service), op(op), completion(completion) , work(new io_service::work(service)) {} @@ -2927,7 +2929,7 @@ struct async_op : boost::enable_shared_from_this, private boost::nonco }; ``` 它们被*operation*结构体包含在内部。注意当有一个操作在等待时,我们在操作的构造方法中构造一个*io_service::work*实例,从而保证直到我们完成异步调用之前*service.run()*都不会结束(当*io_service::work*实例保持活动时,*service.run()*就会认为它有工作需要做)。参考下面的代码片段: -``` +```cpp struct async_op : ... { typedef boost::shared_ptr ptr; static ptr new_() { return ptr(new async_op); } @@ -2954,7 +2956,7 @@ struct async_op : ... { *run()*方法就是后台线程;它仅仅观察是否有工作需要做,如果有,就一个一个地运行这些异步方法。在每个调用结束的时候,它会调用相关的完成处理方法。 为了测试,我们创建一个会被异步执行的*compute_file-checksum*方法 -``` +```cpp size_t checksum = 0; boost::system::error_code compute_file_checksum(std::string file_name) { @@ -2983,7 +2985,7 @@ int main(int argc, char* argv[]) { 注意我展示给你的只是实现异步调用一个方法的一种可能。除了像我这样实现一个后台线程,你可以使用一个内部*io_service*实例,然后推送(*post()*)异步方法给这个实例调用。这个作为一个练习留给读者。 你也可以扩展这个类让其可以展示一个异步操作的进度(比如,使用百分比)。这样做你就可以在主线程通过一个进度条来显示进度。 -###代理实现 +### 代理实现 代理一般位于客户端和服务端之间。它接受客户端的请求,可能会对请求进行修改,然后接着把请求发送到服务端。然后从服务端取回结果,可能也会对结果进行修改,然后接着把结果发送到客户端。 ![](http://d.pcs.baidu.com/thumbnail/0d46b36ce96944c80215c9d222b320d7?fid=3238002958-250528-198047002317925&time=1420772400&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-TCr9tmAKwz40%2F9XC%2FhzcAzQbtN8%3D&rt=sh&expires=2h&r=321797592&sharesign=unknown&size=c710_u500&quality=100) @@ -2996,7 +2998,7 @@ int main(int argc, char* argv[]) { * 在我们的方案中,我们在构造函数中能拿到两个连接。但不是所有的情况都这样,比如对于一个web代理来说,客户端只告诉我们服务端的地址。 * 因为比较简单,所以不是线程安全的。参考如下的代码: -``` +```cpp class proxy : public boost::enable_shared_from_this { proxy(ip::tcp::endpoint ep_client, ip::tcp::endpoint ep_server) : ... {} public: @@ -3029,7 +3031,7 @@ private: }; ``` 这是个非常简单的代理。当我们两个端都连接时,它开始从两个端读取(*on_start()*方法): -``` +```cpp class proxy : public boost::enable_shared_from_this { ... void on_read(ip::tcp::socket & sock, const error_code& err, size_t bytes) { @@ -3056,7 +3058,7 @@ class proxy : public boost::enable_shared_from_this { 对每一个成功的读取操作(*on_read*),它都会发送消息到另外一个部分。只要消息一发送成功(*on_write*),我们就从来源那部分再次读取。 使用下面的代码片段让这个流程运转起来: -``` +```cpp int main(int argc, char* argv[]) { ip::tcp::endpoint ep_c(ip::address::from_string("127.0.0.1"),8001); ip::tcp::endpoint ep_s(ip::address::from_string("127.0.0.1"),8002); @@ -3071,7 +3073,7 @@ int main(int argc, char* argv[]) { 我会把这个当作练习留给你们。 -###小结 +### 小结 在选择同步或者异步时需要考虑很多事情。最先需要考虑的就是避免混淆它们。 在这一章中,我们已经看到: @@ -3083,9 +3085,9 @@ int main(int argc, char* argv[]) { 接下来,我们会了解一些Boost.Asio不那么为人知晓的特性,中间就有我最喜欢的Boost.Asio特性-协程,它可以让你轻松地取异步之精华,去异步之糟粕。 -##Boost.Asio-其他特性 +## Boost.Asio-其他特性 这章我们讲了解一些Boost.Asio不那么为人所知的特性。标准的stream和streambuf对象有时候会更难用一些,但正如你所见,它们也有它们的益处。最后,你会看到姗姗来迟的Boost.Asio协程的入口,它可以让你的异步代码变得非常易读。这是非常惊人的一个特性。 -###标准stream和标准I/O buffer +### 标准stream和标准I/O buffer 读这一章节之前你需要对STL stream和STL streambuf对象有所了解。 Boost.Asio在处理I/O操作时支持两种类型的buffer: @@ -3093,7 +3095,7 @@ Boost.Asio在处理I/O操作时支持两种类型的buffer: * *boost::asio::streambuf*:这个buffer继承自*std::streambuf*,在网络编程中可以和STL stream一起使用 纵观全书,之前的例子中最常见的例子如下: -``` +```cpp size_t read_complete(boost::system::error_code, size_t bytes){ ... } char buff[1024]; read(sock, buffer(buff), read_complete); @@ -3102,21 +3104,21 @@ write(sock, buffer("echo\n")); 通常来说使用这个就能满足你的需要,如果你想要更复杂,你可以使用*streambuf*来实现。 这个就是你可以用*streambuf*对象做的最简单也是最坏的事情: -``` +```cpp streambuf buf; read(sock, buf); ``` 这个会一直读到*streambuf*对象满了,然后因为*streambuf*对象可以通过自己重新开辟空间从而获取更多的空间,它基本会读到连接被关闭。 你可以使用*read_until*一直读到一个特定的字符串: -``` +```cpp streambuf buf; read_until(sock, buf, "\n"); ``` -这个例子会一直读到一个“\n”为止,把它添加到*buffer*的末尾,然后退出*read*方法。 +这个例子会一直读到一个“\n”为止,把它添加到 *buffer* 的末尾,然后退出 *read* 方法。 向一个*streambuf*对象写一些东西,你需要做一些类似下面的事情: -``` +```cpp streambuf buf; std::ostream out(&buf); out << "echo" << std::endl; @@ -3124,11 +3126,11 @@ write(sock, buf); ``` 这是非常直观的;你在构造函数中传递你的*streambuf*对象来构建一个STL stream,将其写入到你想要发送的消息中,然后使用*write*来发送buffer的内容。 -###Boost.Asio和STL stream +### Boost.Asio和STL stream Boost.Asio在集成STL stream和网络方面做了很棒的工作。也就是说,如果你已经在使用STL扩展,你肯定就已经拥有了大量重载了操作符<<和>>的类。从socket读或者写入它们就好像在公园漫步一样简单。 假设你有下面的代码片段: -``` +```cpp struct person { std::string first_name, last_name; int age; @@ -3141,7 +3143,7 @@ std::istream& operator>>(std::istream & in, person & p) { } ``` 通过网络发送这个*person*就像下面的代码片段这么简单: -``` +```cpp streambuf buf; std::ostream out(&buf); person p; @@ -3150,7 +3152,7 @@ out << p << std::endl; write(sock, buf); ``` 另外一个部分也可以非常简单的读取: -``` +```cpp read_until(sock, buf, "\n"); std::istream in(&buf); person p; @@ -3161,20 +3163,20 @@ in >> p; * 同样,在另外一个部分,当读取一个消息时,你需要解析它,也就是说,读取到一个片段的数据时,如果这个数据不是字符串,你需要将它转换为字符串。当你使用>>操作符读取一些东西时这些也是默认就做了的。 最后要给出的是一个非常著名,非常酷的诀窍,使用下面的代码片段把*streambuf*的内容输出到console中 -``` +```cpp streambuf buf; ... std::cout << &buf << std::endl; //把所有内容输出到console中 ``` -同样的,使用下面的代码片段来把它的内容转换为一个*string* -``` +同样的,使用下面的代码片段来把它的内容转换为一个 *string* +```cpp std::string to_string(streambuf &buf) { std::ostringstream out; out << &buf; return out.str(); } ``` -###streambuf类 +### streambuf类 我之前说过,*streambuf*继承自*std::streambuf*。就像*std::streambuf*本身,它不能拷贝构造。 另外,它有一些额外的方法,如下: @@ -3187,35 +3189,35 @@ std::string to_string(streambuf &buf) { * *max_size()*:这个方法返回最多能保存的字节数。 除了最后的两个方法,其他的方法不是那么容易理解。首先,大部分时间你会把*streambuf*以参数的方式传递给*read/write*自由函数,就像下面的代码片段展示的一样: -``` +```cpp read_until(sock, buf, "\n"); // 读取到buf中 write(sock, buf); // 从buf写入 ``` 如果你想之前的代码片段展示的一样把整个buffer都传递到一个自由函数中,方法会保证把buffer的输入输出指针指向的位置进行增加。也就是说,如果有数据需要读,你就能读到它。比如: -``` +```cpp read_until(sock, buf, '\n'); std::cout << &buf << std::endl; ``` 上述代码会把你刚从socket写入的东西输出。而下面的代码不会输出任何东西: -``` +```cpp read(sock, buf.prepare(16), transfer_exactly(16) ); std::cout << &buf << std::endl; ``` 字节被读取了,但是输入指针没有移动,你需要自己移动它,就像下面的代码片段所展示的: -``` +```cpp read(sock, buf.prepare(16), transfer_exactly(16) ); buf.commit(16); std::cout << &buf << std::endl; ``` 同样的,假设你需要从*streambuf*对象中写入,如果你使用了*write*自由函数,则需要像下面一样: -``` +```cpp streambuf buf; std::ostream out(&buf); out << "hi there" << std::endl; write(sock, buf); ``` 下面的代码会把hi there发送三次: -``` +```cpp streambuf buf; std::ostream out(&buf); out << "hi there" << std::endl; @@ -3223,7 +3225,7 @@ for ( int i = 0; i < 3; ++i) write(sock, buf.data()); ``` 发生的原因是因为buffer从来没有被消耗过,因为数据还在。如果你想消耗它,使用下面的代码片段: -``` +```cpp streambuf buf; std::ostream out(&buf); out << "hi there" << std::endl; @@ -3234,7 +3236,7 @@ buf.consume(9); 尽管你可以在读和写操作时使用同一个*streambuf*,你仍然建议你分开使用两个,一个读另外一个写,它会让事情变的简单,清晰,同时你也会减少很多导致bug的可能。 -###处理streambuf对象的自由函数 +### 处理streambuf对象的自由函数 下面列出了Boost.Asio中处理streambuf对象的自由函数: * *read(sock, buf[, completion_function])*:这个方法把内容从socket读取到*streambuf*对象中。*completion*方法是可选的。如果有,它会在每次*read*操作成功之后被调用,然后告诉Boost.Asio这个操作是否完成(如果没有,它继续读取)。它的格式是:*size_t completion(const boost::system::error_code & err, size_t bytes_transfered);*,如果*completion*方法返回0,我们认为*read*操作完成了,如果非0,它表示下一次调用stream的*read_some*方法需要读取的最大的字节数。 * *read_at(random_stream, offset, buf [, completion_function])*: 这个方法从一个支持随机读取的stream中读取。注意它没有被应用到socket中(因为他们没有随机读取的模型,它们是单向的,一直向前)。 @@ -3248,7 +3250,7 @@ buf.consume(9); * *async_write_at(random_stream,offset, buf [, completion_function] , handler)*: 这个方法是*write_at()*的异步实现。 我们假设你需要一直读取直到读到一个元音字母: -``` +```cpp streambuf buf; bool is_vowel(char c) { return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'; @@ -3267,11 +3269,11 @@ read(sock, buf, read_complete); 这里需要注意的事情是对*read_complete()*中buffer的访问,也就是*buffer_cast<>*和*buf.data*。 如果你使用正则,上面的例子会更简单: -``` +```cpp read_until(sock, buf, boost::regex("^[aeiou]+") ); ``` 或者我们修改例子来让*match_condition*方法工作起来: -``` +```cpp streambuf buf; bool is_vowel(char c) { return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'; @@ -3286,11 +3288,11 @@ std::pair match_vowel(iterator b, iterator e) { size_t bytes = read_until(sock, buf, match_vowel); ``` 当使用*read_until*时会有个难点:你需要记住你已经读取的字节数,因为下层的buffer可能多读取了一些字节(不像使用*read()*时)。比如: -``` +```cpp std::cout << &buf << std::endl; ``` 上述代码输出的字节可能比*read_until*读取到的多。 -###协程 +### 协程 Boost.Asio的作者在2009-2010年间实现了非常酷的一个部分,协程,它能让你更简单地设计你的异步应用。 它们可以让你同时享受同步和异步两个世界中最好的部分,也就是:异步编程但是很简单就能遵循流程控制,就好像应用是按流程实现的。 @@ -3308,7 +3310,7 @@ Boost.Asio的作者在2009-2010年间实现了非常酷的一个部分,协程 为了更好的理解,我们来看一个例子。我们会重新实现 **第四章 异步客户端** 中的应用,这是一个可以登录,ping,然后能告诉你其他已登录客户端的简单客户端应用。 核心代码和下面的代码片段类似: -``` +```cpp class talk_to_svr : public boost::enable_shared_from_this, public coroutine, boost::noncopyable { ... void step(const error_code & err = error_code(), size_t bytes = 0) { @@ -3330,7 +3332,7 @@ class talk_to_svr : public boost::enable_shared_from_this, public c 在*reenter*代码块中,你会发现几个*yield*声明。你第一次进入方法时,*async_write*方法被执行,第二次*async_read_until*方法被执行,第三次*service.post*方法被执行,然后第四次*async_write*方法被执行,然后一直循环下去。 你需要一直记住*for(;;){}*实例。参考下面的代码片段: -``` +```cpp void step(const error_code & err = error_code(), size_t bytes = 0) { reenter(this) { yield async_write(sock_, write_buffer_, MEM_FN2(step,_1,_2) ); @@ -3340,7 +3342,7 @@ void step(const error_code & err = error_code(), size_t bytes = 0) { } ``` 如果我们第三次使用上述的代码片段,我们会进入方法然后执行*service.post*。当我们第四次进入方法时,我们跳过*service.post*,不执行任何东西。当执行第五次时仍然不执行任何东西,然后一直这样下去: -``` +```cpp class talk_to_svr : public boost::enable_shared_from_this, public coroutine, boost::noncopyable { talk_to_svr(const std::string & username) : ... {} void start(ip::tcp::endpoint ep) { @@ -3391,7 +3393,7 @@ private: 在那之后,我们调用*async_write*,然后调用*async_read_until*,再处理消息(*on_answer_from_server*)。 我们在*on_answer_from_server*处理接收到的消息;我们读取第一个字符,然后把它分发到相应的方法。剩下的消息(如果还有一些消息没读完)我们都忽略掉: -``` +```cpp class talk_to_svr : ... { ... void on_login() { do_ask_clients(); } @@ -3424,7 +3426,7 @@ class talk_to_svr : ... { 完整的例子还会更复杂一点,因为我们需要随机地ping服务端。实现这个功能我们需要在第一次请求客户端列表完成之后做一个ping操作。然后,在每个从服务端返回的ping操作的结果中,我们做另外一个ping操作。 使用下面的代码片段来执行整个过程: -``` +```cpp int main(int argc, char* argv[]) { ip::tcp::endpoint ep(ip::address::from_string("127.0.0.1"),8001); talk_to_svr::start(ep, "John"); @@ -3434,18 +3436,18 @@ int main(int argc, char* argv[]) { 使用协程,我们节约了15行代码,而且代码也变的更加易读。 在这里我们仅仅接触了协程的一点皮毛。如果你想要了解更多,请登录作者的个人主页:[http://blog.think-async.com/2010_03_01_archive.html](http://blog.think-async.com/2010_03_01_archive.html)。 -###总结 +### 总结 我们已经了解了如何使用Boost.Asio玩转STL stream和streambuf对象。我们也了解了如何使用协程来让我们的代码更加简洁和易读。 下面就是重头戏了,比如Asio VS Boost.Asio,高级调试,SSL和平台相关特性。 -##Boost.Asio-进阶话题 +## Boost.Asio-进阶话题 这一章对Boost.Asio的一些进阶话题进行了阐述。在日常编程中深入研究这些问题是不太可能的,但是知道这些肯定是有好处的: * 如果调试失败,你需要看Boost.Asio能帮到你什么 * 如果你需要处理SSL,看Boost.Asio能帮你多少 * 如果你指定一个操作系统,看Boost.Asio为你准备了哪些额外的特性 -###Asio VS Boost.Asio +### Asio VS Boost.Asio Boost.Asio的作者也保持了Asio。你可以用Asio的方式来思考,因为它在两种情况中都有:Asio(非Boost的)和Boost.Asio。作者声明过更新都会先在非Boost中出现,然后过段时间后,再加入到Boost的发布中。 不同点被归纳到下面几条: @@ -3462,13 +3464,13 @@ Boost.Asio的作者也保持了Asio。你可以用Asio的方式来思考,因 * Asio和Boost.Asio都是非常成熟的,所以除非你非常需要一些Asio新发布的特性,Boost.Asio是非常保险的选择,而且你也可以同时拥有其他Boost库的资源 尽管我不推荐这样,你可以在一个应用中同时使用Asio和Boost.Asio。在允许的情况下这是很自然的,比如你使用Asio,然后一些第三方库是Boost.Asio,反之亦然。 -###调试 +### 调试 调试同步应用往往比调试异步应用要简单。对于同步应用,如果阻塞了,你会跳转进入调试,然后你会知道你在哪(同步意味着有序的)。然而如果是异步,事件不是有序发生的,所以在调试中是非常难知道到底发生了什么的。 为了避免这种情况,首先,你需要深入了解协程。如果实现正确,基本上你一点也不会碰到异步调试的问题。 以防万一,在做异步编码的时候,Boost.Asio还是对你伸出了援手;Boost.Asio允许“句柄追踪”,当*BOOST_ASIO_ENABLE_HANDLER_TRACKING*被定义时,Boost.Asio会写很多辅助的输出到标准错误流,纪录时间,异步操作,以及操作和完成处理handler的关系。 -####句柄追踪信息 +#### 句柄追踪信息 虽然输出信息不是那么容易理解,但是有总比没有好。Boost.Asio的输出是*@asio|||* 。 第一个标签永远都是*@asio*,因为其他代码也会输出到标准错误流(和*std::error*相当),所以你可以非常简单的用这个标签过滤从Boost.Asio打印出来的信息。*timestamp*实例从1970年1月1号到现在的秒数和毫秒数。*action*实例可以是下面任何一种: @@ -3482,9 +3484,9 @@ Boost.Asio的作者也保持了Asio。你可以用Asio的方式来思考,因 当*n*是0时,操作是在所有(异步)handler之外被执行的;你经常会在第一个操作时看到这个,或者当你使用的信号量其中一个被触发时。 你需要特别注意类型为*!n*和*-n*的信息,这些信息大部分都意味着你的代码有错误。在第一种情形中,异步方法没有抛出异常,所以,异常一定是你自己造成的;你不能让异常跑出你的*completion*句柄。第二种情形中,你可能太早就销毁了*io_service*实例,在所有完成处理句被调用之前。 -####一个例子 +#### 一个例子 为了向你展示一个带辅助输出信息的例子,我们修改了在**第六章 Boost.Asio其他特性** 中使用的例子。你所需要做的仅仅是在包含*boost/asio.hpp*之前添加一个*#define* -``` +```cpp #define BOOST_ASIO_ENABLE_HANDLER_TRACKING #include ... @@ -3542,15 +3544,15 @@ John, new client list: John * 句柄8进去,然后输出客户端列表(*on_clients*) 这需要时间去理解,但是一旦你理解了,你就可以分辨出有问题的输出,从而找出需要被修复的那段代码。 -####句柄追踪信息输出到文件 +#### 句柄追踪信息输出到文件 默认情况下,句柄的追踪信息被输出到标准错误流(相当于*std::cerr*)。而把输出重定向到其他地方的可能性是非常高的。对于控制台应用,输出和错误输出都被默认输出到相同的地方,也就是控制台。但是对于一个windows(非命令行)应用来说,默认的错误流是null。 你可以通过命令行把错误输出重定向,比如: -``` +```shell some_application 2>err.txt ``` 或者,如果你不是很懒,你可以代码实现,就像下面的代码片段 -``` +```cpp // 对于Windows HANDLE h = CreateFile("err.txt", GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL , 0); @@ -3559,18 +3561,18 @@ SetStdHandle(STD_ERROR_HANDLE, h); int err_file = open("err.txt", O_WRONLY); dup2(err_file, STDERR_FILENO); ``` -###SSL -Boost.Asio提供了一些支持基本SSL的类。它在幕后使用的其实是OpenSSL,所以,如果你想使用SSL,首先从[www.openssl.org](www.openssl.org)下载OpenSSL然后构建它。你需要注意,构建OpenSSL通常来说不是一个简单的任务,尤其是你没有一个常用的编译器,比如Visual Studio。 +### SSL +Boost.Asio提供了一些支持基本SSL的类。它在幕后使用的其实是OpenSSL,所以,如果你想使用SSL,首先从[www.openssl.org](https://www.openssl.org)下载OpenSSL然后构建它。你需要注意,构建OpenSSL通常来说不是一个简单的任务,尤其是你没有一个常用的编译器,比如Visual Studio。 假如你成功构建了OpenSSL,Boost.Asio就会有一些围绕它的封装类: -* *ssl::stream*:它代替*ip:::socket*来告诉你用什么 +* *ssl::stream*:它代替 *`ip::::socket`* 来告诉你用什么 * *ssl::context*:这是给第一次握手用的上下文 * *ssl::rfc2818_verification*:使用这个类可以根据RFC 2818协议非常简单地通过证书认证一个主机名 首先,你创建和初始化SSL上下文,然后使用这个上下文打开一个连接到指定远程主机的socket,然后做SSL握手。握手一结束,你就可以使用Boost.Asio的*read*/write**等自由函数。 下面是一个连接到Yahoo!的HTTPS客户端例子: -``` +```cpp #include #include using namespace boost::asio; @@ -3604,11 +3606,11 @@ int main(int argc, char* argv[]) { 当实现SSL服务端的时候,事情会变的有点复杂。Boost.Asio有一个SSL服务端的例子,你可以在*boost/libs/asio/example/ssl/server.cpp*中找到。 -###Boost.Asio的Windows特性 +### Boost.Asio的Windows特性 接下来的特性只赋予Windows操作系统 -####流处理 +#### 流处理 Boost.Asio允许你在一个Windows句柄上创建封装,这样你就可以使用大部分的自由函数,比如*read(),read_until(),write(),async_read(),async_read_until()*和*async_write()*。下面告诉你如何从一个文件读取一行: -``` +```cpp HANDLE file = ::CreateFile("readme.txt", GENERIC_READ, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0); windows::stream_handle h(service, file); streambuf buf; @@ -3619,9 +3621,9 @@ std::getline(in, line); std::cout << line << std::endl; ``` *stream_handle*类只有在I/O完成处理端口正在被使用的情况下才有效(这是默认情况)。如果情况满足,*BOOST_ASIO_HAS_WINDOWS_STREAM_HANDLE*就被定义 -####随机访问句柄 +#### 随机访问句柄 Boost.Asio允许对一个指向普通文件的句柄进行随机读取和写入。同样,你为这个句柄创建一个封装,然后使用自由函数,比如*read_at(),write_at(),async_read_at(),async_write_at()*。要从1000的地方读取50个字节,你需要使用下面的代码片段: -``` +```cpp HANDLE file = ::CreateFile("readme.txt", GENERIC_READ, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0); windows::random_access_handle h(service, file); char buf[50]; @@ -3632,9 +3634,9 @@ std::cout << msg << std::endl; 对于Boost.Asio,随机访问句柄只提供随机访问,你不能把它们当作流句柄使用。也就是说,自由函数,比如:*read(),read_until(),write()*以及他们的相对的异步方法都不能在一个随机访问的句柄上使用。 *random_access_handle*类只有在I/O完成处理端口在使用中才有效(这是默认情况)。如果情况满足,*BOOST_ASIO_HAS_WINDOWS_RANDOM_ACCESS_HANDLE*就被定义 -####对象句柄 +#### 对象句柄 你可以通过Windows句柄等待内核对象,比如修改通知,控制台输入,事件,内存资源通知,进程,信号量,线程或者可等待的计时器。或者简单来说,所有可以调用*WaitForSingleObject*的东西。你可以在它们上面创建一个*object_handle*封装,然后在上面使用*wait()*或者*async_wait()*: -``` +```cpp void on_wait_complete(boost::system::error_code err) {} ... HANDLE evt = ::CreateEvent(0, true, true, 0); @@ -3644,21 +3646,21 @@ h.wait(); // 异步等待 h.async_wait(on_wait_complete); ``` -###Boost.Asio POSIX特性 +### Boost.Asio POSIX特性 这些特性只在Unix操作系统上可用 -####本地socket +#### 本地socket Boost.Asio提供了对本地socket的基本支持(也就是著名的Unix 域socket)。 本地socket是一种只能被运行在主机上的应用访问的socket。你可以使用本地socket来实现简单的进程间通讯,连接两端的方式是把一个当作客户端而另一个当作服务端。对于本地socket,端点是一个文件,比如*/tmp/whatever*。很酷的一件事情是你可以给指定的文件赋予权限,从而禁止机器上指定的用户在文件上创建socket。 你可以用客户端socket的方式连接,如下面的代码片段: -``` +```cpp local::stream_protocol::endpoint ep("/tmp/my_cool_app"); local::stream_protocol::socket sock(service); sock.connect(ep); ``` 你可以创建一个服务端socket,如下面的代码片段: -``` +```cpp ::unlink("/tmp/my_cool_app"); local::stream_protocol::endpoint ep("/tmp/my_cool_app"); local::stream_protocol::acceptor acceptor(service, ep); @@ -3668,9 +3670,9 @@ acceptor.accept(sock); 只要socket被成功创建,你就可以像用普通socket一样使用它;它和其他socket类有相同的成员方法,而且你也可以在使用了socket的自由函数中使用。 注意本地socket只有在目标操作系统支持它们的时候才可用,也就是*BOOST_ASIO_HAS_LOCAL_SOCKETS*(如果被定义) -####连接本地socket +#### 连接本地socket 最终,你可以连接两个socket,或者是无连接的(数据报),或者是基于连接的(流): -``` +```cpp // 基于连接 local::stream_protocol::socket s1(service); local::stream_protocol::socket s2(service); @@ -3681,12 +3683,12 @@ local::datagram_protocol::socket s2(service); local::connect_pair(s1, s2); ``` 在内部,*connect_pair*使用的是不那么著名的*POSIX socketpair()*方法。基本上它所作的事情就是在没有复杂socket创建过程的情况下连接两个socket;而且只需要一行代码就可以完成。这在过去是实现线程通信的一种简单方式。而在现代编程中,你可以避免它,然后你会发现在处理使用了socket的遗留代码时它非常有用。 -####POSIX文件描述符 +#### POSIX文件描述符 Boost.Asio允许在一些POSIX文件描述符,比如管道,标准I/O和其他设备(但是不是在普通文件上)上做一些同步和异步的操作。 一旦你为这样一个POSIX文件描述符创建了一个*stream_descriptor*实例,你就可以使用一些Boost.Asio提供的自由函数。比如*read(),read_until(),write(),async_read(),async_read_until()*和*async_write()*。 下面告诉你如何从stdin读取一行然后输出到stdout: -``` +```cpp size_t read_up_to_enter(error_code err, size_t bytes) { ... } posix::stream_descriptor in(service, ::dup(STDIN_FILENO)); posix::stream_descriptor out(service, ::dup(STDOUT_FILENO)); @@ -3695,9 +3697,9 @@ int bytes = read(in, buffer(buff), read_up_to_enter); write(out, buffer(buff, bytes)); ``` *stream_descriptor*类只在目标操作系统支持的情况下有效,也就是*BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR*(如果定义了) -####Fork +#### Fork Boost.Asio支持在程序中使用*fork()*系统调用。你需要告诉*io_service*实例*fork()*方法什么时候会发生以及什么时候发生了。参考下面的代码片段: -``` +```cpp service.notify_fork(io_service::fork_prepare); if (fork() == 0) { // 子进程 @@ -3711,7 +3713,7 @@ if (fork() == 0) { ``` 这意味着会在不同的线程使用即将被调用的*service*。尽管Boost.Asio允许这样,我还是强烈推荐你使用多线程,因为使用*boost::thread*简直就是小菜一碟。 -###总结 +### 总结 为简单明了的代码而奋斗。学习和使用协程会最小化你需要做的调试工作,但仅仅是在代码中有潜在bug的情况下,Boost.Asio才会伸出援手,这一点在关于调试的章节中就已经讲过。 如果你需要使用SSL,Boost.Asio是支持基本的SSL编码的