将数据写入客户端时boost协同路由服务器崩溃
我以boostcoroutineecho服务器为例,简单地接收和写回一些数据。它在向客户端写入数据时崩溃,更奇怪的是,它只在使用多核时崩溃 这是服务器,它读取4个字节,并在1秒内作为超时写回“OK”:将数据写入客户端时boost协同路由服务器崩溃,boost,boost-asio,boost-coroutine,boost-exception,Boost,Boost Asio,Boost Coroutine,Boost Exception,我以boostcoroutineecho服务器为例,简单地接收和写回一些数据。它在向客户端写入数据时崩溃,更奇怪的是,它只在使用多核时崩溃 这是服务器,它读取4个字节,并在1秒内作为超时写回“OK”: #include <winsock2.h> #include <windows.h> #include <iostream> using namespace std; #include <boost/thread/thread.hpp> #inc
#include <winsock2.h>
#include <windows.h>
#include <iostream>
using namespace std;
#include <boost/thread/thread.hpp>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
using namespace boost;
using namespace boost::asio;
using namespace boost::asio::ip;
#define SERVER_PORT 1234
#define DATA_LEN_4 4
#define TIMEOUT_LIMIT 1 // second
struct session : public std::enable_shared_from_this<session>
{
tcp::socket socket_;
boost::asio::steady_timer timer_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
explicit session(boost::asio::io_context& io_context, tcp::socket socket)
: socket_(std::move(socket)),
timer_(io_context),
strand_(io_context.get_executor())
{ }
void go()
{
auto self(shared_from_this());
boost::asio::spawn(strand_, [this, self](boost::asio::yield_context yield)
{
try
{
timer_.expires_from_now(std::chrono::seconds(TIMEOUT_LIMIT));
// recv data
string packet;
packet.resize(DATA_LEN_4); // alloc memory
size_t received_len = 0;
// read data
{
size_t rs;
while(received_len < DATA_LEN_4) { // recv 4 bytes
boost::system::error_code ec;
rs = socket_.async_read_some(
boost::asio::buffer((char*)(packet.c_str()+received_len), DATA_LEN_4-received_len), yield[ec]);
if(ec==boost::asio::error::eof)
break; //connection closed cleanly by peer
else if(ec) {
throw "read_fail";
}
received_len += rs;
}
}
if(received_len < DATA_LEN_4) {
throw "recv too short, maybe timeout";
}
// write back "OK"
{
boost::system::error_code ecw;
boost::asio::async_write(socket_, boost::asio::buffer(string("OK")), yield[ecw]);
if(ecw==boost::asio::error::eof)
return; //connection closed cleanly by peer
else if(ecw)
throw "write_fail"; // some other error
}
}
catch (const char* reason)
{
printf("exception reason: %s\n", reason);
boost::system::error_code ecw;
/*
* Question 1: why this 'async_write' line causes crash?
*/
// write the error reason to client
boost::asio::async_write(socket_, boost::asio::buffer(string(reason)), yield[ecw]);
socket_.close();
timer_.cancel();
}
catch (...)
{
printf("unknown exception\n");
socket_.close();
timer_.cancel();
}
});
boost::asio::spawn(strand_, [this, self](boost::asio::yield_context yield)
{
while (socket_.is_open())
{
boost::system::error_code ignored_ec;
timer_.async_wait(yield[ignored_ec]);
if (timer_.expires_from_now() <= std::chrono::seconds(0))
socket_.close();
}
});
}
};
int main() {
boost::asio::io_context io_context;
boost::asio::spawn(io_context, [&](boost::asio::yield_context yield)
{
tcp::acceptor acceptor(io_context,
tcp::endpoint(tcp::v4(), SERVER_PORT));
for (;;)
{
boost::system::error_code ec;
tcp::socket socket(io_context);
acceptor.async_accept(socket, yield[ec]);
if (!ec)
std::make_shared<session>(io_context, std::move(socket))->go();
}
});
/*
* When run on 1 CPU, it runs fine, no Crash
*/
// io_context.run();
/*
* Question 2:
* But when run on multiple CPUs, it Crashes !!!
* Why?
*/
auto thread_count = std::thread::hardware_concurrency();
boost::thread_group tgroup;
for (auto i = 0; i < thread_count; ++i)
tgroup.create_thread(boost::bind(&boost::asio::io_context::run, &io_context));
tgroup.join_all();
}
#包括
#包括
#包括
使用名称空间std;
#包括
#包括
#包括
使用名称空间boost;
使用名称空间boost::asio;
使用名称空间boost::asio::ip;
#定义服务器端口1234
#定义数据长度4
#定义超时\u限制1//s
结构会话:public std::从\u中启用\u共享\u
{
tcp::socket-socket;
升压::asio::稳定定时器;
boost::asio::串;
显式会话(boost::asio::io_上下文&io_上下文,tcp::socket)
:套接字(标准::移动(套接字)),
计时器(io上下文),
串(io_上下文。get_executor())
{ }
void go()
{
自动自我(从_this()共享_);
boost::asio::spawn(strand_u,[this,self](boost::asio::yield_上下文yield)
{
尝试
{
计时器从现在起过期(标准::计时::秒(超时限制));
//recv数据
字符串包;
packet.resize(DATA_LEN_4);//分配内存
收到的尺寸=0;
//读取数据
{
尺寸;
而(接收长度<数据长度4){//recv 4字节
boost::system::error_code ec;
rs=套接字异步读取(
boost::asio::buffer((char*)(packet.c_str()+received_len),DATA_len_4-received_len),yield[ec];
if(ec==boost::asio::error::eof)
中断;//连接被对等方完全关闭
如果有的话(欧共体){
抛出“读取失败”;
}
接收到的_len+=rs;
}
}
if(接收长度<数据长度4){
抛出“recv太短,可能超时”;
}
//回写“OK”
{
boost::system::错误代码ecw;
boost::asio::async_write(套接字),boost::asio::buffer(字符串(“OK”)),yield[ecw];
if(ecw==boost::asio::error::eof)
return;//对等方已完全关闭连接
否则如果(ecw)
抛出“写入失败”;//其他一些错误
}
}
捕获(常量字符*原因)
{
printf(“异常原因:%s\n”,原因);
boost::system::错误代码ecw;
/*
*问题1:为什么这个“异步写入”行会导致崩溃?
*/
//将错误原因写入客户端
boost::asio::async_write(套接字),boost::asio::buffer(字符串(原因)),yield[ecw]);
插座关闭();
计时器u0.cancel();
}
捕获(…)
{
printf(“未知异常\n”);
插座关闭();
计时器u0.cancel();
}
});
boost::asio::spawn(strand_u,[this,self](boost::asio::yield_上下文yield)
{
while(套接字打开()
{
boost::system::错误\u忽略代码\u ec;
定时器异步等待(产生[忽略];
如果(计时器从现在起过期)go();
}
});
/*
*当在1个CPU上运行时,它运行良好,没有崩溃
*/
//io_context.run();
/*
*问题2:
*但是当在多个CPU上运行时,它会崩溃!!!
*为什么??
*/
自动线程计数=std::thread::hardware_concurrency();
boost::线程组tgroup;
用于(自动i=0;i
请注意,4字节数据包和1秒超时只是为了说明问题,真正的服务器使用大数据包,这可能会在不良网络条件下导致超时。为了模拟这种情况,客户端每秒写入1字节以触发服务器上的读取超时
客户:
#include <iostream>
#include <boost/asio.hpp>
using namespace std;
using boost::asio::ip::tcp;
#define SERVER "127.0.0.1"
#define PORT "1234"
int main() {
boost::asio::io_context io_context;
unsigned i = 1;
while(1) {
try {
tcp::socket s(io_context);
tcp::resolver resolver(io_context);
boost::asio::connect(s, resolver.resolve(SERVER, PORT));
// to simulate the bad network condition,
// write 4 bytes in 4 seconds to trigger the receive timeout on server, which is 1 second
for(int i=0; i<4; i++) {
boost::asio::write(s, boost::asio::buffer(string("A")));
std::this_thread::sleep_for(std::chrono::seconds(1)); // sleep 1 second
}
// read echo
char x[64] = {0};
s.read_some(boost::asio::buffer(x, sizeof(x)));
cout << i++ << ". received: " << x << endl;
} catch (...) {
cout << i++ << " exception" << endl;
}
}
return 0;
}
#包括
#包括
使用名称空间std;
使用boost::asio::ip::tcp;
#定义服务器“127.0.0.1”
#定义端口“1234”
int main(){
boost::asio::io_上下文io_上下文;
无符号i=1;
而(1){
试一试{
tcp::套接字s(io_上下文);
tcp::解析器解析器(io_上下文);
boost::asio::connect(s,resolver.resolve(服务器,端口));
//为了模拟恶劣的网络条件,
//在4秒内写入4个字节以触发服务器上的接收超时,即1秒
对于(int i=0;i估计值1
这会调用未定义的行为,因为您传递了一个临时字符串作为缓冲区,但是异步操作(根据定义)在async\u write
调用返回之前不会完成
因此,缓冲区是一个过时的引用,指向堆栈上被破坏的东西或现在存在的任何东西
发送缓冲区在逻辑上是self
对象的一部分,以获得更合适的生存期。或者,因为您正在执行协同路由,并且无论如何都要结束会话,所以只需使用write
而不是async\u write
问题2
这是因为未定义的行为是。任何事情都可能发生
未请求的
- 使用
read
与transfer\u精确传输(数据长度4)
一起使用read
,而不是read\u,或者read\u直到具有适当的完成条件
- 您可以使用
dynamic\u buffer
代替buffer(保留字符串)
- 您可以直接捕获
系统,而不是抛出神奇的字符串_
ba::async_write(socket_, ba::buffer(string("OK")), yield[ecw]);
try {
timer_.expires_from_now(std::chrono::seconds(TIMEOUT_LIMIT));
// read data
std::string packet;
auto received_len = ba::async_read(socket_,
ba::dynamic_buffer(packet),
ba::transfer_exactly(DATA_LEN_4), yield);
assert(received_len == DATA_LEN_4); // guaranteed
// write back "OK"
ba::write(socket_, ba::buffer("OK"s));
}
catch (boost::system::system_error const& e) {
if (e.code() == ba::error::operation_aborted)
std::cout << "canceled (timeout)" << std::endl;
else if (e.code() == ba::error::eof)
std::cout << "eof" << std::endl;
else throw std::runtime_error(e.code().message());
}
try {
// ...
} catch (std::exception const& e) {
std::cout << "exception: " << std::quoted(e.what()) << std::endl;
boost::system::error_code ignore;
ba::async_write(socket_, ba::buffer(std::string(e.what())), yield[ignore]);
socket_.close();
timer_.cancel();
}
while (socket_.is_open()) {
boost::system::error_code ec;
timer_.async_wait(yield[ec]);
if (ba::error::operation_aborted != ec) // timer was not canceled
socket_.close();
}
#include <iostream>
#include <iomanip>
#include <boost/thread/thread.hpp>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/scope_exit.hpp>
using namespace std::literals;
namespace ba = boost::asio;
using ba::ip::tcp;
static constexpr unsigned short SERVER_PORT = 1234;
static constexpr std::size_t DATA_LEN_4 = 4;
static constexpr auto TIMEOUT_LIMIT = 1s;
struct session : public std::enable_shared_from_this<session>
{
tcp::socket socket_;
ba::steady_timer timer_;
ba::strand<ba::io_context::executor_type> strand_;
explicit session(ba::io_context& io_context, tcp::socket socket)
: socket_(std::move(socket)),
timer_(io_context),
strand_(io_context.get_executor())
{ }
void go() {
ba::spawn(strand_, [this, self = shared_from_this()](ba::yield_context yield) {
spawn(yield, [this, self](ba::yield_context yield) {
timer_.expires_from_now(TIMEOUT_LIMIT);
while (socket_.is_open()) {
boost::system::error_code ec;
timer_.async_wait(yield[ec]);
if (ba::error::operation_aborted != ec) // timer was not canceled
socket_.close();
}
});
try {
// read data
std::string packet;
ba::async_read(socket_,
ba::dynamic_buffer(packet),
ba::transfer_exactly(DATA_LEN_4), yield);
// write back "OK"
ba::write(socket_, ba::buffer("OK"s));
}
catch (boost::system::system_error const& e) {
if (e.code() == ba::error::operation_aborted)
std::cout << "canceled (timeout)" << std::endl;
else if (e.code() == ba::error::eof)
std::cout << "eof" << std::endl;
else // throw std::runtime_error(e.code().message());
std::cout << "other: " << e.code().message() << std::endl;
}
socket_.close();
timer_.cancel(); // cancel the other coro so we don't keep the session alive
});
}
};
int main() {
ba::io_context io_context;
ba::spawn(io_context, [&](ba::yield_context yield) {
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), SERVER_PORT));
for (;;) {
boost::system::error_code ec;
tcp::socket socket(io_context);
acceptor.async_accept(socket, yield[ec]);
if (!ec)
std::make_shared<session>(io_context, std::move(socket))->go();
}
});
boost::thread_group tgroup;
for (auto i = 0u; i < std::thread::hardware_concurrency(); ++i)
tgroup.create_thread([&io_context] {
for (;;) {
try { io_context.run(); break; } // exited normally
catch (std::exception const &e) { std::clog << "[eventloop] exception caught " << std::quoted(e.what()) << std::endl; }
catch (...) { std::clog << "[eventloop] unknown exception caught" << std::endl; }
}
});
tgroup.join_all();
}
std::mt19937 prng { std::random_device{}() };
for (int i = 0; i < 4; i++) {
ba::write(s, ba::buffer(std::string("A")));
std::this_thread::sleep_for(std::uniform_int_distribution<>(200, 400)(prng) * 1ms);
}
1. received: OK
2. received: OK
3. received: OK
canceled (timeout)
4 exception read_some: End of file
5. received: OK
canceled (timeout)
6 exception read_some: End of file
7. received: OK
8. received: OK
ba::spawn(strand_, [this, self = shared_from_this()](ba::yield_context yield) {
try {
ba::steady_timer timer(strand_, TIMEOUT_LIMIT);
timer.async_wait([this](error_code ec) {
if (ba::error::operation_aborted != ec)
socket_.close();
});
std::string packet;
ba::async_read(socket_,
ba::dynamic_buffer(packet),
ba::transfer_exactly(DATA_LEN_4), yield);
ba::write(socket_, ba::buffer("OK"s));
} catch(std::exception const& e) {
std::clog << "error " << std::quoted(e.what()) << std::endl;
}
});
1. received: OK
2. received: OK
3. received: OK
error "Operation canceled"
4 exception read_some: End of file
5. received: OK
6. received: OK
7. received: OK
error "Operation canceled"
8 exception read_some: End of file
error "Operation canceled"
9 exception read_some: End of file