From 24517868a0bbaa3add345c3e13e61a53593228e4 Mon Sep 17 00:00:00 2001 From: past-due <30942300+past-due@users.noreply.github.com> Date: Fri, 10 May 2024 14:59:16 -0400 Subject: [PATCH] netplay: Enable TCP_NODELAY for game sockets Among other reasons, we already use an internal buffer --- lib/netplay/netplay.cpp | 23 +++++++++++++++++++++++ lib/netplay/netplay.h | 2 ++ lib/netplay/netsocket.cpp | 19 +++++++++++++++++++ lib/netplay/netsocket.h | 2 ++ src/configuration.cpp | 3 +++ 5 files changed, 49 insertions(+) diff --git a/lib/netplay/netplay.cpp b/lib/netplay/netplay.cpp index ac667e78d86..fbf2f9c03f5 100644 --- a/lib/netplay/netplay.cpp +++ b/lib/netplay/netplay.cpp @@ -89,6 +89,7 @@ char masterserver_name[255] = {'\0'}; static unsigned int masterserver_port = 0, gameserver_port = 0; static bool bJoinPrefTryIPv6First = true; static bool bDefaultHostFreeChatEnabled = true; +static bool bEnableTCPNoDelay = true; // This is for command line argument override // Disables port saving and reading from/to config @@ -3993,6 +3994,12 @@ static void NETallowJoining() tmp_connectState[i].ip = rIP; tmp_connectState[i].connectTime = std::chrono::steady_clock::now(); tmp_connectState[i].connectState = TmpSocketInfo::TmpConnectState::PendingInitialConnect; + + if (bEnableTCPNoDelay) + { + // Enable TCP_NODELAY + socketSetTCPNoDelay(*tmp_socket[i], true); + } } if (checkSockets(*tmp_socket_set, NET_READ_TIMEOUT) > 0) @@ -4921,6 +4928,12 @@ bool NETjoinGame(const char *host, uint32_t port, const char *playername, const // `client_transient_socket` is used to talk to host machine SocketSet_AddSocket(*client_socket_set, client_transient_socket); + if (bEnableTCPNoDelay) + { + // Enable TCP_NODELAY + socketSetTCPNoDelay(*client_transient_socket, true); + } + // Send NETCODE_VERSION_MAJOR and NETCODE_VERSION_MINOR p_buffer = buffer; auto pushu32 = [&](uint32_t value) { @@ -5170,6 +5183,16 @@ bool NETgetDefaultMPHostFreeChatPreference() return bDefaultHostFreeChatEnabled; } +void NETsetEnableTCPNoDelay(bool enabled) +{ + bEnableTCPNoDelay = enabled; +} + +bool NETgetEnableTCPNoDelay() +{ + return bEnableTCPNoDelay; +} + void NETsetPlayerConnectionStatus(CONNECTION_STATUS status, unsigned player) { unsigned n; diff --git a/lib/netplay/netplay.h b/lib/netplay/netplay.h index de77fadd087..2010ecab5b1 100644 --- a/lib/netplay/netplay.h +++ b/lib/netplay/netplay.h @@ -443,6 +443,8 @@ void NETsetJoinPreferenceIPv6(bool bTryIPv6First); bool NETgetJoinPreferenceIPv6(); void NETsetDefaultMPHostFreeChatPreference(bool enabled); bool NETgetDefaultMPHostFreeChatPreference(); +void NETsetEnableTCPNoDelay(bool enabled); +bool NETgetEnableTCPNoDelay(); void NETsetGamePassword(const char *password); void NETBroadcastPlayerInfo(uint32_t index); diff --git a/lib/netplay/netsocket.cpp b/lib/netplay/netsocket.cpp index 81eb96bcb08..45a1fbe6a6b 100644 --- a/lib/netplay/netsocket.cpp +++ b/lib/netplay/netsocket.cpp @@ -40,6 +40,12 @@ #pragma clang diagnostic ignored "-Wshorten-64-to-32" // FIXME!! #endif +#if defined(WZ_OS_UNIX) +# include // For TCP_NODELAY +#elif defined(WZ_OS_WIN) +// Already included Winsock2.h which defines TCP_NODELAY +#endif + enum { SOCK_CONNECTION, @@ -795,6 +801,19 @@ void socketBeginCompression(Socket& sock) wzMutexUnlock(socketThreadMutex); } +bool socketSetTCPNoDelay(Socket& sock, bool nodelay) +{ +#if defined(TCP_NODELAY) + int value = (nodelay) ? 1 : 0; + int result = setsockopt(sock.fd[SOCK_CONNECTION], IPPROTO_TCP, TCP_NODELAY, (char *) &value, sizeof(int)); + debug(LOG_INFO, "Setting TCP_NODELAY on socket: %s", (result != SOCKET_ERROR) ? "success" : "failure"); + return result != SOCKET_ERROR; +#else + debug(LOG_INFO, "Unable to set TCP_NODELAY on socket - unsupported"); + return false; +#endif +} + Socket::~Socket() { if (isCompressed) diff --git a/lib/netplay/netsocket.h b/lib/netplay/netsocket.h index 431aa602fa1..6946a829cdc 100644 --- a/lib/netplay/netsocket.h +++ b/lib/netplay/netsocket.h @@ -116,6 +116,8 @@ ssize_t readAll(Socket& sock, void *buf, size_t size, unsigned timeout);///< Rea WZ_DECL_NONNULL(2) ssize_t writeAll(Socket& sock, const void *buf, size_t size, size_t *rawByteCount = nullptr); ///< Nonblocking write of size bytes to the Socket. All bytes will be written asynchronously, by a separate thread. Raw count of bytes (after compression) returned in rawByteCount, which will often be 0 until the socket is flushed. +bool socketSetTCPNoDelay(Socket& sock, bool nodelay); ///< nodelay = true disables the Nagle algorithm for TCP socket + // Sockets, compressed. void socketBeginCompression(Socket& sock); ///< Makes future data sent compressed, and future data received expected to be compressed. bool socketReadDisconnected(const Socket& sock); ///< If readNoInt returned 0, returns true if this is the result of a disconnect, or false if the input compressed data just hasn't produced any output bytes. diff --git a/src/configuration.cpp b/src/configuration.cpp index cbd7e3d9dc0..3bf9c88cf7b 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -423,6 +423,7 @@ bool loadConfig() } NETsetJoinPreferenceIPv6(iniGetBool("prefer_ipv6", true).value()); NETsetDefaultMPHostFreeChatPreference(iniGetBool("hostingChatDefault", NETgetDefaultMPHostFreeChatPreference()).value()); + NETsetEnableTCPNoDelay(iniGetBool("tcp_nodelay", NETgetEnableTCPNoDelay()).value()); setPublicIPv4LookupService(iniGetString("publicIPv4LookupService_Url", WZ_DEFAULT_PUBLIC_IPv4_LOOKUP_SERVICE_URL).value(), iniGetString("publicIPv4LookupService_JSONKey", WZ_DEFAULT_PUBLIC_IPv4_LOOKUP_SERVICE_JSONKEY).value()); setPublicIPv6LookupService(iniGetString("publicIPv6LookupService_Url", WZ_DEFAULT_PUBLIC_IPv6_LOOKUP_SERVICE_URL).value(), iniGetString("publicIPv6LookupService_JSONKey", WZ_DEFAULT_PUBLIC_IPv6_LOOKUP_SERVICE_JSONKEY).value()); war_SetFMVmode((FMV_MODE)iniGetInteger("FMVmode", war_GetFMVmode()).value()); @@ -722,6 +723,8 @@ bool saveConfig() } iniSetBool("prefer_ipv6", NETgetJoinPreferenceIPv6()); iniSetInteger("hostingChatDefault", (NETgetDefaultMPHostFreeChatPreference()) ? 1 : 0); + iniSetInteger("tcp_nodelay", (NETgetEnableTCPNoDelay()) ? 1 : 0); + iniSetString("publicIPv4LookupService_Url", getPublicIPv4LookupServiceUrl()); iniSetString("publicIPv4LookupService_JSONKey", getPublicIPv4LookupServiceJSONKey()); iniSetString("publicIPv6LookupService_Url", getPublicIPv6LookupServiceUrl());