boost TCP 服务器在 Linux 下无法正确接受连接

boost TCP server does not correctly accept connections under Linux

提问人:jpo38 提问时间:11/7/2023 最后编辑:jpo38 更新时间:11/7/2023 访问量:57

问:

我正在开发客户端/服务器 TCP 应用程序。它在 Windows 下运行良好,但我在 Linux 下遇到了问题:当与客户端建立新连接时,服务器不会收到通知(很少,但大多数情况下不会)。

我能够在一个非常简单的程序中隔离问题。该程序创建并运行一个服务器(有自己的)接受新连接,然后从客户端建立连接(也使用自己的连接,就像它在单独的应用程序中一样)。boost::asio::io_serviceboost::asio::io_service

代码如下:

#include <iostream>

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

#include <assert.h>

using boost::asio::ip::tcp;

std::shared_ptr<tcp::socket> m_nextConnection;
std::atomic_bool m_continueServerThread = true;

void server_thread_func(boost::asio::io_service* service)
{
    // http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.effect_of_exceptions_thrown_from_handlers
    while (m_continueServerThread) {
        try {
            service->run();
            // don't break, keep looping, else we may exit before a connection is actually established
            //break; // exited normally
        }
        catch (std::exception const& e) {
            std::cerr << "[eventloop] error: " << e.what();
        }
        catch (...) {
            std::cerr << "[eventloop] unexpected error";
        }
    }

}

void on_accept_connection(std::error_code ec)
{
    if (!ec)
    {
        std::cout << "SERVER ACCEPTED CONNECTION" << std::endl;
    }
    else
    {
        std::cout << "Reader connection error " << ec << " (" << ec.message() << ")" << std::endl;
    }
}

void do_accept_connection(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::io_service& service)
{
    m_nextConnection = std::shared_ptr<tcp::socket>(new boost::asio::ip::tcp::socket(service));

    acceptor.async_accept(
            *m_nextConnection, boost::bind(on_accept_connection, boost::asio::placeholders::error));
}

int main( int argc, char* argv[] )
{
    auto endpoint = boost::asio::ip::tcp::endpoint{ boost::asio::ip::address::from_string("127.0.0.1"), 1900 };

    try
    {
        // start server:
        boost::asio::io_service IOServiceServer;
        
        boost::thread serverThread( boost::bind(server_thread_func,&IOServiceServer) );

        boost::this_thread::sleep(boost::posix_time::milliseconds(500));

        boost::asio::ip::tcp::acceptor m_acceptor{ IOServiceServer, endpoint };

        // dunno if this is needed or not here
        //m_acceptor.set_option(tcp::acceptor::reuse_address(true));
        do_accept_connection(m_acceptor, IOServiceServer);

        boost::this_thread::sleep(boost::posix_time::milliseconds(500));

        // start client:
        boost::asio::io_service IOServiceClient;
        tcp::socket socket(IOServiceClient);

        std::cout << "Connecting socket..." << std::endl;        
        socket.connect(endpoint);
        std::cout << "Connected socket" << std::endl;

        boost::this_thread::sleep(boost::posix_time::milliseconds(500));

        // stop/close client:
        IOServiceClient.stop();
        socket.close();
        
        // stop server:
        IOServiceServer.stop();
        m_continueServerThread = false;
        serverThread.join();

        return 0;
    }
    catch (std::exception const& e) {
        std::cerr << "error: " << e.what();
    }
    catch (...) {
        std::cerr << "unexpected error";
    }

    return 1;
}

在 Windows 下运行,如下所示:

Connecting socket...
Connected socket
SERVER ACCEPTED CONNECTION

在 Linux 下运行,这仅显示:

Connecting socket...
Connected socket

如您所见,未被调用,因此服务器不会收到已建立新连接的通知。on_accept_connection

我做错了什么吗?


编辑:也尝试过没有m_nextConnection:

void on_accept_connection(std::error_code ec, tcp::socket socket)
{
    if (!ec)
    {
        std::cout << "SERVER ACCEPTED CONNECTION" << std::endl;
    }
    else
    {
        std::cout << "Reader connection error " << ec << " (" << ec.message() << ")" << std::endl;
    }
}

void do_accept_connection(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::io_service& service)
{
    acceptor.async_accept(service, [&](const std::error_code& ec, tcp::socket newSocket) {
        on_accept_connection(ec, std::move(newSocket));
        });
}

不能解决问题!

C++ boost-asio tcpclient tcpserver

评论

0赞 Jakob Stark 11/7/2023
调用 时,将引用传递给套接字,该套接字可能会在调用完成之前被销毁。我不知道这是否是您问题的原因,但这绝对是一个严重的错误。也许您更愿意使用 that 将连接的套接字返回到完成中。acceptor.async_accepton_accept_connectionasync_accept
0赞 jpo38 11/7/2023
@JakobStark:这是一个共享指针,所以没有理由销毁它。
0赞 Jakob Stark 11/7/2023
一旦指针的最后一个副本超出范围,A 就会销毁基础对象。您将传递给 .这会将基础对象的原始引用传递给函数。指针(以及套接字对象,因为智能指针尚未被复制)在返回时被销毁。shared_ptr*m_nextConnectionasync_acceptdo_accept_connection
0赞 jpo38 11/7/2023
@JakobStark:否,它被声明为全局,因此永远不会被删除。但无论如何,我都想尝试创建套接字本身,也许我正在使用的套接字有一个错误。你有什么例子吗?我不知道如何正确声明和绑定它......m_nextConnectionasync_accepton_accept_connection
0赞 jpo38 11/7/2023
@JakobStark:是从ChatGPT那里得到的......没有区别,bug 在 Linux 下仍然存在。

