此文轉自:http://blog.csdn.net/misskissC/article/details/9985167
1 C++ Boost庫asio網絡通信類核心結構
在C++ Boost庫中用于通信的類的層次為boost::asio::ip,所有有關通信的類别都在這個層次之下。
asio封裝了berkeley socket APIS,使其支援TCP,UDP,ICMP通信協定,提供了一個健壯且易用的網絡通信庫:boost::asio::ip。
(1)ip:tcp
網絡通信TCP部分位于的層次為ip:tcp。類是asio網絡通信(TCP)部分主要的類,主要形式是定義了多個應用于TCP通信的typedef的類,用來協作完成網路通信。包括端點類endpoint,套接字類socket,流類iostream、acceptor及resolver等等。
Socket類是TCP下的類。
ip::tcp的内部類型socket,acceptor及resolver是ip::tcp的核心類,它們封裝了socket的連接配接、斷開和資料收發功能,使用它們很容易編寫出socket程式。
<1>socket class
屬TCP通信的基本類,調用成員函數connect()可以連接配接到一個指定的通信端點,成功連接配接後用local_endpoint()和remote_endpoin()獲得連接配接兩端的端點資訊,用read_some()和write_some()阻塞讀寫資料,當操作完成後使用close()函數關閉socket[ 如果不關閉其析構函數也自動調用close關閉 socket]
<2>acceptor class
acceptor類對應socket API的accept()函數功能,它用于伺服器端,在指定的端口号接受連接配接,必須配合socket類讓其對象作為參數才能完成通信。
<3>resolver class
resolver類對應socket API的getaddrinfo()系列函數,用于用戶端解析網址獲得可用的IP位址,解析得到的IP位址可以使用socket對象連接配接。在實際生活中大多數我們不可能知道socket連接配接另一端的位址,而隻有域名,這個時候就應該使用resolver類來通過域名獲得可用的IP。它可以實作與IP位址無關的網址解析。
(2)ip:address
ip位址獨立于TCP等通信協定,asio庫使用ip::address來表示ip位址支援ipv4及ipv6。
ip位址再加上一個端口号就構成了一個端點(ip::tcp::endpoint),它的主要用法是通過構造函數建立一個可用于socket通信的的端點對象,端點的位址和端口号可以由address和port擷取。
可簡要查閱其各IP下類的類摘要。
2 編寫socket程式
首先明确使用socket類在C++ boost庫内的asio庫下的ip::tcp下,是以,它得遵循asio基本程式的結構和流程[vs2010 boost庫配置 asio程式結構流程]。
步驟
(1)建立控制台程式
用VS2010建立兩個控制台程式,一個用于socket程式的伺服器端,一個用于socket程式的用戶端。配置好boost庫環境。明确boost庫下各類需要包含的頭檔案、命名空間及宏。
(2)用socket類編寫服務端程式
[1]遵循asio程式的結構和流程,首先定義io_service對象
io_service io_s; |
[2]用io_service對象,TCP協定及端口号初始化acceptor
ip::tcp::acceptor accpt( io_s, ip::tcp::endpoint( ip::tcp::v4(), 55 ) ); |
[3]建立伺服器端的socket,将io_service對象傳入
ip::tcp::socket ser_socket( io_s ); |
[4]用acceptor對象調用accept函數同步等待用戶端的連接配接
accpt.accept( ser_socket ); |
在用戶端與此伺服器端連接配接上之前,此程式會在此語句處停留直到與用戶端相連。
與用戶端連上之後,可以用本地socket對象調用remote_endpoint().address()檢視用戶端的ip位址資訊。
[5]向用戶端傳送資料[如果要聊天,則可以使用循環來實作,不過對于連接配接的伺服器端與用戶端可能會斷開,是以呢,要做一個完善的網絡程式設計還得随時檢測兩者的連接配接是否斷開 ]
此資料可以是使用者程式内定也可以由互動式的使用者輸入。
string input_str; cout << "Input your words what you want to say to client:"; cin >> input_str; //Send message to client ser_socket.write_some( buffer( input_str ) ); |
write_some函數就是向用戶端發送資料函數。buffer自由函數可以包裝很多種類的容器稱為asio元件可用的緩沖區類型。
[6]接受用戶端發送的資料并顯示[ ^-^,進階的以後再說 ]
deadline_timer d_t( io_s, posix_time::seconds(2) ); vector<char> str( 100, 0 ); ser_socket.read_some( buffer( str ) ); cout << "client's answer:" << &str[0] << "\n\n"; |
第一行是為了接受用戶端發送的資料而故意延遲的。從這裡可以看出來,這個入門的伺服器端和用戶端肯定不能進行流暢的聊天。嘎嘎^-^
read_some就是讀取用戶端發送的資料。
(3)用socket類編寫用戶端
用socket類編寫用戶端的步驟和代碼跟編寫伺服器端差不多。
[1]建立io_service io_s對象
[2]在用戶端之上建立socket對象,建立連接配接的端口,端口值跟伺服器端口保持一緻,并指明具體的ipv4 ip位址值
ip::tcp::socket client_socket( io_s ); ip::tcp::endpoint client_ep( ip::address::from_string( "127.0.0.1"), 55 ); |
[3]連接配接伺服器端
client_socket.connect( client_ep ); |
[4]接收伺服器端資料,向伺服器端發送資料
client_socket.read_some( buffer( str ), e_c ); cout << "\nreceive form" << client_socket.remote_endpoint().address() << ":"; cout << &str[0]<< "\n"; memset( &str[0], 0, str.size() ); cout << "my reply is:>> Slient!\n"; //Send client_socket.write_some( buffer( "您好我現在有事不在,一會兒跟您聯系!") ); |
采取先讀伺服器端資料并顯示再發送固定資料的方式來完成這個操作。用戶端向伺服器端發送資料的方式之是以采用固定字元串是因為伺服器端和用戶端兩邊的程式都是單獨運作的,如果在伺服器端執行到read_some之前用戶端還沒有發送資料,就會出錯。這裡采用固定字元串的方式來對應伺服器端延時兩秒。在時間的邏輯上是走的通的。
memset的作用是清楚str的内容而不受上次所存内容的影響。
3程式分析和運作結果
(1)實作似聊天的功能
先啟動伺服器端,伺服器端的socket對象被建立,由acccptor對象調用accept函數來等待用戶端的連接配接。稍後啟動用戶端,用戶端的socket對象和端點對象被建立,用戶端socket兌現調用connect函數去連接配接伺服器端,如果一切正常就會建立伺服器端和用戶端的一次連接配接。在不考慮伺服器端用戶端之間連接配接上會意外斷開的可能,那麼在二者連接配接後就可以在它們之間實作很多操作(如資料傳輸)。對于帶周期規律性的操作如聊天,就可以采取循環操作來完成。如果隻實作程式中描述的聊天模式,那麼隻需要在伺服器端資料傳輸和資料接收部分加一個條件循環[如while(true) ],隻需要在用戶端資料接收和資料發送部分加一個條件循環[如while( tue ) ]。
(2)程式中的隐患
對于兩個程式之間的通信,很可能出現許多的異常,此時為了程式的可調性和對使用者的良好性,就應該對各種異常做出回應。最好是使用C++的try-throw-catch機制來解決這個問題。socket類中的函數幾乎每個函數都接受一個檢測異常的boost::system::error_code e_c;
參數,并會将錯誤傳回給參數e_c。
程式中有以下幾個地方存在這樣的隐患:
[1]用戶端在連接配接伺服器端時,需要判斷兩者是否真的連接配接成功。如果不判斷程式會繼續進行,後面的資料處理就沒有意義而且會引發錯誤。
client_socket.connect( client_ep, e_c ); if( e_c ) { throw boost::system::system_error( e_c ); } |
[2]在接收資料時應該判斷伺服器端和用戶端的連接配接是否已經斷開
if ( e_c == boost::asio::error::eof ) { break; } else if ( e_c ) { throw boost::system::system_error( e_c ); } |
呵呵,當然了在有throw的情況下,try-throw-catch使用的結構就要有了。
(3)程式運作結果
先運作伺服器端程式,後運作用戶端程式,這樣子才能保證用戶端能連接配接上伺服器端。
[1]運作伺服器端,确定已經在等待用戶端的連接配接

