Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define Socket Options, instead of writing a method to each one #272

Open
sgtcortez opened this issue Jul 25, 2023 · 1 comment
Open

Define Socket Options, instead of writing a method to each one #272

sgtcortez opened this issue Jul 25, 2023 · 1 comment

Comments

@sgtcortez
Copy link

Description

Hi, would be nice as user point of view, to have the socket options already created, and also, let the user define if needed. So instead of him, know what is the option level, the option name, what is the value and so on ...
We could have something like:

static const Option<std::int32_t> TCP_CORKING;
static const Option<std::int32_t> REUSE_ADDRESS;

With this, we could create as many options are there, and, with the benefit of templates compile time code generation, we could write just one setsockopt ...

I was seeing Socket.h, and noticied:

///
/// Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm).
///
void setTcpNoDelay(bool on);

///
/// Enable/disable SO_REUSEADDR
///
void setReuseAddr(bool on);

///
/// Enable/disable SO_REUSEPORT
///
void setReusePort(bool on);

///
/// Enable/disable SO_KEEPALIVE
///
void setKeepAlive(bool on);

This works fine, but leads to duplicated code ...

Example

A more detailed example: Socket.h

class Socket
{
public:
    template <typename OPTION_TYPE>
    struct Option
    {
        Option(const std::int32_t level, const std::int32_t value)
            : level(level), value(value), size(sizeof(OPTION_TYPE))
        {
        }

        /**
            * The level argument
            */
        const std::int32_t level;

        /**
        * The optname argument
        )*/
        const std::int32_t value;

        /**
            * The optlen argument
            */
        const std::int32_t size;

        /**
            * read from socket buffer size
            */
        static const Option<std::int32_t> READ_BUFFER_SIZE_TYPE;

        /**
            * When reading from socket, we define the timeout
            */
        static const Option<struct timeval> READ_TIMEOUT;

        /**
            * Minimum number of bytes to consider the buffer as readable
            * Can be useful if want to use batch messages
            */
        static const Option<std::int32_t>
            MINIMUM_BYTES_TO_CONSIDER_BUFFER_AS_READABLE;

        /**
            * This option is used to enable the TCP Corking mechanism,
            * which delays sending small packets in order to optimize network
            * throughput.
            */
        static const Option<std::int32_t> TCP_CORKING;

        /**
            * Indicates that the rules used in validating addresses
            * supplied in a bind(2) call should allow reuse of local
            * addresses. For AF_INET sockets this means that a socket
            * may bind, except when there is an active listening socket
            * bound to the address. When the listening socket is bound
            * to INADDR_ANY with a specific port then it is not possible
            * to bind to this port for any local address.
            */
        static const Option<std::int32_t> REUSE_ADDRESS;

        /**
            * Permits multiple AF_INET or AF_INET6 sockets to be bound
            * to an identical socket address. This option must be set
            * on each socket (including the first socket) prior to
            * calling bind(2) on the socket. To prevent port hijacking,
            * all of the processes binding to the same address must have
            * the same effective UID. This option can be employed with
            * both TCP and UDP sockets.
            */
        static const Option<std::int32_t> REUSE_PORT;
        /*
            *  If set, disable the Nagle algorithm.  This means that
            *  segments are always sent as soon as possible, even if
            *  there is only a small amount of data.  When not set, data
            *  is buffered until there is a sufficient amount to send
            *  out, thereby avoiding the frequent sending of small
            *  packets, which results in poor utilization of the network.

            * https://en.wikipedia.org/wiki/Nagle%27s_algorithm its useful to
            undestand this ...
        */
        static const Option<std::int32_t> TCP_NO_DELAY;

            /**
            * If defined, tries to send al enqued messages before socket.close or
            * socket.shutdown return. So, if the send exceeds the timeout defined
            * in the linger option
            */
        static const Option<struct linger> LINGER;
    };

    template <typename VALUE_TYPE>
    struct OptionValue
    {
        OptionValue(const VALUE_TYPE value_type, const Option<VALUE_TYPE>& type) : value_type(value_type), type(type)
        {}

        /**
        * Since the options are static instances, we can use lvalue here
        */
        const Option<VALUE_TYPE>& type;

