将数据写入客户端时boost协同路由服务器崩溃

将数据写入客户端时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

我以boostcoroutineecho服务器为例,简单地接收和写回一些数据。它在向客户端写入数据时崩溃,更奇怪的是,它只在使用多核时崩溃

这是服务器,它读取4个字节,并在1秒内作为超时写回“OK”:

#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