Pagghiu / SaneCppLibraries

Sane C++ Libraries
https://pagghiu.github.io/SaneCppLibraries
MIT License
507 stars 11 forks source link

Support more advanced UDP specific features in Sockets library #6

Open Pagghiu opened 8 months ago

Pagghiu commented 8 months ago

Sockets library only supports TCP so far and adding UDP support would be great 😀

silent-tech commented 3 months ago

I am interested in this task, so I have already started to work on it :) Before I start to actually implement it, I would share with you the definition of the class.

/// @brief Provides User Datagram Protocol (UDP) network services.
///
/// The UdpClient class provides simple methods for sending and receiving connectionless UDP datagrams
/// in blocking synchronous mode. Because UDP is a connectionless transport protocol, you do not need
/// to establish a remote host connection prior to sending and receiving data.
/// You can create an instaince of the UdpSocket class and using the remote host address as a parameter:
/**
 * @code{.cpp}
    SocketDescriptor udpSocket;
    UdpSocket        udp(udpSocket);

    // Write some data to the socket
    char buf[1] = {testValue};
    SC_TRY(udp.write(serverAddress, udpPort, {buf, sizeof(buf)}));
    buf[0]++; // change the value and write again
    SC_TRY(udp.write(serverAddress, udpPort, {buf, sizeof(buf)}));

    // Close the socket
    SC_TRY(udp.close());
    @endcode
*/
/// Example of doing two synchronous reads:
/**
 * @code{.cpp}
    SocketDescriptor udpSocket;
    UdpSocket        udp(udpSocket, localPort);
    SocketIPAddress  remoteAddress;

    // Read some data blocking until it's available
    Span<char>   readData;
    SC_TRY(udp.read({buf, sizeof(buf)}, readData, remoteAddress));
    SC_TRY(buf[0] == testValue and testValue != 0);

    // Read again blocking but with a timeout of 10 seconds
    SC_TRY(udp.readWithTimeout({buf, sizeof(buf)}, readData, remoteAddress, 10000_ms));
    SC_TRY(buf[0] == testValue + 1);

    // Close the client
    SC_TRY(udp.close());
    @endcode
*/
struct SC::UdpSocket
{
    /// @brief Constructs a UdpSocket from a SocketDescriptor
    /// @param socket A socket descriptor
    UdpSocket(SocketDescriptor& socket);

    /// @brief Constructs a UdpSocket from a SocketDescriptor and binds it to the local port number
    /// @param socket A socket descriptor
    /// @param port Local port number to bind
    UdpSocket(SocketDescriptor& socket, uint16_t port);

    /// @brief Calls SocketDescriptor::close
    /// @return The Result of SocketDescriptor::close
    [[nodiscard]] Result close();

    /// @brief Sends a UDP datagram to a remote host at the specified remote address
    /// @param ipAddress Remote IP address
    /// @param data Bytes to write to this socket
    /// @return Valid Result if bytes have been written successfully
    [[nodiscard]] Result write(SocketIPAddress ipAddress, Span<const char> data);

    /// @brief Sends a UDP datagram to a remote host at the specified remote host address and port
    /// @param address Address as string
    /// @param port Port on a specified remote host
    /// @param data Bytes to write to this socket
    /// @return Valid Result if bytes have been written successfully
    [[nodiscard]] Result write(StringView address, uint16_t port, Span<const char> data);

    /// @brief Reads a UDP datagram that was sent by a remote host
    /// @param[in] data Span of memory pointing at a buffer that will receive the read data
    /// @param[out] readData A sub-Span of `data` that has the length of actually read bytes
    /// @param[out] ipAddress IP address of the sender host
    /// @return Valid Result if bytes have been read successfully
    [[nodiscard]] Result read(Span<char> data, Span<char>& readData, SocketIPAddress& ipAddress);

    /// @brief Reads a UDP datagram that was sent by a remote host. Socket is blocked until they're actually received or
    /// timeout occurs
    /// @param[in] data Span of memory pointing at a buffer that will receive the read data
    /// @param[out] readData A sub-Span of `data` that has the length of actually read bytes
    /// @param[out] ipAddress IP address of the sender host
    /// @param[in] timeout For how many milliseconds the read should wait before timing out
    /// @return Valid Result if bytes have been read successfully and timeout didn't occur
    [[nodiscard]] Result readWithTimeout(Span<char> data, Span<char>& readData, SocketIPAddress& ipAddress,
                                         Time::Milliseconds timeout);

  private:
    SocketDescriptor& socket;
};
Pagghiu commented 3 months ago

Hi @silent-tech,

The current SocketDescriptor / SocketServer / SocketClient classes should already allow handling basic connected UDP sockets, that I think is what you're trying to do looking at your header definition.

It probably wasn't clear because of:

I have been adding SocketServer::bind and a simple (connected) UDP socket test (see cf3034e). I've been using this occasion cleanup that library a little bit in a couple of additional commits, splitting it into more organized files and updating docs / refactoring/renaming a couple of methods.

If you need some more UDP specific features (like unconnected send, multicast addresses or similar additions) I suggest to create a SocketClientUDP class deriving from SocketClient to add the wanted functionality there (in an Internal/SocketClientUDP.inl file), making them clearly not applicable to non-UDP sockets (like the regular TCP ones).

Regarding this issue we can maybe re-target it to add "more advanced UDP features".