c++ - 使用 C++/boost 套接字的简单客户端/服务器在 Windows 下工作,但在 Linux 下失败

标签 c++ sockets boost boost-asio

我正在尝试使用 boost::socket 编写一个非常简单的客户端/服务器应用程序。我需要一个服务器来运行,一个客户端来连接、发送数据、断开连接,并可能稍后重新连接并重复。

减少到最少的代码在这里:

服务器应用程序:

#include <iostream>

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

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

class TheServer
{
public:
    TheServer(int port) : m_port(port)
    {
        m_pIOService = new boost::asio::io_service;

        m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this));

        listenForNewConnection();
    }
    ~TheServer()
    {
        m_bContinueReading = false;

        m_pIOService->stop();
        m_pThread->join();

        delete m_pThread;
        delete m_pSocket;
        delete m_pAcceptor;
        delete m_pIOService;
    }

    void listenForNewConnection()
    {
        if (m_pSocket)
            delete m_pSocket;
        if (m_pAcceptor)
            delete m_pAcceptor;
        
        // start new acceptor operation
        m_pSocket = new tcp::socket(*m_pIOService);
        m_pAcceptor = new tcp::acceptor(*m_pIOService, tcp::endpoint(tcp::v4(), m_port));

        std::cout << "Starting async_accept" << std::endl;

        m_pAcceptor->async_accept(*m_pSocket,
            boost::bind<void>(&TheServer::readSession, this, boost::asio::placeholders::error));
    }

    void readSession(boost::system::error_code error)
    {
        if (!error)
        {
            std::cout << "Connection established" << std::endl;
            while ( m_bContinueReading )
            {
                static unsigned char buffer[1000];
                boost::system::error_code error;
                size_t length = m_pSocket->read_some(boost::asio::buffer(&buffer, 1000), error);
                if (!error && length != 0)
                {
                    std::cout << "Received " << buffer << std::endl;
                }
                else
                {
                    std::cout << "Received error, connection likely closed by peer" << std::endl;
                    break;
                }
            }
            std::cout << "Connection closed" << std::endl;
            listenForNewConnection();
        }
        else
        {
            std::cout << "Connection error" << std::endl;
        }

        std::cout << "Ending readSession" << std::endl;
    }

    void run()
    {
        while (m_bContinueReading)
            m_pIOService->run_one();
        std::cout << "Exiting run thread" << std::endl;
    }

    bool m_bContinueReading = true;
    boost::asio::io_service* m_pIOService = NULL;
    tcp::socket* m_pSocket = NULL;
    tcp::acceptor* m_pAcceptor = NULL;
    boost::thread* m_pThread = NULL;
    int m_port;
};

int main(int argc, char* argv[])
{
    TheServer* server = new TheServer(1900);

    std::cout << "Press Enter to quit" << std::endl;
    std::string sGot;
    getline(std::cin, sGot);

    delete server;

    return 0;
}

客户端应用程序:

#include <iostream>

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

