简体   繁体   English

我应该如何在UDP中找到我在Boost Asio中收到的客户端?

[英]How should I find which client I am receiving from in Boost Asio in UDP?

So the only way that I know how to find which client I received from is by comparing the received endpoint in a loop of all the clients, and I was wondering if there was a more elegant way of handling this. 因此,我知道如何找到我收到的客户端的唯一方法是通过在所有客户端的循环中比较接收到的端点,我想知道是否有更优雅的方式来处理它。

In tcp, every client has its own socket, and with it, it can find which client it receives from instantly. 在tcp中,每个客户端都有自己的套接字,通过它,它可以立即找到它接收的客户端。 If I make every client have its own socket in udp, will it be more or less efficient? 如果我让每个客户端在udp中都有自己的套接字,那么它的效率会更高或更低吗?

I was also thinking of making a global socket, and making every client object listen to only their endpoint's, but I don't think that's possible, or efficient in asio. 我也在考虑创建一个全局套接字,并使每个客户端对象只监听它们的端点,但我认为这不是可能的,也不是高效的asio。

The application code is responsible for demultiplexing. 应用程序代码负责解复用。 At a high-level, there are two options: 在高级别,有两种选择:

  • Use a single endpoint to conceptually function as an acceptor. 使用单个端点在概念上充当接受器。 Upon receiving a handshake message, the client would instantiate a new local endpoint, and inform the client to use the newly constructed endpoint for the remainder of the client's session. 在接收到握手消息时,客户端将实例化新的本地端点,并通知客户端将新构造的端点用于客户端会话的剩余部分。 This results in a socket per client, and with connected UDP sockets , a client can be guaranteed to only receive messages from the expected remote endpoint. 这导致每个客户端的套接字,并且通过连接的UDP套接字 ,可以保证客户端仅接收来自预期远程端点的消息。 This should be no less efficient than the same approach used with TCP sockets. 这应该不比使用TCP套接字的方法有效。 However, it requires making changes to the application protocol on both the sender and receiver. 但是,它需要在发送方和接收方上更改应用程序协议。
  • Use a single socket. 使用单个插槽。 Upon receiving a message, the remote endpoint is used to demultiplex to the client object. 在接收到消息时,远程端点用于解复用到客户端对象。 If the application depends upon the demultiplex abstraction, then the implementation may be freely changed to best suit the application's usage. 如果应用程序依赖于解复用抽象,则可以自由地更改实现以最适合应用程序的使用。 This requires no changes to the application protocol. 这不需要更改应用程序协议。

The first option will more easily support higher concurrency levels, as each client can control the lifetime of its asynchronous call chain. 第一个选项将更容易支持更高的并发级别,因为每个客户端都可以控制其异步调用链的生命周期。 While it is possible to have a call chain per client in the second option, controlling the lifetime introduces complexity, as all asynchronous call chains are bound to the same I/O object. 虽然在第二个选项中每个客户端都可以有一个调用链,但控制生命周期会带来复杂性,因为所有异步调用链都绑定到同一个I / O对象。

On the other hand, as concurrency increase, so does memory. 另一方面,随着并发性的增加,内存也会增加。 Hence, the first option is likely to use more memory than the second option. 因此,第一个选项可能比第二个选项使用更多内存。 Furthermore, controlling overall memory is easier in the second, as the concurrency level will not be completely dynamic. 此外,在第二个控制整体内存更容易,因为并发级别不是完全动态的。 In either case, reactor style operations can be used to mitigate the overall memory usage. 在任何一种情况下, 反应器样式操作都可用于减轻总体内存使用量。

In the end, abstract the application from the implementation whilst keeping the code maintainable. 最后,从实现中抽象出应用程序,同时保持代码的可维护性。 Once the application is working, profile, identify bottlenecks, and make choices based on actual data. 应用程序运行后,分析,识别瓶颈,并根据实际数据做出选择。


To expand slightly on the second option, here is an complete minimal example of a basic client_manager that associates endpoints to client objects: 要稍微扩展第二个选项,这里是一个完整的最小示例 ,它将端点与客户端对象关联起来的基本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);
}

Note that as the application only depends upon the client_manager abstraction (ie pre and post conditions for client_manager::add() and client_manager::get() ), then the client_manager implementation can be changed without affecting the application as long as the implementation maintains the pre and post conditions. 请注意,由于应用程序仅依赖于client_manager抽象(即client_manager::add()client_manager::get()前置和后置条件),因此只要实现维护,就可以更改client_manager实现而不会影响应用程序。前后条件。 For instance, instead of using std::unordered_map , it could be implemented with a sequence container, such as std::vector , or an ordered associated container, such as std::map . 例如,它可以使用序列容器(如std::vector )或有序关联容器(如std::map来实现,而不是使用std::unordered_map Choose a container that best fits the expected usage. 选择最符合预期用途的容器。 After profiling, if the container choice is an identified bottleneck, then change the implementation of client_manager to use a more suitable container based on the actual usage. 在分析之后,如果容器选择是已识别的瓶颈,则根据实际使用情况更改client_manager的实现以使用更合适的容器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM