網絡通信
asio庫支持TCP、UDP、ICMP通信協議,它在名字空間boost::asio::ip裡提供了大量的網絡通信方面的函數和類,很好地封裝了原始的Berkeley Socket Api,展現給asio用戶一個方便易用且健壯的網絡通信庫。
ip::tcp類是asio網絡通信(TCP)部分主要的類,但它本身並沒有太多的功能,而是定義了數個用於TCP通信的typedef類型,用來協作完成網絡通信。這些typedef包括端點類endpoint、套接字類socket、流類iostream,以及接收器acceptor、解析器resolver等等。從某種程度上來看,ip::tcp類更像是一個名字空間。
1、IP地址和端點
IP地址獨立於TCP、UDP等通信協議,asio庫使用類ip::address來表示IP地址,可以同時支持ipv4和ipv6兩種地址。
[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[])
{
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;
}
#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]
#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;
}
#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;
}
2、同步socket處理
ip::tcp的內部類型socket、acceptor和resolver是asio庫TCP通信中最核心的一組類,它們封裝了socket的連接、斷開和數據收發功能,使用它們可以很容易地編寫出socket程序。
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]
// 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;
}
// 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]
// 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;
}
// 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;
}
3、異步socket處理
我們把剛才的同步socket程序改為異步調用方式。異步程序的處理流程與同步程序基本相同,只需要把原有的同步調用函數都換成前綴是async_的異步調用函數,並增加回調函數,在回調函數中再啟動一個異步調用
服務器端:
[cpp]
// 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;
}
// 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]
// 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;
}
// 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;
}
4、查詢網絡地址
之前關於tcp通信的所有論述都是使用直接的ip地址,但在實際生活中大多數時候,都不大可能知道socket鏈接另一端的地址,而只有一個域名,這時候我們就需要使用resolver類來通過域名獲得可用的ip,它可以實現與ip版本無關的網址解析
resolver使用內部類query和iterator共同完成查詢ip地址的工作:首先使用網址和服務名創建query對象,然後由resolve()函數生成iterator對象,它代表了查詢到的ip端點。之後就可以使用socket對象嘗試連接,知道找到一個可用的為止。
[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 "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;
}
#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地址值的限制,以更直觀更靈活的域名來連接服務器。