<think>为了将`ClientSession`改为使用`strand`序列化所有异步操作,我们需要确保所有异步操作都通过`strand`分发执行。这样,即使`io_context`在多线程环境下运行,同一个`ClientSession`实例的异步操作也不会并发执行,从而保证线程安全。
### 修改步骤:
1. **添加strand成员**:在`ClientSession`类中添加一个`net::strand`成员,用于序列化所有异步操作。
2. **修改构造函数**:初始化`strand_`成员。
3. **修改所有异步操作**:确保每个异步操作的开始(包括回调)都通过`strand_`分发。可以使用`net::dispatch`(立即执行)或`net::post`(排队执行)来将操作提交到`strand`中。另外,在异步操作的完成处理函数中,使用`bind_executor`绑定到`strand_`,确保回调在`strand_`中执行。
4. **调整`Run`方法**:将实际执行操作的`DoRun`方法通过`strand_`分发。
5. **调整`TakeStream`方法**:由于`TakeStream`可能被外部调用,我们也需要确保它在`strand`中执行,或者考虑其线程安全性(这里可能不需要,因为通常是在操作完成后调用,但为了安全,我们可以加锁或通过`strand`分发)。不过,根据需求,如果外部调用者能保证在正确的时机调用,可以暂时不修改,但为了线程安全,最好也通过`strand`分发。
### 代码修改:
```cpp
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/asio.hpp>
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = net::ip::tcp;
using TypeCallbackClientOnDone = std::function<void(const char*, const std::string&, const char*)>;
class ClientSession : public std::enable_shared_from_this<ClientSession>
{
public:
explicit ClientSession(net::io_context& ioc, std::shared_ptr<beast::tcp_stream> stream, const std::string& host, int port)
: ioc_(ioc)
, strand_(net::make_strand(ioc)) // 创建strand
, stream_(stream)
, host_(host)
, port_(port)
{
}
void Run(int timeout, char const* target, const char* contentType,
const std::string& content, const TypeCallbackClientOnDone& cb)
{
// 通过strand分发执行,确保线程安全
net::post(strand_,
[self = shared_from_this(), timeout, target = std::string(target),
contentType = std::string(contentType), content, cb]()
{
self->DoRun(timeout, target, contentType, content, cb);
});
}
// 注意:TakeStream可能需要考虑线程安全,如果外部调用者不确定,可以也通过strand分发
std::shared_ptr<beast::tcp_stream> TakeStream()
{
// 由于TakeStream可能被外部调用,我们通过strand确保安全
// 使用promise和future来同步获取结果
auto promise = std::make_shared<std::promise<std::shared_ptr<beast::tcp_stream>>>();
auto future = promise->get_future();
net::post(strand_, [self = shared_from_this(), promise]() {
promise->set_value(self->stream_);
// 注意:TakeStream后,当前session不再持有stream
self->stream_.reset();
});
return future.get();
}
private:
void DoRun(int timeout, const std::string& target, const std::string& contentType,
const std::string& content, const TypeCallbackClientOnDone& cb)
{
// 保存回调
funcOnDone_ = cb;
// 设置请求
request_.version(11); // HTTP/1.1
request_.method(http::verb::post);
request_.target(target);
request_.set(http::field::host, host_);
request_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
request_.set(http::field::content_type, contentType);
request_.body() = content;
request_.prepare_payload();
// 设置超时
stream_->expires_after(std::chrono::milliseconds(timeout));
// 如果stream已经连接,直接进行写操作;否则需要先连接
if (stream_->socket().is_open())
{
DoWrite(timeout);
}
else
{
// 异步连接
auto const address = net::ip::make_address(host_);
stream_->async_connect(
tcp::endpoint{ address, static_cast<unsigned short>(port_) },
beast::bind_front_handler(
&ClientSession::OnConnect,
shared_from_this(),
timeout
)
);
}
}
void OnConnect(beast::error_code ec, int timeout)
{
if (ec)
{
return ReportFail(ec.message(), "connect");
}
DoWrite(timeout);
}
void DoWrite(int timeout)
{
// 发送HTTP请求
http::async_write(
*stream_,
request_,
beast::bind_front_handler(
&ClientSession::OnWrite,
shared_from_this(),
timeout
)
);
}
void OnWrite(beast::error_code ec, std::size_t, int timeout)
{
if (ec)
{
return ReportFail(ec.message(), "write");
}
// 读取响应
http::async_read(
*stream_,
buffer_,
res_,
beast::bind_front_handler(
&ClientSession::OnRead,
shared_from_this(),
timeout
)
);
}
void OnRead(beast::error_code ec, std::size_t, int timeout)
{
if (ec)
{
return ReportFail(ec.message(), "read");
}
// 调用回调,传递响应结果
if (funcOnDone_)
{
funcOnDone_(res_.body().c_str(), "", nullptr);
}
// 注意:这里不关闭socket,以便复用
// 重置buffer和响应对象,以便下次使用
buffer_.consume(buffer_.size());
res_ = {};
}
void ReportFail(const std::string& errMsg, char const* what)
{
if (funcOnDone_)
{
funcOnDone_(nullptr, errMsg, what);
}
// 失败后关闭socket
CloseSocket();
}
void CloseSocket()
{
beast::error_code ec;
stream_->socket().shutdown(tcp::socket::shutdown_both, ec);
stream_->close();
buffer_.consume(buffer_.size());
}
private:
net::io_context& ioc_;
net::strand<net::io_context::executor_type> strand_; // 添加strand成员
std::shared_ptr<beast::tcp_stream> stream_;
std::string host_;
int port_;
beast::flat_buffer buffer_;
http::request<http::string_body> request_;
http::response<http::string_body> res_;
TypeCallbackClientOnDone funcOnDone_;
};
```
### 关键点解释:
1. **添加strand**:使用`net::make_strand(ioc)`创建strand,它关联到`io_context`的执行器。
2. **通过strand分发操作**:所有公共接口(如`Run`和`TakeStream`)都通过`net::post`将实际工作分发到`strand`中,确保操作在`strand`中顺序执行。
3. **异步操作回调绑定到strand**:在发起异步操作(如`async_connect`、`async_write`、`async_read`)时,使用`beast::bind_front_handler`绑定成员函数,这些回调函数会在异步操作完成时被调用。由于我们在`strand`中启动这些操作,并且这些操作的回调也是通过`strand`分发(因为我们在`strand`中启动操作,所以回调默认会在同一个`strand`中执行),因此保证了整个操作链都在同一个`strand`中执行,不会并发。
4. **TakeStream的线程安全**:在`TakeStream`中,我们通过`net::post`将获取`stream_`的操作放到`strand`中,并使用`promise`和`future`来等待结果。这样外部调用`TakeStream`的线程会阻塞直到`strand`中执行完该操作,从而保证线程安全。
### 注意事项:
- 使用`strand`后,同一个`ClientSession`实例的所有异步操作(包括回调)都会在同一个`strand`中顺序执行,因此不会出现并发访问成员变量的情况。
- 在`TakeStream`中,我们使用了阻塞等待(`future.get()`),这可能会导致调用线程阻塞。如果不想阻塞,可以设计一个异步版本的`TakeStream`,通过回调返回结果。
- 在`Run`方法中,我们将参数复制到lambda中,确保在分发到`strand`时参数仍然有效。
### 相关问题:
§§相关问题§§:
1. 在Boost.Asio中,strand和io_context的关系是什么?
2. 使用strand时,如何避免死锁或竞争条件?
3. 在异步操作链中,如何确保对象的生命周期?
4. Boost.Asio中的`bind_executor`和`bind_front_handler`有什么区别和联系?</think>### 使用 Strand 序列化改造 ClientSession
以下是使用 `boost::asio::strand` 重构 `ClientSession` 的完整实现,确保所有异步操作线程安全:
```cpp
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/asio.hpp>
#include <memory>
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = net::ip::tcp;
using TypeCallbackClientOnDone = std::function<void(const char*, const std::string&, const char*)>;
class ClientSession : public std::enable_shared_from_this<ClientSession>
{
public:
explicit ClientSession(net::io_context& ioc,
std::shared_ptr<beast::tcp_stream> stream,
const std::string& host,
int port)
: ioc_(ioc)
, strand_(net::make_strand(ioc)) // 关键:创建strand
, stream_(std::move(stream))
, host_(host)
, port_(port)
{}
void Run(int timeout,
char const* target,
const char* contentType,
const std::string& content,
const TypeCallbackClientOnDone& cb)
{
// 通过strand分发操作
net::post(
strand_,
[self = shared_from_this(), timeout,
target = std::string(target),
contentType = std::string(contentType),
content, cb]()
{
self->DoRun(timeout, target, contentType, content, cb);
}
);
}
std::shared_ptr<beast::tcp_stream> TakeStream()
{
// 使用promise/future确保线程安全
auto promise = std::make_shared<std::promise<std::shared_ptr<beast::tcp_stream>>>();
auto future = promise->get_future();
net::post(
strand_,
[self = shared_from_this(), promise]()
{
promise->set_value(self->stream_);
self->stream_.reset(); // 转移所有权后重置
}
);
return future.get();
}
private:
void DoRun(int timeout,
const std::string& target,
const std::string& contentType,
const std::string& content,
const TypeCallbackClientOnDone& cb)
{
funcOnDone_ = cb;
request_.version(11); // HTTP/1.1
request_.method(http::verb::post);
request_.target(target);
request_.set(http::field::host, host_);
request_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
request_.set(http::field::content_type, contentType);
request_.body() = content;
request_.prepare_payload();
// 设置超时
stream_->expires_after(std::chrono::milliseconds(timeout));
if (stream_->socket().is_open()) {
DoWrite(timeout);
} else {
auto const address = net::ip::make_address(host_);
// 异步连接(绑定到strand)
stream_->async_connect(
tcp::endpoint{address, static_cast<unsigned short>(port_)},
net::bind_executor(
strand_,
beast::bind_front_handler(
&ClientSession::OnConnect,
shared_from_this(),
timeout
)
)
);
}
}
void OnConnect(beast::error_code ec, int timeout)
{
if (ec) {
return ReportFail(ec.message(), "connect");
}
DoWrite(timeout);
}
void DoWrite(int timeout)
{
// 使用strand绑定写操作
http::async_write(
*stream_,
request_,
net::bind_executor(
strand_,
beast::bind_front_handler(
&ClientSession::OnWrite,
shared_from_this(),
timeout
)
)
);
}
void OnWrite(beast::error_code ec, std::size_t, int timeout)
{
if (ec) {
return ReportFail(ec.message(), "write");
}
// 使用strand绑定读操作
http::async_read(
*stream_,
buffer_,
res_,
net::bind_executor(
strand_,
beast::bind_front_handler(
&ClientSession::OnRead,
shared_from_this(),
timeout
)
)
);
}
void OnRead(beast::error_code ec, std::size_t, int timeout)
{
if (ec) {
return ReportFail(ec.message(), "read");
}
// 在strand中执行回调
if (funcOnDone_) {
funcOnDone_(res_.body().c_str(), "", nullptr);
}
// 清理状态
buffer_.consume(buffer_.size());
res_ = {};
}
void ReportFail(const std::string& errMsg, char const* what)
{
// 确保回调在strand中执行
net::post(
strand_,
[self = shared_from_this(), errMsg, what]()
{
if (self->funcOnDone_) {
self->funcOnDone_(nullptr, errMsg, what);
}
self->CloseSocket();
}
);
}
void CloseSocket()
{
beast::error_code ec;
stream_->cancel();
stream_->socket().shutdown(tcp::socket::shutdown_both, ec);
stream_->close(ec);
buffer_.consume(buffer_.size());
}
private:
net::io_context& ioc_;
net::strand<net::io_context::executor_type> strand_; // 核心同步机制
std::shared_ptr<beast::tcp_stream> stream_;
std::string host_;
int port_;
beast::flat_buffer buffer_;
http::request<http::string_body> request_;
http::response<http::string_body> res_;
TypeCallbackClientOnDone funcOnDone_;
};
```
### 关键改造点详解
1. **Strand 集成**
```cpp
net::strand<net::io_context::executor_type> strand_;
```
- 使用 `net::make_strand(ioc)` 创建与 io_context 关联的 strand
- 所有操作通过 `net::post(strand_, ...)` 分发
2. **异步操作绑定**
```cpp
http::async_read(
*stream_,
buffer_,
res_,
net::bind_executor( // 关键绑定
strand_,
beast::bind_front_handler(...)
)
);
```
- `net::bind_executor` 确保回调在 strand 上下文中执行
- `beast::bind_front_handler` 适配成员函数
3. **线程安全状态管理**
```cpp
net::post(strand_, [self = shared_from_this(), ...]() {
// 所有状态访问都在strand内
self->request_.body() = content;
});
```
- 使用 `shared_from_this()` 确保对象生命周期
- 所有成员变量访问都在 strand 上下文中
4. **安全回调执行**
```cpp
net::post(strand_, [self, errMsg, what]() {
if (self->funcOnDone_) {
self->funcOnDone_(...);
}
});
```
- 用户回调在 strand 中执行,避免并发问题
- 防止回调执行期间对象被销毁
### Strand 工作流程
```mermaid
sequenceDiagram
participant Caller as 外部调用者
participant Strand as Strand队列
participant Socket as Socket操作
participant Callback as 用户回调
Caller->>Strand: post(Run)
Strand->>Socket: async_connect
Socket-->>Strand: OnConnect
Strand->>Socket: async_write
Socket-->>Strand: OnWrite
Strand->>Socket: async_read
Socket-->>Strand: OnRead
Strand->>Callback: 安全执行funcOnDone_
```
### 性能优化技巧
1. **移动语义优化**
```cpp
Run(..., const std::string& content, ...) {
net::post(strand_, [self, content = std::move(content)]() {...});
}
```
2. **Strand 局部化**
```cpp
// 为不同资源使用独立strand
net::strand read_strand_ = net::make_strand(ioc_);
net::strand write_strand_ = net::make_strand(ioc_);
```
3. **批处理操作**
```cpp
void BatchRequests(const std::vector<Request>& requests) {
net::post(strand_, [self, requests]() {
for (auto& req : requests) {
self->request_queue_.push(req);
}
if (self->request_queue_.size() == requests.size()) {
self->ProcessBatch();
}
});
}
```
4. **动态超时调整**
```cpp
void OnRead(..., int timeout) {
// 根据响应大小调整后续超时
int new_timeout = CalculateTimeout(res_.body().size());
stream_->expires_after(std::chrono::milliseconds(new_timeout));
}
```