Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

The Generic Network Endpoint Class for the Boost's Asio Framework

4.69/5 (5 votes)
4 Aug 2015CPOL2 min read 14.8K   199  
The generic network endpoint (IP address and port) class that can be compared with and cast to/from the protocol aware boost::asio::ip::udp::endpoint and boost::asio::ip::tcp::endpoint objects.

Motivation

The Boost.Asio (http://www.boost.org/doc/libs/1_58_0/doc/html/boost_asio.html) is a popular communication library with well-developed class system. This library uses two separated classes to represent network endpoints (IP address + port) for UPD and TCP protocols: udp::endpoint and tcp::endpoint, correspondingly.

It makes perfect sense on the communication layer where each element (udp::socket, for example) is aware of communication protocol it works with. However, the application layer can be “protocol blind”, i.e. it can process all requests arrived via both UDP and TCP protocols in the same way.

In this case, it is useful to have a “protocol neutral” endpoint (IP + port) class. Objects of the class should be convertible and comparable with the standard udp::endpoint and tcp::endpoint objects. The article describes a lightweight wrapper that can serve as such generic endpoint object.
(Just in case, please notice that boost::asio::generic::basic_endpoint is not what we are looking for.)
 

Description

In the Boost.Asio both standard endpoint classes, udp::endpoin and tcp::endpoin, are implemented as a protocol specialization of the base template ip::basic_endpoint<InternetProtocol>:

C++
<code>
class udp
{
public:
  /// The type of a UDP endpoint.
  typedef basic_endpoint<udp> endpoint;
...
};

class tcp
{
public:
  /// The type of a TCP endpoint.
  typedef basic_endpoint<tcp> endpoint;
...
};
</code>

It is important that these endpoint classes are not related, and cannot be compared or converted to each other.

Fortunately, the base class basic_endpoint defines all required endpoint functionality while the InternetProtocol just specifies the protocol family the object will work with.

That allows us to introduce a new class derived from the basic_endpoint<none> template specialization. This new class will have all required endpoint functionality derived from its parent, and, in the same time, it will be protocol neutrual because class <font face="Courier New">none</font> does not represent any protocol.

That is exactly what we need. To complete the work we just should define the implicit casting operations to/from the standard endpoint classes using appropriate casting operators and constructors :

C++
<code>
class endpoint : public boost::asio::ip::basic_endpoint<none>
{
public:
    typedef boost::asio::ip::basic_endpoint<none> base_t;

    /// ctor creates an empty endpoint object.
    endpoint() : base_t(){}
    /// ctor creates endpoint object with given IP and port values.
    endpoint(const address_type& address, port_type port) : base_t(address, port){}
    /// Casting ctor creates endpoint object from a UDP enpoint
    explicit endpoint(const udp::endpoint& ep) : base_t(ep.address(), ep.port()) {}
    /// Casting ctor creates endpoint object from a TCP enpoint
    explicit endpoint(const tcp::endpoint& ep) : base_t(ep.address(), ep.port()) {}
    /// copy ctor
    endpoint(const endpoint& ep) : base_t(ep.address(), ep.port())     {}
    /// Assign operator from UDP/TCP enpoint
    template<typename T> endpoint& operator=(const T& ep) 
    {
        BOOST_STATIC_ASSERT
        ( 
            boost::mpl::or_
            < 
                boost::is_same<T, tcp::endpoint>::type, 
                boost::is_same<T, udp::endpoint>::type
            >::value
        );
        
        address(ep.address());
        port(ep.port());

        return *this;
    }
    /// Casting operator represents the endpoint as a UDP/TCP enpoint object
    template<typename T> 
    const T& as() const
    {
        BOOST_STATIC_ASSERT
        ( 
            boost::mpl::or_
            < 
                boost::is_same<T, tcp::endpoint>::type, 
                boost::is_same<T, udp::endpoint>::type
            >::value
        );

        return reinterpret_cast<const T&>(*this);
    }
    /// Casting operator represents a boost::asio TCP/UDP enpoint object as an endpoint
    template<typename T>
    static const endpoint& cast(const T& other)
    {
        BOOST_STATIC_ASSERT
            (
                boost::mpl::or_
                <
                boost::is_same<T, tcp_endpoint>::type,
                boost::is_same<T, udp_endpoint>::type
                >::value
                );

        return reinterpret_cast<const endpoint&>(other);
    }
    /// Casting operator represents the endpoint as a UDP enpoint object
    operator const udp::endpoint&() const
    {
        return as<udp::endpoint>();
    }
    /// Casting operator represents the endpoint as a TCP enpoint object
    operator const tcp::endpoint&() const
    {
        return as<tcp::endpoint>();
    }
};
</code>

It is important that the casting operations:

  • operator const tcp::endpoint&() const
  • static const endpoint& cast(const T& other)
  • operator const upd::endpoint&() const

do not create any temproray objects and, therefore, do not have any hidden performance costs.

The casting ctors explicit endpoint(const udp_endpoint& ep)/explicit endpoint(const udp_endpoint& ep) are declared as explicit to avoid unintentional temproray object creation.
The <font face="Courier New">static const endpoint& cast(const T& other)</font> function should be used instead of the casting ctor if only casting is required and no new object should be created, for example, if the endpoint object is used as a search key.

The following are examples how the endpoint objects can be converted/compared with objects of the tcp::endpoint and the upd::endpoint classes (without any hidden temproray object creation):

C++
<code>
endpoint ep(boost::asio::ip::address::from_string("0.0.0.1"), 1001);
udp::endpoint udp_ep(boost::asio::ip::address::from_string("0.0.0.1"), 1001);
tcp::endpoint tcp_ep(boost::asio::ip::address::from_string("0.0.0.1"), 1001);

// create/assign
endpoint ep1(udp_ep);
ep1=(endpoint)tcp_ep;
ep1=static_cast<endpoint>(tcp_ep);
// NOTE not allowed: ep1=tcp_ep; explicit casting is required
tcp::endpoint tcp_ep1=ep;
udp::endpoint udp_ep1=ep;

// compare with UDP/TCP endpoints
if(ep == udp_ep || ep == tcp_ep)
{
...
}

if(ep <= udp_ep && ep != tcp_ep)
{
...
}

// using as a search key 
typedef std::set<endpoint> ep_set_type;
typedef std::set<udp::endpoint> udp_set_type;
typedef std::set<tcp::endpoint> tcp_set_type;
ep_set_type ep_set;
udp_set_type udp_set;
udp_set_type tcp_set;

ep_set_type::iterator it=ep_set.find(endpoint::cast(udp_ep));
// NOTE: ep_set_type::iterator it=ep_set.find((endpoint)udp_ep); 
// will use the explicit casting ctor endpoint(const udp::endpoint& ep) 
// and will create temproray object

upd_set_type::iterator it=udp_set.find(ep);
tcp_set.erase(ep);

</code>

Platforms

  • Compilers/IDE: MS Visual Studio 2015
  • Boost library: 1.58.0
  • Operating systems: Windows 8.1

References

History

  • Initial version: 08/05/2015.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)