網絡通訊
asio庫支持TCP、UDP、ICMP通訊協議,它在名字空間boost::asio::ip裏提供了大量的網絡通訊方面的函數和類,很好地封裝了原始的Berkeley Socket Api,展示給asio用戶一個方便易用且健壯的網絡通訊庫。ios
ip::tcp類是asio網絡通訊(TCP)部分主要的類,但它自己並無太多的功能,而是定義了數個用於TCP通訊的typedef類型,用來協做完成網絡通訊。這些typedef包括端點類endpoint、套接字類socket、流類iostream,以及接收器acceptor、解析器resolver等等。從某種程度上來看,ip::tcp類更像是一個名字空間。數組
一、IP地址和端點
IP地址獨立於TCP、UDP等通訊協議,asio庫使用類ip::address來表示IP地址,能夠同時支持ipv4和ipv6兩種地址。服務器
[cpp] view plain copy網絡
print?app
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::asio::ip::address addr; // 聲明一個ip地址對象
- addr = addr.from_string("127.0.0.1"); // 從字符串產生IP地址
- assert(addr.is_v4()); // ipv4的地址
- cout << addr.to_string() << endl;
-
- addr = addr.from_string("2000:0000:0000:0000:0001:2345:6789:abcd");
- assert(addr.is_v6());
- cout << addr.to_string() << endl;
-
- return 0;
- }
有了IP地址,再加上通訊用的端口號就構成了一個socket端點,在asio庫中用ip::tcp::endpoint類來表示。它的主要用法就是經過構造函數建立一個可用於socket通訊的端點對象,端點的地址和端口號能夠用address()和port()得到:異步
[cpp] view plain copysocket
print?async
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::asio::ip::address addr; // 聲明一個ip地址對象
- addr = addr.from_string("127.0.0.1"); // 從字符串產生IP地址
-
- boost::asio::ip::tcp::endpoint ep(addr, 6688);
-
- assert(ep.address() == addr);
- assert(ep.port() == 6688);
-
- return 0;
- }
二、同步socket處理
ip::tcp的內部類型socket、acceptor和resolver是asio庫TCP通訊中最核心的一組類,它們封裝了socket的鏈接、斷開和數據收發功能,使用它們能夠很容易地編寫出socket程序。tcp
socket類是TCP通訊的基本類,調用成員函數connect()能夠鏈接到一個指定的通訊端點,鏈接成功後用local_endpoint()和remote_endpoint()得到鏈接兩端的端點信息,用read_some()和write_some()阻塞讀寫數據,當操做完成後使用close()函數關閉socket。若是不關閉socket,那麼在socket對象析構時也會自動調用close()關閉。函數
acceptor類對應socketAPI的accept()函數功能,它用於服務器端,在指定的端口號接受鏈接,必須配合socket類才能完成通訊。
resolver類對應socketAPI的getaddrinfo()系列函數,用於客戶端解析網址得到可用的IP地址,解析獲得的IP地址可使用socket對象鏈接。
下面是一個使用socket類和acceptor類來實現一對同步通訊的服務器和客戶端程序:
服務器端(它使用一個acceptor對象在6688端口接受鏈接,當有鏈接時使用一個socket對象發送一個字符串):
server.cpp:
[cpp] view plain copy
print?
- // server.cpp : 定義控制檯應用程序的入口點。
- //
-
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- try
- {
- cout << "server start" << endl;
- boost::asio::io_service ios;
-
- boost::asio::ip::tcp::acceptor acceptor(ios,
- boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 6688));
-
- cout << acceptor.local_endpoint().address() << endl;
-
- while (true)
- {
- boost::asio::ip::tcp::socket sock(ios);
- acceptor.accept(sock);
-
- cout << "client : ";
- cout << sock.remote_endpoint().address() << endl;
-
- sock.write_some(boost::asio::buffer("hello asio"));
- }
- }
-
- catch (std::exception& e)
- {
- cout << e.what() << endl;
- }
-
- return 0;
- }
服務器端程序裏要注意的是自由函數buffer(),他能夠包裝不少種類的容器成爲asio組件可用的緩衝區類型。一般不能直接把數組、vercor等容器用做asio的讀寫參數,必須使用buffer()函數包裝
client:
[cpp] view plain copy
print?
- // client.cpp : 定義控制檯應用程序的入口點。
- //
-
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
- #include "vector"
-
-
- class AsynTimer
- {
- public:
- template<typename F> // 模板類型,能夠接受任意可調用物
- AsynTimer(boost::asio::io_service& ios, int x, F func)
- :f(func), count_max(x), count(0), // 初始化回調函數和計數器
- t(ios, boost::posix_time::millisec(500)) // 啓動計時器
- {
- t.async_wait(boost::bind(&AsynTimer::CallBack, // 異步等待計時器
- this, boost::asio::placeholders::error)); // 註冊回調函數
- }
-
- void CallBack(const boost::system::error_code& error)
- {
- if (count >= count_max) // 若是計數器達到上限則返回
- {
- return;
- }
- ++count;
- f(); // 調用function對象
-
- // 設置定時器的終止時間爲0.5秒以後
- t.expires_at(t.expires_at() + boost::posix_time::microsec(500));
- // 再次啓動定時器,異步等待
- t.async_wait(boost::bind(&AsynTimer::CallBack, this, boost::asio::placeholders::error));
- }
-
- private:
- int count;
- int count_max;
- boost::function<void()> f; // function對象,持有無參無返回值的可調用物
- boost::asio::deadline_timer t; // asio定時器對象
- };
-
-
- void client(boost::asio::io_service& ios)
- {
- try
- {
- cout << "client start." << endl;
-
- boost::asio::ip::tcp::socket sock(ios);
- boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 6688);
-
- sock.connect(ep);
-
- vector<char> str(100, 0);
- sock.read_some(boost::asio::buffer(str));
-
- cout << "recive from" << sock.remote_endpoint().address();
- cout << &str[0] << endl;
-
- }
- catch (std::exception& e)
- {
- cout << e.what() << endl;
- }
- }
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::asio::io_service ios;
- AsynTimer at(ios, 50000, boost::bind(client, boost::ref(ios)));
- ios.run();
-
- return 0;
- }
三、異步socket處理
咱們把剛纔的同步socket程序改成異步調用方式。異步程序的處理流程與同步程序基本相同,只須要把原有的同步調用函數都換成前綴是async_的異步調用函數,並增長回調函數,在回調函數中再啓動一個異步調用
服務器端:
[cpp] view plain copy
print?
- // server.cpp : 定義控制檯應用程序的入口點。
- //
-
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
-
-
- class Server
- {
- private:
- boost::asio::io_service& ios;
- boost::asio::ip::tcp::acceptor acceptor;
- typedef boost::shared_ptr<boost::asio::ip::tcp::socket> sock_pt;
-
- public:
- Server(boost::asio::io_service& io) : ios(io),
- acceptor(ios, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 6688))
- {
- Start();
- }
- ~Server()
- {
-
- }
-
- void Start()
- {
- sock_pt sock(new boost::asio::ip::tcp::socket(ios)); // 智能指針
-
- // 異步偵聽服務
- acceptor.async_accept(*sock, boost::bind(&Server::acceptor_handle,
- this, boost::asio::placeholders::error, sock));
- }
-
- void acceptor_handle(const boost::system::error_code& error, sock_pt sock)
- {
- if (error)
- {
- return;
- }
- cout << "client : ";
-
- // 輸出鏈接的客戶端信息
- cout << sock->remote_endpoint().address() << endl;
-
- //
- sock->async_write_some( boost::asio::buffer("hello asio"),
- boost::bind(&Server::write_handle,
- this, boost::asio::placeholders::error));
-
- Start(); // 再次啓動異步接受鏈接
- }
-
- void write_handle(const boost::system::error_code& error)
- {
- cout << "send message is complate" << endl;
- }
-
- };
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- try
- {
- cout << "server start." << endl;
-
- boost::asio::io_service ios;
-
- Server serv(ios);
-
- ios.run();
- }
- catch (std::exception& e)
- {
- cout << e.what() << endl;
- }
-
- return 0;
- }
首先檢查asio傳遞的error_code,保證沒有錯誤發生。而後調用socket對象的async_write_some()異步發送數據。一樣,咱們必須再爲這個異步調用編寫回調函數write_handler()。當發送完數據後不要忘記調用Start()再次啓動服務器接受連接,不然當完成數據發送後io_service將由於沒有時間處理而結束運行。
發送數據的回調函數write_handler()很簡單,由於不須要作更多的工做,能夠直接實現一個空函數,在這裏簡單地輸出一條信息,表示異步發送數據完成
客戶端:
[cpp] view plain copy
print?
- // client.cpp : 定義控制檯應用程序的入口點。
- //
-
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
- #include "vector"
-
-
- class Client
- {
- private:
- boost::asio::io_service& ios;
- boost::asio::ip::tcp::endpoint ep; // tcp端點
- typedef boost::shared_ptr<boost::asio::ip::tcp::socket> sock_pt;
-
- public:
- Client(boost::asio::io_service& io) : ios(io),
- ep(boost::asio::ip::address::from_string("127.0.0.1"), 6688)
- {
- Start(); // 啓動異步鏈接
- }
-
- ~Client()
- {
-
- }
-
- void Start()
- {
- sock_pt sock(new boost::asio::ip::tcp::socket(ios));
- sock->async_connect(ep, boost::bind(&Client::conn_handle, this,
- boost::asio::placeholders::error, sock));
- }
-
- void conn_handle(const boost::system::error_code& error, sock_pt sock)
- {
- if (error)
- {
- return;
- }
- cout << "recive from : " << sock->remote_endpoint().address();
-
- // 創建接收數據的緩衝區
- boost::shared_ptr<vector<char> > str(new vector<char>(100, 0));
-
- // 異步讀取數據
- sock->async_read_some(boost::asio::buffer(*str), boost::bind(&Client::read_handle,
- this,
- boost::asio::placeholders::error,
- str));
- Start(); // 再次啓動異步鏈接
- }
-
- void read_handle(const boost::system::error_code& error,
- boost::shared_ptr<vector<char> > str)
- {
- if (error)
- {
- return;
- }
- cout << &(*str)[0] << endl;
- }
- };
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- try
- {
- cout << "client start." << endl;
- boost::asio::io_service ios;
-
- Client client(ios);
-
- ios.run();
- }
- catch (std::exception& e)
- {
- cout << e.what() << endl;
- }
-
- return 0;
- }
四、查詢網絡地址
以前關於tcp通訊的全部論述都是使用直接的ip地址,但在實際生活中大多數時候,都不大可能知道socket連接另外一端的地址,而只有一個域名,這時候咱們就須要使用resolver類來經過域名得到可用的ip,它能夠實現與ip版本無關的網址解析
resolver使用內部類query和iterator共同完成查詢ip地址的工做:首先使用網址和服務名建立query對象,而後由resolve()函數生成iterator對象,它表明了查詢到的ip端點。以後就可使用socket對象嘗試鏈接,知道找到一個可用的爲止。
[cpp] view plain copy
print?
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "boost/lexical_cast.hpp"
- #include "boost/asio/error.hpp"
- #include "iostream"
- using namespace std;
-
-
- void resolv_connect(boost::asio::ip::tcp::socket& sock, const char* name, int port)
- {
- boost::asio::ip::tcp::resolver rlv(sock.get_io_service());
- boost::asio::ip::tcp::resolver::query qry(name, boost::lexical_cast<string>(port));
-
- boost::asio::ip::tcp::resolver::iterator iter = rlv.resolve(qry);
- boost::asio::ip::tcp::resolver::iterator end;
-
- boost::system::error_code ec = boost::asio::error::host_not_found;
- for (; ec && iter != end; ++iter)
- {
- sock.close();
- sock.connect(*iter, ec);
- }
-
- if (ec)
- {
- cout << "can't connect." << endl;
- throw boost::system::error_code(ec);
- }
-
- cout << "connet suceessd." << endl;
- }
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- try
- {
- boost::asio::io_service ios;
- boost::asio::ip::tcp::socket sock(ios);
-
- resolv_connect(sock, "www.boost.org", 80);
-
- ios.run();
- }
- catch (std::exception& e)
- {
- cout << e.what() << endl;
- }
-
- return 0;
- }
resolv_connect()函數中使用lexical_cast,這是由於query對象只接受字符串參數,因此咱們須要把端口號由整數轉換爲字符串。
當開始resolver的迭代時,須要使用error_code和逾尾迭代器兩個條件來控制循環,由於有可能迭代完全部解析到的端點都沒法鏈接,只有當error_code爲0才表示鏈接成功。
有了resolv_connect()函數,就能夠不受具體ip地址值的限制,以更直觀更靈活的域名來鏈接服務器。