首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >嵌入矢量时TCP客户端套接字问题(解决方案和/或改进方案)

嵌入矢量时TCP客户端套接字问题(解决方案和/或改进方案)
EN

Stack Overflow用户
提问于 2022-08-24 00:07:46
回答 1查看 62关注 0票数 1

下一段代码包含一个tcp客户端类,它应该在一个文件中创建一次或多次(对示例来说是硬编码的),并放置到std::vector对象中,然后连接到相应的服务器套接字。

https://godbolt.org/z/hzK9jhzjc链接

代码语言:javascript
运行
复制
#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?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-08-24 00:55:54

push_back不返回值(返回void类型)。如果你有c++17,可以这样用

代码语言:javascript
运行
复制
    auto& client = clients.emplace_back(Client{id, {}});

但是向量可以重新分配,这就需要移动或复制所有元素。因为client既不能复制,也不能移动,所以不能工作。这只是好的,因为否则当向量被重新分配时,async_操作将运行到UB中。

考虑提供参考稳定性的dequelist (意味着元素不会重新分配,或者在较少的情况下)。在这里,std::list是两种方法中比较安全的:

代码语言:javascript
运行
复制
std::list<Client> clients;

这能帮你找个地方。不过,我会注意到以下几点:

  • 为每个客户端创建单独的IO服务是不有效的
  • 手动轮询它们不是典型的。
  • 您有从未使用过的hostport成员
  • bytes_received正在被覆盖
  • write_some不能保证整个缓冲区都会被写入
  • 您正在混合异步和同步操作(async_readwrite_some)。这并不总是一个好主意。我认为对于tcp::socket,这在给定的用例中是可以的,但是不要期望IO对象一般支持这一点。
  • 没有理由为boost::asio::buffer提供数组长度--它将被推导出来。更好的方法是使用std::array而不是C风格的数组
  • 我看到了您的#include <thread>;如果您打算在多个线程上运行,请注意strands:为什么在使用boost::asio时每个连接都需要串?

下面是一个简化的、固定的版本,上面有以下内容:

住在Coliru

代码语言:javascript
运行
复制
#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";
}

打印

代码语言:javascript
运行
复制
(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

其他备注

读取操作形成一个循环(隐式串)。

请注意,确保没有写操作重叠仍然是您的责任。如果有必要,引入一个队列,以便您可以有多个消息挂起。参见例如如何从多个线程安全地写入套接字?

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73466140

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档