避免全局变量制作 boost asio 套接字包装器

Avoiding global variables making boost asio socket wrapper

我有这个 socket-tcp.h 使用 boost asio 的套接字实现包装器:

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

static const std::string PORT = "65432";  
static const std::string HOST = "127.0.0.1";  

struct Client
{
    boost::asio::io_service& io_service;
    boost::asio::ip::tcp::socket socket;
    
    Client(boost::asio::io_service& svc, std::string const& host, std::string const& port) 
        : io_service(svc), socket(io_service) 
    {
        boost::asio::ip::tcp::resolver resolver(io_service);
        boost::asio::ip::tcp::resolver::iterator endpoint = resolver.resolve(boost::asio::ip::tcp::resolver::query(host, port));
        boost::asio::connect(this->socket, endpoint);
    };

    void send(std::string const& message) {
        socket.send(boost::asio::buffer(message));
    }
};


boost::asio::io_service svc;
Client client(svc, HOST, PORT);

class TcpSocket
{

    private:
        std::string HOST; 
        std::string PORT;
        
    public:
        
        TcpSocket (std::string const& host, std::string const& port): HOST{ host },PORT{ port }
        {
        }

        void send(std::string const& message)
        {
            boost::thread t(client_thread,boost::ref(message),boost::ref(HOST),boost::ref(PORT));
            t.join();
        }

        static void client_thread(std::string const& message,std::string const& host,std::string const& port) 
        {
            client.send(message); 
        }
            
};

所以我的主文件看起来像:

#include "socket-tcp.h"

int main()
{
    TcpSocket socket(PORT,HOST);
    std::string message = "socket implemented using global variables";
    while (true)
    {
        socket.send(message);
    }
}

我正在尝试找出一种无需全局变量即可实现的方法

boost::asio::io_service svc;
Client client(svc, HOST, PORT);

这样 TcpSocket 就像:

class TcpSocket
{

    //Object* myObject; // Will not try to call the constructor or do any initializing
    //myObject = new Object(...);  // Initialised now
    private:
        std::string HOST; 
        std::string PORT;
        boost::asio::io_service svc;
        
    public:
        
        TcpSocket (std::string const& host, std::string const& port): HOST{ host },PORT{ port }
        {
            Client client(svc, HOST, PORT);
        }

        void send(std::string const& message)
        {
            boost::thread t(client_thread,boost::ref(message),boost::ref(HOST),boost::ref(PORT));
            t.join();
        }

        static void client_thread(std::string const& message,std::string const& host,std::string const& port) 
        {
            client.send(message); 
        }
            
};

但我遇到了运行时错误:

terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::system::system_error> >'
  what():  resolve: Service not found

有没有办法避免使用这些全局变量(对象),并始终保持同一个套接字打开,而无需在每次收到新消息时关闭并再次打开它?

我接受这个包装器的更好实现或建议,但目标是使 main 尽可能简单明了。

“找不到服务”仅表示该端口不是有效服务。那是因为你交换了主机和端口参数。

但这是你最不担心的事情。


您对对象的生命周期玩得很随意。例如,您将 message 一直传递给另一个线程 && 通过引用** (std::string const&),但引用的对象存在于堆栈中(作为函数参数),因此调用 Undefined Behaviour .

此外,不清楚线程(或 client_thread)应该如何访问 Client 实例(您的代码仅在 TcpSocket 构造函数中构造为局部变量).更不用说跨线程共享该客户端是不安全的了。

中度修复

Live On Coliru

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

static const std::string HOST = "127.0.0.1";  
static const std::string PORT = "65432";  

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

struct Client
{
    tcp::socket socket;

    //template <typename Executor>
    using Executor = boost::asio::io_service&;
    Client(Executor executor, std::string const& host, std::string const& port)
        : socket(executor)
    {
        std::cout << std::quoted(host) << std::endl;
        std::cout << std::quoted(port) << std::endl;
        auto ep = tcp::resolver{socket.get_executor()}.resolve(host, port);
        connect(socket, ep);
    };

    void send(std::string const& message) {
        socket.send(boost::asio::buffer(message));
    }
};

class TcpSocket {
  private:
    std::string             _host;
    std::string             _port;
    boost::asio::io_service _svc;

  public:
    TcpSocket(std::string const& host, std::string const& port)
        : _host{host}
        , _port{port}
    {
    }

    void send(std::string const& message)
    {
        boost::thread t( //
            client_thread, boost::ref(_svc), message, _host, _port);
        t.join();
    }

    static void client_thread( //
        boost::asio::io_service& svc,
        std::string              message, // NOT A REFERENCE
        std::string              host,    // same
        std::string              port)
    {
        Client client(svc, host, port);
        client.send(message);
    }
};

//#include "socket-tcp.h"

int main()
{
    TcpSocket   socket(HOST, PORT);
    std::string const message = "socket implemented using global variables";
    while (true) {
        socket.send(message);
    }
}

更多笔记

根据定义,该线程是无用的,因为您会立即加入它。但如果你真的想要那个,请考虑 c++11 风格:

void send(std::string const& message)
{
    boost::thread t([=, &_svc] {
        Client client(_svc, _host, _port);
        client.send(message);
    });
    t.join();
}

在主线程上做更有意义:

void send(std::string const& message)
{
    Client client(_svc, _host, _port);
    client.send(message);
}

或者使用异步IO。请注意,您可能更愿意传递执行程序而不是共享对执行上下文的引用。此外,更喜欢 io_context 因为 io_service 已被弃用。

这里有一个简化的程序,它仍然执行相同的操作:Live On Coliru

奖励 - 与线程池异步

您似乎希望以异步方式传递消息。但是,您不知道如何在后台获取线程。无论如何,每次都“只创建一个新线程”并不是一个安全的想法。专业的做法是使用线程池。

此示例使用 ddefault boost::asio::thread_pool 并抛出一个链(即使当前不需要)和一个会话对象来保存单个消息的所有状态。请注意,我们现在缓存解析器结果。这可能是好事,也可能是坏事,具体取决于您的应用程序。

Live On Coliru

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

using boost::asio::ip::tcp;
using boost::system::error_code;

class TcpClient {
  public:
    TcpClient(std::string const& host, std::string const& port)
        : _ep(tcp::resolver{_io}.resolve(host, port))
    { }

    void send(std::string message)
    {
        boost::make_shared<Session>( //
            make_strand(_io.get_executor()), std::move(message), _ep)
            ->run();
    }

    ~TcpClient() {
        //_io.stop(); // optionally
        _io.join();
    }

  private:
    boost::asio::thread_pool _io;
    tcp::resolver::results_type _ep ;

    struct Session : boost::enable_shared_from_this<Session> {
        Session(auto executor, std::string message,
                tcp::resolver::results_type ep)
            : _socket(executor)
            , _message(std::move(message))
            , _ep(ep)
        {
        }

        void run() {
            async_connect( //
                _socket, _ep,
                [this, self = shared_from_this()](error_code ec,
                                                  tcp::endpoint) {
                    async_write(_socket, boost::asio::buffer(_message),
                                [this, self](error_code ec, size_t) {});
                });
        }

        tcp::socket                 _socket;
        std::string                 _message;
        tcp::resolver::results_type _ep;
    };
};

//#include "socket-tcp.h"

int main()
{
    TcpClient socket("127.0.0.1", "65432");
    for (int i = 0; i<100; ++i) {
        socket.send("socket implemented using async IO on thread pool " +
                    std::to_string(i) + "\n");
    }
}