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. If I make every client have its own socket in udp, will it be more or less efficient?
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.
The application code is responsible for demultiplexing. At a high-level, there are two options:
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.
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:
#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. 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
. 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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.