Boost asio 官方教程

7.1. 概述
 
 本章介紹了 Boost C++ 庫 Asio,它是異步輸入輸出的核心。 名字本身就說明了一切:Asio 意即異步輸入/輸出。 該庫可以讓 C++ 異步地處理數據,且平臺獨立。 異步數據處理就是指,任務觸發後不需要等待它們完成。 相反,Boost.Asio 會在任務完成時觸發一個應用。 異步任務的主要優點在於,在等待任務完成時不需要阻塞應用程序,可以去執行其它任務。
 
 異步任務的典型例子是網絡應用。 如果數據被髮送出去了,比如發送至 Internet,通常需要知道數據是否發送成功。 如果沒有一個象 Boost.Asio 這樣的庫,就必須對函數的返回值進行求值。 但是,這樣就要求待至所有數據發送完畢,並得到一個確認或是錯誤代碼。 而使用 Boost.Asio,這個過程被分爲兩個單獨的步驟:第一步是作爲一個異步任務開始數據傳輸。 一旦傳輸完成,不論成功或是錯誤,應用程序都會在第二步中得到關於相應的結果通知。 主要的區別在於,應用程序無需阻塞至傳輸完成,而可以在這段時間裏執行其它操作。
 7.2. I/O 服務與 I/O 對象
 
 使用 Boost.Asio 進行異步數據處理的應用程序基於兩個概念:I/O 服務和 I/O 對象。 I/O 服務抽象了操作系統的接口,允許第一時間進行異步數據處理,而 I/O 對象則用於初始化特定的操作。 鑑於 Boost.Asio 只提供了一個名爲 boost::asio::io_service 的類作爲 I/O 服務,它針對所支持的每一個操作系統都分別實現了優化的類,另外庫中還包含了針對不同 I/O 對象的幾個類。 其中,類 boost::asio::ip::tcp::socket 用於通過網絡發送和接收數據,而類  boost::asio::deadline_timer 則提供了一個計時器,用於測量某個固定時間點到來或是一段指定的時長過去了。 以下第一個例子中就使用了計時器,因爲與 Asio 所提供的其它 I/O 對象相比較而言,它不需要任何有關於網絡編程的知識。

#include <boost/asio.hpp> 
#include <iostream> 
 
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(); 
} 

函數 main() 首先定義了一個 I/O 服務 io_service,用於初始化 I/O 對象 timer。 就象 boost::asio::deadline_timer 那樣,所有 I/O 對象通常都需要一個 I/O 服務作爲它們的構造函數的第一個參數。 由於 timer 的作用類似於一個鬧鐘,所以 boost::asio::deadline_timer 的構造函數可以傳入第二個參數,用於表示在某個時間點或是在某段時長之後鬧鐘停止。 以上例子指定了五秒的時長,該鬧鐘在 timer 被定義之後立即開始計時。
 
 雖然我們可以調用一個在五秒後返回的函數,但是通過調用方法 async_wait() 並傳入 handler() 函數的名字作爲唯一參數,可以讓 Asio 啓動一個異步操作。 請留意,我們只是傳入了 handler() 函數的名字,而該函數本身並沒有被調用。
 
 async_wait() 的好處是,該函數調用會立即返回,而不是等待五秒鐘。 一旦鬧鐘時間到,作爲參數所提供的函數就會被相應調用。 因此,應用程序可以在調用了 async_wait() 之後執行其它操作,而不是阻塞在這裏。
 
 象 async_wait() 這樣的方法被稱爲是非阻塞式的。 I/O 對象通常還提供了阻塞式的方法,可以讓執行流在特定操作完成之前保持阻塞。 例如,可以調用阻塞式的 wait() 方法,取代 boost::asio::deadline_timer 的調用。 由於它會阻塞調用,所以它不需要傳入一個函數名,而是在指定時間點或指定時長之後返回。
 
 再看看上面的源代碼,可以留意到在調用 async_wait() 之後,又在 I/O 服務之上調用了一個名爲 run() 的方法。這是必須的,因爲控制權必須被操作系統接管,才能在五秒之後調用 handler() 函數。
 
 async_wait() 會啓動一個異步操作並立即返回,而 run() 則是阻塞的。因此調用 run() 後程序執行會停止。 具有諷刺意味的是,許多操作系統只是通過阻塞函數來支持異步操作。 以下例子顯示了爲什麼這個限制通常不會成爲問題。

函數 main() 首先定義了一個 I/O 服務 io_service,用於初始化 I/O 對象 timer。 就象 boost::asio::deadline_timer 那樣,所有 I/O 對象通常都需要一個 I/O 服務作爲它們的構造函數的第一個參數。 由於 timer 的作用類似於一個鬧鐘,所以 boost::asio::deadline_timer 的構造函數可以傳入第二個參數,用於表示在某個時間點或是在某段時長之後鬧鐘停止。 以上例子指定了五秒的時長,該鬧鐘在 timer 被定義之後立即開始計時。
 
 雖然我們可以調用一個在五秒後返回的函數,但是通過調用方法 async_wait() 並傳入 handler() 函數的名字作爲唯一參數,可以讓 Asio 啓動一個異步操作。 請留意,我們只是傳入了 handler() 函數的名字,而該函數本身並沒有被調用。
 
 async_wait() 的好處是,該函數調用會立即返回,而不是等待五秒鐘。 一旦鬧鐘時間到,作爲參數所提供的函數就會被相應調用。 因此,應用程序可以在調用了 async_wait() 之後執行其它操作,而不是阻塞在這裏。
 
 象 async_wait() 這樣的方法被稱爲是非阻塞式的。 I/O 對象通常還提供了阻塞式的方法,可以讓執行流在特定操作完成之前保持阻塞。 例如,可以調用阻塞式的 wait() 方法,取代 boost::asio::deadline_timer 的調用。 由於它會阻塞調用,所以它不需要傳入一個函數名,而是在指定時間點或指定時長之後返回。
 
 再看看上面的源代碼,可以留意到在調用 async_wait() 之後,又在 I/O 服務之上調用了一個名爲 run() 的方法。這是必須的,因爲控制權必須被操作系統接管,才能在五秒之後調用 handler() 函數。
 
 async_wait() 會啓動一個異步操作並立即返回,而 run() 則是阻塞的。因此調用 run() 後程序執行會停止。 具有諷刺意味的是,許多操作系統只是通過阻塞函數來支持異步操作。 以下例子顯示了爲什麼這個限制通常不會成爲問題。
 

#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(); 
} 

上面的程序用了兩個 boost::asio::deadline_timer 類型的 I/O 對象。 第一個 I/O 對象表示一個五秒後觸發的鬧鐘,而第二個則表示一個十秒後觸發的鬧鐘。 每一段指定時長過去後,都會相應地調用函數 handler1() 和 handler2()。

在 main() 的最後,再次在唯一的 I/O 服務之上調用了 run() 方法。 如前所述,這個函數將阻塞執行,把控制權交給操作系統以接管異步處理。 在操作系統的幫助下,handler1() 函數會在五秒後被調用,而 handler2() 函數則在十秒後被調用。

乍一看,你可能會覺得有些奇怪,爲什麼異步處理還要調用阻塞式的 run() 方法。 然而,由於應用程序必須防止被中止執行,所以這樣做實際上不會有任何問題。 如果 run() 不是阻塞的,main() 就會結束從而中止該應用程序。 如果應用程序不應被阻塞,那麼就應該在一個新的線程內部調用 run(),它自然就會僅僅阻塞那個線程。

一旦特定的 I/O 服務的所有異步操作都完成了,控制權就會返回給 run() 方法,然後它就會返回。 以上兩個例子中,應用程序都會在鬧鐘到時間後馬上結束。

7.3. 可擴展性與多線程

用 Boost.Asio 這樣的庫來開發應用程序,與一般的 C++ 風格不同。 那些可能需要較長時間才返回的函數不再是以順序的方式來調用。 不再是調用阻塞式的函數,Boost.Asio 是啓動一個異步操作。 而那些需要在操作結束後調用的函數則實現爲相應的句柄。 這種方法的缺點是,本來順序執行的功能變得在物理上分割開來了,從而令相應的代碼更難理解。

象 Boost.Asio 這樣的庫通常是爲了令應用程序具有更高的效率。 應用程序不需要等待特定的函數執行完成,而可以在期間執行其它任務,如開始另一個需要較長時間的操作。

可擴展性是指,一個應用程序從新增資源有效地獲得好處的能力。 如果那些執行時間較長的操作不應該阻塞其它操作的話,那麼建議使用 Boost.Asio. 由於現今的PC機通常都具有多核處理器,所以線程的應用可以進一步提高一個基於 Boost.Asio 的應用程序的可擴展性。

如果在某個 boost::asio::io_service 類型的對象之上調用 run() 方法,則相關聯的句柄也會在同一個線程內被執行。 通過使用多線程,應用程序可以同時調用多個 run() 方法。 一旦某個異步操作結束,相應的 I/O 服務就將在這些線程中的某一個之中執行句柄。 如果第二個操作在第一個操作之後很快也結束了,則 I/O 服務可以在另一個線程中執行句柄,而無需等待第一個句柄終止。

 #include <boost/asio.hpp> 
 #include <boost/thread.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 << "5 s." << std::endl; 
 } 
 
 boost::asio::io_service io_service; 
 
 void run() 
 { 
   io_service.run(); 
 } 
 
 int main() 
 { 
   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(5)); 
   timer2.async_wait(handler2); 
   boost::thread thread1(run); 
   boost::thread thread2(run); 
   thread1.join(); 
   thread2.join(); 
 } 

上一節中的例子現在變成了一個多線程的應用。 通過使用在 boost/thread.hpp 中定義的 boost::thread 類,它來自於 Boost C++ 庫 Thread,我們在 main() 中創建了兩個線程。 這兩個線程均針對同一個 I/O 服務調用了 run() 方法。 這樣當異步操作完成時,這個 I/O 服務就可以使用兩個線程去執行句柄函數。

這個例子中的兩個計時數均被設爲在五秒後觸發。 由於有兩個線程,所以 handler1() 和 handler2() 可以同時執行。 如果第二個計時器觸發時第一個仍在執行,則第二個句柄就會在第二個線程中執行。 如果第一個計時器的句柄已經終止,則 I/O 服務可以自由選擇任一線程。

線程可以提高應用程序的性能。 因爲線程是在處理器內核上執行的,所以創建比內核數更多的線程是沒有意義的。 這樣可以確保每個線程在其自己的內核上執行,而沒有同一內核上的其它線程與之競爭。

要注意,使用線程並不總是值得的。 以上例子的運行會導致不同信息在標準輸出流上混合輸出,因爲這兩個句柄可能會並行運行,訪問同一個共享資源:標準輸出流 std::cout。 這種訪問必須被同步,以保證每一條信息在另一個線程可以向標準輸出流寫出另一條信息之前被完全寫出。 在這種情形下使用線程並不能提供多少好處,如果各個獨立句柄不能獨立地並行運行。

多次調用同一個 I/O 服務的 run() 方法,是爲基於 Boost.Asio 的應用程序增加可擴展性的推薦方法。 另外還有一個不同的方法:不要綁定多個線程到單個 I/O 服務,而是創建多個 I/O 服務。 然後每一個 I/O 服務使用一個線程。 如果 I/O 服務的數量與系統的處理器內核數量相匹配,則異步操作都可以在各自的內核上執行。


 #include <boost/asio.hpp> 
 #include <boost/thread.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 << "5 s." << std::endl; 
 } 
 
 boost::asio::io_service io_service1; 
 boost::asio::io_service io_service2; 
 
 void run1() 
 { 
   io_service1.run(); 
 } 
 
 void run2() 
 { 
   io_service2.run(); 
 } 
 
 int main() 
 { 
   boost::asio::deadline_timer timer1(io_service1, boost::posix_time::seconds(5)); 
   timer1.async_wait(handler1); 
   boost::asio::deadline_timer timer2(io_service2, boost::posix_time::seconds(5)); 
   timer2.async_wait(handler2); 
   boost::thread thread1(run1); 
   boost::thread thread2(run2); 
   thread1.join(); 
   thread2.join(); 
 } 

前面的那個使用兩個計時器的例子被重寫爲使用兩個 I/O 服務。 這個應用程序仍然基於兩個線程;但是現在每個線程被綁定至不同的 I/O 服務。 此外,兩個 I/O 對象 timer1 和 timer2 現在也被綁定至不同的 I/O 服務。
 
 這個應用程序的功能與前一個相同。 在一定條件下使用多個 I/O 服務是有好處的,每個 I/O 服務有自己的線程,最好是運行在各自的處理器內核上,這樣每一個異步操作連同它們的句柄就可以局部化執行。 如果沒有遠端的數據或函數需要訪問,那麼每一個 I/O 服務就象一個小的自主應用。 這裏的局部和遠端是指象高速緩存、內存頁這樣的資源。 由於在確定優化策略之前需要對底層硬件、操作系統、編譯器以及潛在的瓶頸有專門的瞭解,所以應該僅在清楚這些好處的情況下使用多個 I/O 服務。
 7.4. 網絡編程
 
 雖然 Boost.Asio 是一個可以異步處理任何種類數據的庫,但是它主要被用於網絡編程。 這是由於,事實上 Boost.Asio 在加入其它 I/O 對象之前很久就已經支持網絡功能了。 網絡功能是異步處理的一個很好的例子,因爲通過網絡進行數據傳輸可能會需要較長時間,從而不能直接獲得確認或錯誤條件。
 
 Boost.Asio 提供了多個 I/O 對象以開發網絡應用。 以下例子使用了 boost::asio::ip::tcp::socket 類來建立與中另一臺PC的連接,並下載 'Highscore' 主頁;就象一個瀏覽器在指向 www.highscore.de 時所要做的。

