1. 程式人生 > >C++ Boost asio庫網路通訊 [同/非同步] 筆記

C++ Boost asio庫網路通訊 [同/非同步] 筆記

此文轉自: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,流類iostreamacceptorresolver等等。

Socket類是TCP下的類。

ip::tcp的內部型別socketacceptorresolverip::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 APIaccept()函式功能,它用於伺服器端,在指定的埠號接受連線,必須配合socket類讓其物件作為引數才能完成通訊。

<3>resolver class

resolver類對應socket APIgetaddrinfo()系列函式,用於客戶端解析網址獲得可用的IP地址,解析得到的IP地址可以使用socket物件連線。在實際生活中大多數我們不可能知道

socket連線另一端的地址,而只有域名,這個時候就應該使用resolver類來通過域名獲得可用的IP。它可以實現與IP地址無關的網址解析。

(2)ip:address

ip地址獨立於TCP等通訊協議,asio庫使用ip::address來表示ip地址支援ipv4及ipv6。

ip地址再加上一個埠號就構成了一個端點(ip::tcp::endpoint,它的主要用法是通過建構函式建立一個可用於socket通訊的的端點物件,端點的地址和埠號可以由addressport獲取

可簡要查閱其各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]關於如何利用同步/非同步方式

這還得歸結於對同步/非同步執行機制的把握和設計功底了。再加上如果用上多執行緒,嘿嘿,功能應該就會變得很強大,哪怕是同步機制。只是這種組合會稍微難點,但每個模組( 多執行緒,同步/非同步,網路通訊)在程式設計時一般都不會被孤立。

此次筆記記錄完畢。