在 Boost.Asio 中使用协程

从 1.54.0 版本开始,Boost.Asio 开始支持协程。异步编程是复杂的,协程可以让我们以同步的方式编写出异步的代码,在提高代码可读性的同时又不会丢失性能。

在 Boost.Asio 要怎样才能使用协程呢?可以使用boost::asio::spawn()开启一个协程:

1
2
3
4
5
boost::asio::spawn(strand, echo);
void echo(boost::asio::yield_context yield) // 协程
{
// ...
}

spawn()的第一个参数可以是io_service,也可以是strand(如果需要在多线程中保证同步,可以使用strand)。


协程可以提供代码可读性,例如,如果没有使用协程,那么我们需要编写很多回调函数:

1
2
3
4
5
6
void handleRead(boost::system::error_code ec, std::size_t bytes_transferred)
{
// ...
}

socket.async_read_some(boost::asio::buffer(buffer, buffer.size()), handleRead);

使用协程之后,就不需要回调函数了:

1
2
3
4
5
6
7
8
9
10
try {    
std::size_t n = socket_.async_read_some(
boost::asio::buffer(buffer, buffer.size()),
yield
);
}
catch (std::exception& e)
{
// ...
}

上面的代码,如果出现错误就会抛出异常。当然我们也可以使用错误码替代异常:

1
2
3
4
5
boost::system::error_code ec;
std::size_t n = socket_.async_read_some(
boost::asio::buffer(data, data.size()),
yield[ec]
);

下面是通过协程实现的EchoServer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class TCPConnection : public std::enable_shared_from_this<TCPConnection> 
{
public:
TCPConnection(boost::asio::io_service &io_service)
: socket_(io_service),
strand_(io_service)
{ }

tcp::socket &socket() { return socket_; }
void start()
{
auto self = shared_from_this();
boost::asio::spawn(strand_,
[this, self](boost::asio::yield_context yield)
{
std::array<char, 8192> data;
while (true)
{
boost::system::error_code ec;
std::size_t n = socket_.async_read_some(
boost::asio::buffer(data, data.size()),
yield[ec]
);
if (ec) { break; }

boost::asio::async_write(
socket_,
boost::asio::buffer(data, n),
yield[ec]
);
if (ec) { break; }
}
});
}

private:
tcp::socket socket_;
boost::asio::io_service::strand strand_;
};

class EchoServer
{
public:
EchoServer(boost::asio::io_service &io_service, unsigned short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
boost::asio::spawn(io_service_,
[this] (boost::asio::yield_context yield)
{
while (true)
{
auto conn = std::make_shared<TCPConnection>(io_service_);

boost::system::error_code ec;
acceptor_.async_accept(conn->socket(), yield[ec]);

if (!ec) { conn->start(); }
}
});
}

private:
boost::asio::io_service &io_service_;
tcp::acceptor acceptor_;
};

参考资料