#include <boost/asio.hpp> 
#include <boost/array.hpp> 
#include <iostream> 
#include <string> 

boost::asio::io_service io_service; 
boost::asio::ip::tcp::resolver resolver(io_service); 
boost::asio::ip::tcp::socket sock(io_service); 
boost::array<char, 4096> buffer; 

void read_handler(const boost::system::error_code &ec, std::size_t bytes_transferred) 
{ 
  if (!ec) 
  { 
    std::cout << std::string(buffer.data(), bytes_transferred) << std::endl; 
    sock.async_read_some(boost::asio::buffer(buffer), read_handler); 
  } 
} 

void connect_handler(const boost::system::error_code &ec) 
{ 
  if (!ec) 
  { 
    boost::asio::write(sock, boost::asio::buffer("GET / HTTP 1.1\r\nHost: highscore.de\r\n\r\n")); 
    sock.async_read_some(boost::asio::buffer(buffer), read_handler); 
  } 
} 

void resolve_handler(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator it) 
{ 
  if (!ec) 
  { 
    sock.async_connect(*it, connect_handler); 
  } 
} 

int main() 
{ 
  boost::asio::ip::tcp::resolver::query query("www.highscore.de", "80"); 
  resolver.async_resolve(query, resolve_handler); 
  io_service.run(); 
} 

這個程序最明顯的部分是三個句柄的使用:connect_handler() 和 read_handler() 函數會分別在連接被建立後以及接收到數據後被調用。 那麼爲什麼需要 resolve_handler() 函數呢?
 
 互聯網使用了所謂的IP地址來標識每臺PC。 IP地址實際上只是一長串數字,難以記住。 而記住象 www.highscore.de 這樣的名字就容易得多。 爲了在互聯網上使用類似的名字,需要通過一個叫作域名解析的過程將它們翻譯成相應的IP地址。 這個過程由所謂的域名解析器來完成,對應的 I/O 對象是:boost::asio::ip::tcp::resolver。
 
 域名解析也是一個需要連接到互聯網的過程。 有些專門的PC,被稱爲DNS服務器,其作用就象是電話本,它知曉哪個IP地址被賦給了哪臺PC。 由於這個過程本身的透明的,只要明白其背後的概念以及爲何需要 boost::asio::ip::tcp::resolver I/O 對象就可以了。 由於域名解析不是發生在本地的,所以它也被實現爲一個異步操作。 一旦域名解析成功或被某個錯誤中斷,resolve_handler() 函數就會被調用。
 
 因爲接收數據需要一個成功的連接,進而需要一次成功的域名解析,所以這三個不同的異步操作要以三個不同的句柄來啓動。 resolve_handler() 訪問 I/O 對象 sock,用由迭代器 it 所提供的解析後地址創建一個連接。 而 sock 也在 connect_handler() 的內部被使用,發送 HTTP 請求並啓動數據的接收。 因爲所有這些操作都是異步的,各個句柄的名字被作爲參數傳遞。 取決於各個句柄,需要相應的其它參數,如指向解析後地址的迭代器 it 或用於保存接收到的數據的緩衝區 buffer。
 
 開始執行後,該應用將創建一個類型爲 boost::asio::ip::tcp::resolver::query 的對象 query,表示一個查詢,其中含有名字 www.highscore.de 以及互聯網常用的端口80。 這個查詢被傳遞給 async_resolve() 方法以解析該名字。 最後,main() 只要調用 I/O 服務的 run() 方法,將控制交給操作系統進行異步操作即可。
 
 當域名解析的過程完成後,resolve_handler() 被調用,檢查域名是否能被解析。 如果解析成功,則存有錯誤條件的對象 ec 被設爲0。 只有在這種情況下,纔會相應地訪問 socket 以創建連接。 服務器的地址是通過類型爲 boost::asio::ip::tcp::resolver::iterator 的第二個參數來提供的。
 
 調用了 async_connect() 方法之後,connect_handler() 會被自動調用。 在該句柄的內部,會訪問 ec 對象以檢查連接是否已建立。 如果連接是有效的,則對相應的 socket 調用 async_read_some() 方法,啓動讀數據操作。 爲了保存接收到的數據,要提供一個緩衝區作爲第一個參數。 在以上例子中,緩衝區的類型是 boost::array,它來自 Boost C++ 庫 Array,定義於 boost/array.hpp.
 
 每當有一個或多個字節被接收並保存至緩衝區時,read_handler() 函數就會被調用。 準確的字節數通過 std::size_t 類型的參數 bytes_transferred 給出。 同樣的規則,該句柄應該首先看看參數 ec 以檢查有沒有接收錯誤。 如果是成功接收,則將數據寫出至標準輸出流。
 
 請留意,read_handler() 在將數據寫出至 std::cout 之後,會再次調用 async_read_some() 方法。 這是必需的,因爲無法保證僅在一次異步操作中就可以接收到整個網頁。 async_read_some() 和 read_handler() 的交替調用只有當連接被破壞時才中止,如當 web 服務器已經傳送完整個網頁時。 這種情況下,在 read_handler() 內部將報告一個錯誤,以防止進一步將數據輸出至標準輸出流,以及進一步對該 socket 調用 async_read()  方法。 這時該例程將停止,因爲沒有更多的異步操作了。
 
 上個例子是用來取出 www.highscore.de 的網頁的,而下一個例子則示範了一個簡單的 web 服務器。 其主要差別在於,這個應用不會連接至其它PC,而是等待連接。

 #include <boost/asio.hpp> 
 #include <string> 
 
 boost::asio::io_service io_service; 
 boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 80); 
 boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint); 
 boost::asio::ip::tcp::socket sock(io_service); 
 std::string data = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!"; 
 
 void write_handler(const boost::system::error_code &ec, std::size_t bytes_transferred) 
 { 
 } 
 
 void accept_handler(const boost::system::error_code &ec) 
 { 
   if (!ec) 
   { 
     boost::asio::async_write(sock, boost::asio::buffer(data), write_handler); 
   } 
 } 
 
 int main() 
 { 
   acceptor.listen(); 
   acceptor.async_accept(sock, accept_handler); 
   io_service.run(); 
 } 

