避免全局变量制作 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
构造函数中构造为局部变量).更不用说跨线程共享该客户端是不安全的了。
中度修复
#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
并抛出一个链(即使当前不需要)和一个会话对象来保存单个消息的所有状态。请注意,我们现在缓存解析器结果。这可能是好事,也可能是坏事,具体取决于您的应用程序。
#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");
}
}
我有这个 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
构造函数中构造为局部变量).更不用说跨线程共享该客户端是不安全的了。
中度修复
#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
并抛出一个链(即使当前不需要)和一个会话对象来保存单个消息的所有状态。请注意,我们现在缓存解析器结果。这可能是好事,也可能是坏事,具体取决于您的应用程序。
#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");
}
}