asio(asynchronous input and output) 表示异步输入输出,是基于操作系统提供的异步机制,异步数据处理表示触发后不需要等待完成,期间可以执行其他任务,而且不要求使用多线程和锁定,有效避免了条件竞争,死锁等。asio主要用于网络通信方面,支持TCP,UDP,ICMP等网络通信协议。
asio主要分为同步和异步两种方式,同步表示同步等待,比如网络连接时,客户端发出请求后,它会把控制权交给操作系统,会一直等待连接成功或者出错才返回。而异步方式不需要阻塞程序一直等待应答,它会立即返回,接着执行其他任务,当系统完成操作时,会调用回调函数通知它。
//***********************************************************
//FUNCTION::synchronization
void synchronization()
{
boost::asio::io_service IOService;
boost::asio::deadline_timer t(IOService, boost::posix_time::seconds(3));
std::cout << t.expires_at() << std::endl;//查看终止绝对时间
t.wait();//调用wait同步等待
std::cout << "Hello asio" << std::endl;
}
asio的和核心类是io_service,必须使用它来支持所有IO功能,而且必须调用run()方法开启IO服务事件循环,它会把控制权交给操作系统,负责分发异步回调事件,只有所有异步回调事件所有完成后才返回,不执行run(),异步函数也不会调用。
//***********************************************************
//FUNCTION::
void printAsio(const boost::system::error_code &ec)
{
std::cout << "hello asio" << std::endl;
}
//***********************************************************
//FUNCTION::asynchronization
void asynchronization()
{
boost::asio::io_service IOService;
boost::asio::deadline_timer t(IOService, boost::posix_time::seconds(5));
t.async_wait(printAsio);//异步等待,传入回调函数,立即返回
std::cout << "It show before t expired." << std::endl;
IOService.run();//异步IO必须
}
首先定义IO服务 IOService, 用来初始化IO对象, 所有IO对象通常需要一个IO服务作为构造函数的第一个参数,boost::asio::dealline_timer
的第二个参数,用于表示某个时间点或者某段时间后闹钟停止async_wait()
的好处是,该函数调用会立即返回,而不是等待五秒钟。 一旦闹钟时间到,作为参数所提供的函数就会被相应调用。 因此,应用程序可以在调用了 async_wait() 之后执行其它操作,而不是阻塞在这里。
注意到回调函数的形参是void printAsio(const boost::system::error_code &ec) ,其asio使用system库的error_code和system_error来表示运行时错误。io_service将错误的操作结果翻译为boost::system::error_code类型。error_code可与特定值进行比较,或作为boolean值检测(false表示无错误)。结果再传递给IO对象。
1. 一种形式是有error_code的输出参数,调用后必须检查这个参数验证是否发生了错误。
2. 另一种没有error_code的参数,如果发生了错误会抛出system_error异常,调用代码必须使用try_catch块捕获错误。
ASIO通过buffer函数生成一个内部使用的缓冲区类,它能把数组、指针(同时指定大 小)、std::vector、std::string、boost::array包装成缓冲区类。自动管理缓冲区大小以防止缓冲区溢出
boost::asio::buffer(Temp); //Construct: array, pointer, string, vector, boost::array
const char* pChar = boost::asio::buffer_cast<const char*> (boost::asio::buffer(Temp));//Cast
std::size_t Size = boost::asio::buffer_size(boost::asio::buffer(Temp)); //Size
ip::tcp类是asio网络通信(tcp)主要类,本身没有太多的功能,而是定义了大量的typedef类型,用来协作完成网络通信。
Example:通过编程实现服务器端和客户端的简单通信,要求服务器端输出客户IP,并向客户端发出hello文字
服务端:通常服务端是一个死循环,监听客户端的连接请求。
#include <boost/asio.hpp>
#include <iostream>
int main()
{
try
{
std::cout << "Server start." << std::endl;
boost::asio::io_service IOService;
boost::asio::ip::tcp::acceptor Acceptor(IOService, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 6688));
std::cout << Acceptor.local_endpoint().address() << std::endl;
while (true)
{
boost::asio::ip::tcp::socket Socket(IOService);
Acceptor.accept(Socket); //阻塞等待socket连接
std::cout << "Client: ";
std::cout << Socket.remote_endpoint().address() << std::endl;
Socket.write_some(boost::asio::buffer("Hello asio"));
}
}
catch(std::exception& e)
{
std::cout << e.what() << std::endl;
}
}
Endpoint:服务器和客户端双方都以端点表示。包括IP地址,和端口。服务器端同意IPv4协议,端口为6688。
Acceptor:建立接收器对象来监听连接,初始化为tcp端点,只在指定的6688端口上等待连接。
Socket:通信桥梁。服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。读写操作。
Accept:一直监听等待,直到有客户端连接过来
有连接后通过socket调用远端端点的地址函数,输出客户端IP地址。
用write方法向客户端输入信息。
While里面发送一次数据后析构,若想双方一直通信,可将socket和accept定义在循环体外。
客服端:
#include <boost/asio.hpp>
#include <iostream>
int main()
{
std::cout << "Client start." << std::endl;
try
{
boost::asio::io_service IOService;
boost::asio::ip::tcp::socket Socket(IOService);
boost::asio::ip::tcp::endpoint EndPoint(boost::asio::ip::address::from_string("127.0.0.1"), 6688);
Socket.connect(EndPoint);//发出连接请求
std::vector<char> Str(100, 0);
Socket.read_some(boost::asio::buffer(Str));//阻塞等待服务端的响应信息
std::cout << "Receive from: " << Socket.remote_endpoint().address() << std::endl;
std::cout << &Str[0] << std::endl;
}
catch(std::exception& e)
{
std::cout << e.what() << std::endl;
}
}
由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
异步服务端:
异步程序处理与同步基本相同,同步函数改成异步调用函数,并增加回调函数,并在回调函数中再启动一个异步调用。
#pragma once
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
class CServer
{
private:
boost::asio::io_service& m_IOService;
boost::asio::ip::tcp::acceptor m_Acceptor;
typedef boost::shared_ptr<boost::asio::ip::tcp::socket> SocketPt;
public:
CServer(boost::asio::io_service& vIOService) : m_IOService(vIOService), m_Acceptor(m_IOService, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 6688))
{
start();
}
~CServer(){}
void start()
{
SocketPt Socket(new boost::asio::ip::tcp::socket(m_IOService));
m_Acceptor.async_accept(*Socket, boost::bind(&CServer::acceptHandler, this, boost::asio::placeholders::error, Socket));
}
void acceptHandler(const boost::system::error_code& vErrorCode, SocketPt vSocket)
{
if (vErrorCode) return;//检查错误码
std::cout << "Client: ";
std::cout << vSocket->remote_endpoint().address() << std::endl;//输出客户端信息
vSocket->async_write_some(boost::asio::buffer("Hello asio"), boost::bind(&CServer::writeHandler, this, boost::asio::placeholders::error));
start();//再次启动异步接受连接
}
void writeHandler(const boost::system::error_code& vErrorCode)
{
std::cout << "Send message complete." << std::endl;
}
};
tcp通信的必备要素是io_service,和acceptor对象。随后的智能指针指向socket对象,用来在回调函数中传递。start()函数启动异步服务,而智能指针表示socket对象能够被异步调用后还能使用,可以存在整个程序运行周期,直到没人使用为止。当tcp连接发生时,accept_handler()函数被调用,使用socket对象发送数据。首先必须检查asio传递的error_code,保证没有错误发生。异步发送数据async_write_some,而且需要为异步调用编写回调函数write_handler()。当发送完数据后,再次调用start()启动服务器接受连接,不然io_service没有事件处理而结束运行。
异步客户端
#pragma once
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
class CClient
{
private:
boost::asio::io_service& m_IOService;
boost::asio::ip::tcp::endpoint m_EndPoint;
typedef boost::shared_ptr<boost::asio::ip::tcp::socket> SocketPt;
public:
CClient(boost::asio::io_service& vIOService) : m_IOService(vIOService), m_EndPoint(boost::asio::ip::address::from_string("127.0.0.1"), 6688)
{
start();
}
~CClient(){}
void start()
{
SocketPt Socket(new boost::asio::ip::tcp::socket(m_IOService));
Socket->async_connect(m_EndPoint, boost::bind(&CClient::connectHandler, this, boost::asio::placeholders::error, Socket));
}
void connectHandler(const boost::system::error_code& vErrorCode, SocketPt vSocket)
{
if (vErrorCode) return;
std::cout << "Receive from: ";
std::cout << vSocket->remote_endpoint().address() << std::endl;
boost::shared_ptr<std::vector<char> > Str(new std::vector<char>(100, 0));//建立数据接收缓冲区
vSocket->async_read_some(boost::asio::buffer(*Str), boost::bind(&CClient::readHandler, this, boost::asio::placeholders::error, Str));//异步读取数据
start();
}
void readHandler(const boost::system::error_code& vErrorCode, boost::shared_ptr<std::vector<char> > vStr)
{
if (vErrorCode) return;
std::cout << &(*vStr)[0] << std::endl;
}
};
通常情况下,我们是不知道服务器的地址,而是知道它的域名。这时候需要使用resolver来通过域名获得可用的ip。resolver使用内部类query和iterator共同完成查询ip的工作:首先使用网址和服务名创建query对象,然后用resolve()函数生产iterator对象,它代表了查询到的ip端点。
//***********************************************************
//FUNCTION::socket,网址,端口号
void resolveConnect(boost::asio::ip::tcp::socket& vSocket, const char* vName, int vPort)
{
boost::asio::ip::tcp::resolver RLV(vSocket.get_io_service());
boost::asio::ip::tcp::resolver::query Query(vName, boost::lexical_cast<std::string>(vPort));
//使用resolve()开始迭代端点
boost::asio::ip::tcp::resolver::iterator Iter = RLV.resolve(Query);
boost::asio::ip::tcp::resolver::iterator End;
boost::system::error_code ErrorCode = boost::asio::error::host_not_found;
for (; ErrorCode && Iter!=End; ++Iter)
{
vSocket.close();
vSocket.connect(*Iter, ErrorCode);
}
if (ErrorCode)
{
std::cout << "Can't connect." << std::endl;
throw boost::system::system_error(ErrorCode);
}
std::cout << "Connect success." << std::endl;
}
使用error_code和迭代器控制循环,因为可能迭代器完所有解析到的端点都无法连接,只有当error_code为0才表示连接成功。
//***********************************************************
//FUNCTION::
void queryWebSite()
{
boost::asio::io_service IOService;
boost::asio::ip::tcp::socket Socket(IOService);
resolveConnect(Socket, "www.boost.org", 80);
IOService.run();
}
对于有连接的tcp协议,asio有ip::tcp::iostream类简化socket通信,其是std::basic_iostream子类。内部集成了resolver的域名解析和acceptor的接受连接功能,能够非常简单完成tcp通信。
客户端:
int main()
{
for (int i=0; i<5; i++)
{
boost::asio::ip::tcp::iostream tcp_stream("127.0.0.1", "6688");
std::string Str;
getline(tcp_stream, Str);//tcp流中读取一行数据
std::cout << Str << std::endl;
}
return 0;
}
服务端:
#include <boost/asio.hpp>
int main()
{
boost::asio::io_service IOService;
boost::asio::ip::tcp::endpoint EndPoint(boost::asio::ip::tcp::v4(), 6688);
boost::asio::ip::tcp::acceptor Acceptor(IOService, EndPoint);
for (; ;)
{
boost::asio::ip::tcp::iostream tcp_iostream;
Acceptor.accept(*tcp_iostream.rdbuf());
tcp_iostream << "hello tcp_stream" << std::endl;
}
return 0;
}
我们都知道TCP是3次握手可靠连接,而UDP通信不需要建立连接的过程。使用send_to()和receive_from()就可以直接通过端点发送数据。
#include <boost/asio.hpp>
#include <iostream>
int main()
{
std::cout << "UDP server start." << std::endl;
boost::asio::io_service IOService;
boost::asio::ip::udp::socket Socket(IOService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 6699));
for (; ;)
{
char Buf[1];//临时缓冲区
boost::asio::ip::udp::endpoint EndPoint;//要接受连接的远程端点
boost::system::error_code ErrorCode;
Socket.receive_from(boost::asio::buffer(Buf), EndPoint, 0 , ErrorCode);//阻塞等待远程连接,连接的端点信息保存在EndPoint中
if (ErrorCode && ErrorCode != boost::asio::error::message_size)
{
throw boost::system::system_error(ErrorCode);
}
std::cout << "Send to " << EndPoint.address() << std::endl;
Socket.send_to(boost::asio::buffer("Hello asio udp"), EndPoint);//发送数据
}
return 0;
}
#include <boost/asio.hpp>
#include <iostream>
int main()
{
std::cout << "Client start" << std::endl;
boost::asio::io_service IOService;
boost::asio::ip::udp::endpoint SendEndPoint(boost::asio::ip::address::from_string("127.0.0.1"), 6699);
boost::asio::ip::udp::socket Socket(IOService);
Socket.open(boost::asio::ip::udp::v4());//ipv4打开socket
char Buf[1];
Socket.send_to(boost::asio::buffer(Buf), SendEndPoint);//向连接端点发送连接数据
std::vector<char> V(100, 0);
boost::asio::ip::udp::endpoint RecvEndPoint;
Socket.receive_from(boost::asio::buffer(V), RecvEndPoint);//接受数据
std::cout << "Receive from: " << RecvEndPoint.address() << std::endl;
std::cout << &V[0] << std::endl;
return 0;
}
【参考资料】
【1】Boost 程序库完全开发指南
【2】http://zh.highscore.de/cpp/boost/asio.html
【3】https://www.gitbook.com/book/mmoaay/boost-asio-cpp-network-programming-chinese/details