類型爲 boost::asio::ip::tcp::acceptor 的 I/O 對象 acceptor - 被初始化爲指定的協議和端口號 - 用於等待從其它PC傳入的連接。 初始化工作是通過 endpoint 對象完成的,該對象的類型爲 boost::asio::ip::tcp::endpoint,將本例子中的接收器配置爲使用端口80來等待 IP v4 的傳入連接,這是 WWW 通常所使用的端口和協議。
 
 接收器初始化完成後,main() 首先調用 listen() 方法將接收器置於接收狀態,然後再用 async_accept() 方法等待初始連接。 用於發送和接收數據的 socket 被作爲第一個參數傳遞。
 
 當一個PC試圖建立一個連接時,accept_handler() 被自動調用。 如果該連接請求成功,就執行自由函數 boost::asio::async_write() 來通過 socket 發送保存在 data 中的信息。 boost::asio::ip::tcp::socket 還有一個名爲 async_write_some() 的方法也可以發送數據;不過它會在發送了至少一個字節之後調用相關聯的句柄。 該句柄需要計算還剩餘多少字節,並反覆調用 async_write_some() 直至所有字節發送完畢。 而使用  boost::asio::async_write() 可以避免這些,因爲這個異步操作僅在緩衝區的所有字節都被髮送後才結束。
 
 在這個例子中,當所有數據發送完畢,空函數 write_handler() 將被調用。 由於所有異步操作都已完成,所以應用程序終止。 與其它PC的連接也被相應關閉。
 7.5. 開發 Boost.Asio 擴展
 
 雖然 Boost.Asio 主要是支持網絡功能的,但是加入其它 I/O 對象以執行其它的異步操作也非常容易。 本節將介紹 Boost.Asio 擴展的一個總體佈局。 雖然這不是必須的,但它爲其它擴展提供了一個可行的框架作爲起點。
 
 要向 Boost.Asio 中增加新的異步操作,需要實現以下三個類:
 
     一個派生自 boost::asio::basic_io_object 的類,以表示新的 I/O 對象。使用這個新的 Boost.Asio 擴展的開發者將只會看到這個 I/O 對象。
 
     一個派生自 boost::asio::io_service::service 的類,表示一個服務,它被註冊爲 I/O 服務,可以從 I/O 對象訪問它。 服務與 I/O 對象之間的區別是很重要的,因爲在任意給定的時間點,每個 I/O 服務只能有一個服務實例,而一個服務可以被多個 I/O 對象訪問。
 
     一個不派生自任何其它類的類,表示該服務的具體實現。 由於在任意給定的時間點每個 I/O 服務只能有一個服務實例,所以服務會爲每個 I/O 對象創建一個其具體實現的實例。 該實例管理與相應 I/O 對象有關的內部數據。
 
 本節中開發的 Boost.Asio 擴展並不僅僅提供一個框架,而是模擬一個可用的 boost::asio::deadline_timer 對象。 它與原來的 boost::asio::deadline_timer 的區別在於,計時器的時長是作爲參數傳遞給 wait() 或 async_wait() 方法的,而不是傳給構造函數。