答:

2赞 sehe 11/7/2023 #1

服务器线程已从此处修改。评论很能说明问题:

// don't break, keep looping, else we may exit before a connection is actually established

事实并非如此。在建立连接之前看到它退出的原因是 IO 服务耗尽了工作。由于你让服务耗尽了工作,所以循环是没有意义的,除非你真的使用了 restart():

对 、 或 的后续调用将立即返回,除非之前调用了run()run_one()poll()poll_one()restart()

因此,真正的解决方法是保持循环的预期,但在开始之前放置工作,例如更改

std::thread serverThread(server_thread_func, std::ref(ioc));
do_accept_connection(m_acceptor, ioc);

do_accept_connection(m_acceptor, ioc);
std::thread serverThread(server_thread_func, std::ref(ioc));

其他问题

全局连接变量是一个问题,尤其是当您接受 1 个以上的连接时。通过不使用全局来修复它,例如:

void on_accept_connection(std::error_code ec, std::shared_ptr<tcp::socket> s) {
    if (!ec)
        std::cout << "SERVER ACCEPTED CONNECTION from " << s->remote_endpoint() << std::endl;
    else
        std::cout << "Reader connection error " << ec << " (" << ec.message() << ")" << std::endl;
}

void do_accept_connection(tcp::acceptor& acceptor, asio::io_context& service) {
    auto s = std::make_shared<tcp::socket>(service);
    acceptor.async_accept(*s, bind(on_accept_connection, _1, s));
}

请注意我们如何将共享指针绑定到完成处理程序中,以使其保持活动状态。

这已经有效了:https://coliru.stacked-crooked.com/a/df135c6936c5e37e

Connecting socket...
Connected socket
SERVER ACCEPTED CONNECTION from 127.0.0.1:45958
[eventloop] exit
Closing socket

修复一切

但是,您应该采用移动套接字的重载,使用内置线程池,不使用第二个 io 服务,使用而不是已弃用的和其他东西,例如不使用不必要的原始指针(或完全绑定)和传递执行上下文而不是执行器等: Live On Coliruio_contextio_serviceboost::bindboost::thread

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

namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::asio::ip::tcp;

void on_accept_connection(std::error_code ec, tcp::socket s) {
    if (!ec)
        std::cout << "Accepted from " << s.remote_endpoint() << std::endl;
    else
        std::cout << std::endl;
}

void accept_loop(tcp::acceptor& acc) {
    acc.async_accept(make_strand(acc.get_executor()), on_accept_connection);
}

int main() {
    tcp::endpoint ep{{}, 1900};
    using std::this_thread::sleep_for;

    asio::thread_pool ioc(1);

    tcp::acceptor listener{ioc, ep};
    accept_loop(listener);

    sleep_for(500ms);

    {
        // start client:
        tcp::socket socket(ioc);

        std::cout << "Connecting socket..." << std::endl;
        socket.connect(ep);
        std::cout << "Connected socket" << std::endl;

        sleep_for(500ms);
        std::cout << "Closing socket" << std::endl;
    }

    ioc.join();
}

印刷

Connecting socket...
Connected socket
Accepted from 127.0.0.1:47014
Closing socket

奖金

为了使它完整,实际上make loop:accept_loop

在 Coliru 上直播

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

namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::system::error_code;
using boost::asio::ip::tcp;

struct session : std::enable_shared_from_this<session> {
    session(tcp::socket s) : s_(std::move(s)) {}

    void run() {
        std::cout << "Session from " << s_.remote_endpoint() << std::endl;
        async_write(s_,
                    asio::buffer(message_), //
                    consign(asio::detached, shared_from_this()));
    }

  private:
    tcp::socket s_;
    std::string message_ = "Hello from server\n";
};

struct listener {
    listener(tcp::endpoint ep) : acc_(ioc_, ep) { accept_loop(); }

  private:
    asio::thread_pool ioc_{1};
    tcp::acceptor     acc_;

    void accept_loop() {
        acc_.async_accept(make_strand(acc_.get_executor()), [this](error_code ec, tcp::socket s) {
            if (!ec) {
                std::make_shared<session>(std::move(s))->run();
                accept_loop();
            } else
                std::cout << std::endl;
        });
    }
};

int main() {
    tcp::endpoint ep{{}, 1900};
    listener      server(ep);

    std::this_thread::sleep_for(50ms);

    for (auto i = 0; i < 10; ++i) {
        tcp::iostream is(ep);
        std::cout << "Connected: " << is.rdbuf() << std::endl;
    }
}

印刷,例如

Connected: Session from 127.0.0.1:48586
Hello from server

Connected: Session from 127.0.0.1:48588
Hello from server

Connected: Session from 127.0.0.1:48590
Hello from server

Connected: Session from 127.0.0.1:48592
Hello from server

Connected: Session from 127.0.0.1:48594
Hello from server

Connected: Session from 127.0.0.1:48596
Hello from server

Connected: Session from 127.0.0.1:48598
Hello from server

Connected: Session from 127.0.0.1:48600
Hello from server

Connected: Session from 127.0.0.1:48602
Hello from server

Connected: Session from 127.0.0.1:48604
Hello from server

评论

0赞 sehe 11/7/2023
由于您的客户端似乎更喜欢同步代码,因此您是否希望有一个同步会话处理程序: coliru.stacked-crooked.com/a/dbda43d54cc16131 实时交互式演示 i.imgur.com/k78fy2X.mp4
0赞 jpo38 11/7/2023
非常感谢。只是在修复问题之前添加。我现在将使用此修复程序,并考虑稍后进行其他更改以增强代码。restart()run()server_thread_func