C++11以来,提供了不少遍历,原来在boost库中的一些工具,也进入到了C++标准库中。boost作为“准”标准库也C++尽可能需要掌握的知识和技能。
现在采用std::bind方式实现异步echo服务器端,后面会采用lambda表达式重新实现一遍,比较二者的差异,采用自己喜欢的方法就可以了。
完整代码:
// Asynchronous echo server.
#include <array>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include "boost/asio.hpp"
#include "boost/core/ignore_unused.hpp"
using boost::asio::ip::tcp;
enum { BUF_SIZE = 1024 };
class Session : public std::enable_shared_from_this<Session> {
public:
Session(tcp::socket socket) : socket_(std::move(socket)) {
}
void Start() {
DoRead();
}
private:
void DoRead() {
socket_.async_read_some(boost::asio::buffer(buffer_),
std::bind(&Session::OnRead, shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void DoWrite(std::size_t length) {
boost::asio::async_write(socket_,
boost::asio::buffer(buffer_, length),
std::bind(&Session::OnWrite, shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void OnRead(boost::system::error_code ec, std::size_t length) {
if (!ec) {
DoWrite(length);
}
else {
if (ec == boost::asio::error::eof) {
std::cerr << "Socket read EOF: " << ec.message() << std::endl;
}
else if (ec == boost::asio::error::operation_aborted) {
// The socket of this connection has been closed.
// This happens, e.g., when the server was stopped by a signal (Ctrl-C).
std::cerr << "Socket operation aborted: " << ec.message() << std::endl;
}
else {
std::cerr << "Socket read error: " << ec.message() << std::endl;
}
}
}
void OnWrite(boost::system::error_code ec, std::size_t length) {
boost::ignore_unused(length);
if (!ec) {
DoRead();
}
}
tcp::socket socket_;
std::array<char, BUF_SIZE> buffer_;
};
class Server {
public:
Server(boost::asio::io_context& io_context, std::uint16_t port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
DoAccept();
}
private:
void DoAccept() {
acceptor_.async_accept(std::bind(&Server::accept_handler, this,
std::placeholders::_1,
std::placeholders::_2));
}
void accept_handler(const boost::system::error_code& error,
boost::asio::ip::tcp::socket peer)
{
if (!error)
{
// Accept succeeded.
std::make_shared<Session>(std::move(peer))->Start();
}
DoAccept();
}
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;
return 1;
}
std::uint16_t port = std::atoi(argv[1]);
boost::asio::io_context io_context;
Server server{ io_context, port };
io_context.run();
return 0;
}
题外话:关于帮助文档的查询
在你的boost目录中是有文档可以查询的,比如我这里的boosts 1.7.2中帮助文档如图所示,打开reference.html这个网页,就可以看到所有类的索引,非常方便。
讲解:
先从Server的构造函数说起
Server(boost::asio::io_context& io_context, std::uint16_t port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
DoAccept();
}
这里的tcp::acceptor的头文件在boost/asio/ip/tcp.hpp中
在这个头文件中看到其构造函数:
/// The TCP acceptor type.
typedef basic_socket_acceptor<tcp> acceptor;
然后我们去看basic_socket_acceptor的构造函数
basic_socket_acceptor::async_accept
然后看到一堆模板重载的
template<
typename MoveAcceptHandler = DEFAULT>
DEDUCED async_accept(
MoveAcceptHandler && handler = DEFAULT);
这个是我们用的,然后我又去查这个MoveAcceptHandler
会看到几种写法,有普通函数的写法,仿函数的写法和lambda表达式的写法,还介绍了std::bind的写法
初学boost.asio会发现到处都是模板,函数模板,类模板,查帮助文档也似乎不那么方便,慢慢会习惯的。
构造函数构造完成后,就开始了DoAccept();
这个函数的作用的不断接收新的请求,然后利用socket,构造一个Session,在Session中处理tcp数据的接收和发送。
在Session中boost::asio::async_read_some,不断读,读一点,就用boost::asio::async_write写一点,直到async_read_some读到结束,或者socket被动关闭,或者有错误发生。然后Session就完成了自动的使命,自动析构。
因为Session接收数据和发送数据都是异步的,就利用了std::shared_ptr来延长生命周期,使得Session不被过早释放。具体智能指针延长生命周期的知识点,大家可以查std::enable_shared_from_this的用法。
客户端的话,可以用之前去同步echo客户端就可以,后面我们会继续实现一个异步echo客户端。