#include <boost/asio.hpp> 
 #include <cstddef> 
 
 template <typename Service> 
 class basic_timer 
   : public boost::asio::basic_io_object<Service> 
 { 
   public: 
     explicit basic_timer(boost::asio::io_service &io_service) 
       : boost::asio::basic_io_object<Service>(io_service) 
     { 
     } 
 
     void wait(std::size_t seconds) 
     { 
       return this->service.wait(this->implementation, seconds); 
     } 
 
     template <typename Handler> 
     void async_wait(std::size_t seconds, Handler handler) 
     { 
       this->service.async_wait(this->implementation, seconds, handler); 
     } 
 }; 


每個 I/O 對象通常被實現爲一個模板類,要求以一個服務來實例化 - 通常就是那個特定爲此 I/O 對象開發的服務。 當一個 I/O 對象被實例化時,該服務會通過父類 boost::asio::basic_io_object 自動註冊爲 I/O 服務,除非它之前已經註冊。 這樣可確保任何 I/O 對象所使用的服務只會每個 I/O 服務只註冊一次。
 
 在 I/O 對象的內部,可以通過 service 引用來訪問相應的服務,通常的訪問就是將方法調用前轉至該服務。 由於服務需要爲每一個 I/O 對象保存數據,所以要爲每一個使用該服務的 I/O 對象自動創建一個實例。 這還是在父類 boost::asio::basic_io_object 的幫助下實現的。 實際的服務實現被作爲一個參數傳遞給任一方法調用,使得服務可以知道是哪個 I/O 對象啓動了這次調用。 服務的具體實現是通過 implementation 屬性來訪問的。
 
 一般一上諭,I/O 對象是相對簡單的:服務的安裝以及服務實現的創建都是由父類 boost::asio::basic_io_object 來完成的,方法調用則只是前轉至相應的服務;以 I/O 對象的實際服務實現作爲參數即可。

#include <boost/asio.hpp> 
 #include <boost/thread.hpp> 
 #include <boost/bind.hpp> 
 #include <boost/scoped_ptr.hpp> 
 #include <boost/shared_ptr.hpp> 
 #include <boost/weak_ptr.hpp> 
 #include <boost/system/error_code.hpp> 
 
 template <typename TimerImplementation = timer_impl> 
 class basic_timer_service 
   : public boost::asio::io_service::service 
 { 
   public: 
     static boost::asio::io_service::id id; 
 
     explicit basic_timer_service(boost::asio::io_service &io_service) 
       : boost::asio::io_service::service(io_service), 
       async_work_(new boost::asio::io_service::work(async_io_service_)), 
       async_thread_(boost::bind(&boost::asio::io_service::run, &async_io_service_)) 
     { 
     } 
 
     ~basic_timer_service() 
     { 
       async_work_.reset(); 
       async_io_service_.stop(); 
       async_thread_.join(); 
     } 
 
     typedef boost::shared_ptr<TimerImplementation> implementation_type; 
 
     void construct(implementation_type &impl) 
     { 
       impl.reset(new TimerImplementation()); 
     } 
 
     void destroy(implementation_type &impl) 
     { 
       impl->destroy(); 
       impl.reset(); 
     } 
 
     void wait(implementation_type &impl, std::size_t seconds) 
     { 
       boost::system::error_code ec; 
       impl->wait(seconds, ec); 
       boost::asio::detail::throw_error(ec); 
     } 
 
     template <typename Handler> 
     class wait_operation 
     { 
       public: 
         wait_operation(implementation_type &impl, boost::asio::io_service &io_service, std::size_t seconds, Handler handler) 
           : impl_(impl), 
           io_service_(io_service), 
           work_(io_service), 
           seconds_(seconds), 
           handler_(handler) 
         { 
         } 
 
         void operator()() const 
         { 
           implementation_type impl = impl_.lock(); 
           if (impl) 
           { 
               boost::system::error_code ec; 
               impl->wait(seconds_, ec); 
               this->io_service_.post(boost::asio::detail::bind_handler(handler_, ec)); 
           } 
           else 
           { 
               this->io_service_.post(boost::asio::detail::bind_handler(handler_, boost::asio::error::operation_aborted)); 
           } 
       } 
 
       private: 
         boost::weak_ptr<TimerImplementation> impl_; 
         boost::asio::io_service &io_service_; 
         boost::asio::io_service::work work_; 
         std::size_t seconds_; 
         Handler handler_; 
     }; 
 
     template <typename Handler> 
     void async_wait(implementation_type &impl, std::size_t seconds, Handler handler) 
     { 
       this->async_io_service_.post(wait_operation<Handler>(impl, this->get_io_service(), seconds, handler)); 
     } 
 
   private: 
     void shutdown_service() 
     { 
     } 
 
     boost::asio::io_service async_io_service_; 
     boost::scoped_ptr<boost::asio::io_service::work> async_work_; 
     boost::thread async_thread_; 
 }; 
 
 template <typename TimerImplementation> 
 boost::asio::io_service::id basic_timer_service<TimerImplementation>::id; 


     爲了與 Boost.Asio 集成,一個服務必須符合幾個要求:
 
     它必須派生自 boost::asio::io_service::service。 構造函數必須接受一個指向 I/O 服務的引用,該 I/O 服務會被相應地傳給 boost::asio::io_service::service 的構造函數。
 
     任何服務都必須包含一個類型爲 boost::asio::io_service::id 的靜態公有屬性 id。在 I/O 服務的內部是用該屬性來識別服務的。
 
     必須定義兩個名爲 construct() 和 destruct() 的公有方法,均要求一個類型爲 implementation_type 的參數。 implementation_type 通常是該服務的具體實現的類型定義。 正如上面例子所示,在 construct() 中可以很容易地使用一個 boost::shared_ptr 對象來初始化一個服務實現,以及在 destruct() 中相應地析構它。 由於這兩個方法都會在一個 I/O 對象被創建或銷燬時自動被調用,所以一個服務可以分別使用 construct()  和 destruct() 爲每個 I/O 對象創建和銷燬服務實現。
 
     必須定義一個名爲 shutdown_service() 的方法;不過它可以是私有的。 對於一般的 Boost.Asio 擴展來說,它通常是一個空方法。 只有與 Boost.Asio 集成得非常緊密的服務纔會使用它。 但是這個方法必須要有,這樣擴展才能編譯成功。
 
 爲了將方法調用前轉至相應的服務,必須爲相應的 I/O 對象定義要前轉的方法。 這些方法通常具有與 I/O 對象中的方法相似的名字,如上例中的 wait() 和 async_wait()。 同步方法,如 wait(),只是訪問該服務的具體實現去調用一個阻塞式的方法,而異步方法,如 async_wait(),則是在一個線程中調用這個阻塞式方法。
 
 在線程的協助下使用異步操作,通常是通過訪問一個新的 I/O 服務來完成的。 上述例子中包含了一個名爲 async_io_service_ 的屬性,其類型爲 boost::asio::io_service。 這個 I/O 服務的 run() 方法是在它自己的線程中啓動的,而它的線程是在該服務的構造函數內部由類型爲 boost::thread 的 async_thread_ 創建的。 第三個屬性 async_work_ 的類型爲 boost::scoped_ptr<boost::asio::io_service::work>,用於避免  run() 方法立即返回。 否則,這可能會發生,因爲已沒有其它的異步操作在創建。 創建一個類型爲 boost::asio::io_service::work 的對象並將它綁定至該 I/O 服務,這個動作也是發生在該服務的構造函數中,可以防止 run() 方法立即返回。
 
 一個服務也可以無需訪問它自身的 I/O 服務來實現 - 單線程就足夠的。 爲新增的線程使用一個新的 I/O 服務的原因是,這樣更簡單: 線程間可以用 I/O 服務來非常容易地相互通信。 在這個例子中,async_wait() 創建了一個類型爲 wait_operation 的函數對象,並通過 post() 方法將它傳遞給內部的 I/O 服務。 然後,在用於執行這個內部 I/O 服務的 run() 方法的線程內,調用該函數對象的重載 operator()()。 post() 提供了一個簡單的方法,在另一個線程中執行一個函數對象。
 
 wait_operation 的重載 operator()() 操作符基本上就是執行了和 wait() 方法相同的工作:調用服務實現中的阻塞式 wait() 方法。 但是,有可能這個 I/O 對象以及它的服務實現在這個線程執行 operator()() 操作符期間被銷燬。 如果服務實現是在 destruct() 中銷燬的,則 operator()() 操作符將不能再訪問它。 這種情形是通過使用一個弱指針來防止的,從第一章中我們知道:如果在調用 lock() 時服務實現仍然存在,則弱指針 impl_ 返回它的一個共享指針,否則它將返回0。  在這種情況下,operator()() 不會訪問這個服務實現,而是以一個 boost::asio::error::operation_aborted 錯誤來調用句柄。

 #include <boost/system/error_code.hpp> 
 #include <cstddef> 
 #include <windows.h> 
 
 class timer_impl 
 { 
   public: 
     timer_impl() 
       : handle_(CreateEvent(NULL, FALSE, FALSE, NULL)) 
     { 
     } 
 
     ~timer_impl() 
     { 
       CloseHandle(handle_); 
     } 
 
     void destroy() 
     { 
       SetEvent(handle_); 
     } 
 
     void wait(std::size_t seconds, boost::system::error_code &ec) 
     { 
       DWORD res = WaitForSingleObject(handle_, seconds * 1000); 
       if (res == WAIT_OBJECT_0) 
         ec = boost::asio::error::operation_aborted; 
       else 
         ec = boost::system::error_code(); 
     } 
 
 private: 
     HANDLE handle_; 
 }; 


