resolver
resolver类对应Socket API的getaddrinfo()系列函数,用于解析网址获得可用的IP地址,解析得到的IP地址可以使用socket对象连接。
之前关于TCP通信的所有论述我们都是使用直接的IP地址,但在实际生活中大多数时候我们不可能知道socket 连接另一端的地址,而只有一个域名,这时候我们就需要使用resolver类来通过域名获得可用的IP,它可以实现与IP版本无关的网址解析。
template<typename InterProtocol,typename... as>
class basic_resolver
{
public:
typedef InterProtocol protocol_type;
typedef typename InterProtocol::endpoint endpoint_type;
typedef basic_resolver_query<InterProtocol> query;
typedef basic_resolver_iterator<InterProtocol> iteraror;
explicit basic_resolver(io_service&io_service);
void cancel();
iteraror resolve(const query&q);
iteraror resolve(const endpoint_type&e);
};
template<typename InternetProtocol>
class basic_revolve_query
{
typedef InternetProtocol protocol_type;
basic_resolver_query(const std::string&service,
flags resolve_flags=passive|address_configured)
...
};
resolver使用内部类query和 iterator共同完成查询IP地址的工作:首先使用网址和服务名(通常是端口号)创建query对象,然后由resolve ()成员函数生成iterator对象,它代表了查询到的IP端点。之后就可以使用socket对象尝试连接,直到找到一个可用的为止。
在这里我们使用一个函数resolve_connect ()来封装resolver的调用过程,代码如下:
void resolve_connect(ip::tcp::socket&sock,const char*name,int port)
{
ip::tcp::resolver r(sock.get_io_service());
ip::tcp::resolver::query q(name,lexical_cast<string>(port));
auto iter=r.resolve(q);
decltype(iter) end;
error_code ec=error::host_not_found;
for(;ec&&iter!=end;++iter)
{
sock.close();
sock.connect(*iter,ec);
}
if(ec)
{
cout<<"can't connect"<<endl;
throw system_error(ec);
}
cout<<"connect success"<<endl;
}
int main()
{
try{
io_service io;
ip::tcp::socket sock(io);
resolve_connect(sock,"www.baidu.com",80);
cout<<sock.remote_endpoint()<<endl;
}
catch(std::exception&e){
cout<<e.what()<<endl;
}
}
resolve_connect ()函数中使用了lexical_cast,这是因为query对象只接受字符串参数,所以我们需要把端口号由整数转换为字符串。
当开始resolver的迭代时,我们需要使用error_code和逾尾迭代器两个条件来控制循环,因为有可能迭代完所有解析到的端点都无法连接,只有当error_code为0才表示连接成功。
有了resolve_connect()函数,我们就可以不受具体IP地址值的限制,以更直观更灵活的域名来连接服务器
协程
协程(coroutine)是泛化的例程(routine)。例程只有一个入口和多个出口,C++里的函数就是这样,从最开始的函数入口开始,可以在某个时刻用return返回,例程就结束了。而协程则不同,它可以有多个入口多个出口,从最开始的入口进入之后可以随时用yield调用返回,之后再调用协程就会从刚才返回的地方继续执行。现代的很多编程语言都已经内置了协程支持,其中最著名的就是Lua。
类摘要
template<typename Handler>
class basic_yield_context
{
public:
basic_yield_context(
const detail::weak_ptr<callee_typr>&coro,
caller_type&ca,Handler& handler
);
basic_yield_context operator[](
boost::system::error_code&ec
)const;
detail::weak_ptr<callee_type>coro_;
...
yield_context的接口很简单,它保存了协程的运行环境,交替执行主协程(caller)和从协程(callee),达到异步的目的。operator[]用于外部获取发生的错误码,如果不使用operator[]则会抛出system_error异常未报告错误。
通常我们不直接创建yield_context对象,而是使用函数spawn ( )。它产生
yield_context对象,再传递给使用yield_context的函数。spawn()有多个重载形式,常用的是如下两种:
void spawn(strand s,Function function);
void spawn(io_service io,Function function);
function必须符合如下的函数签名
void func(boost::asio::yield_context yield);
用法:
在 asio 的协程用法里yield_context取代了我们之前使用的回调handler,async_xxx()函数不需要再写回调函数,而是使用yield _context对象。async_xxx ()调用后会自动交出控制权,执行其他的协程,直到异步操作完成时才会返回继续执行之后的动作,异步读写操作的字节数可以从async_xxx( )的返回值获取。整个程序的流程很像是同步模式,但因为有协程的存在实际上是异步而高效的。
int main()
{
typedef ip::tcp::acceptor acceptor_type;
typedef ip::tcp::endpoint endpoint_type;
typedef ip::tcp::socket socket_type;
io_service io;
spawn(io,[&](yield_context yield)
{
acceptor_type acceptor(io,endpoint(ip::tcp::v4(),6688));
for(;;)
{
socket_type sock(io);
acceptor.async_accept(sock,yield[ec]);
if(ec)
{
return;
}
auto len=sock.async_write_some(
buffer("hello coroutine"),yield
);
cout<<"send"<<len<<"bytes"<<endl;
})
}
io.run();
return
}
我们可以对比一下同步服务器代码,两者都是在一个for循环里监听端口,但协程版本是在 spawn ()函数里利用协程实现的,接受连接和发送数据使用的则是异步的async_accept ()和 async_write_some ( ),这样在每个异步调用处并不会阻塞,而是转入其他的协程,只有真正发生连接事件和写完成事件时才会继续执行后续的代码。