简体   繁体   English

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

[英]Simple client/server using C++/boost socket works under Windows but fails under Linux

I'm trying to write a very simple client/server app with boost::socket .我正在尝试使用boost::socket编写一个非常简单的客户端/服务器应用程序。 I need a server to run and a single client to connect, send data, disconnect and possibly reconnect later and repeat.我需要一个服务器来运行和一个客户端来连接、发送数据、断开连接并可能稍后重新连接并重复。

The code reduced to the minimum is here:减少到最少的代码在这里:

Server app:服务器应用程序:

#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;
}

Client app:客户端应用程序:

#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;
}

Note that I use non-blocking async_accept to be able to cleanly stop the server when Enter is pressed.请注意,我使用非阻塞 async_accept 以便能够在按下 Enter 时干净地停止服务器。

Under Windows, it works perfectly fine, I run the server, it outputs:在 Windows 下,它工作得很好,我运行服务器,它输出:

Starting async_accept
Press Enter to quit

For each client app run, it outpts:对于每个客户端应用程序运行,它会输出:

Starting client
Client conected

and server app outputs:和服务器应用程序输出:

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

Then when I press Enter in server app console, it outputs Exiting run thread and cleanly stops.然后,当我在服务器应用程序控制台中按 Enter 时,它会输出Exiting run thread并干净地停止。

Now, when I compile this same code under Linux, the client outputs the same as under Windows, but nothing happens on the server side...现在,当我在 Linux 下编译相同的代码时,客户端输出与 Windows 下相同,但服务器端没有任何反应......

Any idea what's wrong?知道出了什么问题吗?

There are many questionable elements.有很多值得怀疑的因素。

  1. There is a classical data race on m_bContinueReading . m_bContinueReading上存在经典数据竞争。 You write from another thread, but the other thread may never see the change because of the data race.您从另一个线程写入,但由于数据竞争,另一个线程可能永远看不到更改。

  2. The second race condition is likely your problem:第二个竞争条件可能是你的问题:

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

    Here the run thread may complete before you ever post the first work.在这里, run线程可能会在您发布第一个作品之前完成。 You can use a work-guard to prevent this.您可以使用工作防护来防止这种情况。 In your specific code you would already fix it by reordering the lines:在您的特定代码中,您已经通过重新排序行来修复它:

     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. There is a lot of raw pointer handling and new/delete going on, which merely invites errors.有很多原始指针处理和新建/删除正在进行,这只会引发错误。

  4. You use the buffer assuming that it is NUL-terminated.假定缓冲区以 NUL 结尾,您就可以使用该buffer This is especially unwarranted because you use read_some which will read partial messages as they arrive on the wire.这是特别没有根据的,因为您使用read_some它将在消息到达线路时读取部分消息。

  5. You use a static buffer while the code may have different instances of the class. This is very false optimization.您使用static缓冲区,而代码可能具有 class 的不同实例。这是非常错误的优化。 Instead, prevent all the allocations: Combining with the previous item:相反,阻止所有分配:结合上一项:

     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. You start a new acceptor always, where there is no need: a single acceptor can accept as many connections as you wish.您总是在不需要的地方启动一个新的接受器:一个接受器可以接受任意数量的连接。 In fact, the method shown runs into the problems事实上,显示的方法遇到了问题

    • that lingering connections can prevent the new acceptor from binding to the same port.挥之不去的连接可以阻止新的接受者绑定到同一个端口。 You could also alleviate that with可以用

      m_Acceptor.set_option(tcp::acceptor::reuse_address(true));
    • the destroyed acceptor may have backlogged connections, which are discarded被破坏的接受者可能有积压的连接,这些连接被丢弃

    Typically you want to support concurrent connection, so you can split of a "readSession" and immediately accept the next connection.通常您希望支持并发连接,因此您可以拆分一个“readSession”并立即接受下一个连接。 Now, strangely your code seems to expect clients to be connected until the server is prompted to shutdown (from the console) but after that you somehow start listening to new connections (even though you know the service will be stopping, and m_bContinueReading will remain false ).现在,奇怪的是,您的代码似乎希望在提示服务器关闭(从控制台)之前连接客户端,在那之后您以某种方式开始侦听新连接(即使您知道服务将停止,并且m_bContinueReading将保持为false ).

    In the grand scheme of things, you don't want to destroy the acceptor unless something invalidated it.在宏伟的计划中,除非有什么东西使接受者无效,否则您不想销毁接受者。 In practice this is rare (eg on Linux the acceptor will happily survive disabling/re-enabling the.network adaptor).实际上,这种情况很少见(例如,在 Linux 上,接受器将很高兴地在禁用/重新启用 the.network 适配器后幸存下来)。

  7. you have spurious explicit template arguments ( bind<void> ).你有虚假的显式模板 arguments ( bind<void> )。 This is an anti-pattern and may lead to subtle problems这是一种反模式,可能会导致一些微妙的问题

  8. similar with the buffer (just say asio::buffer(buffer) and no longer have correctness concerns. In fact, don't use C-style arrays:与缓冲区类似(只是说asio::buffer(buffer)并且不再有正确性问题。事实上,不要使用 C 风格 arrays:

     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. Instead of running a manual run_one() loop (where you forget to handle exceptions ), consider "just" letting the service run() .与其运行手动run_one()循环(您忘记处理异常),不如考虑“只是”让服务run() Then you can .cancel() the acceptor to let the service run out of work.然后你可以.cancel()接受器让服务停止工作。

    In fact, this subtlety isn't required in your code, since your code already forces "ungraceful" shutdown anyways:事实上,您的代码中不需要这种微妙之处,因为您的代码无论如何已经强制“不正常”关闭:

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

    More gentle would be eg更温和的是例如

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

    In which case you can respond to the completion error_code == error::operation_aborted to stop the session/accept loop.在这种情况下,您可以响应完成error_code == error::operation_aborted以停止会话/接受循环。

    Technically, you may be able to do away with the boolean flag altogether.从技术上讲,您可以完全取消 boolean 标志。 I keep it because it allows us to handle multiple session-per-thread in "fire-and-forget" manner.我保留它是因为它允许我们以“即发即弃”的方式处理每个线程的多个会话。

  10. In the client you have many of the same problems, and also a gotcha where you only look at the first resolver result (assuming there was one), ignoring the rest. You can use asio::connect instead of m_Socket.connect to try all resolved entries在客户端你有很多相同的问题,还有一个陷阱,你只看第一个解析器结果(假设有一个),忽略 rest。你可以使用asio::connect而不是m_Socket.connect来尝试所有已解决条目

Addressing the majority of these issues, simplifying the code:解决其中大部分问题,简化代码:

Live On Coliru生活在 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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 提升asio async_accept在Windows下运行但在FreeBSD下失败。怎么了? - Boost asio async_accept works under Windows but fails under FreeBSD. What's wrong? C++ boost 1.72 重新连接 tcp::socket 在 linux 上抛出 WSAEADDRINUSE 异常,但适用于 Windows - C++ boost 1.72 reconnect on tcp::socket throwing an exception with WSAEADDRINUSE on linux, but works on Windows ROS使用Linux下的C ++使用boost :: filesystem库链接错误 - ROS Linking errors using boost::filesystem library using C++ under linux 套接字监听并没有在Linux下的C ++中取消绑定 - Socket listen doesn't unbind in C++ under linux C ++下的套接字问题 - Socket problem under C++ 在Windows下使用C ++计算处理器 - Count Processors using C++ under Windows 在Windows 7下从C#调用C ++ DLL可以,但在Windows 10下失败 - Calling C++ DLL from C# is ok under Windows 7 but fails under Windows 10 使用Linux下的MinGW在Windows 7中交叉编译C和C ++应用程序 - Cross-Compiling C and C++ application in Windows 7, using MinGW under linux C ++ Boost.ASIO:使用Windows API将可接受的TCP连接从一个打开的套接字传递到另一个(与Linux API一起使用)吗? - C++ Boost.ASIO: passing accepted TCP connection from one opened socket to another using Windows APIs ( while works with Linux APIs)? linux下C++启动 - C++ start under linux
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM