diff --git a/modules/juce_core/network/juce_Socket.cpp b/modules/juce_core/network/juce_Socket.cpp index c788ba6b17..a1b2b599aa 100644 --- a/modules/juce_core/network/juce_Socket.cpp +++ b/modules/juce_core/network/juce_Socket.cpp @@ -82,11 +82,38 @@ namespace SocketHelpers return setOption (handle, SOL_SOCKET, property, value); } - static bool resetSocketOptions (SocketHandle handle, bool isDatagram, bool allowBroadcast) noexcept + static std::optional getBufferSize (SocketHandle handle, int property) { + int result; + socklen_t outParamSize = sizeof (result); + + if (getsockopt (handle, SOL_SOCKET, property, reinterpret_cast (&result), &outParamSize) != 0 + || outParamSize != sizeof (result)) + { + return std::nullopt; + } + + return result; + } + + static bool resetSocketOptions (SocketHandle handle, bool isDatagram, bool allowBroadcast, const SocketOptions& options) noexcept + { + auto getCurrentBufferSizeWithMinimum = [handle] (int property) + { + constexpr auto minBufferSize = 65536; + + if (auto currentBufferSize = getBufferSize (handle, property)) + return std::max (*currentBufferSize, minBufferSize); + + return minBufferSize; + }; + + const auto receiveBufferSize = options.getReceiveBufferSize().value_or (getCurrentBufferSizeWithMinimum (SO_RCVBUF)); + const auto sendBufferSize = options.getSendBufferSize() .value_or (getCurrentBufferSizeWithMinimum (SO_SNDBUF)); + return handle != invalidSocket - && setOption (handle, SO_RCVBUF, (int) 65536) - && setOption (handle, SO_SNDBUF, (int) 65536) + && setOption (handle, SO_RCVBUF, receiveBufferSize) + && setOption (handle, SO_SNDBUF, sendBufferSize) && (isDatagram ? ((! allowBroadcast) || setOption (handle, SO_BROADCAST, (int) 1)) : setOption (handle, IPPROTO_TCP, TCP_NODELAY, (int) 1)); } @@ -370,7 +397,8 @@ namespace SocketHelpers CriticalSection& readLock, const String& hostName, int portNumber, - int timeOutMillisecs) noexcept + int timeOutMillisecs, + const SocketOptions& options) noexcept { bool success = false; @@ -421,7 +449,7 @@ namespace SocketHelpers { auto h = (SocketHandle) handle.load(); setSocketBlockingState (h, true); - resetSocketOptions (h, false, false); + resetSocketOptions (h, false, false, options); } } @@ -458,8 +486,9 @@ StreamingSocket::StreamingSocket() SocketHelpers::initSockets(); } -StreamingSocket::StreamingSocket (const String& host, int portNum, int h) - : hostName (host), +StreamingSocket::StreamingSocket (const String& host, int portNum, int h, const SocketOptions& optionsIn) + : options (optionsIn), + hostName (host), portNumber (portNum), handle (h), connected (true) @@ -467,7 +496,7 @@ StreamingSocket::StreamingSocket (const String& host, int portNum, int h) jassert (SocketHelpers::isValidPortNumber (portNum)); SocketHelpers::initSockets(); - SocketHelpers::resetSocketOptions ((SocketHandle) h, false, false); + SocketHelpers::resetSocketOptions ((SocketHandle) h, false, false, options); } StreamingSocket::~StreamingSocket() @@ -535,12 +564,12 @@ bool StreamingSocket::connect (const String& remoteHostName, int remotePortNumbe isListener = false; connected = SocketHelpers::connectSocket (handle, readLock, remoteHostName, - remotePortNumber, timeOutMillisecs); + remotePortNumber, timeOutMillisecs, options); if (! connected) return false; - if (! SocketHelpers::resetSocketOptions ((SocketHandle) handle.load(), false, false)) + if (! SocketHelpers::resetSocketOptions ((SocketHandle) handle.load(), false, false, options)) { close(); return false; @@ -606,7 +635,7 @@ StreamingSocket* StreamingSocket::waitForNextConnection() const if (newSocket >= 0 && connected) return new StreamingSocket (inet_ntoa (((struct sockaddr_in*) &address)->sin_addr), - portNumber, newSocket); + portNumber, newSocket, options); } return nullptr; @@ -629,7 +658,8 @@ bool StreamingSocket::isLocal() const noexcept //============================================================================== //============================================================================== -DatagramSocket::DatagramSocket (bool canBroadcast) +DatagramSocket::DatagramSocket (bool canBroadcast, const SocketOptions& optionsIn) + : options { optionsIn } { SocketHelpers::initSockets(); @@ -637,7 +667,7 @@ DatagramSocket::DatagramSocket (bool canBroadcast) if (handle >= 0) { - SocketHelpers::resetSocketOptions ((SocketHandle) handle.load(), true, canBroadcast); + SocketHelpers::resetSocketOptions ((SocketHandle) handle.load(), true, canBroadcast, options); SocketHelpers::makeReusable (handle); } } diff --git a/modules/juce_core/network/juce_Socket.h b/modules/juce_core/network/juce_Socket.h index 689161718d..41a46a0bc2 100644 --- a/modules/juce_core/network/juce_Socket.h +++ b/modules/juce_core/network/juce_Socket.h @@ -23,6 +23,50 @@ namespace juce { +/** + Options used for the configuration of the underlying system socket in the + StreamingSocket and DatagramSocket classes. + + @see StreamingSocket, DatagramSocket + + @tags{Core} +*/ +class JUCE_API SocketOptions +{ +public: + /** The provided size will be used to configure the socket's SO_RCVBUF property. Increasing the + buffer size can reduce the number of lost packets with the DatagramSocket class, if the + socket is to receive packets in large bursts. + + If this property is not specified, the system default value will be used, but a minimum of + 65536 will be ensured. + */ + [[nodiscard]] SocketOptions withReceiveBufferSize (int size) const + { + return withMember (*this, &SocketOptions::receiveBufferSize, size); + } + + /** The provided size will be used to configure the socket's SO_SNDBUF property. + + If this property is not specified, the system default value will be used, but a minimum of + 65536 will be ensured. + */ + [[nodiscard]] SocketOptions withSendBufferSize (int size) const + { + return withMember (*this, &SocketOptions::sendBufferSize, size); + } + + /** @see withReceiveBufferSize() */ + [[nodiscard]] auto getReceiveBufferSize() const { return receiveBufferSize; } + + /** @see withSendBufferSize() */ + [[nodiscard]] auto getSendBufferSize() const { return sendBufferSize; } + +private: + std::optional receiveBufferSize; + std::optional sendBufferSize; +}; + //============================================================================== /** A wrapper for a streaming (TCP) socket. @@ -37,6 +81,8 @@ namespace juce class JUCE_API StreamingSocket final { public: + using Options = SocketOptions; + //============================================================================== /** Creates an uninitialised socket. @@ -49,6 +95,20 @@ public: */ StreamingSocket(); + /** Creates an uninitialised socket and allows specifying options related to the + configuration of the underlying socket. + + To connect it, use the connect() method, after which you can read() or write() + to it. + + To wait for other sockets to connect to this one, the createListener() method + enters "listener" mode, and can be used to spawn new sockets for each connection + that comes along. + */ + explicit StreamingSocket (const SocketOptions& optionsIn) + : options { optionsIn } + {} + /** Destructor. */ ~StreamingSocket(); @@ -178,12 +238,13 @@ public: private: //============================================================================== + SocketOptions options; String hostName; std::atomic portNumber { 0 }, handle { -1 }; std::atomic connected { false }, isListener { false }; mutable CriticalSection readLock; - StreamingSocket (const String& hostname, int portNumber, int handle); + StreamingSocket (const String& hostname, int portNumber, int handle, const SocketOptions& options); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StreamingSocket) }; @@ -203,7 +264,20 @@ private: class JUCE_API DatagramSocket final { public: + using Options = SocketOptions; + //============================================================================== + /** Creates a datagram socket and allows specifying options related to the + configuration of the underlying socket. + + You first need to bind this socket to a port with bindToPort if you intend to read + from this socket. + + If enableBroadcasting is true, the socket will be allowed to send broadcast messages + (may require extra privileges on linux) + */ + DatagramSocket (bool enableBroadcasting, const SocketOptions& optionsIn); + /** Creates a datagram socket. You first need to bind this socket to a port with bindToPort if you intend to read @@ -212,8 +286,19 @@ public: If enableBroadcasting is true, the socket will be allowed to send broadcast messages (may require extra privileges on linux) */ - DatagramSocket (bool enableBroadcasting = false); + explicit DatagramSocket (bool enableBroadcasting) + : DatagramSocket (enableBroadcasting, SocketOptions{}) + {} + /** Creates a datagram socket. + + You first need to bind this socket to a port with bindToPort if you intend to read + from this socket. + + This constructor creates a socket that does not allow sending broadcast messages. + */ + DatagramSocket() : DatagramSocket (false) + {} /** Destructor. */ ~DatagramSocket(); @@ -354,6 +439,7 @@ public: private: //============================================================================== + SocketOptions options; std::atomic handle { -1 }; bool isBound = false; String lastBindAddress, lastServerHost;