int main(int argc, char* argv[])
{
    std::cout << std::endl;
    
    std::cout << "Starting client" << std::endl;

    using boost::asio::ip::tcp;
    boost::asio::io_service* m_pIOService = NULL;
    tcp::socket* m_pSocket = NULL;

    try
    {
        m_pIOService = new boost::asio::io_service;

        std::stringstream sPort;
        sPort << 1900;

        tcp::resolver resolver(*m_pIOService);
        tcp::resolver::query query(tcp::v4(), "localhost", sPort.str());
        tcp::resolver::iterator iterator = resolver.resolve(query);

        m_pSocket = new tcp::socket(*m_pIOService);
        m_pSocket->connect(*iterator);

        std::cout << "Client conected" << std::endl;

        std::string hello = "Hello World";
        boost::asio::write( *m_pSocket, boost::asio::buffer(hello.data(), hello.size()) );
        boost::this_thread::sleep(boost::posix_time::milliseconds(100));
        hello += "(2)";
        boost::asio::write(*m_pSocket, boost::asio::buffer(hello.data(), hello.size()));

    }
    catch (std::exception& e)
    {
        delete m_pSocket;
        m_pSocket = NULL;
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

请注意,我使用非阻塞 async_accept 以便在按下 Enter 时能够干净地停止服务器。

在Windows下,它工作得很好,我运行服务器,它输出:

Starting async_accept
Press Enter to quit

对于每个客户端应用程序运行,它会输出:

Starting client
Client conected

和服务器应用程序输出:

Connection established
Received Hello World
Received Hello World(2)
Received error, connection likely closed by peer
Connection closed
Starting async_accept
Ending readSession

然后,当我在服务器应用程序控制台中按 Enter 时,它会输出 Exiting run thread 并干净地停止。

现在,当我在 Linux 下编译相同的代码时,客户端输出与 Windows 下相同,但服务器端没有任何反应...

知道出了什么问题吗?

最佳答案

有很多可疑的元素。

  1. m_bContinueReading 上有一场经典的数据竞赛。 。您从另一个线程写入,但由于数据竞争,另一个线程可能永远不会看到更改。

  2. 第二个竞争条件可能是您的问题:

    m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this));
    listenForNewConnection();
    

    这里是run线程可能会在您发布第一篇作品之前完成。您可以使用工作防护装置来防止这种情况发生。在您的特定代码中,您已经通过重新排序行来修复它:

    listenForNewConnection();
    m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this));
    

    I would not do this, because I would not have those statements in my constructor body. See below for the work guard solution

  3. 存在大量原始指针处理和新建/删除操作,这只会引发错误。

  4. 您使用 buffer假设它以 NUL 结尾。这是特别没有根据的,因为您使用 read_some当消息到达网络时,它将读取部分消息。

  5. 您使用 static buffer 而代码可能有不同的类实例。这是非常错误的优化。相反,阻止所有分配!结合上一条:

    char buffer[1000];
    while (m_bContinueReading) {
        size_t length = m_Socket.read_some(asio::buffer(&buffer, 1000), ec);
    
        std::cout << "Received " << length << " (" << quoted(std::string(buffer, length)) << "), "
                  << ec.message() << std::endl;
        if (ec.failed())
            break;
    }
    
  6. 您总是在不需要的情况下启动一个新的接受器:单个接受器可以接受任意数量的连接。事实上,所示的方法遇到了问题

    • 延迟连接可能会阻止新接受器绑定(bind)到同一端口。您还可以通过以下方式缓解这种情况:

      m_Acceptor.set_option(tcp::acceptor::reuse_address(true));
      
    • 被破坏的接受器可能有积压的连接,这些连接将被丢弃

    通常您希望支持并发连接,因此您可以拆分“readSession”并立即接受下一个连接。现在,奇怪的是,您的代码似乎期望客户端连接,直到服务器被提示关闭(从控制台)但是之后您以某种方式开始监听新连接(即使您知道该服务将被关闭)停止,m_bContinueReading 将保留 false )。

    从长远来看,你不想销毁接受器,除非有什么东西使它无效。实际上,这种情况很少见(例如,在 Linux 上,接受器将很高兴地在禁用/重新启用网络适配器的情况下幸存下来)。

  7. 您有虚假的显式模板参数 ( bind<void> )。这是一种反模式,可能会导致微妙的问题

  8. 与缓冲区类似(只需说 asio::buffer(buffer) ,不再有正确性问题。事实上,不要使用 C 样式数组:

    std::array<char, 1000> buffer;
    size_t n = m_Socket.read_some(asio::buffer(buffer), ec);
    
    std::cout << "Received " << n << " " << quoted(std::string(buffer.data(), n))
              << " (" << ec.message() << ")" << std::endl;
    
  9. 而不是运行手册 run_one()循环(其中 forget to handle exceptions ),考虑“只是”让服务 run() 。然后你就可以.cancel()接受者让服务停止工作。

    事实上,您的代码中不需要这种微妙之处,因为您的代码无论如何已经强制“不正常”关闭:

    m_IOService.stop(); // nuclear option
    m_Thread.join();
    

    例如更温和

    m_Acceptor.cancel();
    m_Socket.cancel();
    m_Thread.join();
    

    在这种情况下,您可以回复完成 error_code == error::operation_aborted停止 session /接受循环。

    Technically, you may be able to do away with the boolean flag altogether. I keep it because it allows us to handle multiple session-per-thread in "fire-and-forget" manner.

  10. 在客户端中,您会遇到许多相同的问题,而且还有一个问题 您只查看第一个解析器结果(假设有一个), 忽略其余的。您可以使用asio::connect代替 m_Socket.connect尝试所有已解决的条目

解决大部分问题,简化代码:

<强> Live On Coliru

#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/optional.hpp>
#include <iomanip>
#include <iostream>

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

class TheServer {
  public:
    TheServer(int port) : m_port(port) {
        m_Acceptor.set_option(tcp::acceptor::reuse_address(true));
        do_accept();
    }

    ~TheServer() {
        m_shutdownRequested = true;
        m_Work.reset(); // release the work-guard
        m_Acceptor.cancel();
        m_Thread.join();
    }

  private:
    void do_accept() {
        std::cout << "Starting async_accept" << std::endl;

        m_Acceptor.async_accept( //
            m_Socket, boost::bind(&TheServer::on_accept, this, asio::placeholders::error));
    }

    void on_accept(error_code ec) {
        if (!ec) {
            std::cout << "Connection established " << m_Socket.remote_endpoint() << std::endl;

            // leave session running in the background:
            std::thread(&TheServer::read_session_thread, this, std::move(m_Socket)).detach();

            do_accept(); // and immediately accept new connection(s)
        } else {
            std::cout << "Connection error (" << ec.message() << ")" << std::endl;
            std::cout << "Ending readSession" << std::endl;
        }
    }

    void read_session_thread(tcp::socket sock) {
        std::array<char, 1000> buffer;

        for (error_code ec;;) {
            size_t n = sock.read_some(asio::buffer(buffer), ec);
            std::cout << "Received " << n << " " << quoted(std::string(buffer.data(), n)) << " ("
                      << ec.message() << ")" << std::endl;

            if (ec.failed() || m_shutdownRequested)
                break;
        }

        std::cout << "Connection closed" << std::endl;
    }

    void thread_func() {
        // 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
        for (;;) {
            try {
                m_IOService.run();
                break; // exited normally
            } catch (std::exception const& e) {
                std::cerr << "[eventloop] error: " << e.what();
            } catch (...) {
                std::cerr << "[eventloop] unexpected error";
            }
        }

        std::cout << "Exiting service thread" << std::endl;
    }

    std::atomic_bool m_shutdownRequested{false};

    uint16_t                                m_port;
    asio::io_service                        m_IOService;
    boost::optional<asio::io_service::work> m_Work{m_IOService};
    tcp::socket                             m_Socket{m_IOService};
    tcp::acceptor                           m_Acceptor{m_IOService, tcp::endpoint{tcp::v4(), m_port}};
    std::thread                             m_Thread{boost::bind(&TheServer::thread_func, this)};
};

constexpr uint16_t s_port = 1900;

void run_server() {
    TheServer server(s_port);

    std::cout << "Press Enter to quit" << std::endl;
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}

void run_client() {
    std::cout << std::endl;

    std::cout << "Starting client" << std::endl;

    using asio::ip::tcp;

    try {
        asio::io_service m_IOService;

        tcp::resolver resolver(m_IOService);
        auto iterator = resolver.resolve("localhost", std::to_string(s_port));

        tcp::socket m_Socket(m_IOService);
        connect(m_Socket, iterator);

        std::cout << "Client connected" << std::endl;

        std::string hello = "Hello World";
        write(m_Socket, asio::buffer(hello));

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

        hello += "(2)";
        write(m_Socket, asio::buffer(hello));
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }
}

int main(int argc, char**) {
    if (argc>1)
        run_server();
    else
        run_client();
}

关于c++ - 使用 C++/boost 套接字的简单客户端/服务器在 Windows 下工作,但在 Linux 下失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75211089/

相关文章:

c++ - 为什么我的 C/C++ header 解析器不工作?

c++ - 从 GetSystemTimePreciseAsFileTime() 到本地时间的可靠快速方法

java - 脚轮和 socket

python - SWIG 和 Boost::变体

Python 2.6 聊天循环问题。不能同时接收和发送

c - 使用 CURL 测试时,我的 C 程序在接受连接时被卡住

c++ - 为什么将引用计数器值读取为对 volatile 常量的引用?

c++ - new A[0] -- 合法,但有什么用?它实际上做了什么?

c++ - 如何使用互斥使两个线程严格交替?

c++ - std::move 和 static_cast<T&&> 不同的结果