簡體   English   中英

為什么 io_context 出現在我的 boost asio 協程服務器中

[英]Why io_context sopped in my boost asio coroutine server

我的服務器基於 boost spawn echo 服務器示例,並在此線程中進行了改進。 真正的服務器很復雜,我做了一個更簡單的服務器來顯示問題:

服務器監聽 12345 端口,從新連接接收 0x4000 字節數據。

客戶端運行 1000 個線程,連接到服務器並發送 0x4000 字節數據。

問題:當客戶端運行時,1秒后通過控制台中的Ctrl-C終止客戶端進程,然后服務器的io_context將停止,服務器進入無限循環並消耗100%的cpu。 如果這沒有發生,重復啟動客戶端並殺死它幾次,它就會發生。 也許幾次后它會用完 TCP 端口,等幾分鍾再試一次,在我的機器上殺死客戶端 3~15 次后會發生這種情況。

boost 文檔io_context.stopped()用於確定它是否已停止

要么通過顯式調用 stop(),要么由於工作用完

我從不調用io_context.stop() ,並使用make_work_guard(io_context)來保持io_context不停止,但為什么它仍然停止?

我的環境:Win10-64bit,boost 1.71.0

服務器代碼:

#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;
namespace ba=boost::asio;

#define SERVER_PORT 12345
#define DATA_LEN 0x4000


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)
        {
            spawn(yield, [this, self](ba::yield_context yield) {
                timer_.expires_from_now(10s); // 10 second
                while (socket_.is_open()) {
                    boost::system::error_code ec;
                    timer_.async_wait(yield[ec]);
                    // timeout triggered, timer was not canceled
                    if (ba::error::operation_aborted != ec) {
                        socket_.close();
                    }
                }
            });

            try
            {
                // recv data
                string packet;

                // read data
                boost::system::error_code ec;

                ba::async_read(socket_,
                               ba::dynamic_buffer(packet),
                               ba::transfer_exactly(DATA_LEN),
                               yield[ec]);
                if(ec) {
                    throw "read_fail";
                }

            }
            catch (...)
            {
                cout << "exception" << endl;
            }

            timer_.cancel();
            socket_.close();
        });

    }
};
struct my_server {  
    my_server() { }
    ~my_server() { } 

    void start() {
        ba::io_context io_context;
        auto worker = ba::make_work_guard(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();
                } 
            }
        });

        // Run io_context on All CPUs
        auto thread_count = std::thread::hardware_concurrency();
        boost::thread_group tgroup;
        for (auto i = 0; i < thread_count; ++i) 
            tgroup.create_thread([&] {
                for (;;) {
                    try { 
                        if (io_context.stopped()) { // <- this happens after killing Client process several times
                            cout << "io_context STOPPED, now server runs infinit loop with full cpu usage" << endl;
                        }
                        io_context.run(); 
                    }
                    catch(const std::exception& e) { 
                        MessageBox(0, "This never popup", e.what(), 0); 
                    }
                    catch(const boost::exception& e) { 
                        MessageBox(0, "This never popup", boost::diagnostic_information(e).data(), 0); 
                    }
                    catch(...) { MessageBox(0, "This never popup", "", 0); }
                }
            });
        tgroup.join_all();
    }
};  

int main() {
    my_server svr;
    svr.start();
}

客戶端:

#include <iostream>
#include <random>
#include <thread>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using namespace std;

using boost::asio::ip::tcp;
namespace ba=boost::asio;

#define SERVER "127.0.0.1"
#define PORT "12345"

int main() {
    boost::asio::io_context io_context;

    static string data_0x4000(0x4000, 'a');

    boost::thread_group tgroup;
    for (auto i = 0; i < 1000; ++i) 
        tgroup.create_thread([&] {
            for(;;) {

                try {
                    tcp::socket s(io_context);
                    tcp::resolver resolver(io_context);
                    boost::asio::connect(s, resolver.resolve(SERVER, PORT));

                    ba::write(s, ba::buffer(data_0x4000));
                } catch (std::exception e) {
                    cout << " exception: " << e.what() << endl;
                } catch (...) {
                    cout << "unknown exception" << endl;
                }
            }
        });

    tgroup.join_all();

    return 0;
}

更新解決方法:

我猜io_context和協程會出現問題,所以我嘗試將不必要的spawn替換為std::thread ,並且它有效, 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();
        } 
    }
});

至:

std::thread([&]()
{
    tcp::acceptor acceptor(io_context,
    tcp::endpoint(tcp::v4(), SERVER_PORT));

    for (;;)
    {
        boost::system::error_code ec;

        tcp::socket socket(io_context);
        acceptor.accept(socket, ec);
        if (!ec) {
            std::make_shared<session>(io_context, std::move(socket))->go();
        } 
    }
}).detach();

io_context::run文檔

run() function 阻塞,直到所有工作完成並且沒有更多的處理程序要調度,或者直到io_context已停止。

