1. 程式人生 > >boost-asio-cpp-network-programming-in-chinese-master

boost-asio-cpp-network-programming-in-chinese-master

#實戰出精華
*在具體的C++網路程式設計中提升你的逼格*

*John Torjo*

---

####Boost.Asio C++ 網路程式設計

Copyright © 2013 Packt Publishing

---

##關於作者
做為權威的C++專家,除了偶爾用C#和Java寫程式,**John Torjo**把他超過15年程式設計生涯中的大部分時間都貢獻給了C++。

他也很喜歡在C++ Users Journal和其他雜誌上寫一些程式設計相關的文章。

閒暇的時候,他喜歡玩撲克、開快車。他有很多個自由職業,其中一個就把他的兩個愛好結合在一起,一個是玩撲克,另外一個是程式設計。如果你想聯絡他,可以發郵件到[

[email protected]]([email protected])。

---
我要感謝我的朋友Alexandru Chis, Aurelian Hale, Bela Tibor Bartha, Cristian Fatu, Horia Uifaleanu, Nicolae Ghimbovschi以及Ovidiu Deac。感謝他們對本書提出的反饋和意見。同時我也要感謝Packt公司各位對我頻繁錯過截稿日期行為的包容。然後最需要感謝的是Chris Kohlhoff,Boost.Asio的作者,是他寫出瞭如此偉大的庫。

把這本書獻給我最好的朋友Darius。

---
##關於評審員
Béla Tibor Bartha

一個使用過多種技術和語言進行開發的專業軟體工程師。儘管在過去的4年裡,他做的是iOS和OSX應用開發,但是C++陪伴他度過了早期個人遊戲專案開發的激情歲月。

---
我要感謝John,因為他我才能做這本書的評審

---
Nicolae Ghimbovschi

一個參加各類C++專案超過5年的天才個人開發者。他主要參與一些企業通訊工程的專案。作為一個狂熱的Linux愛好者,他喜歡利用不同的作業系統、指令碼工具和程式語言進行測試和實驗。除了程式設計,他還喜歡騎自行車、瑜伽和冥想。

---
我要感謝John讓我來評審這本書

---
##關於譯者
非主流程式猿mmoaay,技術很爛,喜歡平面設計、鼠繪、交友、運動和翻譯,但是確作為一隻程式猿混跡在IT行業。熱愛開源,技術爛就只好做做設計和翻譯的工作。

微博:[http://weibo.com/smmoaay](http://weibo.com/smmoaay)

---
##關於avplayer
[http://avplayer.org](http://avplayer.org) 中國第一技術社群。

---

##目錄
---
前言

---
第一章:Boost.Asio入門

    什麼是Boost.Asio?
        歷史
        依賴
        編譯 Boost.Asio
        重要的巨集
    同步VS非同步
    異常VS錯誤程式碼
    Boost.Asio中的多執行緒
    不僅僅是網路
    計時器
    io_service類
    總結

---
第二章:Boost.Asio基本原理

    網路API
    Boost.Asio名稱空間
    IP地址
    端點
    Sockets
        同步錯誤程式碼
        Socket成員函式
        其他注意事項
    read/write/connect自由函式
        connect函式
        read/write函式
    非同步程式設計
        為什麼要非同步?
        非同步run(),run_one(),poll(),poll_one()
            持續執行
            run_one(),poll(),poll_one()函式
        非同步工作
        非同步post() VS dispatch() VS wrap()
    保持執行
    總結

---
第三章:回顯服務端/客戶端

    TCP回顯服務端/客戶端
        TCP同步客戶端
        TCP同步服務端
        TCP非同步客戶端
        TCP同步服務端
        程式碼
    UDP回顯服務端/客戶端
        UDP同步回顯客戶端
        UDP同步回顯服務端
    總結

---
第四章:客戶端和服務端

    同步客戶端/服務端
        同步客戶端
        同步服務端
    非同步客戶端/服務端
        非同步客戶端
        非同步服務端
    總結

---
第五章:同步VS非同步

    同步非同步混合程式設計
    客戶端和服務端之間訊息的互相傳遞
    客戶端軟體中的同步I/O
    服務端軟體中的同步I/O
        同步服務端中的執行緒
    客戶端軟體中的非同步I/O
    服務端軟體中的非同步I/O
        非同步服務端中的執行緒
    非同步操作
    代理實現
    總結

---
第六章:Boost.Asio-其他特性

    std streams和std buffer I/O
    Boost.Asio和STL流
    streambuf類
    處理streambuf物件的自由函式
    協程
    總結

---
第七章:Boost.Asio-進階

    Asio VS Boost.Asio
    除錯
        處理程式跟蹤資訊
        例子
        處理程式跟蹤檔案
    SSL
    Boost.Asio的Windows特性
        流處理
        隨機儲存處理
        物件處理
    Boost.Asio的POSIX特性
        本地sockects
        連線本地sockets
        POSIX檔案描述符
        Fork
        總結

---
索引

---
##前言
網路程式設計由來已久,並且是一個極富挑戰性的任務。Boost.Asio對網路程式設計做了一個極好的抽象,從而保證只需要少量的程式設計就可以實現一個優雅的客戶端/服務端軟體。在實現的過程中,它能讓你體會到極大的樂趣。而且更為有益的是:Boost.Asio包含了一些非網路的特性,用Boost.Asio寫出來的程式碼緊湊、易讀,而且如果你按照我在書中所講的來做,你的程式碼會無懈可擊。

這本書涵蓋了什麼?

*第一章:Boost.Asio入門*將告訴你Boost.Asio是什麼?怎麼編譯它?順帶著會有一些例子。你會發現Boost.Asio不僅僅是一個網路庫。同時你也會接觸到Boost.Asio中最核心的類io_service。

*第二章:Boost.Asio基本原理*包含了你必須瞭解的內容:什麼時候用Boost.Asio?我們將深入瞭解非同步程式設計——一種比同步更需要技巧,且更有樂趣的程式設計方式。這一章也是在開發你自己的網路應用時可以作為參考的一章。

*第三章:回顯服務端/客戶端*將會告訴你如何實現一個小的客戶端/服務端應用;也許這會是你寫過的最簡單的客戶端/服務端應用。回顯應用就是把客戶端發過來的訊息傳送回去然後關閉客戶端連線的服務。我們會先實現一個同步的版本,然後再實現一個非同步的版本,這樣就可以非常容易地看到它們之間的不同。

*第四章:客戶端和服務端*會深入討論如何用Boost.Asio建立一個簡單的客戶端/服務端應用。我們將討論如何避免諸如記憶體洩漏和死鎖的缺陷。所有的程式都只是實現一個簡單的框架,從而使你能更方便地對它們進行擴充套件以滿足你的需求。

*第五章:同步VS非同步*會帶你瞭解在同步和非同步方式之間做選擇時需要考慮的事情。首要的事情就是不要混淆它們。在這一章,我們會發現實現、測試和除錯每個型別應用是非常容易的。

*第六章:Boost.Asio的其他特性*將帶你瞭解Boost.Asio一些不為人知的特性。你會發現,雖然std streams和streambufs有一點點難用,但是卻表現出了它們得天獨厚的優勢。最後,是姍姍來遲的Boost.Asio協程,它可以讓你用一種更易讀的方式來寫非同步程式碼。(就好像寫同步程式碼一樣)

*第七章: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。讀者還需要知道阻塞的概念以及“非阻塞”操作。

###約定

本書使用不同樣式的文字來區分不同種類的資訊。這裡給出這些樣式的例子以及它們的解釋。

文字中的程式碼會這樣顯示:“通常一個*io_service*的例子就足夠了”。

一段程式碼是下面這個樣子的:


```
read(stream, buffer [, extra options])

async_read(stream, buffer [, extra options], handler)

write(stream, buffer [, extra options])

async_write(stream, buffer [, extra options], handler)
```

**專業詞彙和重要的單詞**用黑體顯示

[*!警告或者重要的註釋在這樣的一個框裡面*]

[*?技巧在這樣的一個框裡面*]

###讀者反饋

我們歡迎來自讀者的反饋。告訴我們你對這本書的看法——你喜歡哪部分,不喜歡哪部分。讀者的反饋對我們非常重要,它能讓我們寫出對讀者幫助更大的書。

你只需要傳送一封郵件到[[email protected]]([email protected])即可進行反饋,注意在郵件的主題中註明書名。

如果你有一個擅長的專題,想撰寫一本書或者為某本書做貢獻。請閱讀我們在[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),然後通過選擇書名的方式來檢視。

###答疑

如果你有關於本書任何方面的問題,你可以通過[[email protected]]([email protected])聯絡我們。我們將盡我們最大的努力進行解答

##Boost.Asio入門
首先,讓我們先來了解一下什麼是Boost.Asio?怎麼編譯它?瞭解的過程中我們會給出一些例子。然後在發現Boost.Asio不僅僅是一個網路庫的同時你也會接觸到Boost.Asio中最核心的類——*io_service*。
###什麼是Boost.Asio
簡單來說,Boost.Asio是一個跨平臺的、主要用於網路和其他一些底層輸入/輸出程式設計的C++庫。

計算機網路的設計方式有很多種,但是Boost.Asio的的方式遠遠優於其它的設計方式。它在2005年就被包含進Boost,然後被大量Boost的使用者測試並在很多專案中使用,比如Remobo([http://www.remobo.com](http://www.remobo.com)),可以讓你建立你自己的**即時私有網路(IPN)**的應用,libtorrent([http://www.rasterbar.com/products/libtorrent]([http://www.rasterbar.com/products/libtorrent](http://www.rasterbar.com/products/libtorrent)))一個實現了位元流客戶端的庫,PokerTH ([http://www.pokerth.net](http://www.pokerth.net))一個支援LAN和網際網路對戰的紙牌遊戲。

Boost.Asio在網路通訊、COM串列埠和檔案上成功地抽象了輸入輸出的概念。你可以基於這些進行同步或者非同步的輸入輸出程式設計。

```
read(stream, buffer [, extra options])
async_read(stream, buffer [, extra options], handler)
write(stream, buffer [, extra options])
async_write(stream, buffer [, extra options], handler)
```

從前面的程式碼片段可以看出,這些函式支援傳入包含任意內容(不僅僅是一個socket,我們可以對它進行讀寫)的流例項。

作為一個跨平臺的庫,Boost.Asio可以在大多數作業系統上使用。能同時支援數千個併發的連線。其網路部分的靈感來源於**伯克利軟體分發(BSD)socket**,它提供了一套可以支援**傳輸控制協議(TCP)**socket、**使用者資料報協議(UDP)**socket和**Internet控制訊息協議(IMCP)**socket的API,而且如果有需要,你可以對其進行擴充套件以支援你自己的協議。
###歷史
Boost.Asio在2003被開發出來,然後於2005年的12月引入到Boost 1.35版本中。原作者是Christopher M. Kohlhoff,你可以通過[[email protected]]([email protected])聯絡他。

這個庫在以下的平臺和編譯器上測試通過:

* 32-bit和64-bit Windows,使用Visual C++ 7.1及以上
* Windows下使用MinGW
* Windows下使用Cygwin(確保已經定義 __USE_232_SOCKETS)
* 基於2.4和2.6核心的Linux,使用g++ 3.3及以上
* Solaris下使用g++ 3.3及以上
* 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))
* **Boost.Regex**:使用這個庫(可選的)以便你過載*read_until()*或者*async_read_until()*時使用*boost::regex*引數。
* **Boost.DateTime**:使用這個庫(可選的)以便你使用Boost.Asio中的計時器
* **OpenSSL**:使用這個庫(可選的)以便你使用Boost.Asio提供的SSL支援。

###編譯Boost.Asio
Boost.Asio是一個只需要引入標頭檔案就可以使用的庫。然而,考慮到你的編譯器和程式的大小,你可以選擇用原始檔的方式來編譯Boost.Asio。如果你想要這麼做以減少編譯時間,有如下幾種方式:

在某個原始檔中,新增*#include "boost/asio/impl/src.hpp"*(如果你在使用SSL,新增*#include "boost/asio/ssl/impl/src.hpp"*)
在所有的原始檔中,新增*#define BOOST_ASIO_SEPARATE_COMPILATION*

注意Boost.Asio依賴於Boost.System,必要的時候還依賴於Boost.Regex,所以你需要用如下的指令先編譯Boost:

*bjam –with-system –with-regex stage*

如果你還想同時編譯tests,你需要使用如下的指令:

*bjam –with-system –with-thread –with-date_time –with-regex –with-serialization stage*

這個庫有大量的例子,你可以連同本書中的例子一塊看看。
### 重要的巨集
如果設定了*BOOST_ASIO_DISABLE_THREADS*;不管你是否在編譯Boost的過程中使用了執行緒支援,Boost.Asio中的執行緒支援都會失效。
### 同步VS非同步
首先,非同步程式設計和同步程式設計是非常不同的。在同步程式設計中,所有的操作都是順序執行的,比如從socket中讀取(請求),然後寫入(迴應)到socket中。每一個操作都是阻塞的。因為操作是阻塞的,所以為了不影響主程式,當在socket上讀寫時,通常會建立一個或多個執行緒來處理socket的輸入/輸出。因此,同步的服務端/客戶端通常是多執行緒的。

相反的,非同步程式設計是事件驅動的。雖然啟動了一個操作,但是你不知道它何時會結束;它只是提供一個回撥給你,當操作結束時,它會呼叫這個API,並返回操作結果。對於有著豐富經驗的QT(諾基亞用來建立跨平臺圖形使用者介面應用程式的庫)程式設計師來說,這就是他們的第二天性。因此,在非同步程式設計中,你只需要一個執行緒。

因為中途做改變會非常困難而且容易出錯,所以你在專案初期(最好是一開始)就得決定用同步還是非同步的方式實現網路通訊。不僅API有極大的不同,你程式的語意也會完全改變(非同步網路通訊通常比同步網路通訊更加難以測試和除錯)。你需要考慮是採用阻塞呼叫和多執行緒的方式(同步,通常比較簡單),或者是更少的執行緒和事件驅動(非同步,通常更復雜)。

下面是一個基礎的同步客戶端例子:
```
using boost::asio;
io_service service;
ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 2001);
ip::tcp::socket sock(service);
sock.connect(ep);
```
首先,你的程式至少需要一個*io_service*例項。Boost.Asio使用*io_service*同作業系統的輸入/輸出服務進行互動。通常一個*io_service*的例項就足夠了。然後,建立你想要連線的地址和埠,再建立socket。把socket連線到你建立的地址和埠。

下面是一個簡單的使用Boost.Asio的服務端:
```
typedef boost::shared_ptr<ip::tcp::socket> socket_ptr;
io_service service;
ip::tcp::endpoint ep( ip::tcp::v4(), 2001)); // listen on 2001
ip::tcp::acceptor acc(service, ep);
while ( true) {
    socket_ptr sock(new ip::tcp::socket(service));
    acc.accept(*sock);
    boost::thread( boost::bind(client_session, sock));
}
void client_session(socket_ptr sock) {
    while ( true) {
        char data[512];
        size_t len = sock->read_some(buffer(data));
        if ( len > 0)
            write(*sock, buffer("ok", 2));
    }
}
```
首先,同樣是至少需要一個*io_service*例項。然後你指定你想要監聽的埠,再建立一個接收器——一個用來接收客戶端連線的物件。 在接下來的迴圈中,你建立一個虛擬的socket來等待客戶端的連線。然後當一個連線被建立時,你建立一個執行緒來處理這個連線。

*在client_session*執行緒中來讀取一個客戶端的請求,進行解析,然後返回結果。

而建立一個非同步的客戶端,你需要做如下的事情:
```
using boost::asio;
io_service service;
ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 2001);
ip::tcp::socket sock(service);
sock.async_connect(ep, connect_handler);
service.run();
void connect_handler(const boost::system::error_code & ec) {
    // 如果ec返回成功我們就可以知道連線成功了
}
```
在程式中你需要建立至少一個*io_service*例項。你需要指定連線的地址以及建立socket。

當連線完成時(其完成處理程式)你就非同步地連線到了指定的地址和埠,也就是說,*connect_handler*被呼叫了。

當*connect_handler*被呼叫時,檢查錯誤程式碼(*ec*),如果成功,你就可以向服務端進行非同步的寫入。

注意:只要還有待處理的非同步操作,*servece.run()*迴圈就會一直執行。在上述例子中,只執行了一個這樣的操作,就是socket的*async_connect*。在這之後,*service.run()*就退出了。

每一個非同步操作都有一個完成處理程式——一個操作完成之後被呼叫的函式。 下面的程式碼是一個基本的非同步服務端
```
using boost::asio;
typedef boost::shared_ptr<ip::tcp::socket> socket_ptr;
io_service service;
ip::tcp::endpoint ep( ip::tcp::v4(), 2001)); // 監聽埠2001
ip::tcp::acceptor acc(service, ep);
socket_ptr sock(new ip::tcp::socket(service));
start_accept(sock);
service.run();
void start_accept(socket_ptr sock) {
    acc.async_accept(*sock, boost::bind( handle_accept, sock, _1) );
}
void handle_accept(socket_ptr sock, const boost::system::error_code &
err) {
    if ( err) return;
    // 從這裡開始, 你可以從socket讀取或者寫入
    socket_ptr sock(new ip::tcp::socket(service));
    start_accept(sock);
}
```
在上述程式碼片段中,首先,你建立一個*io_service*例項,指定監聽的埠。然後,你建立接收器acc——一個接受客戶端連線,建立虛擬的socket,非同步等待客戶端連線的物件。

最後,執行非同步*service.run()*迴圈。當接收到客戶端連線時,*handle_accept*被呼叫(呼叫*async_accept*的完成處理程式)。如果沒有錯誤,這個socket就可以用來做讀寫操作。

在使用這個socket之後,你建立了一個新的socket,然後再次呼叫*start_accept()*,用來建立另外一個“等待客戶端連線”的非同步操作,從而使*service.run()*迴圈一直保持忙碌狀態。
### 異常處理VS錯誤程式碼
Boost.Asio允許同時使用異常處理或者錯誤程式碼,所有的非同步函式都有丟擲錯誤和返回錯誤碼兩種方式的過載。當函式丟擲錯誤時,它通常丟擲*boost::system::system_error*的錯誤。
```
using boost::asio;
ip::tcp::endpoint ep;
ip::tcp::socket sock(service);
sock.connect(ep); // 第一行
boost::system::error_code err;
sock.connect(ep, err); // 第二行
```
在前面的程式碼中,*sock.connect(ep)*會丟擲錯誤,*sock.connect(ep, err)*則會返回一個錯誤碼。

看一下下面的程式碼片段:
```
try {
    sock.connect(ep);
} catch(boost::system::system_error e) {
    std::cout << e.code() << std::endl;
}
```
下面的程式碼片段和前面的是一樣的:
```
boost::system::error_code err;
sock.connect(ep, err);
if ( err)
    std::cout << err << std::endl;
```
當使用非同步函式時,你可以在你的回撥函式裡面檢查其返回的錯誤碼。非同步函式從來不丟擲異常,因為這樣做毫無意義。那誰會捕獲到它呢?

在你的非同步函式中,你可以使用異常處理或者錯誤碼(隨心所欲),但要保持一致性。同時使用這兩種方式會導致問題,大部分時候是崩潰(當你不小心出錯,忘記去處理一個丟擲來的異常時)。如果你的程式碼很複雜(呼叫很多socket讀寫函式),你最好選擇異常處理的方式,把你的讀寫包含在一個函式*try {} catch*塊裡面。
```
void client_session(socket_ptr sock) {
    try {
        ...
    } catch ( boost::system::system_error e) {
        // 處理錯誤
    }
}
```
如果使用錯誤碼,你可以使用下面的程式碼片段很好地檢測連線是何時關閉的:
```
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的執行緒時,我們經常在討論:

* *io_service*:*io_service*是執行緒安全的。幾個執行緒可以同時呼叫*io_service::run()*。大多數情況下你可能在一個單執行緒函式中呼叫*io_service::run()*,這個函式必須等待所有非同步操作完成之後才能繼續執行。然而,事實上你可以在多個執行緒中呼叫*io_service::run()*。這會阻塞所有呼叫*io_service::run()*的執行緒。只要當中任何一個執行緒呼叫了*io_service::run()*,所有的回撥都會同時被呼叫;這也就意味著,當你在一個執行緒中呼叫*io_service::run()*時,所有的回撥都被呼叫了。
* *socket*:*socket*類不是執行緒安全的。所以,你要避免在某個執行緒裡讀一個socket時,同時在另外一個執行緒裡面對其進行寫入操作。(通常來說這種操作都是不推薦的,更別說Boost.Asio)。
* *utility*:就*utility*來說,因為它不是執行緒安全的,所以通常也不提倡在多個執行緒裡面同時使用。裡面的方法經常只是在很短的時間裡面使用一下,然後就釋放了。

除了你自己建立的執行緒,Boost.Asio本身也包含幾個執行緒。但是可以保證的是那些執行緒不會呼叫你的程式碼。這也意味著,只有呼叫了*io_service::run()*方法的執行緒才會呼叫回撥函式。
### 不僅僅是網路通訊
除了網路通訊,Boost.Asio還包含了其他的I/O功能。

Boost.Asio支援訊號量,比如*SIGTERM*(軟體終止)、*SIGINT*(中斷訊號)、*SIGSEGV*(段錯誤)等等。 你可以建立一個*signal_set*例項,指定非同步等待的訊號量,然後當這些訊號量產生時,就會呼叫你的非同步處理程式:
```
void signal_handler(const boost::system::error_code & err, int signal)
{
    // 紀錄日誌,然後退出應用
}
boost::asio::signal_set sig(service, SIGINT, SIGTERM);
sig.async_wait(signal_handler);
```

如果*SIGINT*產生,你就能在你的*signal_handler*回撥中捕獲到它。

你可以使用Boost.Asio輕鬆地連線到一個串列埠。在Windows上埠名稱是*COM7*,在POSIX平臺上是*/dev/ttyS0*。
```
io_service service;
serial_port sp(service, "COM7");
```
開啟埠後,你就可以使用下面的程式碼設定一些埠選項,比如埠的波特率、奇偶校驗和停止位。
```
serial_port::baud_rate rate(9600);
sp.set_option(rate);
```
開啟埠後,你可以把這個串列埠看做一個流,然後基於它使用自由函式對串列埠進行讀/寫操作。比如*async_read(), write, async_write(),* 就像下面的程式碼片段:
```
char data[512];
read(sp, buffer(data, 512));
```
Boost.Asio也可以連線到Windows的檔案,然後同樣使用自由函式,比如*read(), asyn_read()*等等,就像下面的程式碼片段:
```
HANDLE h = ::OpenFile(...);
windows::stream_handle sh(service, h);
char data[512];
read(h, buffer(data, 512));
```
對於POXIS檔案描述符,比如管道,標準I/O和各種裝置(但不包括普通檔案)你也可以這樣做,就像下面的程式碼所做的一樣:
```
posix::stream_descriptor sd_in(service, ::dup(STDIN_FILENO));
char data[512];
read(sd_in, buffer(data, 512));
```
### 計時器
一些I/O操作需要一個超時時間。這隻能應用在非同步操作上(同步意味著阻塞,因此沒有超時時間)。例如,下一條資訊必須在100毫秒內從你的同伴那傳遞給你。
```
bool read = false;
void deadline_handler(const boost::system::error_code &) {
    std::cout << (read ? "read successfully" : "read failed") << std::endl;
}
void read_handler(const boost::system::error_code &) {
    read = true;
}
ip::tcp::socket sock(service);

read = false;
char data[512];
sock.async_read_some(buffer(data, 512));
deadline_timer t(service, boost::posix_time::milliseconds(100));
t.async_wait(&deadline_handler);
service.run();
```
在上述程式碼片段中,如果你在超時之前讀完了資料,*read*則被設定成true,這樣我們的夥伴就及時地通知了我們。否則,當*deadline_handler*被呼叫時,*read*還是false,也就意味著我們的操作超時了。

Boost.Asio也支援同步計時器,但是它們通常和一個簡單的sleep操作是一樣的。*boost::this_thread::sleep(500);*這段程式碼和下面的程式碼片段完成了同一件事情:
```
deadline_timer t(service, boost::posix_time::milliseconds(500));
t.wait();
```
### io_service類

你應該已經發現大部分使用Boost.Asio編寫的程式碼都會使用幾個*io_service*的例項。*io_service*是這個庫裡面最重要的類;它負責和作業系統打交道,等待所有非同步操作的結束,然後為每一個非同步操作呼叫其完成處理程式。

如果你選擇用同步的方式來建立你的應用,你則不需要考慮我將在這一節向你展示的東西。
你有多種不同的方式來使用*io_service*。在下面的例子中,我們有3個非同步操作,2個socket連線操作和一個計時器等待操作:
* 有一個*io_service*例項和一個處理執行緒的單執行緒例子: 
```
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); 
sock2.async_connect( ep, connect_handler); 
deadline_timer t(service, boost::posixtime::seconds(5));
t.async_wait(timeout_handler); 
service.run();
```
* 有一個io_service例項和多個處理執行緒的多執行緒例子:
 
```
io_service service;
ip::tcp::socket sock1(service);
ip::tcp::socket sock2(service);
sock1.asyncconnect( ep, connect_handler);
sock2.async_connect( ep, connect_handler);
deadline_timer t(service, boost::posixtime::seconds(5));
t.async_wait(timeout_handler);
for ( int i = 0; i < 5; ++i)
    boost::thread( run_service);
void run_service()
{
    service.run();
}
```
* 有多個*io_service*例項和多個處理執行緒的多執行緒例子: 

```
io_service service[2];
ip::tcp::socket sock1(service[0]);
ip::tcp::socket sock2(service[1]);
sock1.asyncconnect( ep, connect_handler);
sock2.async_connect( ep, connect_handler);
deadline_timer t(service[0], boost::posixtime::seconds(5));
t.async_wait(timeout_handler);
for ( int i = 0; i < 2; ++i)
    boost::thread( boost::bind(run_service, i));
void run_service(int idx)
{
    service[idx].run();
}
```

首先,要注意你不能擁有多個*io_service*例項卻只有一個執行緒。下面的程式碼片段沒有任何意義:

```
for ( int i = 0; i < 2; ++i)
    service[i].run();
```
上面的程式碼片段沒有意義是因為*service[1].run()*需要*service[0].run()*先結束。因此,所有由*service[1]*處理的非同步操作都需要等待,這顯然不是一個好主意。

在前面的3個方案中,我們在等待3個非同步操作結束。為了解釋它們之間的不同點,我們假設:過一會操作1完成,然後接著操作2完成。同時我們假設每一個完成處理程式需要1秒鐘來完成執行。

在第一個例子中,我們在一個執行緒中等待三個操作全部完成,第1個操作一完成,我們就呼叫它的完成處理程式。儘管操作2緊接著完成了,但是操作2的完成處理程式需要在1秒鐘後,也就是操作1的完成處理程式完成時才會被呼叫。

第二個例子,我們在兩個執行緒中等待3個非同步操作結束。當操作1完成時,我們在第1個執行緒中呼叫它的完成處理程式。當操作2完成時,緊接著,我們就在第2個執行緒中呼叫它的完成處理程式(當執行緒1在忙著響應操作1的處理程式時,執行緒2空閒著並且可以迴應任何新進來的操作)。

在第三個例子中,因為操作1是*sock1*的*connect*,操作2是*sock2*的*connect*,所以應用程式會表現得像第二個例子一樣。執行緒1會處理*sock1* *connect*操作的完成處理程式,執行緒2會處理*sock2*的*connect*操作的完成處理程式。然而,如果*sock1*的*connect*操作是操作1,*deadline_timer t*的超時操作是操作2,執行緒1會結束正在處理的*sock1* *connect*操作的完成處理程式。因而,*deadline_timer t*的超時操作必須等*sock1* *connect*操作的完成處理程式結束(等待1秒鐘),因為執行緒1要處理*sock1*的連線處理程式和*t*的超時處理程式。 

下面是你需要從前面的例子中學到的: 
* 第一種情況是非常基礎的應用程式。因為是序列的方式,所以當幾個處理程式需要被同時呼叫時,你通常會遇到瓶頸。如果一個處理程式需要花費很長的時間來執行,所有隨後的處理程式都不得不等待。
* 第二種情況是比較適用的應用程式。他是非常強壯的——如果幾個處理程式被同時呼叫了(這是有可能的),它們會在各自的執行緒裡面被呼叫。唯一的瓶頸就是所有的處理執行緒都很忙的同時又有新的處理程式被呼叫。然而,這是有快速的解決方式的,增加處理執行緒的數目即可。
* 第三種情況是最複雜和最難理解的。你只有在第二種情況不能滿足需求時才使用它。這種情況一般就是當你有成千上萬實時(socket)連線時。你可以認為每一個處理執行緒(執行*io_service::run()*的執行緒)有它自己的*select/epoll*迴圈;它等待任意一個socket連線,然後等待一個讀寫操作,當它發現這種操作時,就執行。大部分情況下,你不需要擔心什麼,唯一你需要擔心的就是當你監控的socket數目以指數級的方式增長時(超過1000個的socket)。在那種情況下,有多個select/epoll迴圈會增加應用的響應時間。

如果你覺得你的應用程式可能需要轉換到第三種模式,請確保監聽操作的這段程式碼(呼叫*io_service::run()*的程式碼)和應用程式其他部分是隔離的,這樣你就可以很輕鬆地對其進行更改。

最後,需要一直記住的是如果沒有其他需要監控的操作,*.run()*就會結束,就像下面的程式碼片段: 

```
io_service service; 
tcp::socket sock(service); 
sock.async_connect( ep, connect_handler); 
service.run();
```

在上面的例子中,只要sock建立了一個連線,*connect_handler*就會被呼叫,然後接著*service.run()*就會完成執行。

如果你想要*service.run()*接著執行,你需要分配更多的工作給它。這裡有兩個方式來完成這個目標。一種方式是在*connect_handler*中啟動另外一個非同步操作來分配更多的工作。 另一種方式會模擬一些工作給它,用下面的程式碼片段: 
```
typedef boost::shared_ptr work_ptr;
work_ptr dummy_work(new io_service::work(service));
```

上面的程式碼可以保證*service.run()*一直執行直到你呼叫*useservice.stop()*或者 *dummy_work.reset(0);*// 銷燬 dummy_work.
### 總結
做為一個複雜的庫,Boost.Asio讓網路程式設計變得異常簡單。構建起來也簡單。而且在避免使用巨集這一點上也做得很好;它雖然定義了少部分的巨集來做選項開關,但是你需要關心的很少。

Boost.Asio支援同步和非同步程式設計。他們有很大不同;你需要在專案早期就選擇其中的一種來實現,因為它們之間的轉換是非常複雜而且易錯的。

如果你選擇同步,你可以選擇異常處理或者錯誤碼,從異常處理轉到錯誤碼;只需要在call函式中增加一個引數即可(錯誤碼)。

Boost.Asio不僅僅可以用來做網路程式設計。它還有其他更多的特性,這讓它顯得更有價值,比如訊號量,計時器等等。

下一章我們將深入研究大量Boost.Asio中用來做網路程式設計的函式和類。同時我們也會學一些非同步程式設計的訣竅。

##Boost.Asio基本原理
這一章涵蓋了使用Boost.Asio時必須知道的一些事情。我們也將深入研究比同步程式設計更復雜、更有樂趣的非同步程式設計。

###網路API
這一部分包含了當使用Boost.Asio編寫網路應用程式時必須知道的事情。

###Boost.Asio名稱空間

Boost.Asio的所有內容都包含在boost::asio名稱空間或者其子名稱空間內。
* *boost::asio*:這是核心類和函式所在的地方。重要的類有io_service和streambuf。類似*read, read_at, read_until*方法,它們的非同步方法,它們的寫方法和非同步寫方法等自由函式也在這裡。
* *boost::asio::ip*:這是網路通訊部分所在的地方。重要的類有*address, endpoint, tcp,
 udp和icmp*,重要的自由函式有*connect*和*async_connect*。要注意的是在*boost::asio::ip::tcp::socket*中間,*socket*只是*boost::asio::ip::tcp*類中間的一個*typedef*關鍵字。
* *boost::asio::error*:這個名稱空間包含了呼叫I/O例程時返回的錯誤碼
* *boost::asio::ssl*:包含了SSL處理類的名稱空間
* *boost::asio::local*:這個名稱空間包含了POSIX特性的類
* *boost::asio::windows*:這個名稱空間包含了Windows特性的類

###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::to_string()* :這個函式返回這個地址的字串。
* *ip::address_v4::broadcast([addr, mask])*:這個函式建立了一個廣播地址
*ip::address_v4::any()*:這個函式返回一個能表示任意地址的地址。
* *ip::address_v4::loopback(), ip_address_v6::loopback()*:這個函式返回環路地址(為v4/v6協議)
* *ip::host_name()*:這個函式用string資料型別返回當前的主機名。

大多數情況你會選擇用*ip::address::from_string*:
```
ip::address addr = ip::address::from_string("127.0.0.1");
```

如果你想通過一個主機名進行連線,下面的程式碼片段是無用的:
```
// 丟擲異常
ip::address addr = ip::address::from_string("www.yahoo.com");
```


###端點
端點是使用某個埠連線到的一個地址。不同型別的socket有它自己的*endpoint*類,比如*ip::tcp::endpoint、ip::udp::endpoint*和*ip::icmp::endpoint*

如果想連線到本機的80埠,你可以這樣做:
```
ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80);
```

有三種方式來讓你建立一個端點:
* *endpoint()*:這是預設建構函式,某些時候可以用來建立UDP/ICMP socket。
* *endpoint(protocol, port)*:這個方法通常用來建立可以接受新連線的伺服器端socket。
* *endpoint(addr, port)*:這個方法建立了一個連線到某個地址和埠的端點。

例子如下:
```
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地址),你需要這樣做:
```
// 輸出 "87.248.122.122"
io_service service;
ip::tcp::resolver resolver(service);
ip::tcp::resolver::query query("www.yahoo.com", "80");
ip::tcp::resolver::iterator iter = resolver.resolve( query);
ip::tcp::endpoint ep = *iter;
std::cout << ep.address().to_string() << std::endl;
```

你可以用你需要的socket型別來替換tcp。首先,為你想要查詢的名字建立一個查詢器,然後用resolve()函式解析它。如果成功,它至少會返回一個入口。你可以利用返回的迭代器,使用第一個入口或者遍歷整個列表來拿到全部的入口。

給定一個端點,可以獲得他的地址,埠和IP協議(v4或者v6):
```
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*類當作佔位符;它們可以讓你便捷地訪問其他類/函式,如下所示:
* *ip::tcp::socket, ip::tcp::acceptor, ip::tcp::endpoint,ip::tcp::resolver, ip::tcp::iostream*
* *ip::udp::socket, ip::udp::endpoint, ip::udp::resolver*
* *ip::icmp::socket, ip::icmp::endpoint, ip::icmp::resolver*

*socket*類建立一個相應的*socket*。而且總是在構造的時候傳入io_service例項:
```
io_service service;
ip::udp::socket sock(service)
sock.set_option(ip::udp::socket::reuse_address(true));
```

每一個socket的名字都是一個typedef關鍵字
* *ip::tcp::socket = basic_stream_socket<tcp>*
* *ip::udp::socket = basic_datagram_socket<udp>*
* *ip::icmp::socket = basic_raw_socket<icmp>*

###同步錯誤碼
所有的同步函式都有丟擲異常或者返回錯誤碼的過載,比如下面的程式碼片段:
```
sync_func( arg1, arg2 ... argN); // 丟擲異常
boost::system::error_code ec;
sync_func( arg1 arg2, ..., argN, ec); // 返回錯誤碼
```

在這一章剩下的部分,你會見到大量的同步函式。簡單起見,我省略了有返回錯誤碼的過載,但是不可否認它們確實是存在的。

###socket成員方法
這些方法被分成了幾組。並不是所有的方法都可以在各個型別的套接字裡使用。這個部分的結尾將有一個列表來展示各個方法分別屬於哪個socket類。

注意所有的非同步方法都立刻返回,而它們相對的同步實現需要操作完成之後才能返回。

###連線相關的函式
這些方法是用來連線或繫結socket、斷開socket字連線以及查詢連線是活動還是非活動的:
* *assign(protocol,socket)*:這個函式分配了一個原生的socket給這個socket例項。當處理老(舊)程式時會使用它(也就是說,原生socket已經被建立了)
* *open(protocol)*:這個函式用給定的IP協議(v4或者v6)開啟一個socket。你主要在UDP/ICMP socket,或者服務端socket上使用。
* *bind(endpoint)*:這個函式繫結到一個地址
* *connect(endpoint)*:這個函式用同步的方式連線到一個地址
* *async_connect(endpoint)*:這個函式用非同步的方式連線到一個地址
* *is_open()*:如果套接字已經開啟,這個函式返回true
* *close()*:這個函式用來關閉套接字。呼叫時這個套接字上任何的非同步操作都會被立即關閉,同時返回*error::operation_aborted*錯誤碼。
* *shutdown(type_of_shutdown)*:這個函式立即使send或者receive操作失效,或者兩者都失效。
* *cancel()*:這個函式取消套接字上所有的非同步操作。這個套接字上任何的非同步操作都會立即結束,然後返回*error::operation_aborted*錯誤碼。

例子如下:
```
ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80);
ip::tcp::socket sock(service);
sock.open(ip::tcp::v4()); n
sock.connect(ep);
sock.write_some(buffer("GET /index.html\r\n"));
char buff[1024]; sock.read_some(buffer(buff,1024));
sock.shutdown(ip::tcp::socket::shutdown_receive);
sock.close();
```


###讀寫函式
這些是在套接字上執行I/O操作的函式。

對於非同步函式來說,處理程式的格式*void handler(const boost::system::error_code& e, size_t bytes)*都是一樣的

* *async_receive(buffer, [flags,] handler)*:這個函式啟動從套接字非同步接收資料的操作。
* *async_read_some(buffer,handler)*:這個函式和*async_receive(buffer, handler)*功能一樣。
* *async_receive_from(buffer, endpoint[, flags], handler)*:這個函式啟動從一個指定端點非同步接收資料的操作。
* *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])*:這個函式非同步地從所給的緩衝區讀取資料。在讀完所有資料或者錯誤出現之前,這個函式都是阻塞的。
* *read_some(buffer)*:這個函式的功能和*receive(buffer)*是一致的。
* * receive_from(buffer, endpoint [, flags])*:這個函式非同步地從一個指定的端點獲取資料並寫入到給定的緩衝區。在讀完所有資料或者錯誤出現之前,這個函式都是阻塞的。
* *send(buffer [, flags])*:這個函式同步地傳送緩衝區的資料。在所有資料傳送成功或者出現錯誤之前,這個函式都是阻塞的。
* *write_some(buffer)*:這個函式和*send(buffer)*的功能一致。
* *send_to(buffer, endpoint [, flags])*:這個函式同步地把緩衝區資料傳送到一個指定的端點。在所有資料傳送成功或者出現錯誤之前,這個函式都是阻塞的。
* *available()*:這個函式返回有多少位元組的資料可以無阻塞地進行同步讀取。

稍後我們將討論緩衝區。讓我們先來了解一下標記。標記的預設值是0,但是也可以是以下幾種:
* *ip::socket_type::socket::message_peek*:這個標記只監測並返回某個訊息,但是下一次讀訊息的呼叫會重新讀取這個訊息。
* *ip::socket_type::socket::message_out_of_band*:這個標記處理帶外(OOB)資料,OOB資料是被標記為比正常資料更重要的資料。關於OOB的討論在這本書的內容之外。
* *ip::socket_type::socket::message_do_not_route*:這個標記指定資料不使用路由表來發送。
* *ip::socket_type::socket::message_end_of_record*:這個標記指定的資料標識了記錄的結束。在Windows下不支援。

你最常用的可能是*message_peek*,使用方法請參照下面的程式碼片段:
```
char buff[1024];
sock.receive(buffer(buff), ip::tcp::socket::message_peek );
memset(buff,1024, 0);
// 重新讀取之前已經讀取過的內容
sock.receive(buffer(buff) );
```

下面的是一些教你如何同步或非同步地從不同型別的套接字上讀取資料的例子:

* 例1是在一個TCP套接字上進行同步讀寫:
```
ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80);
ip::tcp::socket sock(service);
sock.connect(ep);
sock.write_some(buffer("GET /index.html\r\n"));
std::cout << "bytes available " << sock.available() << std::endl;
char buff[512];
size_t read = sock.read_some(buffer(buff));
```


* 例2是在一個UDP套接字上進行同步讀寫:
```
ip::udp::socket sock(service);
sock.open(ip::udp::v4());
ip::udp::endpoint receiver_ep("87.248.112.181", 80);
sock.send_to(buffer("testing\n"), receiver_ep);
char buff[512];
ip::udp::endpoint sender_ep;
sock.receive_from(buffer(buff), sender_ep);
```


*[?注意:就像上述程式碼片段所展示的那樣,使用receive_from從一個UDP套接字讀取資料時,你需要構造一個預設的端點]*

* 例3是從一個UDP服務套接字中非同步讀取資料:
```
using namespace boost::asio;
io_service service;
ip::udp::socket sock(service);
boost::asio::ip::udp::endpoint sender_ep;
char buff[512];
void on_read(const boost::system::error_code & err, std::size_t read_bytes) {
    std::cout << "read " << read_bytes << std::endl;
    sock.async_receive_from(buffer(buff), sender_ep, on_read);
}
int main(int argc, char* argv[]) {
    ip::udp::endpoint ep(ip::address::from_string("127.0.0.1"),
8001);
    sock.open(ep.protocol());
    sock.set_option(boost::asio::ip::udp::socket::reuse_address(true));
    sock.bind(ep);
    sock.async_receive_from(buffer(buff,512), sender_ep, on_read);
    service.run();
}
```
###套接字控制:

這些函式用來處理套接字的高階選項:
* *get_io_service()*:這個函式返回建構函式中傳入的io_service例項
* *get_option(option)*:這個函式返回一個套接字的屬性
* *set_option(option)*:這個函式設定一個套接字的屬性
* *io_control(cmd)*:這個函式在套接字上執行一個I/O指令

這些是你可以獲取/設定的套接字選項:

| 名字 | 描述 | 型別 |
| -- | -- |
| broadcast | 如果為true,允許廣播訊息 | bool |
| debug | 如果為true,啟用套接字級別的除錯 | bool | 
|do_not_route | 如果為true,則阻止路由選擇只使用本地介面 | bool | 
| enable_connection_aborted | 如果為true,記錄在accept()時中斷的連線 | bool | 
| keep_alive | 如果為true,會發送心跳 | bool | 
| linger | 如果為true,套接字會在有未傳送資料的情況下掛起close() | bool | 
| receive_buffer_size | 套接字接收緩衝區大小 | int | 
| receive_low_watemark  | 規定套接字輸入處理的最小位元組數 | int | 
| reuse_address | 如果為true,套接字能繫結到一個已用的地址 | bool | 
| send_buffer_size  | 套接字傳送緩衝區大小 | int | 
| send_low_watermark | 規定套接字資料傳送的最小位元組數 | int | 
| ip::v6_only | 如果為true,則只允許IPv6的連線 | bool | 

每個名字代表了一個內部套接字typedef或者類。下面是對它們的使用:
```
ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80);
ip::tcp::socket sock(service);
sock.connect(ep);
// TCP套接字可以重用地址
ip::tcp::socket::reuse_address ra(true);
sock.set_option(ra);
// 獲取套接字讀取的資料
ip::tcp::socket::receive_buffer_size rbs;
sock.get_option(rbs);
std::cout << rbs.value() << std::endl;
// 把套接字的緩衝區大小設定為8192
ip::tcp::socket::send_buffer_size sbs(8192);
sock.set_option(sbs);
```

*[?在上述特性工作之前,套接字要被開啟。否則,會丟擲異常]*
###TCP VS UDP VS ICMP
就像我之前所說,不是所有的成員方法在所有的套接字類中都可用。我做了一個包含成員函式不同點的列表。如果一個成員函式沒有出現在這,說明它在所有的套接字類都是可用的。

| 名字 | TCP | UDP | ICMP |
| -- | -- | -- | -- |
|async_read_some | 是 | - | - |
|async_receive_from | - | 是 | 是 |
|async_write_some | 是 | - | - |
|async_send_to | - | 是 | 是 |
|read_some | 是 | - | - |
|receive_from | - | 是 | 是 |
|write_some | 是 | - | - |
|send_to | - | 是 | 是 |

###其他方法
其他與連線和I/O無關的函式如下:
* *local_endpoint()*:這個方法返回套接字本地連線的地址。
* *remote_endpoint()*:這個方法返回套接字連線到的遠端地址。
* *native_handle()*:這個方法返回原始套接字的處理程式。你只有在呼叫一個Boost.Asio不支援的原始方法時才需要用到它。
* *non_blocking()*:如果套接字是非阻塞的,這個方法返回true,否則false。
* *native_non_blocking()*:如果套接字是非阻塞的,這個方法返回true,否則返回false。但是,它是基於原生的套接字來呼叫本地的api。所以通常來說,你不需要呼叫這個方法(non_blocking()已經快取了這個結果);你只有在直接呼叫native_handle()這個方法的時候才需要用到這個方法。
* *at_mark()*:如果套接字要讀的是一段OOB資料,這個方法返回true。這個方法你很少會用到。

###其他需要考慮的事情
最後要注意的一點,套接字例項不能被拷貝,因為拷貝構造方法和=操作符是不可訪問的。
```
ip::tcp::socket s1(service), s2(service);
s1 = s2; // 編譯時報錯
ip::tcp::socket s3(s1); // 編譯時報錯
```

這是非常有意義的,因為每一個例項都擁有並管理著一個資源(原生套接字本身)。如果我們允許拷貝構造,結果是我們會有兩個例項擁有同樣的原生套接字;這樣我們就需要去處理所有者的問題(讓一個例項擁有所有權?或者使用引用計數?還是其他的方法)Boost.Asio選擇不允許拷貝(如果你想要建立一個備份,請使用共享指標)
```
typedef boost::shared_ptr<ip::tcp::socket> socket_ptr;
socket_ptr sock1(new ip::tcp::socket(service));
socket_ptr sock2(sock1); // ok
socket_ptr sock3;            
sock3 = sock1; // ok
```

###套接字緩衝區
當從一個套接字讀寫內容時,你需要一個緩衝區,用來儲存讀取和寫入的資料。緩衝區記憶體的有效時間必須比I/O操作的時間要長;你需要保證它們在I/O操作結束之前不被釋放。
對於同步操作來說,這很容易;當然,這個緩衝區在receive和send時都存在。
```
char buff[512];
...
sock.receive(buffer(buff));
strcpy(buff, "ok\n");
sock.send(buffer(buff));
```

但是在非同步操作時就沒這麼簡單了,看下面的程式碼片段:            
```
// 非常差勁的程式碼 ...
void on_read(const boost::system::error_code & err, std::size_t read_bytes)
{ ... }
void func() {
    char buff[512];
    sock.async_receive(buffer(buff), on_read);
}
```

在我們呼叫*async_receive()*之後,buff就已經超出有效範圍,它的記憶體當然會被釋放。當我們開始從套接字接收一些資料時,我們會把它們拷貝到一片已經不屬於我們的記憶體中;它可能會被釋放,或者被其他程式碼重新開闢來存入其他的資料,結果就是:記憶體衝突。

對於上面的問題有幾個解決方案:

* 使用全域性緩衝區
* 建立一個緩衝區,然後在操作結束時釋放它
* 使用一個集合物件管理這些套接字和其他的資料,比如緩衝區陣列

第一個方法顯然不是很好,因為我們都知道全域性變數非常不好。此外,如果兩個例項使用同一個緩衝區怎麼辦?

下面是第二種方式的實現:                        
```
void on_read(char * ptr, const boost::system::error_code & err, std::size_t read_bytes) {                        
    delete[] ptr;
}
....
char * buff = new char[512];
sock.async_receive(buffer(buff, 512), boost::bind(on_read,buff,_1,_2))
```

或者,如果你想要緩衝區在操作結束後自動超出範圍,使用共享指標
```
struct shared_buffer {
    boost::shared_array<char> buff;
    int size;
    shared_buffer(size_t size) : buff(new char[size]), size(size) {
    }
    mutable_buffers_1 asio_buff() const {
        return buffer(buff.get(), size);
    }
};


// 當on_read超出範圍時, boost::bind物件被釋放了,
// 同時也會釋放共享指標
void on_read(shared_buffer, const boost::system::error_code & err, std::size_t read_bytes) {}
sock.async_receive(buff.asio_buff(), boost::bind(on_read,buff,_1,_2));
```

shared_buffer類擁有實質的*shared_array<>*,*shared_array<>*存在的目的是用來儲存*shared_buffer*例項的拷貝-當最後一個*share_array<>*元素超出範圍時,*shared_array<>*就被自動銷燬了,而這就是我們想要的結果。

因為Boost.Asio會給完成處理控制代碼保留一個拷貝,當操作完成時就會呼叫這個完成處理控制代碼,所以你的目的達到了。那個拷貝是一個boost::bind的仿函式,它擁有著實際的*shared_buffer*例項。這是非常優雅的!

第三個選擇是使用一個連線物件來管理套接字和其他資料,比如緩衝區,通常來說這是正確的解決方案但是非常複雜。在這一章的末尾我們會對這種方法進行討論。
###緩衝區封裝函式
縱觀所有程式碼,你會發現:無論什麼時候,當我們需要對一個buffer進行讀寫操作時,程式碼會把實際的緩衝區物件封裝在一個buffer()方法中,然後再把它傳遞給方法呼叫:
```
char buff[512];
sock.async_receive(buffer(buff), on_read);
```

基本上我們都會把緩衝區包含在一個類中以便Boost.Asio的方法能遍歷這個緩衝區,比方說,使用下面的程式碼:                
```
sock.async_receive(some_buffer, on_read);
```

例項*some_buffer*需要滿足一些需求,叫做*ConstBufferSequence*或者*MutableBufferSequence*(你可以在Boost.Asio的文件中檢視它們)。建立你自己的類去處理這些需求的細節是非常複雜的,但是Boost.Asio已經提供了一些類用來處理這些需求。所以你不用直接訪問這些緩衝區,而可以使用*buffer()*方法。

自信地講,你可以把下面列出來的型別都包裝到一個buffer()方法中:
* 一個char[] const 陣列
* 一個位元組大小的void *指標
* 一個std::string型別的字串
* 一個POD const陣列(POD代表純資料,這意味著構造器和釋放器不做任何操作)
* 一個pod資料的std::vector
* 一個包含pod資料的boost::array
* 一個包含pod資料的std::array

下面的程式碼都是有效的:
```
struct pod_sample { int i; long l; char c; };
...
char b1[512];
void * b2 = new char[512];
std::string b3; b3.resize(128);
pod_sample b4[16];
std::vector<pod_sample> b5; b5.resize(16);
boost::array<pod_sample,16> b6;
std::array<pod_sample,16> b7;
sock.async_send(buffer(b1), on_read);
sock.async_send(buffer(b2,512), on_read);
sock.async_send(buffer(b3), on_read);
sock.async_send(buffer(b4), on_read);
sock.async_send(buffer(b5), on_read);
sock.async_send(buffer(b6), on_read);
sock.async_send(buffer(b7), on_read);
```

總的來說就是:與其建立你自己的類來處理*ConstBufferSequence*或者*MutableBufferSequence*的需求,不如建立一個能在你需要的時候保留緩衝區,然後返回一個mutable_buffers_1例項的類,而我們早在shared_buffer類中就這樣做了。

###read/write/connect自由函式
Boost.Asio提供了處理I/O的自由函式,我們分四組來分析它們。
####connect方法
這些方法把套接字連線到一個端點。

* *connect(socket, begin [, end] [, condition])*:這個方法遍歷佇列中從start到end的端點來嘗試同步連線。begin迭代器是呼叫*socket_type::resolver::query*的返回結果(你可能需要回顧一下端點這個章節)。特別提示end迭代器是可選的;你可以忽略它。你還可以提供一個condition的方法給每次連線嘗試之後呼叫。用法是*Iterator connect_condition(const boost::system::error_code & err,I