本文列举 Asio 各类值得注意的细节。git
另见:基于 Asio 的 C++ 网络编程编程
在包含 Asio 头文件以前,定义宏 BOOST_ASIO_NO_DEPRECATED
,这样在编译时,Asio 就会剔除那些已通过时的接口。segmentfault
好比在最新的 Boost 1.66 中,io_service
已经更名为 io_context
,若是没有 BOOST_ASIO_NO_DEPRECATED
,仍是能够用 io_service
的,虽然那只是 io_context
的一个 typedef
。网络
BOOST_ASIO_NO_DEPRECATED
能够保证你用的是最新修订的 API。长期来看,有便于代码的维护。况且,这些修订正是 Asio 进入标准库的前奏。socket
#define BOOST_ASIO_NO_DEPRECATED #include "boost/asio/io_context.hpp" #include "boost/asio/deadline_timer.hpp" ...
在 Windows 平台,编译时会遇到关于 _WIN32_WINNT
的警告。
能够说,这是 Asio 自身的问题。
它应该在某个地方包含 SDKDDKVer.h
。
不该该让用户本身去定义平台的版本。async
若是你用 CMake,能够借助下面这个宏自动检测 _WIN32_WINNT
:
(详见:https://stackoverflow.com/a/4...tcp
if (WIN32) macro(get_WIN32_WINNT version) if (CMAKE_SYSTEM_VERSION) set(ver ${CMAKE_SYSTEM_VERSION}) string(REGEX MATCH "^([0-9]+).([0-9])" ver ${ver}) string(REGEX MATCH "^([0-9]+)" verMajor ${ver}) # Check for Windows 10, b/c we'll need to convert to hex 'A'. if ("${verMajor}" MATCHES "10") set(verMajor "A") string(REGEX REPLACE "^([0-9]+)" ${verMajor} ver ${ver}) endif ("${verMajor}" MATCHES "10") # Remove all remaining '.' characters. string(REPLACE "." "" ver ${ver}) # Prepend each digit with a zero. string(REGEX REPLACE "([0-9A-Z])" "0\\1" ver ${ver}) set(${version} "0x${ver}") endif(CMAKE_SYSTEM_VERSION) endmacro(get_WIN32_WINNT) get_WIN32_WINNT(ver) add_definitions(-D_WIN32_WINNT=${ver}) endif(WIN32)
尽可能不要直接包含大而全的 boost/asio.hpp
。
这样作,是为了帮助本身记忆哪一个类源于哪一个具体的头文件,以及避免包含那些没必要要的头文件。函数
在实际项目中,在你本身的某个「头文件」里简单粗暴的包含 boost/asio.hpp
是很不妥的;固然,在你的「源文件」里包含 boost/asio.hpp
是能够接受的,毕竟实际项目依赖的东西比较多,很难搞清楚每个定义源自哪里。this
虽然关于 Handler 的签名,文档里都有说明,可是直接定位到源码,更方便,也更精确。spa
以 deadline_timer.async_wait()
为例,在 IDE 里定位到 async_wait()
的定义,代码(片断)以下:
template <typename WaitHandler> BOOST_ASIO_INITFN_RESULT_TYPE(WaitHandler, void (boost::system::error_code)) async_wait(BOOST_ASIO_MOVE_ARG(WaitHandler) handler) { ...
经过宏 BOOST_ASIO_INITFN_RESULT_TYPE
,WaitHandler
的签名一目了然。
其实,早期的版本应该是 const boost::system::error_code&
,如今文档和代码注释里还有这么写的,估计是没来得及更新。
前面在说 Handler 签名时,已经看到 BOOST_ASIO_INITFN_RESULT_TYPE
这个宏的提示做用,翻一翻 Asio 源码,error_code
其实都已经传值了。
奇怪的是,即便你的 Handler 传 error_code
为引用,编译运行也都没有问题。
void Print(const boost::system::error_code& ec) { std::cout << "Hello, world!" << std::endl; } int main() { boost::asio::io_context ioc; boost::asio::deadline_timer timer(ioc, boost::posix_time::seconds(3)); timer.async_wait(&Print); ioc.run(); return 0; }
而我发现,当 Handler 是成员函数时,就不行了。下面这个 timer 的例子,若是把 Print
的 error_code
改为引用,就不能编译了。
class Printer { public: ... void Start() { timer_.async_wait(std::bind(&Printer::Print, this, std::placeholders::_1)); } private: // 不能用 const boost::system::error_code& void Print(boost::system::error_code ec) { ... } private: boost::asio::deadline_timer timer_; int count_; };
这个问题在习惯了引用的状况下,害苦了我,真是百思不得其解!也算是 Boost 比较坑的一个地方吧。
调用 bind
时,使用了占位符(placeholder),其实下面四种写法均可以:
boost::bind(Print, boost::asio::placeholders::error, &timer, &count) boost::bind(Print, boost::placeholders::_1, &timer, &count); boost::bind(Print, _1, &timer, &count); std::bind(Print, std::placeholders::_1, &timer, &count);
第一种,占位符是 Boost Asio 定义的。
第二种,占位符是 Boost Bind 定义的。
第三种,同第二种,之因此可行,是由于 boost/bind.hpp
里有一句 using namespace boost::placeholders;
。
// boost/bind.hpp #include <boost/bind/bind.hpp> #ifndef BOOST_BIND_NO_PLACEHOLDERS using namespace boost::placeholders; ...
第四种,STL Bind,相似于 Boost Bind,只是没有声明 using namespace std::placeholders;
。
四种写法,推荐使用二或四。至因而用 Boost Bind 仍是 STL Bind,没那么重要。
此外,数字占位符共有 9 个,_1
- _9
。
不要写成 "end point"。
TCP Server 的 acceptor 通常是这样构造的:
tcp::acceptor(io_context, tcp::endpoint(tcp::v4(), port))
也就是说,指定 protocol (tcp::v4()
) 和 port 就好了。
可是,Asio 的 http 这个例子,确实用了 resolver,根据 IP 地址 resolve 出 endpoint:
tcp::resolver resolver(io_context_); tcp::resolver::results_type endpoints = resolver.resolve(address, port); tcp::endpoint endpoint = *endpoints.begin(); acceptor_.open(endpoint.protocol()); acceptor_.set_option(tcp::acceptor::reuse_address(true)); acceptor_.bind(endpoint); acceptor_.listen(); acceptor_.async_accept(...);
http 这个例子之因此这么写,主要是初始化 acceptor_
时,还拿不到 endpoint,不然能够直接用下面这个构造函数:
basic_socket_acceptor(boost::asio::io_context& io_context, const endpoint_type& endpoint, bool reuse_addr = true)
这个构造函数注释说它等价于下面这段代码:
basic_socket_acceptor<Protocol> acceptor(io_context); acceptor.open(endpoint.protocol()); if (reuse_addr) acceptor.set_option(socket_base::reuse_address(true)); acceptor.bind(endpoint); acceptor.listen(listen_backlog);
下面是不一样的 address
对应的 endpoints 结果(假定 port 都是 8080
):
使用 acceptor.async_accept
时,发现了 Move Acceptable Handler。
简单来讲,async_accept
接受两种 AcceptHandler,直接看源码:
template <typename MoveAcceptHandler> BOOST_ASIO_INITFN_RESULT_TYPE(MoveAcceptHandler, void (boost::system::error_code, typename Protocol::socket)) async_accept(BOOST_ASIO_MOVE_ARG(MoveAcceptHandler) handler)
template <typename Protocol1, typename AcceptHandler> BOOST_ASIO_INITFN_RESULT_TYPE(AcceptHandler, void (boost::system::error_code)) async_accept(basic_socket<Protocol1>& peer, BOOST_ASIO_MOVE_ARG(AcceptHandler) handler, typename enable_if<is_convertible<Protocol, Protocol1>::value>::type* = 0)
第一种是 Move Acceptable Handler,它的第二个参数是新 accept 的 socket。
第二种是普通的 Handler,它的第一个参数是预先构造的 socket。
对于 Move Acceptable Handler,用 bind 行不通。好比给定:
void Server::HandleAccept(boost::system::error_code ec, boost::asio::ip::tcp::socket socket) { }
在 VS 2015 下(支持 C++14),std::bind
能够编译,boost::bind
则不行。
// std::bind 能够,boost::bind 不能够。 acceptor_.async_accept(std::bind(&Server::HandleAccept, this, std::placeholders::_1, std::placeholders::_2));
在 VS 2013 下,std::bind
和 boost::bind
都不行。
结论是,对于 Move Acceptable Handler,不要用 bind,直接用 lambda 表达式:
void DoAccept() { acceptor_.async_accept( [this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) { // Check whether the server was stopped by a signal before this // completion handler had a chance to run. if (!acceptor_.is_open()) { return; } if (!ec) { connection_manager_.Start( std::make_shared<Connection>(std::move(socket), connection_manager_, request_handler_)); } DoAccept(); }); }