如果沒有連接客戶端,則沒有更多工作要做(所有工作都已完成),因此服務停止。

如果你想做更多的工作(即你希望有更多的連接到達),那么你需要像你已經做的那樣在循環中調用run

即使進行了(非常)廣泛的壓力測試,我也無法在 linux 上重現您的問題。

除了某些會話按預期到達“EOF”消息外,即使是硬殺客戶端進程也沒有顯示任何其他影響。

存在可用端口用完的問題,但這主要是因為您在客戶端中重新連接的速度太快了。

開箱即用的思考

  • 會不會是您在沒有同步的情況下使用std::cout和/或MessageBox ²,而 MSVC 的標准庫不能很好地處理它?
  • 會不會是 asio 運行循環引發了catch處理程序未正確捕獲的異常? 我不知道這是否相關,但 MSVC 確實有 SEH(結構化異常)¹
  • 沒有必要在那里保持“運行”的緊密循環。 如果你真的想繼續運行循環,你應該調用io_context.restart(); 介於兩者之間。 我不建議這樣做,因為它會使任何定期關機變得不可能。

如果您有興趣,請對代碼進行一些小的調整。 它添加了一些處理/制作的會話/連接的可視化。 請注意, client大部分未更改,但server有一些更改可能會激發您的想法:

服務器.cpp

#include <iostream>
#include <iomanip>

#include <boost/thread/thread.hpp>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>

namespace ba = boost::asio;
using boost::asio::ip::tcp;
using namespace std::literals;

#define SERVER_PORT 12345
#define DATA_LEN 0x4000

void MessageBox(int, std::string const& caption, std::string const& message, ...) {
    std::cerr << caption << ": " << std::quoted(message) << std::endl;
}

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()
    {
        auto self(shared_from_this());
        ba::spawn(strand_, [this, self](ba::yield_context yield)
        {
            spawn(yield, [this, self](ba::yield_context yield) {
                while (socket_.is_open()) {
                    timer_.expires_from_now(10s); 
                    boost::system::error_code ec;
                    timer_.async_wait(yield[ec]);
                    // timeout triggered, timer was not canceled
                    if (ba::error::operation_aborted != ec) {
                        socket_.close(ec);
                    }
                }
            });

            try
            {
                // recv data
                std::string packet;

                // read data
                ba::async_read(socket_,
                               ba::dynamic_buffer(packet),
                               ba::transfer_exactly(DATA_LEN),
                               yield);

                std::cout << std::unitbuf << ".";
            }
            catch (std::exception const& e) {
                std::cout << "exception: " << std::quoted(e.what()) << std::endl;
            }
            catch (...) {
                std::cout << "exception" << std::endl;
            }

            boost::system::error_code ec;
            timer_.cancel(ec);
            socket_.close(ec);
        });

    }
};

struct my_server {  
    void start() {
        ba::io_context io_context;
        auto worker = ba::make_work_guard(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();
                } 
            }
        });

        // Run io_context on All CPUs
        auto thread_count = std::thread::hardware_concurrency();
        boost::thread_group tgroup;
        for (auto i = 0u; i < thread_count; ++i) 
            tgroup.create_thread([&] {
                for (;;) {
                    try { 
                        io_context.run(); 
                        break;
                    }
                    catch(const std::exception& e) { 
                        MessageBox(0, "This never popup", e.what(), 0); 
                    }
                    catch(const boost::exception& e) { 
                        MessageBox(0, "This never popup", boost::diagnostic_information(e).data(), 0); 
                    }
                    catch(...) { MessageBox(0, "This never popup", "", 0); }
                }

                std::cout << "stopped: " << io_context.stopped() << std::endl;
            });
        tgroup.join_all();
    }
};  

int main() {
    my_server svr;
    svr.start();
}

客戶端.cpp

#include <iostream>
#include <random>
#include <thread>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

using boost::asio::ip::tcp;
namespace ba=boost::asio;

#define SERVER "127.0.0.1"
#define PORT "12345"

int main() {
    ba::io_context io_context;

    static std::string const data_0x4000(0x4000, 'a');

    boost::thread_group tgroup;
    for (auto i = 0; i < 1000; ++i) 
        tgroup.create_thread([&] {
            for(;;) {

                try {
                    tcp::socket s(io_context);

                    tcp::resolver resolver(io_context);
                    ba::connect(s, resolver.resolve(SERVER, PORT));
                    s.set_option(ba::socket_base::reuse_address(true));

                    ba::write(s, ba::buffer(data_0x4000));
                } catch (std::exception const& e) {
                    std::cout << " exception: " << e.what() << std::endl;
                } catch (...) {
                    std::cout << "unknown exception" << std::endl;
                }
                std::cout << std::unitbuf << ".";
            }
        });

    tgroup.join_all();
}

¹ 參見例如https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=vs-2019#remarks

² 也許MessageBox只允許來自“UI”線程。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM