Boost asio 官方教程简介
1. 概述
?本章介紹了 Boost C++ 庫 Asio,它是異步輸入輸出的核心。 名字本身就說明了一切:Asio 意即異步輸入/輸出。 該庫可以讓 C++ 異步地處理數(shù)據(jù),且平臺獨(dú)立。 異步數(shù)據(jù)處理就是指,任務(wù)觸發(fā)后不需要等待它們完成。 相反,Boost.Asio 會在任務(wù)完成時(shí)觸發(fā)一個(gè)應(yīng)用。 異步任務(wù)的主要優(yōu)點(diǎn)在于,在等待任務(wù)完成時(shí)不需要阻塞應(yīng)用程序,可以去執(zhí)行其它任務(wù)。
?
?異步任務(wù)的典型例子是網(wǎng)絡(luò)應(yīng)用。 如果數(shù)據(jù)被發(fā)送出去了,比如發(fā)送至 Internet,通常需要知道數(shù)據(jù)是否發(fā)送成功。 如果沒有一個(gè)象 Boost.Asio 這樣的庫,就必須對函數(shù)的返回值進(jìn)行求值。 但是,這樣就要求待至所有數(shù)據(jù)發(fā)送完畢,并得到一個(gè)確認(rèn)或是錯(cuò)誤代碼。 而使用 Boost.Asio,這個(gè)過程被分為兩個(gè)單獨(dú)的步驟:第一步是作為一個(gè)異步任務(wù)開始數(shù)據(jù)傳輸。 一旦傳輸完成,不論成功或是錯(cuò)誤,應(yīng)用程序都會在第二步中得到關(guān)于相應(yīng)的結(jié)果通知。 主要的區(qū)別在于,應(yīng)用程序無需阻塞至傳輸完成,而可以在這段時(shí)間里執(zhí)行其它操作。
?
2. I/O 服務(wù)與 I/O 對象
使用 Boost.Asio 進(jìn)行異步數(shù)據(jù)處理的應(yīng)用程序基于兩個(gè)概念:I/O 服務(wù)和 I/O 對象。 I/O 服務(wù)抽象了操作系統(tǒng)的接口,允許第一時(shí)間進(jìn)行異步數(shù)據(jù)處理,而 I/O 對象則用于初始化特定的操作。 鑒于 Boost.Asio 只提供了一個(gè)名為 boost::asio::io_service 的類作為 I/O 服務(wù),它針對所支持的每一個(gè)操作系統(tǒng)都分別實(shí)現(xiàn)了優(yōu)化的類,另外庫中還包含了針對不同 I/O 對象的幾個(gè)類。 其中,類 boost::asio::ip::tcp::socket 用于通過網(wǎng)絡(luò)發(fā)送和接收數(shù)據(jù),而類 ?boost::asio::deadline_timer 則提供了一個(gè)計(jì)時(shí)器,用于測量某個(gè)固定時(shí)間點(diǎn)到來或是一段指定的時(shí)長過去了。 以下第一個(gè)例子中就使用了計(jì)時(shí)器,因?yàn)榕c Asio 所提供的其它 I/O 對象相比較而言,它不需要任何有關(guān)于網(wǎng)絡(luò)編程的知識。
#include <iostream>?? #include <boost/asio.hpp>?void handler(const boost::system::error_code &ec)?{?std::cout << "5 s." << std::endl;?}?int main()?{?boost::asio::io_service io_service;?boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(5));?timer.async_wait(handler);?io_service.run();?}?
?函數(shù) main() 首先定義了一個(gè) I/O 服務(wù) io_service,用于初始化 I/O 對象 timer。 就象 boost::asio::deadline_timer 那樣,所有 I/O 對象通常都需要一個(gè) I/O 服務(wù)作為它們的構(gòu)造函數(shù)的第一個(gè)參數(shù)。 由于 timer 的作用類似于一個(gè)鬧鐘,所以 boost::asio::deadline_timer 的構(gòu)造函數(shù)可以傳入第二個(gè)參數(shù),用于表示在某個(gè)時(shí)間點(diǎn)或是在某段時(shí)長之后鬧鐘停止。 以上例子指定了五秒的時(shí)長,該鬧鐘在 timer 被定義之后立即開始計(jì)時(shí)。
?
?雖然我們可以調(diào)用一個(gè)在五秒后返回的函數(shù),但是通過調(diào)用方法 async_wait() 并傳入 handler() 函數(shù)的名字作為唯一參數(shù),可以讓 Asio 啟動一個(gè)異步操作。 請留意,我們只是傳入了 handler() 函數(shù)的名字,而該函數(shù)本身并沒有被調(diào)用。
?
?async_wait() 的好處是,該函數(shù)調(diào)用會立即返回,而不是等待五秒鐘。 一旦鬧鐘時(shí)間到,作為參數(shù)所提供的函數(shù)就會被相應(yīng)調(diào)用。 因此,應(yīng)用程序可以在調(diào)用了 async_wait() 之后執(zhí)行其它操作,而不是阻塞在這里。
?
?象 async_wait() 這樣的方法被稱為是非阻塞式的。 I/O 對象通常還提供了阻塞式的方法,可以讓執(zhí)行流在特定操作完成之前保持阻塞。 例如,可以調(diào)用阻塞式的 wait() 方法,取代 boost::asio::deadline_timer 的調(diào)用。 由于它會阻塞調(diào)用,所以它不需要傳入一個(gè)函數(shù)名,而是在指定時(shí)間點(diǎn)或指定時(shí)長之后返回。
?
?再看看上面的源代碼,可以留意到在調(diào)用 async_wait() 之后,又在 I/O 服務(wù)之上調(diào)用了一個(gè)名為 run() 的方法。這是必須的,因?yàn)榭刂茩?quán)必須被操作系統(tǒng)接管,才能在五秒之后調(diào)用 handler() 函數(shù)。
?
?async_wait() 會啟動一個(gè)異步操作并立即返回,而 run() 則是阻塞的。因此調(diào)用 run() 后程序執(zhí)行會停止。 具有諷刺意味的是,許多操作系統(tǒng)只是通過阻塞函數(shù)來支持異步操作。 以下例子顯示了為什么這個(gè)限制通常不會成為問題。如果不想阻塞,可以使用 poll()。 Using?io_service::poll?instead of?io_service::run?is perfectly acceptable. The difference is explained in the?documentation
The poll() function may also be used to dispatch ready handlers, but without blocking.
#include <boost/asio.hpp>?#include <iostream>?void handler1(const boost::system::error_code &ec)?{?std::cout << "5 s." << std::endl;?}?void handler2(const boost::system::error_code &ec)?{?std::cout << "10 s." << std::endl;?}?int main()?{?boost::asio::io_service io_service;?boost::asio::deadline_timer timer1(io_service, boost::posix_time::seconds(5));?timer1.async_wait(handler1);?boost::asio::deadline_timer timer2(io_service, boost::posix_time::seconds(10));?timer2.async_wait(handler2);?io_service.run();?}?
上面的程序用了兩個(gè) boost::asio::deadline_timer 類型的 I/O 對象。 第一個(gè) I/O 對象表示一個(gè)五秒后觸發(fā)的鬧鐘,而第二個(gè)則表示一個(gè)十秒后觸發(fā)的鬧鐘。 每一段指定時(shí)長過去后,都會相應(yīng)地調(diào)用函數(shù) handler1() 和 handler2()。
?
?在 main() 的最后,再次在唯一的 I/O 服務(wù)之上調(diào)用了 run() 方法。 如前所述,這個(gè)函數(shù)將阻塞執(zhí)行,把控制權(quán)交給操作系統(tǒng)以接管異步處理。 在操作系統(tǒng)的幫助下,handler1() 函數(shù)會在五秒后被調(diào)用,而 handler2() 函數(shù)則在十秒后被調(diào)用。
?
?乍一看,你可能會覺得有些奇怪,為什么異步處理還要調(diào)用阻塞式的 run() 方法。 然而,由于應(yīng)用程序必須防止被中止執(zhí)行,所以這樣做實(shí)際上不會有任何問題。 如果 run() 不是阻塞的,main() 就會結(jié)束從而中止該應(yīng)用程序。 如果應(yīng)用程序不應(yīng)被阻塞,那么就應(yīng)該在一個(gè)新的線程內(nèi)部調(diào)用 run(),它自然就會僅僅阻塞那個(gè)線程。
?
?一旦特定的 I/O 服務(wù)的所有異步操作都完成了,控制權(quán)就會返回給 run() 方法,然后它就會返回。 以上兩個(gè)例子中,應(yīng)用程序都會在鬧鐘到時(shí)間后馬上結(jié)束。
?
3. 可擴(kuò)展性與多線程
?用 Boost.Asio 這樣的庫來開發(fā)應(yīng)用程序,與一般的 C++ 風(fēng)格不同。 那些可能需要較長時(shí)間才返回的函數(shù)不再是以順序的方式來調(diào)用。 不再是調(diào)用阻塞式的函數(shù),Boost.Asio 是啟動一個(gè)異步操作。 而那些需要在操作結(jié)束后調(diào)用的函數(shù)則實(shí)現(xiàn)為相應(yīng)的句柄。 這種方法的缺點(diǎn)是,本來順序執(zhí)行的功能變得在物理上分割開來了,從而令相應(yīng)的代碼更難理解。
?
?象 Boost.Asio 這樣的庫通常是為了令應(yīng)用程序具有更高的效率。 應(yīng)用程序不需要等待特定的函數(shù)執(zhí)行完成,而可以在期間執(zhí)行其它任務(wù),如開始另一個(gè)需要較長時(shí)間的操作。
?
?可擴(kuò)展性是指,一個(gè)應(yīng)用程序從新增資源有效地獲得好處的能力。 如果那些執(zhí)行時(shí)間較長的操作不應(yīng)該阻塞其它操作的話,那么建議使用 Boost.Asio. 由于現(xiàn)今的PC機(jī)通常都具有多核處理器,所以線程的應(yīng)用可以進(jìn)一步提高一個(gè)基于 Boost.Asio 的應(yīng)用程序的可擴(kuò)展性。
?
?如果在某個(gè) boost::asio::io_service 類型的對象之上調(diào)用 run() 方法,則相關(guān)聯(lián)的句柄也會在同一個(gè)線程內(nèi)被執(zhí)行。 通過使用多線程,應(yīng)用程序可以同時(shí)調(diào)用多個(gè) run() 方法。 一旦某個(gè)異步操作結(jié)束,相應(yīng)的 I/O 服務(wù)就將在這些線程中的某一個(gè)之中執(zhí)行句柄。 如果第二個(gè)操作在第一個(gè)操作之后很快也結(jié)束了,則 I/O 服務(wù)可以在另一個(gè)線程中執(zhí)行句柄,而無需等待第一個(gè)句柄終止。
上一節(jié)中的例子現(xiàn)在變成了一個(gè)多線程的應(yīng)用。 通過使用在 boost/thread.hpp 中定義的 boost::thread 類,它來自于 Boost C++ 庫 Thread,我們在 main() 中創(chuàng)建了兩個(gè)線程。 這兩個(gè)線程均針對同一個(gè) I/O 服務(wù)調(diào)用了 run() 方法。 這樣當(dāng)異步操作完成時(shí),這個(gè) I/O 服務(wù)就可以使用兩個(gè)線程去執(zhí)行句柄函數(shù)。
?
?這個(gè)例子中的兩個(gè)計(jì)時(shí)數(shù)均被設(shè)為在五秒后觸發(fā)。 由于有兩個(gè)線程,所以 handler1() 和 handler2() 可以同時(shí)執(zhí)行。 如果第二個(gè)計(jì)時(shí)器觸發(fā)時(shí)第一個(gè)仍在執(zhí)行,則第二個(gè)句柄就會在第二個(gè)線程中執(zhí)行。 如果第一個(gè)計(jì)時(shí)器的句柄已經(jīng)終止,則 I/O 服務(wù)可以自由選擇任一線程。
?
?線程可以提高應(yīng)用程序的性能。 因?yàn)榫€程是在處理器內(nèi)核上執(zhí)行的,所以創(chuàng)建比內(nèi)核數(shù)更多的線程是沒有意義的。 這樣可以確保每個(gè)線程在其自己的內(nèi)核上執(zhí)行,而沒有同一內(nèi)核上的其它線程與之競爭。
?
?要注意,使用線程并不總是值得的。 以上例子的運(yùn)行會導(dǎo)致不同信息在標(biāo)準(zhǔn)輸出流上混合輸出,因?yàn)檫@兩個(gè)句柄可能會并行運(yùn)行,訪問同一個(gè)共享資源:標(biāo)準(zhǔn)輸出流 std::cout。 這種訪問必須被同步,以保證每一條信息在另一個(gè)線程可以向標(biāo)準(zhǔn)輸出流寫出另一條信息之前被完全寫出。 在這種情形下使用線程并不能提供多少好處,如果各個(gè)獨(dú)立句柄不能獨(dú)立地并行運(yùn)行。
?
?多次調(diào)用同一個(gè) I/O 服務(wù)的 run() 方法,是為基于 Boost.Asio 的應(yīng)用程序增加可擴(kuò)展性的推薦方法。 另外還有一個(gè)不同的方法:不要綁定多個(gè)線程到單個(gè) I/O 服務(wù),而是創(chuàng)建多個(gè) I/O 服務(wù)。 然后每一個(gè) I/O 服務(wù)使用一個(gè)線程。 如果 I/O 服務(wù)的數(shù)量與系統(tǒng)的處理器內(nèi)核數(shù)量相匹配,則異步操作都可以在各自的內(nèi)核上執(zhí)行。
前面的那個(gè)使用兩個(gè)計(jì)時(shí)器的例子被重寫為使用兩個(gè) I/O 服務(wù)。 這個(gè)應(yīng)用程序仍然基于兩個(gè)線程;但是現(xiàn)在每個(gè)線程被綁定至不同的 I/O 服務(wù)。 此外,兩個(gè) I/O 對象 timer1 和 timer2 現(xiàn)在也被綁定至不同的 I/O 服務(wù)。
?
?這個(gè)應(yīng)用程序的功能與前一個(gè)相同。 在一定條件下使用多個(gè) I/O 服務(wù)是有好處的,每個(gè) I/O 服務(wù)有自己的線程,最好是運(yùn)行在各自的處理器內(nèi)核上,這樣每一個(gè)異步操作連同它們的句柄就可以局部化執(zhí)行。 如果沒有遠(yuǎn)端的數(shù)據(jù)或函數(shù)需要訪問,那么每一個(gè) I/O 服務(wù)就象一個(gè)小的自主應(yīng)用。 這里的局部和遠(yuǎn)端是指象高速緩存、內(nèi)存頁這樣的資源。 由于在確定優(yōu)化策略之前需要對底層硬件、操作系統(tǒng)、編譯器以及潛在的瓶頸有專門的了解,所以應(yīng)該僅在清楚這些好處的情況下使用多個(gè) I/O 服務(wù)。
?
4. 網(wǎng)絡(luò)編程?
?雖然 Boost.Asio 是一個(gè)可以異步處理任何種類數(shù)據(jù)的庫,但是它主要被用于網(wǎng)絡(luò)編程。 這是由于,事實(shí)上 Boost.Asio 在加入其它 I/O 對象之前很久就已經(jīng)支持網(wǎng)絡(luò)功能了。 網(wǎng)絡(luò)功能是異步處理的一個(gè)很好的例子,因?yàn)橥ㄟ^網(wǎng)絡(luò)進(jìn)行數(shù)據(jù)傳輸可能會需要較長時(shí)間,從而不能直接獲得確認(rèn)或錯(cuò)誤條件。
?
?Boost.Asio 提供了多個(gè) I/O 對象以開發(fā)網(wǎng)絡(luò)應(yīng)用。 以下例子使用了 boost::asio::ip::tcp::socket 類來建立與中另一臺PC的連接,并下載 'Highscore' 主頁;就象一個(gè)瀏覽器在指向 www.highscore.de 時(shí)所要做的。
這個(gè)程序最明顯的部分是三個(gè)句柄的使用:connect_handler() 和 read_handler() 函數(shù)會分別在連接被建立后以及接收到數(shù)據(jù)后被調(diào)用。 那么為什么需要 resolve_handler() 函數(shù)呢?
?
?互聯(lián)網(wǎng)使用了所謂的IP地址來標(biāo)識每臺PC。 IP地址實(shí)際上只是一長串?dāng)?shù)字,難以記住。 而記住象 www.highscore.de 這樣的名字就容易得多。 為了在互聯(lián)網(wǎng)上使用類似的名字,需要通過一個(gè)叫作域名解析的過程將它們翻譯成相應(yīng)的IP地址。 這個(gè)過程由所謂的域名解析器來完成,對應(yīng)的 I/O 對象是:boost::asio::ip::tcp::resolver。
?
?域名解析也是一個(gè)需要連接到互聯(lián)網(wǎng)的過程。 有些專門的PC,被稱為DNS服務(wù)器,其作用就象是電話本,它知曉哪個(gè)IP地址被賦給了哪臺PC。 由于這個(gè)過程本身的透明的,只要明白其背后的概念以及為何需要 boost::asio::ip::tcp::resolver I/O 對象就可以了。 由于域名解析不是發(fā)生在本地的,所以它也被實(shí)現(xiàn)為一個(gè)異步操作。 一旦域名解析成功或被某個(gè)錯(cuò)誤中斷,resolve_handler() 函數(shù)就會被調(diào)用。
?
?因?yàn)榻邮諗?shù)據(jù)需要一個(gè)成功的連接,進(jìn)而需要一次成功的域名解析,所以這三個(gè)不同的異步操作要以三個(gè)不同的句柄來啟動。 resolve_handler() 訪問 I/O 對象 sock,用由迭代器 it 所提供的解析后地址創(chuàng)建一個(gè)連接。 而 sock 也在 connect_handler() 的內(nèi)部被使用,發(fā)送 HTTP 請求并啟動數(shù)據(jù)的接收。 因?yàn)樗羞@些操作都是異步的,各個(gè)句柄的名字被作為參數(shù)傳遞。 取決于各個(gè)句柄,需要相應(yīng)的其它參數(shù),如指向解析后地址的迭代器 it 或用于保存接收到的數(shù)據(jù)的緩沖區(qū) buffer。
?
?開始執(zhí)行后,該應(yīng)用將創(chuàng)建一個(gè)類型為 boost::asio::ip::tcp::resolver::query 的對象 query,表示一個(gè)查詢,其中含有名字 www.highscore.de 以及互聯(lián)網(wǎng)常用的端口80。 這個(gè)查詢被傳遞給 async_resolve() 方法以解析該名字。 最后,main() 只要調(diào)用 I/O 服務(wù)的 run() 方法,將控制交給操作系統(tǒng)進(jìn)行異步操作即可。
?
?當(dāng)域名解析的過程完成后,resolve_handler() 被調(diào)用,檢查域名是否能被解析。 如果解析成功,則存有錯(cuò)誤條件的對象 ec 被設(shè)為0。 只有在這種情況下,才會相應(yīng)地訪問 socket 以創(chuàng)建連接。 服務(wù)器的地址是通過類型為 boost::asio::ip::tcp::resolver::iterator 的第二個(gè)參數(shù)來提供的。
?
?調(diào)用了 async_connect() 方法之后,connect_handler() 會被自動調(diào)用。 在該句柄的內(nèi)部,會訪問 ec 對象以檢查連接是否已建立。 如果連接是有效的,則對相應(yīng)的 socket 調(diào)用 async_read_some() 方法,啟動讀數(shù)據(jù)操作。 為了保存接收到的數(shù)據(jù),要提供一個(gè)緩沖區(qū)作為第一個(gè)參數(shù)。 在以上例子中,緩沖區(qū)的類型是 boost::array,它來自 Boost C++ 庫 Array,定義于 boost/array.hpp.
?
?每當(dāng)有一個(gè)或多個(gè)字節(jié)被接收并保存至緩沖區(qū)時(shí),read_handler() 函數(shù)就會被調(diào)用。 準(zhǔn)確的字節(jié)數(shù)通過 std::size_t 類型的參數(shù) bytes_transferred 給出。 同樣的規(guī)則,該句柄應(yīng)該首先看看參數(shù) ec 以檢查有沒有接收錯(cuò)誤。 如果是成功接收,則將數(shù)據(jù)寫出至標(biāo)準(zhǔn)輸出流。
?
?請留意,read_handler() 在將數(shù)據(jù)寫出至 std::cout 之后,會再次調(diào)用 async_read_some() 方法。 這是必需的,因?yàn)闊o法保證僅在一次異步操作中就可以接收到整個(gè)網(wǎng)頁。 async_read_some() 和 read_handler() 的交替調(diào)用只有當(dāng)連接被破壞時(shí)才中止,如當(dāng) web 服務(wù)器已經(jīng)傳送完整個(gè)網(wǎng)頁時(shí)。 這種情況下,在 read_handler() 內(nèi)部將報(bào)告一個(gè)錯(cuò)誤,以防止進(jìn)一步將數(shù)據(jù)輸出至標(biāo)準(zhǔn)輸出流,以及進(jìn)一步對該 socket 調(diào)用 async_read() ?方法。 這時(shí)該例程將停止,因?yàn)闆]有更多的異步操作了。
?
?上個(gè)例子是用來取出 www.highscore.de 的網(wǎng)頁的,而下一個(gè)例子則示范了一個(gè)簡單的 web 服務(wù)器。 其主要差別在于,這個(gè)應(yīng)用不會連接至其它PC,而是等待連接。
?
類型為 boost::asio::ip::tcp::acceptor 的 I/O 對象 acceptor - 被初始化為指定的協(xié)議和端口號 - 用于等待從其它PC傳入的連接。 初始化工作是通過 endpoint 對象完成的,該對象的類型為 boost::asio::ip::tcp::endpoint,將本例子中的接收器配置為使用端口80來等待 IP v4 的傳入連接,這是 WWW 通常所使用的端口和協(xié)議。
?
?接收器初始化完成后,main() 首先調(diào)用 listen() 方法將接收器置于接收狀態(tài),然后再用 async_accept() 方法等待初始連接。 用于發(fā)送和接收數(shù)據(jù)的 socket 被作為第一個(gè)參數(shù)傳遞。
?
?當(dāng)一個(gè)PC試圖建立一個(gè)連接時(shí),accept_handler() 被自動調(diào)用。 如果該連接請求成功,就執(zhí)行自由函數(shù) boost::asio::async_write() 來通過 socket 發(fā)送保存在 data 中的信息。 boost::asio::ip::tcp::socket 還有一個(gè)名為 async_write_some() 的方法也可以發(fā)送數(shù)據(jù);不過它會在發(fā)送了至少一個(gè)字節(jié)之后調(diào)用相關(guān)聯(lián)的句柄。 該句柄需要計(jì)算還剩余多少字節(jié),并反復(fù)調(diào)用 async_write_some() 直至所有字節(jié)發(fā)送完畢。 而使用 ?boost::asio::async_write() 可以避免這些,因?yàn)檫@個(gè)異步操作僅在緩沖區(qū)的所有字節(jié)都被發(fā)送后才結(jié)束。
?
?在這個(gè)例子中,當(dāng)所有數(shù)據(jù)發(fā)送完畢,空函數(shù) write_handler() 將被調(diào)用。 由于所有異步操作都已完成,所以應(yīng)用程序終止。 與其它PC的連接也被相應(yīng)關(guān)閉。
5. 開發(fā) Boost.Asio 擴(kuò)展
?雖然 Boost.Asio 主要是支持網(wǎng)絡(luò)功能的,但是加入其它 I/O 對象以執(zhí)行其它的異步操作也非常容易。 本節(jié)將介紹 Boost.Asio 擴(kuò)展的一個(gè)總體布局。 雖然這不是必須的,但它為其它擴(kuò)展提供了一個(gè)可行的框架作為起點(diǎn)。
?
?要向 Boost.Asio 中增加新的異步操作,需要實(shí)現(xiàn)以下三個(gè)類:
?
? ? ?一個(gè)派生自 boost::asio::basic_io_object 的類,以表示新的 I/O 對象。使用這個(gè)新的 Boost.Asio 擴(kuò)展的開發(fā)者將只會看到這個(gè) I/O 對象。
?
? ? ?一個(gè)派生自 boost::asio::io_service::service 的類,表示一個(gè)服務(wù),它被注冊為 I/O 服務(wù),可以從 I/O 對象訪問它。 服務(wù)與 I/O 對象之間的區(qū)別是很重要的,因?yàn)樵谌我饨o定的時(shí)間點(diǎn),每個(gè) I/O 服務(wù)只能有一個(gè)服務(wù)實(shí)例,而一個(gè)服務(wù)可以被多個(gè) I/O 對象訪問。
?
? ? ?一個(gè)不派生自任何其它類的類,表示該服務(wù)的具體實(shí)現(xiàn)。 由于在任意給定的時(shí)間點(diǎn)每個(gè) I/O 服務(wù)只能有一個(gè)服務(wù)實(shí)例,所以服務(wù)會為每個(gè) I/O 對象創(chuàng)建一個(gè)其具體實(shí)現(xiàn)的實(shí)例。 該實(shí)例管理與相應(yīng) I/O 對象有關(guān)的內(nèi)部數(shù)據(jù)。
?
?本節(jié)中開發(fā)的 Boost.Asio 擴(kuò)展并不僅僅提供一個(gè)框架,而是模擬一個(gè)可用的 boost::asio::deadline_timer 對象。 它與原來的 boost::asio::deadline_timer 的區(qū)別在于,計(jì)時(shí)器的時(shí)長是作為參數(shù)傳遞給 wait() 或 async_wait() 方法的,而不是傳給構(gòu)造函數(shù)。
每個(gè) I/O 對象通常被實(shí)現(xiàn)為一個(gè)模板類,要求以一個(gè)服務(wù)來實(shí)例化 - 通常就是那個(gè)特定為此 I/O 對象開發(fā)的服務(wù)。 當(dāng)一個(gè) I/O 對象被實(shí)例化時(shí),該服務(wù)會通過父類 boost::asio::basic_io_object 自動注冊為 I/O 服務(wù),除非它之前已經(jīng)注冊。 這樣可確保任何 I/O 對象所使用的服務(wù)只會每個(gè) I/O 服務(wù)只注冊一次。
?
?在 I/O 對象的內(nèi)部,可以通過 service 引用來訪問相應(yīng)的服務(wù),通常的訪問就是將方法調(diào)用前轉(zhuǎn)至該服務(wù)。 由于服務(wù)需要為每一個(gè) I/O 對象保存數(shù)據(jù),所以要為每一個(gè)使用該服務(wù)的 I/O 對象自動創(chuàng)建一個(gè)實(shí)例。 這還是在父類 boost::asio::basic_io_object 的幫助下實(shí)現(xiàn)的。 實(shí)際的服務(wù)實(shí)現(xiàn)被作為一個(gè)參數(shù)傳遞給任一方法調(diào)用,使得服務(wù)可以知道是哪個(gè) I/O 對象啟動了這次調(diào)用。 服務(wù)的具體實(shí)現(xiàn)是通過 implementation 屬性來訪問的。
?
?一般一上諭,I/O 對象是相對簡單的:服務(wù)的安裝以及服務(wù)實(shí)現(xiàn)的創(chuàng)建都是由父類 boost::asio::basic_io_object 來完成的,方法調(diào)用則只是前轉(zhuǎn)至相應(yīng)的服務(wù);以 I/O 對象的實(shí)際服務(wù)實(shí)現(xiàn)作為參數(shù)即可。
? ? ?為了與 Boost.Asio 集成,一個(gè)服務(wù)必須符合幾個(gè)要求:
?
? ? ?它必須派生自 boost::asio::io_service::service。 構(gòu)造函數(shù)必須接受一個(gè)指向 I/O 服務(wù)的引用,該 I/O 服務(wù)會被相應(yīng)地傳給 boost::asio::io_service::service 的構(gòu)造函數(shù)。
?
? ? ?任何服務(wù)都必須包含一個(gè)類型為 boost::asio::io_service::id 的靜態(tài)公有屬性 id。在 I/O 服務(wù)的內(nèi)部是用該屬性來識別服務(wù)的。
?
? ? ?必須定義兩個(gè)名為 construct() 和 destruct() 的公有方法,均要求一個(gè)類型為 implementation_type 的參數(shù)。 implementation_type 通常是該服務(wù)的具體實(shí)現(xiàn)的類型定義。 正如上面例子所示,在 construct() 中可以很容易地使用一個(gè) boost::shared_ptr 對象來初始化一個(gè)服務(wù)實(shí)現(xiàn),以及在 destruct() 中相應(yīng)地析構(gòu)它。 由于這兩個(gè)方法都會在一個(gè) I/O 對象被創(chuàng)建或銷毀時(shí)自動被調(diào)用,所以一個(gè)服務(wù)可以分別使用 construct() ?和 destruct() 為每個(gè) I/O 對象創(chuàng)建和銷毀服務(wù)實(shí)現(xiàn)。
?
? ? ?必須定義一個(gè)名為 shutdown_service() 的方法;不過它可以是私有的。 對于一般的 Boost.Asio 擴(kuò)展來說,它通常是一個(gè)空方法。 只有與 Boost.Asio 集成得非常緊密的服務(wù)才會使用它。 但是這個(gè)方法必須要有,這樣擴(kuò)展才能編譯成功。
?
?為了將方法調(diào)用前轉(zhuǎn)至相應(yīng)的服務(wù),必須為相應(yīng)的 I/O 對象定義要前轉(zhuǎn)的方法。 這些方法通常具有與 I/O 對象中的方法相似的名字,如上例中的 wait() 和 async_wait()。 同步方法,如 wait(),只是訪問該服務(wù)的具體實(shí)現(xiàn)去調(diào)用一個(gè)阻塞式的方法,而異步方法,如 async_wait(),則是在一個(gè)線程中調(diào)用這個(gè)阻塞式方法。
?
?在線程的協(xié)助下使用異步操作,通常是通過訪問一個(gè)新的 I/O 服務(wù)來完成的。 上述例子中包含了一個(gè)名為 async_io_service_ 的屬性,其類型為 boost::asio::io_service。 這個(gè) I/O 服務(wù)的 run() 方法是在它自己的線程中啟動的,而它的線程是在該服務(wù)的構(gòu)造函數(shù)內(nèi)部由類型為 boost::thread 的 async_thread_ 創(chuàng)建的。 第三個(gè)屬性 async_work_ 的類型為 boost::scoped_ptr<boost::asio::io_service::work>,用于避免 ?run() 方法立即返回。 否則,這可能會發(fā)生,因?yàn)橐褯]有其它的異步操作在創(chuàng)建。 創(chuàng)建一個(gè)類型為 boost::asio::io_service::work 的對象并將它綁定至該 I/O 服務(wù),這個(gè)動作也是發(fā)生在該服務(wù)的構(gòu)造函數(shù)中,可以防止 run() 方法立即返回。
?
?一個(gè)服務(wù)也可以無需訪問它自身的 I/O 服務(wù)來實(shí)現(xiàn) - 單線程就足夠的。 為新增的線程使用一個(gè)新的 I/O 服務(wù)的原因是,這樣更簡單: 線程間可以用 I/O 服務(wù)來非常容易地相互通信。 在這個(gè)例子中,async_wait() 創(chuàng)建了一個(gè)類型為 wait_operation 的函數(shù)對象,并通過 post() 方法將它傳遞給內(nèi)部的 I/O 服務(wù)。 然后,在用于執(zhí)行這個(gè)內(nèi)部 I/O 服務(wù)的 run() 方法的線程內(nèi),調(diào)用該函數(shù)對象的重載 operator()()。 post() 提供了一個(gè)簡單的方法,在另一個(gè)線程中執(zhí)行一個(gè)函數(shù)對象。
?
?wait_operation 的重載 operator()() 操作符基本上就是執(zhí)行了和 wait() 方法相同的工作:調(diào)用服務(wù)實(shí)現(xiàn)中的阻塞式 wait() 方法。 但是,有可能這個(gè) I/O 對象以及它的服務(wù)實(shí)現(xiàn)在這個(gè)線程執(zhí)行 operator()() 操作符期間被銷毀。 如果服務(wù)實(shí)現(xiàn)是在 destruct() 中銷毀的,則 operator()() 操作符將不能再訪問它。 這種情形是通過使用一個(gè)弱指針來防止的,從第一章中我們知道:如果在調(diào)用 lock() 時(shí)服務(wù)實(shí)現(xiàn)仍然存在,則弱指針 impl_ 返回它的一個(gè)共享指針,否則它將返回0。 ?在這種情況下,operator()() 不會訪問這個(gè)服務(wù)實(shí)現(xiàn),而是以一個(gè) boost::asio::error::operation_aborted 錯(cuò)誤來調(diào)用句柄。
服務(wù)實(shí)現(xiàn) timer_impl 使用了 Windows API 函數(shù),只能在 Windows 中編譯和使用。 這個(gè)例子的目的只是為了說明一種潛在的實(shí)現(xiàn)。
?
?timer_impl 提供兩個(gè)基本方法:wait() 用于等待數(shù)秒。 destroy() 則用于取消一個(gè)等待操作,這是必須要有的,因?yàn)閷τ诋惒讲僮鱽碚f,wait() 方法是在其自身的線程中調(diào)用的。 如果 I/O 對象及其服務(wù)實(shí)現(xiàn)被銷毀,那么阻塞式的 wait() 方法就要盡使用 destroy() 來取消。
?
?這個(gè) Boost.Asio 擴(kuò)展可以如下使用。?
?
與本章開始的例子相比,這個(gè) Boost.Asio 擴(kuò)展的用法類似于 boost::asio::deadline_timer。 在實(shí)踐上,應(yīng)該優(yōu)先使用 boost::asio::deadline_timer,因?yàn)樗呀?jīng)集成在 Boost.Asio 中了。 這個(gè)擴(kuò)展的唯一目的就是示范一下 Boost.Asio 是如何擴(kuò)展新的異步操作的。
?
?目錄監(jiān)視器(Directory Monitor) 是現(xiàn)實(shí)中的一個(gè) Boost.Asio 擴(kuò)展,它提供了一個(gè)可以監(jiān)視目錄的 I/O 對象。 如果被監(jiān)視目錄中的某個(gè)文件被創(chuàng)建、修改或是刪除,就會相應(yīng)地調(diào)用一個(gè)句柄。 當(dāng)前的版本支持 Windows 和 Linux (內(nèi)核版本 2.6.13 或以上)。
6.Boost.Asio 異步執(zhí)行方法,post()\dispach()\wrap().
Asio中的異步操作不僅包括 異步的客戶端服務(wù)端的連接和異步的數(shù)據(jù)讀寫,還包括很多可以異步執(zhí)行的操作。
Asio中有三種方式異步執(zhí)行你指定的方法:post()、dispach()、wrap()。
post()這個(gè)方法能立即返回,并且請求一個(gè)io_service實(shí)例調(diào)用制定的函數(shù)操作(function handler),之后會在某一個(gè)盜用io_service.run()的線程中執(zhí)行。
dispach()這個(gè)方法請求一個(gè)io_service實(shí)例調(diào)用函數(shù)操作,但是如果當(dāng)前線程執(zhí)行了io_service.run(),它就會直接調(diào)用handler。
wrap()這個(gè)方法包裝一個(gè)方法,當(dāng)它被調(diào)用時(shí)它會調(diào)用io_service.dispach().
post()例子:
運(yùn)行結(jié)果:
上面的程序中有三個(gè)線程啟動了io_server.run(),循環(huán)請求執(zhí)行func(int i )方法,io_service會選擇一個(gè)線程去執(zhí)行func方法。所以無法確定順序。
dispach()例子:
#include <boost/asio.hpp> #include <iostream> using namespace boost::asio; io_service service; void func(int i) {std::cout << "func called, i= " << i << std::endl; } void run_dispatch_and_post() {for (int i = 0; i < 10; i += 2) {service.dispatch(std::bind(func, i));service.post(std::bind(func, i + 1));} } int main(int argc, char* argv[]) {service.post(run_dispatch_and_post);service.run();getchar(); }
運(yùn)行結(jié)果:
程序先輸出偶數(shù)后輸出奇數(shù),因?yàn)榕紨?shù)使用dispatch()執(zhí)行,又因?yàn)橹骶€程調(diào)用了service.run(),所以直接調(diào)用,而post執(zhí)行偶數(shù)時(shí),是直接返回的,而后在調(diào)用。
wrap()例子
#include <boost/asio.hpp> #include <boost/thread.hpp> #include <iostream> #include <functional> using namespace boost::asio; io_service service; void dispatched_func_1() {std::cout << "dispatched 1" << std::endl; } void dispatched_func_2() {std::cout << "dispatched 2" << std::endl; } void test(std::function<void()> func) {std::cout << "test" << std::endl;service.dispatch(dispatched_func_1);func(); } void service_run() {service.run(); } int main(int argc, char* argv[]) {test(service.wrap(dispatched_func_2));boost::thread th(service_run);boost::this_thread::sleep(boost::posix_time::millisec(500));th.join();getchar(); }
運(yùn)行結(jié)果:
service.warp()把dispatched_func_2包裝成一個(gè)函數(shù),傳給test(),當(dāng)test函數(shù)去執(zhí)行func()時(shí),跟service.dispatch(dispatched_func_1);是等價(jià)的。
《Boost.Asio C++ Network Programming》
?
7. 練習(xí)
?
? ? ?You can buy solutions to all exercises in this book as a ZIP file.
?
? ? ?修改 第 7.4 節(jié) “網(wǎng)絡(luò)編程” 中的服務(wù)器程序,不在一次請求后即終止,而是可以處理任意多次請求。
?
? ? ?擴(kuò)展 第 7.4 節(jié) “網(wǎng)絡(luò)編程” 中的客戶端程序,即時(shí)在所接收到的HTML代碼中分析某個(gè)URL。 如果找到,則同時(shí)下載相應(yīng)的資源。 對于本練習(xí),只使用第一個(gè)URL。 理想情況下,網(wǎng)站及其資源應(yīng)被保存在兩個(gè)文件中而不是同時(shí)寫出至標(biāo)準(zhǔn)輸出流。
?
? ? ?創(chuàng)建一個(gè)客戶端/服務(wù)器應(yīng)用,在兩臺PC間傳送文件。 當(dāng)服務(wù)器端啟動后,它應(yīng)該顯示所有本地接口的IP地址并等待客戶端連接。 客戶端則應(yīng)將服務(wù)器端的某一個(gè)IP地址以及某個(gè)本地文件的文件名作為命令行參數(shù)。 客戶端應(yīng)將該文件傳送給服務(wù)器,后者則相應(yīng)地保存它。 在傳送過程中,客戶端應(yīng)向用戶提供一些進(jìn)度的可視顯示。
總結(jié)
以上是生活随笔為你收集整理的Boost asio 官方教程简介的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不连续字符的串计数(洛谷P4439题题解
- 下一篇: N进制正反累加判回文数(洛谷P1015题