服務實現 timer_impl 使用了 Windows API 函數,只能在 Windows 中編譯和使用。 這個例子的目的只是爲了說明一種潛在的實現。
 
 timer_impl 提供兩個基本方法:wait() 用於等待數秒。 destroy() 則用於取消一個等待操作,這是必須要有的,因爲對於異步操作來說,wait() 方法是在其自身的線程中調用的。 如果 I/O 對象及其服務實現被銷燬,那麼阻塞式的 wait() 方法就要盡使用 destroy() 來取消。
 
 這個 Boost.Asio 擴展可以如下使用。

#include <boost/asio.hpp> 
 #include <iostream> 
 #include "basic_timer.hpp" 
 #include "timer_impl.hpp" 
 #include "basic_timer_service.hpp" 
 
 void wait_handler(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 typedef basic_timer<basic_timer_service<> > timer; 
 
 int main() 
 { 
   boost::asio::io_service io_service; 
   timer t(io_service); 
   t.async_wait(5, wait_handler); 
   io_service.run(); 
 } 

與本章開始的例子相比,這個 Boost.Asio 擴展的用法類似於 boost::asio::deadline_timer。 在實踐上,應該優先使用 boost::asio::deadline_timer,因爲它已經集成在 Boost.Asio 中了。 這個擴展的唯一目的就是示範一下 Boost.Asio 是如何擴展新的異步操作的。
 
 目錄監視器(Directory Monitor) 是現實中的一個 Boost.Asio 擴展,它提供了一個可以監視目錄的 I/O 對象。 如果被監視目錄中的某個文件被創建、修改或是刪除,就會相應地調用一個句柄。 當前的版本支持 Windows 和 Linux (內核版本 2.6.13 或以上)。

 

端口endpoint

什麼是端口endpoint

在進行網絡通信時,需要知道三項:IP地址、通信協議、端口號,通信協議用來決定如何通信,IP地址和端口號用來進行確定目標,在Boost.Asio中提供了對應的模型來一併表示這三項內容,就是端口:ip::basic_endpoint包含了IP地址和端口號,並以通信協議類型爲模板參數。可以直接使用的有:

ip::tcp::endpoint
ip::udp::endpoint
ip::icmp::endpoint

需要了解的內容

本地端口的構造
指定協議和端口號即可構造,通常用來接收新連接,例如:
tcp::endpoint local_ep(ip::tcp::v4(),1024);

遠程端口的構造
在知道遠程主機IP地址和端口號的情況下可以直接構造端口來進行通信,例如:
tcp::endpoint remote_ep(ip::address::from_string("127.0.0.1"),1024);

如何從主機名和服務名得到端口
需要使用DNS服務得到主機對應的IP地址,在Boost.Asio中提供了ip::tcp::resolver等來獲取端口

總結

端口就相當於具體的地址,根據這個地址來進行通信動作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章