【问题标题】:C++ boost::asio::ip::tcp::acceptor sometimes doesn't accept connector?C++ boost::asio::ip::tcp::acceptor 有时不接受连接器?
【发布时间】:2020-05-22 14:17:43
【问题描述】:

我目前有一个非常奇怪的问题。有时我的 ::acceptor(使用 async_accept)不接受套接字(没有调用 async_accept 中指定的接受函数)但连接器在连接函数上返回 true(boost::asio:: em> 类)。有时一切正常,但有时我没有接受器套接字....我真的不知道为什么了。我用不同的listen_ports在Win10 64bit下测试了所有东西。
我的服务器结构如下:
io_service:

_io_thread = std::make_unique<std::thread>([this]() {
            while (1)
            {
                try
                {
                    _io_service->run();
                    break;
                }
                catch (const boost::exception& e)
                {
                    spdlog::error("IO_SERVICE_EXCEPTION: ", boost::diagnostic_information(e));
                }
            }
        });

接受者:

void Server::initialize(uint16_t listen_port)
    {
        // at this point the io_thread is running already

        auto endpoint = ip::tcp::endpoint(ip::tcp::v4(), listen_port);

        _acceptor = std::make_unique<boost::asio::ip::tcp::acceptor>(*_io_service, endpoint.protocol());
        _acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(false));
        _acceptor->bind(endpoint);
        _acceptor->listen();

        _listen_port = listen_port;

        __accept();
    }

__accept():

void Server::__accept()
    {
        // create socket for next connection
        _acceptor_socket = std::make_unique<ip::tcp::socket>(*_io_service);

        _acceptor->async_accept(*_acceptor_socket, [this](const boost::system::error_code& err)
            {
                spdlog::info("Accept new socket..."); // this sometimes doesn't get called :(

                if (err.failed())
                {
                    // acceptor closed
                    if (err == boost::asio::error::operation_aborted)
                    {
                        spdlog::info("network server stopped accepting sockets");
                        return;
                    }

                    // unknown error
                    spdlog::error("network server accepting socket failed {} message {} num {}", err.failed(), err.message(), err.value());

                    // accept next connection
                    __accept();
                    return;
                }

                // accept new socket
                _new_connections_mutex.lock();

                auto con = __create_new_connection();
                con->initialize(++_connection_id_counter, std::move(_acceptor_socket));
                con->set_handler(_input_handler);
                con->goto_phase(_initial_phase);
                spdlog::info("new connection from {} with id {}", con->get_host_name(), con->get_unique_id());

                _new_connections[con->get_unique_id()] = std::move(con);

                _new_connections_mutex.unlock();

                // wait for next connection
                __accept();
            }
        );
    }



我的客户端连接器很简单:

        auto socket = std::make_unique<boost::asio::ip::tcp::socket>(*_io_service);

        spdlog::info("try to connect to {}:{}", endpoint.address().to_string(), endpoint.port());
        try
        {
            socket->connect(endpoint);
        }
        catch (const boost::system::system_error & e)
        {
            spdlog::error("cannot connect to {}:{}", endpoint.address().to_string(), endpoint.port());
            return false;
        }

        // this succeeds everytime...
        [...]

所以...不应该每次连接器套接字成功连接接受器套接字时也创建吗?我希望有人知道这里出了什么问题:/

【问题讨论】:

  • 等等,你为什么把reuse_address设置为false?我不完全确定它实际上做了什么(它总是一个隐藏的谜团),但它可能会导致它在短时间内无法从同一地址重新打开套接字。
  • __ 开头的标识符在所有范围中都保留;使用它们在技术上是 UB stackoverflow.com/questions/228783/…
  • 代码中有几条日志语句。当您遇到有问题的行为时,您会看到哪些?
  • 如果互斥锁 .lock().unlock() 之间发生异常,您将出现软锁或死锁。很少需要使用这些,而是​​使用异常安全的std::lock_guardstd::unique_lockstd::shared_lock
  • @sehe:到目前为止,我将 __ 用于私有和受保护的成员函数,_ 用于成员变量。你有什么建议吗?我阅读了 glibc 页面,是的 __ 是为全局 C 函数保留的...... std::lock_guard 的要点实际上是一个好点。我会改变的。连接器套接字(“尝试连接到...”)已成功创建(实际上在那之后是另一个验证该内容的日志)但日志“接受新套接字...”有时会出现,有时不会出现(如果它没有'在它可以接受套接字之前我必须重新启动服务器......)

标签: c++ sockets network-programming boost-asio


【解决方案1】:

好的,我找到了答案.... io_service::run 函数没有按我的预期工作。我认为如果不调用 io_service::stop 它将永远阻塞。好像那是错误的。因为我在一个 ::run 调用后中断了我的 while 循环,然后线程完成了 io_service 有时在连接器套接字连接时不再运行。 已更改 io_service::run -> run_one() 并删除了中断。现在将添加一个循环取消变量,因此它不是无限循环...
感谢您的回答!

【讨论】:

  • 啊,您在线程上启动io_service 的事实可能是一个微妙的问题。如果您实际发布工作之前启动它(例如使用 async_* 调用),那么它将返回,因为“没有更多工作”。这意味着在您发布第一个工作之前,后台线程已经完成。发布的代码没有显示线程的创建和对Server::initialize的调用之间的关系。
  • 避免这种竞争条件的常用方法是使用工作守卫(io_service::work or executor_work_guard depending on version used)。如果可以,请务必使用方便的make_work_guard helper
  • 是的,我认为订单不相关。我现在在启动 io_service 线程之前调用了 async_accept。如果 async_accept 每次都在调用时启动另一个 async_accept 请求,那么应该没有任何问题,我不需要 io_service::work 对象,对吗?
  • 确实如此。 [填充]
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-07
  • 1970-01-01
  • 2018-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多