如何在Boost Asio中找到我从哪个客户端接收UDP数据?

3

我知道从哪个客户端接收到数据的唯一方式是在所有客户端的循环中比较接收端点,但我想知道是否有更优雅的处理方式。

TCP中,每个客户端都有自己的套接字,通过它可以立即找出数据来源。如果我让UDP中的每个客户端都拥有自己的套接字,效率会更高还是更低?

我也考虑过创建全局套接字,并使每个客户端对象仅监听其端点,但我认为这在asio中不可能或者不够高效。


使用例如 std::unordered_map 来存储端点-客户端映射,如何? - Some programmer dude
@JoachimPileborg,但它在实践中被使用过吗? - poeteto
在实践中,是为了这个目的(端点-客户端映射),还是“专业地”为其他目的?肯定是后者,可能是前者。对我来说,使用“映射”结构以此方式“映射”数据似乎很自然。 - Some programmer dude
有没有一些大型项目使用这种方法?它的效率如何? - poeteto
2
避免过早优化。如果unordered map被证明是瓶颈,那么评估替代方案。 - Sam Miller
1个回答

2
应用程序代码负责进行分解复用。在高层次上,有两个选项:
  • 使用单个端点概念上作为接收器。在接收到握手消息后,客户端将实例化一个新的本地端点,并通知客户端使用新构造的端点来完成客户端会话的其余部分。这会导致每个客户端一个套接字,并且使用连接的UDP套接字,客户端可以确保仅从预期的远程端点接收消息。这应该不比使用TCP套接字的相同方法效率低。但是,它需要在发送方和接收方都对应用程序协议进行更改。
  • 使用单个套接字。在接收到消息时,远程端点用于分解到客户端对象。如果应用程序依赖于分解抽象,则可以自由更改实现以最好地适合应用程序的使用。这不需要更改应用程序协议。
第一种选择将更容易支持更高的并发级别,因为每个客户端都可以控制其异步调用链的生命周期。虽然第二种选择可以为每个客户端设置一个调用链,但控制生命周期会引入复杂性,因为所有异步调用链都绑定到同一个I/O对象。
另一方面,随着并发增加,内存使用量也会增加。因此,第一种选择可能比第二种选择使用更多的内存。此外,在第二种选择中更容易控制整体内存,因为并发级别不会完全动态。在任何情况下,可以使用反应器样式操作来减少总体内存使用量。
最终,要使应用程序与实现分离,同时保持代码可维护性。一旦应用程序正常工作,请进行分析,识别瓶颈,并根据实际数据做出选择。
稍微详细解释第二个选项,这里有一个完整的最小 示例,展示了一个基本的 client_manager,它将端点与客户端对象关联起来:
#include <memory>
#include <unordered_map>
#include <boost/asio.hpp>

namespace ip = boost::asio::ip;

/// @brief Mockup client.
class client:
  public std::enable_shared_from_this<client>
{
public:

  explicit client(ip::udp::endpoint endpoint)
    : endpoint_(endpoint)
  {}

  const ip::udp::endpoint& endpoint() const { return endpoint_; }

private:

  ip::udp::endpoint endpoint_;
};

/// @brief Basic class that manages clients.  Given an endpoint, the
///        associated client, if any, can be found.
class client_manager
{
private:

  // The underlying implementation used by the manager.
  using container_type = std::unordered_map<
    ip::udp::endpoint, std::shared_ptr<client>,
    std::size_t (*)(const ip::udp::endpoint&)>;

  /// @brief Return a hash value for the provided endpoint.
  static std::size_t get_hash(const ip::udp::endpoint& endpoint)
  {
    std::ostringstream stream;
    stream << endpoint;
    std::hash<std::string> hasher;
    return hasher(stream.str());
  }

public:

  using key_type = container_type::key_type;
  using mapped_type = container_type::mapped_type;

  /// @brief Constructor.
  client_manager()
    : clients_(0, &client_manager::get_hash)
  {}

// The public abstraction upon which the application will depend.
public:

  /// @brief Add a client to the manager.
  void add(mapped_type client)
  {
    clients_[client->endpoint()] = client;
  }

  /// @brief Given an endpoint, retrieve the associated client.  Return
  ///        an empty shared pointer if one is not found.
  mapped_type get(key_type key) const
  {
    auto result = clients_.find(key);
    return clients_.end() != result
      ? result->second // Found client.
      : mapped_type(); // No client found.
  }

private:

  container_type clients_;
};

int main()
{
  // Unique endpoints.
  ip::udp::endpoint endpoint1(ip::address::from_string("11.11.11.11"), 1111);
  ip::udp::endpoint endpoint2(ip::address::from_string("22.22.22.22"), 2222);
  ip::udp::endpoint endpoint3(ip::address::from_string("33.33.33.33"), 3333);

  // Create a client for each endpoint.
  auto client1 = std::make_shared<client>(endpoint1);
  auto client2 = std::make_shared<client>(endpoint2);
  auto client3 = std::make_shared<client>(endpoint3);

  // Add the clients to the manager.
  client_manager manager;
  manager.add(client1);
  manager.add(client2);
  manager.add(client3);

  // Locate a client based on the endpoint.
  auto client_result = manager.get(endpoint2);
  assert(client1 != client_result);
  assert(client2 == client_result);
  assert(client3 != client_result);
}

请注意,由于该应用程序仅依赖于client_manager抽象(即client_manager :: add()client_manager :: get()的先决条件和后置条件),因此只要实现保持先决条件和后置条件,就可以更改client_manager实现而不影响应用程序。例如,可以使用序列容器(如std::vector)或有序关联容器(如std::map)来实现,而不是使用std::unordered_map。选择最适合预期使用情况的容器。在分析后,如果容器选择是已确定的瓶颈,则根据实际使用情况更改client_manager的实现以使用更合适的容器。

是的,我最终做了类似这样的事情,但我使用了 map,因为我认为比起其他人更早加入的人应该有一个像大多数游戏一样的平局优势。 - poeteto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接