        const VALUE_TYPE value_type;
    };

    template <typename TYPE>
    void set_option(const OptionValue<TYPE> option) const
    {
        int32_t level = option.type.level;
        int32_t opt_name = option.type.value;
        TYPE t = option.value_type;
        void* opt_value = &(t);
        socklen_t opt_len = option.type.size;
        if (::setsockopt(file_descriptor, level, opt_name, opt_value, opt_len) == -1)
        {
            perror("setsockopt");
        }
    };

    template <typename TYPE>
    const OptionValue<TYPE> get_option(const Option<TYPE>& type) const
    {
        int32_t socketfd = file_descriptor;
        int32_t level = type.level;
        int32_t opt_name = type.value;

        /**
            * NOTE: This is not a Variable Length Array.
            * We can achive this, with templates, so its know at compile time how
            * big it is.
            */
        std::int8_t buffer[type.size];
        socklen_t opt_len;
        if (::getsockopt(socketfd, level, opt_name, buffer, &opt_len) == -1)
        {
            perror("getsockopt");
        }
        TYPE* t = reinterpret_cast<TYPE*>(buffer);
        const OptionValue<TYPE> result(*t, type);
        return result;
    }
}

template <>
Socket::Option<int32_t> const Socket::Option<int32_t>::TCP_CORKING;

template <>
Socket::Option<int32_t> const Socket::Option<int32_t>::MINIMUM_BYTES_TO_CONSIDER_BUFFER_AS_READABLE;

template <>
Socket::Option<int32_t> const Socket::Option<int32_t>::READ_BUFFER_SIZE_TYPE;

template <>
Socket::Option<struct timeval> const Socket::Option<struct timeval>::READ_TIMEOUT;

template <>
Socket::Option<std::int32_t> const Socket::Option<std::int32_t>::REUSE_ADDRESS;

template <>
Socket::Option<std::int32_t> const Socket::Option<std::int32_t>::REUSE_PORT;

template <>
Socket::Option<std::int32_t> const Socket::Option<std::int32_t>::TCP_NO_DELAY;

template <>
Socket::Option<struct linger> const Socket::Option<struct linger>::LINGER;

And, Socket.cc

template <>
Socket::Option<int32_t> const Socket::Option<int32_t>::READ_BUFFER_SIZE_TYPE(SOL_SOCKET, SO_RCVBUF);

template <>
Socket::Option<struct timeval> const Socket::Option<struct timeval>::READ_TIMEOUT(SOL_SOCKET, SO_RCVTIMEO);

template <>
Socket::Option<int32_t> const Socket::Option<int32_t>::MINIMUM_BYTES_TO_CONSIDER_BUFFER_AS_READABLE(SOL_SOCKET, SO_RCVLOWAT);

template <>
Socket::Option<int32_t> const Socket::Option<int32_t>::TCP_CORKING(IPPROTO_TCP, TCP_CORK);

template <>
Socket::Option<int32_t> const Socket::Option<int32_t>::REUSE_ADDRESS(SOL_SOCKET, SO_REUSEADDR);

template <>
Socket::Option<int32_t> const Socket::Option<int32_t>::REUSE_PORT(SOL_SOCKET,SO_REUSEPORT);

template <>
Socket::Option<int32_t> const Socket::Option<int32_t>::TCP_NO_DELAY(IPPROTO_TCP, TCP_NODELAY);

template <>
Socket::Option<struct linger> const Socket::Option<struct linger>::LINGER(SOL_SOCKET, SO_LINGER);

Note: The code of set_option and get_option must be in the header file, because we are using templates.
Note sure if there is a more elegant way ...

And, the usage:

void on_connection(const Socket& client)
{
    // Enable TCP CORK
    client.set_option(Socket::OptionValue<int32_t>(1, Socket::Option<int32_t>::TCP_CORKING));

    // Check if CORK is enabled
    cout << "CORK enabled? " << (client.get_option(Socket::Option<int32_t>::TCP_CORKING).value_type ? "true" : "false");
}

If you liked, and accept. I would like to implement it!

Thanks!

@nqf
Copy link

nqf commented Oct 26, 2023

31cb406 this ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants