下一段代码包含一个tcp客户端类,它应该在一个文件中创建一次或多次(对示例来说是硬编码的),并放置到std::vector
对象中,然后连接到相应的服务器套接字。
https://godbolt.org/z/hzK9jhzjc链接
#include <chrono>
#include <thread>
#include <fstream>
#include <boost/asio.hpp>
namespace tcpsocket
{
using boost::asio::ip::tcp;
class client
{
public:
void connect(const std::string& host, const std::string& port)
{
if (host.empty() || port.empty()) return;
tcp::resolver resolver{ io_context };
tcp::resolver::results_type endpoints = resolver.resolve(host, port);
boost::asio::async_connect(socket, endpoints, [this](const boost::system::error_code& error, const tcp::endpoint /*endpoint*/)
{
if (!error)
read();
});
}
void read()
{
socket.async_read_some(boost::asio::buffer(data, max_length), [this](const boost::system::error_code& error, std::size_t bytes)
{
if (error) return socket.close();
bytes_received = bytes;
read();
});
}
void write(const std::string& msg)
{
boost::system::error_code error;
size_t bytes = socket.write_some(boost::asio::buffer(msg), error);
if (error) return socket.close();
}
void poll()
{
io_context.poll();
}
private:
std::string host;
std::string port;
size_t bytes_received{};
enum { max_length = 512 };
unsigned char data[max_length];
boost::asio::io_context io_context;
tcp::socket socket{io_context};
};
}//namespace tcpsocket
struct Cfg
{
unsigned id{};
std::string host;
std::string port;
};
struct Client
{
unsigned id{};
tcpsocket::client sck;
};
int main()
{
std::vector<Client> clients;
std::vector<Cfg> config{ {125u, "127.0.0.1", "30000"}, {137u, "127.0.0.1", "30001"} };//In real life, this would come from configuration file
for (auto&[id, host, port] : config)
{
//auto& client = clients.push_back(Client{id, {}});//This is failing (typo error with return value detected by Sehe!!!)
auto& client = clients.emplace_back(id, {});//This is failing
client.sck.connect(host, port);
}
while (true)
{
for (auto&[id, client] : clients)
client.poll();
using namespace std::chrono_literals;
std::this_thread::sleep_for(100ms);
}
}
根据我的理解,由于复制io_ this /套接字时出错,程序没有编译,但在这一点上我可能是错的。
我怎么才能解决这个问题?因此,我是否有其他更好的选择?例如,应该更好地将一些tcp套接字池放入客户端类,并对所有这些类使用相同的io_context?
发布于 2022-08-24 00:55:54
push_back
不返回值(返回void
类型)。如果你有c++17,可以这样用
auto& client = clients.emplace_back(Client{id, {}});
但是向量可以重新分配,这就需要移动或复制所有元素。因为client
既不能复制,也不能移动,所以不能工作。这只是好的,因为否则当向量被重新分配时,async_操作将运行到UB中。
考虑提供参考稳定性的deque
或list
(意味着元素不会重新分配,或者在较少的情况下)。在这里,std::list
是两种方法中比较安全的:
std::list<Client> clients;
这能帮你找个地方。不过,我会注意到以下几点:
host
和port
成员bytes_received
正在被覆盖write_some
不能保证整个缓冲区都会被写入async_read
和write_some
)。这并不总是一个好主意。我认为对于tcp::socket
,这在给定的用例中是可以的,但是不要期望IO对象一般支持这一点。boost::asio::buffer
提供数组长度--它将被推导出来。更好的方法是使用std::array
而不是C风格的数组#include <thread>
;如果您打算在多个线程上运行,请注意strands:为什么在使用boost::asio时每个连接都需要串?下面是一个简化的、固定的版本,上面有以下内容:
#include <boost/asio.hpp>
#include <chrono>
#include <fstream>
#include <iostream>
#include <list>
using namespace std::chrono_literals;
namespace tcpsocket {
using boost::asio::ip::tcp;
using boost::system::error_code;
class client {
public:
client(boost::asio::any_io_executor ex) : socket_(ex) {}
size_t bytes_received() const { return bytes_received_; }
void connect(const std::string& host, const std::string& port) {
post(socket_.get_executor(), [=, this] { do_connect(host, port); });
}
void write(std::string msg) {
post(socket_.get_executor(), [=, this] { do_write(msg); });
}
void read() {
post(socket_.get_executor(), [=, this] { do_read(); });
}
private:
void do_connect(const std::string& host, const std::string& port) {
if (host.empty() || port.empty())
return;
tcp::resolver resolver{socket_.get_executor()};
async_connect(socket_, resolver.resolve(host, port),
[this](error_code ec, tcp::endpoint /*endpoint*/) {
if (!ec)
do_read();
else
std::cerr << ec.message() << std::endl;
});
}
void do_write(const std::string& msg) {
error_code ec;
boost::asio::write(socket_, boost::asio::buffer(msg), ec);
if (ec) {
std::cerr << "Closing (" << ec.message() << ")" << std::endl;
return socket_.close();
}
}
void do_read() {
socket_.async_read_some( //
boost::asio::buffer(data),
[this](error_code ec, std::size_t bytes) {
if (ec)
return socket_.close();
bytes_received_ += bytes;
do_read();
});
}
std::atomic_size_t bytes_received_{0};
std::array<unsigned char, 512> data;
tcp::socket socket_;
};
} // namespace tcpsocket
struct Cfg {
unsigned id{};
std::string host;
std::string port;
};
struct Client {
Client(unsigned id, boost::asio::any_io_executor ex) : id_(id), impl_(ex) {}
unsigned id_;
tcpsocket::client impl_;
};
int main()
{
boost::asio::io_context ioc;
std::list<Client> clients;
std::vector<Cfg> const config{
{125u, "127.0.0.1", "30000"},
{137u, "127.0.0.1", "30001"},
{149u, "127.0.0.1", "30002"},
{161u, "127.0.0.1", "30003"},
{173u, "127.0.0.1", "30004"},
{185u, "127.0.0.1", "30005"},
{197u, "127.0.0.1", "30006"},
{209u, "127.0.0.1", "30007"},
{221u, "127.0.0.1", "30008"},
{233u, "127.0.0.1", "30009"},
{245u, "127.0.0.1", "30010"},
};
for (auto& [id, host, port] : config) {
auto& c = clients.emplace_back(id, make_strand(ioc));
c.impl_.connect(host, port);
c.impl_.write(std::to_string(id) + " connected to " + host + ":" + port + "\n");
}
ioc.run_for(150ms);
for (auto& [id, impl]: clients)
std::cout << id << " received " << impl.bytes_received() << "\n";
}
打印
(for a in {30000..30010}; do netcat -tlp $a < main.cpp & done)
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp
./a.out
125 connected to 127.0.0.1:30000
149 connected to 127.0.0.1:30002
161 connected to 127.0.0.1:30003
185 connected to 127.0.0.1:30005
197 connected to 127.0.0.1:30006
209 connected to 127.0.0.1:30007
221 connected to 127.0.0.1:30008
233 connected to 127.0.0.1:30009
173 connected to 127.0.0.1:30004
245 connected to 127.0.0.1:30010
137 connected to 127.0.0.1:30001
125 received 3386
137 received 3386
149 received 3386
161 received 3386
173 received 3386
185 received 3386
197 received 3386
209 received 3386
221 received 3386
233 received 3386
245 received 3386
其他备注
读取操作形成一个循环(隐式串)。
请注意,确保没有写操作重叠仍然是您的责任。如果有必要,引入一个队列,以便您可以有多个消息挂起。参见例如如何从多个线程安全地写入套接字?
https://stackoverflow.com/questions/73466140
复制相似问题