圖1 運作伺服器端
[2]運作用戶端,簡單的看一下伺服器端和用戶端的通信
圖2 伺服器端與用戶端的資料通信
被封裝的東東就是強大呀,依舊是字元界面的程式設計,比最開始多需要的東西是C++面向對象的思想和開發環境中環境庫的配置。
4.異步方式的網絡通信總結
[1]就網絡通信方面的步驟
跟同步網絡通信的步驟一個樣一個樣的,隻是在每要進行異步操作時所調用的函數不同( 多一個async的字首 ),然後多一個異步的機制。
[2]就同步/異步程式的步驟[ 同步/異步小機制 ]
異步方式就比同步方式多一個回調函數和多調用is_service::run()函數兩個步驟。對于回調函數的參數問題可以用bind來解決,對于異步調用函數的生存空間書中所述說可以用智能指針來解決( 回調函數經異步調用後還能夠被主程式繼續使用 )。
[3]關于如何利用同步/異步方式
這還得歸結于對同步/異步運作機制的把握和設計功底了。再加上如果用上多線程,嘿嘿,功能應該就會變得很強大,哪怕是同步機制。隻是這種組合會稍微難點,但每個子產品( 多線程,同步/異步,網絡通信)在程式設計時一般都不會被孤立。
此次筆記記錄完畢。