系列文章目录
文章目录
前言
异步IO介绍见references
asio和boost.asio的区别
https://think-async.com/Asio/AsioAndBoostAsio.html
1. asio基础概念
host <-> hostname / domain / url
service <-> port
1. 同步操作
asio::io_context io_ctx; // io上下文相当于asio上层与操作系统之间的一个桥梁、中间件
asio::ip::tcp::socket socket(io_ctx);
socket.connect(server_endpoint); // 同步操作
// 应用通过通过connect接口告诉socket对象建立一个tcp链接,socket对象将其转发到io_context
// io execution context调用系统接口建立连接,将结果告诉socket对象,然年后它再返回告诉应用连接结果
2. 异步操作
asio::io_context io_ctx; // io上下文相当于asio上层与操作系统之间的一个桥梁、中间件
asio::ip::tcp::socket socket(io_ctx);
std::function<void(const asio::error_code&)> handler;
handler = [&](const asio::error_code& ec){ } // 在io_context::run()调用线程执行
socket.async_connect(server_endpoint, handler); // 异步操作分两步,1.初始化 2.执行handler函数
io_ctx.run(); // 执行流阻塞在这里,直到没有新的异步io handler可供处理
每调用一次async_X操作时好像入队(生产一个),每次事件来了时都会出队(消费一个),若 .run()时 队中有操作则阻塞,无操作则返回。
3. 异步代理
由于在调用handler之前,所有申请的资源都已释放,因此在handler函数内可继续执行初始化(回调函数注册)操作,此为异步代理。
异步代理包含多个异步操作
handler = [&](const asio::error_code& ec){
socket.async_connect(server_endpoint, handler); // 异步代理
} // 在io_context::run()调用线程执行
4. 相关特征
包括执行器用哪种,内存该如何分配,取消slot如何等
异步操作的实现类似下面:
template <typename CompletionToken, completion_signature... Signatures>
struct async_result
{
template <typename Initiation, completion_handler_for<Signatures...> CompletionHandler, typename... args>
static void initiate(Initiation&& initiation, CompletionHandler&& completion_handler, Args&&... args)
{
template <typename T> using sfw = std::forward<T>;
sfw<Initiation>(Initiation)(sfw<CompletionHandler>(completion_handler), sfw<Args>(args)...);
}
};
using FnType = void(asio::error_code, size_t);
template< FnType CompletionToken>
auto async_read_some(asio::ip::tcp& s, const asio::mutable_buffer& b, FnType&& token)
{
auto init = [](auto completion_handler, asio::ip::tcp::socket* s, const asio::mutable_buffer& b){
std::thread(
[](auto completion_handler, asio::ip::tcp::socket* s, const asio::mutable_buffer &b) {
asio::error_code ec;
size_t n = s->read_some(b, ec);
std::move(completion_handler)(ec, n);
}, std::move(completion_handler), s, b
).detach();
};
return asio::async_result<typename std::decay_t<FnType>, void(asio::error_code, size_t)>
::initiate(init, std::forward<CompletionToken>(token), &s, b);
}
高层次级别抽象
proactor模式和reactor模式的区别
reactor为同步IO模型,主线程负责通知子线程,子线程负责读写以及业务逻辑处理
proactor为异步IO模型,主线程和内核负责通知和读写数据,子线程负责业务逻辑处理
不同对象是线程安全的,同一个对象不能多线程同时读写,他不是安全的
Strands
保证strand中的异步操作不会并发执行,这在服务端处理用户可以避免数据竞争
#include <functional>
#include <iostream>
#include <asio.hpp>
#include <vector>
#include <thread>
using namespace std;
using namespace asio;
void multi_context()
{
vector<io_context*> ctxs;
for (int i = 0; i < 100; ++i)
{
ctxs.push_back(new io_context);
}
for (int i = 0; i < 100; ++i)
{
asio::post( *ctxs[i] , [](){
cout << gettid() << "]" << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << endl;
});
}
vector<std::thread> ths;
for (int i = 0; i < 100; ++i)
{
ths.emplace_back([i, &ctxs](){ ctxs[i]->run(); });
}
for (int i = 0; i < 100; ++i)
{
ths[i].join();
}
}
void no_strand()
{
io_context io_ctx;
// auto strand = asio::make_strand(io_ctx);
for (int i = 0; i < 100; ++i)
{
asio::post( io_ctx , [](){
cout << gettid() << "]" << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << endl;
});
}
vector<std::thread> ths;
for (int i = 0; i < 100; ++i)
{
ths.emplace_back([i, &io_ctx](){ io_ctx.run(); });
}
for (int i=0; i < 100; ++i)
{
ths[i].join();
}
}
void has_strand()
{
io_context io_ctx;
auto strand = asio::make_strand(io_ctx);
for (int i = 0; i < 100; ++i)
{
asio::post( strand , [](){
cout << gettid() << "]" << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << endl;
});
}
vector<std::thread> ths;
for (int i = 0; i < 100; ++i)
{
ths.emplace_back([i, &io_ctx](){ io_ctx.run(); });
}
for (int i=0; i < 100; ++i)
{
ths[i].join();
}
}
Buffers
- A scatter-read receives data into multiple buffers.
- A gather-write transmits multiple buffers.
stream-oriented I/O objects
- There are no message boundaries. The data being transferred is a continuous sequence of bytes.
- Read or write operations may transfer fewer bytes than requested. This is referred to as a short read or short write.
read_some()
async_read_some()
write_some()
async_write_some()
ip::tcp::socket
read(), async_read(), write() and async_write().
ip::tcp::socket socket(my_io_context);
...
socket.non_blocking(true);
...
socket.async_wait(ip::tcp::socket::wait_read, read_handler);
...
void read_handler(asio::error_code ec)
{
if (!ec)
{
std::vector<char> buf(socket.available());
socket.read_some(buffer(buf));
}
}
异步读,直到读到xxx
read_until() and async_read_until()
class http_connection
{
...
void start()
{
asio::async_read_until(socket_, data_, "\r\n",
boost::bind(&http_connection::handle_request_line, this, _1));
}
void handle_request_line(asio::error_code ec)
{
if (!ec)
{
std::string method, uri, version;
char sp1, sp2, cr, lf;
std::istream is(&data_);
is.unsetf(std::ios_base::skipws);
is >> method >> sp1 >> uri >> sp2 >> version >> cr >> lf;
...
}
}
...
asio::ip::tcp::socket socket_;
asio::streambuf data_;
};
// read_until支持多个重载
typedef asio::buffers_iterator<
asio::streambuf::const_buffers_type> iterator;
std::pair<iterator, bool>
match_whitespace(iterator begin, iterator end)
{
iterator i = begin;
while (i != end)
if (std::isspace(*i++))
return std::make_pair(i, true);
return std::make_pair(i, false);
}
...
asio::streambuf b;
asio::read_until(s, b, match_whitespace);
// 可调用类,特性萃取
class match_char
{
public:
explicit match_char(char c) : c_(c) {}
template <typename Iterator>
std::pair<Iterator, bool> operator()(
Iterator begin, Iterator end) const
{
Iterator i = begin;
while (i != end)
if (c_ == *i++)
return std::make_pair(i, true);
return std::make_pair(i, false);
}
private:
char c_;
};
namespace asio {
template <> struct is_match_condition<match_char>
: public boost::true_type {};
} // namespace asio
...
asio::streambuf b;
asio::read_until(s, b, match_char('a'));
定制内存分配器, cancellation_slot
当使用用户定义的对象 Handler h时,获取分配器使用如下方法
asio::associated_allocator_t<Handler> a = asio::get_associated_allocator(h);
class my_handler
{
public:
// Custom implementation of Allocator type requirements.
typedef my_allocator allocator_type;
// Return a custom allocator implementation.
allocator_type get_allocator() const noexcept
{
return my_allocator();
}
void operator()() { ... }
};
namespace asio {
template <typename Allocator>
struct associated_allocator<my_handler, Allocator>
{
// Custom implementation of Allocator type requirements.
typedef my_allocator type;
// Return a custom allocator implementation.
static type get(const my_handler&,
const Allocator& a = Allocator()) noexcept
{
return my_allocator();
}
};
} // namespace asio
asio::async_read(my_socket, my_buffer,
asio::bind_cancellation_slot(
my_cancellation_slot,
[](asio::error_code e, std::size_t n)
{
...
}
)
);
cancellation slot and signal. signal发送,slot接收(一个覆盖式handler)
class session
: public std::enable_shared_from_this<proxy>
{
...
void do_read()
{
auto self = shared_from_this();
socket_.async_read_some(
buffer(data_),
asio::bind_cancellation_slot(
cancel_signal_.slot(),
[self](std::error_code error, std::size_t n)
{
...
}
)
);
}
...
void request_cancel()
{
cancel_signal_.emit(asio::cancellation_type::total);
}
...
asio::cancellation_signal cancel_signal_;
};
debugging
通过加入一些宏
https://think-async.com/Asio/asio-1.30.2/doc/asio/overview/core/handler_tracking.html
显示调试信息
异步操作组合
将几个异步操作组成一个简单的状态机
struct async_echo_implementation
{
asio::ip::tcp::socket& socket_;
asio::mutable_buffer buffer_;
enum {starting, reading, writing} state_;
template <typename Self> void
operator()(Self& self, asio::error_code error = {}, size_t n = 0)
{
switch (state_)
{
case starting:
state_ = reading;
socket_.async_read_some(buffer_, std::move(self));
break;
case reading:
if (error)
{
self.complete(error, 0);
}
else
{
state_ = writing;
asio::async_write(socket_, buffer_, asio::transfer_exactly(n), std::move(self));
}
break;
case writing:
self.complete(error, n);
break;
}
}
};
template<typename CompletionToken>
auto async_echo(ip::tcp::socket& socket, mutable_buffer buffer, CompletionToken&& token) ->
typename async_result<typename std::decay<CompletionToken>::type,
void(error_code, size_t)>::return_type
{
return async_compose<CompletionToken, void(error_code, size_t)>(
async_echo_implementation{socket, buffer, async_echo_implementation::starting},
token,
socket
);
}
Completion Token的适配器
适配器,保持接口不变而改变实现
bind_executor, bind_allocator, bind_cancellation_slot, and bind_immediate_executor
重定向错误适配器
asio::error_code my_error; // N.B. must be valid until operation completes
// ...
my_socket.async_read_some(my_buffer,
asio::redirect_error(
[](std::size_t bytes_transferred)
{
// ...
}, my_error));
5 网络
在socket编程中,ip, tcp, udp, icmp协议在创建socket时通过AFINET(6),SOCKE_STREAM,SOCK_DGRAM等指定
asio通过namespace分离出ip协议,tcp,udp,imcp则定义在类中
通讯节点称为endpoint, 域名/主机名成为hostname, 端口号称为service
ip::tcp::resolver resolver(my_io_context);
ip::tcp::resolver::query query("www.boost.org", "http");
ip::tcp::resolver::iterator iter = resolver.resolve(query);
ip::tcp::resolver::iterator end; // End marker.
while (iter != end)
{
ip::tcp::endpoint endpoint = *iter++;
std::cout << endpoint << std::endl;
}
// 包含ipv4 和 ipv6
ip::tcp::socket socket(my_io_context);
asio::connect(socket, resolver.resolve(query)/*or endpoint*/); // 尝试每个endpoint,直到连接成功
asio::async_connect(socket_, iter,
std::bind(&client::handle_connect, this, asio::placeholders::error));
// ...
struct client{
void handle_connect(const error_code& error)
{
if (!error) // Start read or write operations.
else // Handle error.
}
};
receive(), async_receive(), send() or async_send()
数据量short: read(), async_read(), write() and async_write().
// icmp udp
ip::icmp::resolver resolver(my_io_context);
ip::icmp::resolver::query query("localhost", "");
ip::icmp::resolver::iterator iter = resolver.resolve(query), end;
ip::icmp::endpoint endpoint(ip::icmp::v6(), 0);
ip::icmp::socket socket(my_io_context, endpoint);
// tcp
对sockets的高层次封装
ip::tcp::iostream stream("www.boost.org", "http");
if (!stream)
{
// Can't connect.
std::cout << "Error: " << stream.error().message() << "\n";
}
io_context ioc;
ip::tcp::endpoint endpoint(tcp::v4(), 80);
ip::tcp::acceptor acceptor(ios, endpoint);
for (;;)
{
ip::tcp::iostream stream;
acceptor.accept(stream.socket());
...
}
ip::tcp::iostream stream;
stream.expires_from_now(boost::posix_time::seconds(60));
stream.connect("www.boost.org", "http");
stream << "GET /LICENSE_1_0.txt HTTP/1.0\r\n";
stream << "Host: www.boost.org\r\n";
stream << "Accept: */*\r\n";
stream << "Connection: close\r\n\r\n";
stream.flush();
std::cout << stream.rdbuf();
Any socket operations that occur past the deadline will put the iostream into a “bad” state.
BSD socket API 和 asio的对应关系:https://think-async.com/Asio/asio-1.30.2/doc/asio/overview/networking/bsd_sockets.html
6 一些异步封装
计时器
io_context io_ctx;
steady_timer t1(io_ctx);
steady_timer::time_point time_of_expiry = t1.expiry();
t1.expires_after(asio::chrono::milliseconds(1000));
io_ctx.run();
文件
Files
Asio provides support for manipulating stream-oriented and random-access files. And this feature requires I/O completion ports on Windows, and io_uring on Linux (define ASIO_HAS_IO_URING
to enable).
管道
Pipes
串口
Serial Ports
信号处理
// Construct a signal set registered for process termination.
asio::signal_set signals(io_context, SIGINT, SIGTERM);
// Start an asynchronous wait for one of the signals to occur.
signals.async_wait([](asio::error_code ec,int sig){if(!ec)cout<<sig<<'\n';});
不同线程使用不同的signal_set对象是安全的,使用同一个对象是不安全的。
signal_set sin(io_ctx, SIGINT), sin2(io_ctx, SIGINT);
sin.async_wait(handler);
sin.async_wait(handler3);
sin2.async_wait(handler2);
sin2.async_wait(handler4);
// 所有注册的handler*好像队列依次入队,信号到来,依次被消费,若无handler则信号阻塞在队列中直到wait被调用
7 ssl支持
2. 计时器 & UDP/TCP编程
https://blog.youkuaiyun.com/surfaceyan/article/details/136457991
3. 系统提供的异步接口
4. 最佳实践
客户端
#include "asio.hpp"
#include <iostream>
#include <vector>
using namespace std;
using namespace asio;
int main()
{
io_context io_ctx;
ip::tcp::socket socket(io_ctx);
ip::tcp::endpoint end_point(ip::make_address_v4("127.0.0.1"), 80);
vector<char> vec_buf(1024, 0);
mutable_buffer buffer(vec_buf.data(), vec_buf.size()-1);
function<void(const error_code&, size_t)> read_callback;
read_callback = [&](const error_code& ec, size_t size){
if (!ec)
{
vec_buf[size] = '\0';
cout << "read bytes: " << size << ":"
<< vec_buf.data() << endl;
socket.async_read_some(buffer, read_callback);
}
else
{
cout << "read failure: " << ec.message() << endl;
}
};
socket.async_connect(end_point, [&](const error_code& ec){
cout << "async conn: " << ec.message() << endl;
if (!ec)
{
cout << "connected" << endl;
using namespace buffer_literals;
socket.async_write_some("GET / HTTP/1.1\r\n"
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n"
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0\r\n"
"Upgrade-Insecure-Requests: 1\r\n"
"Connection: close\r\n"
"\r\n"_buf, [&](const error_code& ec, size_t size){
if (!ec)
{
cout << "write ok " << size << endl;
socket.async_read_some(buffer, read_callback);
}
else
{
cout << "write failure: " << ec.message() << endl;
}
});
}
else
{
cout << "connect failure: " << ec.message() << endl;
}
});
io_ctx.run();
cout << "process end" << endl;
}
modern C++ style server
#include <array>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <type_traits>
#include <utility>
#include "asio.hpp"
using asio::ip::tcp;
using namespace asio;
class session: public std::enable_shared_from_this<session>
{
public:
session(tcp::socket socket)
: socket_(std::move(socket))
{
printf("session construct: %p\n", this);
}
void start()
{
do_read();
}
~session()
{
printf("session destruct: %p\n", this);
}
private:
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(asio::buffer(data_),
[this, self](std::error_code ec, std::size_t length)
{
if (!ec)
{
do_write(length);
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this());
asio::async_write(socket_, asio::buffer(data_, length),
[this, self](std::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
do_read();
}
});
}
// The socket used to communicate with the client.
tcp::socket socket_;
// Buffer used to store data received from the client.
std::array<char, 1024> data_;
};
class server
{
public:
server(asio::io_context& io_context, short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
{
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(
[this](std::error_code ec, tcp::socket socket)
{
if (!ec)
{
std::make_shared<session>(std::move(socket))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: server <port>\n";
return 1;
}
asio::io_context io_context;
asio::signal_set signals(io_context);
signals.async_wait([&](asio::error_code ec, int sig){ io_context.stop(); });
server s(io_context, std::atoi(argv[1]));
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
native C++ style server
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include "asio.hpp"
using namespace std;
using asio::ip::tcp;
using namespace asio;
class Client
{
private:
ip::tcp::socket socket_;
std::array<char, 1024> buffer_;
public:
Client(ip::tcp::socket socket) : socket_(std::move(socket)) { }
~Client()
{
socket_.close();
cout << "connect closed: " << this << endl;
}
void do_read()
{
socket_.async_read_some(asio::buffer(buffer_), [this](const error_code& ec, size_t len){
cout << ec.message() << " len: " << len << endl;
if (!ec) this->do_write(len);
else delete this;
});
}
void do_write(size_t len)
{
socket_.async_write_some(asio::buffer(buffer_.data(),len), [this](const error_code& ec, size_t len){
cout << ec.message() << " write len: " << len << endl;
if (!ec) this->do_read();
else delete this;
});
}
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
asio::io_context io_ctx;
ip::tcp::endpoint endp = ip::tcp::endpoint(ip::tcp::v4(), atoi(argv[1]));
ip::tcp::acceptor serv(io_ctx, endp);
std::function<void(std::error_code, tcp::socket)> ac_cb;
ac_cb = [&](std::error_code ec, tcp::socket socket){
if (!ec)
{
auto p = new Client(std::move(socket));
p->do_read();
}
serv.async_accept(ac_cb);
};
serv.async_accept(ac_cb);
io_ctx.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
references
https://think-async.com/Asio/AsioAndBoostAsio.html
https://think-async.com/Asio/
https://think-async.com/Asio/asio-1.30.2/doc/
https://www.cnblogs.com/flashsun/p/14591563.html
BSD socket API 和 asio的对应关系:https://think-async.com/Asio/asio-1.30.2/doc/asio/overview/networking/bsd_sockets.html