From b7f0122a8ae9b4a8057b7b51aa7a2bc7932e5e78 Mon Sep 17 00:00:00 2001 From: Ivan Stojanovski <154389969+egnima@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:03:57 -0600 Subject: [PATCH 1/8] Update SDL_net.h --- include/SDL3_net/SDL_net.h | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/include/SDL3_net/SDL_net.h b/include/SDL3_net/SDL_net.h index 0665594..4332850 100644 --- a/include/SDL3_net/SDL_net.h +++ b/include/SDL3_net/SDL_net.h @@ -425,6 +425,64 @@ extern SDL_DECLSPEC SDLNet_Address **SDLCALL SDLNet_GetLocalAddresses(int *num_a extern SDL_DECLSPEC void SDLCALL SDLNet_FreeLocalAddresses(SDLNet_Address **addresses); +/* Generic socket API... */ + +typedef union SDLNet_GenericSocket SDLNet_GenericSocket; /**< a generic socket. */ + +typedef struct SDLNet_CommonSocket SDLNet_CommonSocket; /**< the base socket. */ + +/** + * Set a socket to blocking or non-blocking mode. At initialization, blocking + * is disabled. + * + * Setting a socket to blocking mode means that when you call a function that + * reads or writes data, it will not return until it has done its work. On the + * other hand, a non-blocking socket will return immediately. + * + * Most games use non-blocking mode. If the data you are sending or receiving + * is critical priority, and you trust the network, it might be okay + * to use blocking mode. Also, if the networking is being done in a separate + * dedicated thread, blocking mode might be okay. It's worth noting that + * blocking mode is preferred to a naive busy-wait loop because it will save + * CPU time and delegate the job of waiting diligently to the system. + * + * \param sock The socket to set blocking mode on. + * \param blocking true to enable blocking mode, false to disable it. + * \returns true on success, false on error; call SDL_GetError() for details. + * + * \since This function is available since SDL_Net 3.0.0. + * + * \sa SDLNet_GetSocketBlocking + * \sa SDLNet_SetBlockingSocketReceiveTimeout + * \sa SDLNet_SetBlockingSocketTimeout + */ +extern SDL_DECLSPEC bool SDLCALL SDLNet_SetSocketBlocking(SDLNet_GenericSocket *sock, bool blocking); + +/** + * Get the blocking mode of a socket. + * + * \param sock The socket to query. + * \returns true if the socket is in blocking mode, false if it is not. + * + * \since This function is available since SDL_Net 3.0.0. + * + * \sa SDLNet_SetSocketBlocking + */ +extern SDL_DECLSPEC bool SDLCALL SDLNet_GetSocketBlocking(SDLNet_GenericSocket *sock); + +/** + * Set the send or receive timeout for a blocking socket. + * + * \param sock The socket to set the send timeout on. + * \param timeout_ms The timeout in milliseconds. + * \param is_recv true to set the receive timeout, false to set the send timeout. + * \returns true on success, false on error; call SDL_GetError() for details. + * + * \sa SDLNet_SetSocketBlocking + */ +extern SDL_DECLSPEC bool SDLCALL SDLNet_SetBlockingSocketTimeout(SDLNet_GenericSocket *sock, int timeout_ms, bool is_recv); + + /* Streaming (TCP) API... */ typedef struct SDLNet_StreamSocket SDLNet_StreamSocket; /**< a TCP socket. Reliable transmission, with the usual pros/cons. */ From ea0337b0f26ba030b5fa4f7baf64163de96453a1 Mon Sep 17 00:00:00 2001 From: Ivan Stojanovski <154389969+egnima@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:17:03 -0600 Subject: [PATCH 2/8] Update SDL_net.c --- src/SDL_net.c | 106 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 11 deletions(-) diff --git a/src/SDL_net.c b/src/SDL_net.c index 8d1bf36..aabee98 100644 --- a/src/SDL_net.c +++ b/src/SDL_net.c @@ -795,12 +795,22 @@ static struct addrinfo *MakeAddrInfoWithPort(const SDLNet_Address *addr, const i } +struct SDLNet_CommonSocket +{ + SDLNet_SocketType socktype; + SDLNet_Address* addr; + Uint16 port; + Socket handle; + bool blocking; +}; + struct SDLNet_StreamSocket { SDLNet_SocketType socktype; SDLNet_Address *addr; Uint16 port; Socket handle; + bool blocking; int status; Uint8 *pending_output_buffer; int pending_output_len; @@ -809,13 +819,22 @@ struct SDLNet_StreamSocket Uint64 simulated_failure_until; }; -static int MakeSocketNonblocking(Socket handle) +static int SetSocketBlocking(Socket handle, bool blocking) { #ifdef _WIN32 - DWORD one = 1; - return ioctlsocket(handle, FIONBIO, &one); + u_long mode = blocking ? 0 : 1; + return ioctlsocket(handle, FIONBIO, &mode); #else - return fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK); + int flags = fcntl(handle, F_GETFL, 0); + if (flags == -1) { + return -1; + } + if (blocking) { + flags &= ~O_NONBLOCK; + } else { + flags |= O_NONBLOCK; + } + return fcntl(handle, F_SETFL, flags); #endif } @@ -863,7 +882,7 @@ SDLNet_StreamSocket *SDLNet_CreateClient(SDLNet_Address *addr, Uint16 port) return NULL; } - if (MakeSocketNonblocking(sock->handle) < 0) { + if (SetSocketBlocking(sock->handle, false) < 0) { CloseSocketHandle(sock->handle); freeaddrinfo(addrwithport); SDL_free(sock); @@ -899,7 +918,7 @@ static int CheckClientConnection(SDLNet_StreamSocket *sock, int timeoutms) if (SDL_GetTicks() >= sock->simulated_failure_ticks) { sock->status = SDL_SetError("simulated failure"); } else */ - if (SDLNet_WaitUntilInputAvailable((void **) &sock, 1, timeoutms) == -1) { + if (SDLNet_WaitUntilInputAvailable((SDLNet_GenericSocket **) &sock, 1, timeoutms) == -1) { sock->status = -1; // just abandon the whole enterprise. } } @@ -923,6 +942,7 @@ struct SDLNet_Server SDLNet_Address *addr; // bound to this address (NULL for any). Uint16 port; Socket handle; + bool blocking; }; SDLNet_Server *SDLNet_CreateServer(SDLNet_Address *addr, Uint16 port) @@ -955,7 +975,7 @@ SDLNet_Server *SDLNet_CreateServer(SDLNet_Address *addr, Uint16 port) return NULL; } - if (MakeSocketNonblocking(server->handle) < 0) { + if (SetSocketBlocking(server->handle, false) < 0) { CloseSocketHandle(server->handle); freeaddrinfo(addrwithport); SDL_free(server); @@ -1012,7 +1032,7 @@ bool SDLNet_AcceptClient(SDLNet_Server *server, SDLNet_StreamSocket **client_str return WouldBlock(err) ? true : SetSocketErrorBool("Failed to accept new connection", err); } - if (MakeSocketNonblocking(handle) < 0) { + if (SetSocketBlocking(server->handle, false) < 0) { CloseSocketHandle(handle); return SDL_SetError("Failed to make incoming socket non-blocking"); } @@ -1172,6 +1192,11 @@ int SDLNet_WaitUntilStreamSocketDrained(SDLNet_StreamSocket *sock, int timeoutms return SDL_InvalidParamError("sock"); } + if (sock->blocking) { + return SDL_SetError("Cannot wait on blocking socket"); + } + + if (timeoutms != 0) { const Uint64 endtime = (timeoutms > 0) ? (SDL_GetTicks() + timeoutms) : 0; while (SDLNet_GetStreamSocketPendingWrites(sock) > 0) { @@ -1268,6 +1293,7 @@ struct SDLNet_DatagramSocket SDLNet_Address *addr; // bound to this address (NULL for any). Uint16 port; Socket handle; + bool blocking; int percent_loss; Uint8 recv_buffer[64*1024]; SDLNet_Datagram **pending_output; @@ -1308,7 +1334,7 @@ SDLNet_DatagramSocket *SDLNet_CreateDatagramSocket(SDLNet_Address *addr, Uint16 return NULL; } - if (MakeSocketNonblocking(sock->handle) < 0) { + if (SetSocketBlocking(sock->handle, false) < 0) { CloseSocketHandle(sock->handle); freeaddrinfo(addrwithport); SDL_free(sock); @@ -1580,15 +1606,73 @@ void SDLNet_DestroyDatagramSocket(SDLNet_DatagramSocket *sock) typedef union SDLNet_GenericSocket { SDLNet_SocketType socktype; + SDLNet_CommonSocket common; SDLNet_StreamSocket stream; SDLNet_DatagramSocket dgram; SDLNet_Server server; } SDLNet_GenericSocket; +bool SDLNet_SetSocketBlocking(SDLNet_GenericSocket *sock, bool blocking) +{ + if (!sock) { + return SDL_InvalidParamError("sock"); + } + + if (sock->common.blocking == blocking) { + return true; // nothing to do. + } + + if (SetSocketBlocking(sock->common.handle, blocking) < 0) { + return false; + } + + sock->common.blocking = blocking; + return true; +} + +bool SDLNet_GetSocketBlocking(SDLNet_GenericSocket *sock) +{ + if (!sock) { + return SDL_InvalidParamError("sock"); + } + + return sock->common.blocking; +} + +bool SDLNet_SetBlockingSocketTimeout(SDLNet_GenericSocket *sock, int timeout_ms, bool is_recv) +{ + if (!sock) { + return SDL_InvalidParamError("sock"); + } + if (!sock->common.blocking) { + return SDL_SetError("Cannot set receive timeout on non-blocking socket"); + } + + #ifdef _WIN32 + DWORD timeout = timeout_ms; + + const char* timeout_pointer = (const char*)&timeout; + #else + #define SDL_US_PER_MS SDL_US_PER_SECOND / SDL_MS_PER_SECOND + + struct timeval timeout; + timeout.tv_sec = timeout_ms / SDL_MS_PER_SECOND; + timeout.tv_usec = (timeout_ms % SDL_US_PER_MS) * SDL_US_PER_MS; + + const void* timeout_pointer = (const void*)&timeout; + #undef SDL_US_PER_MS // I don't understand how this could break something, but you never know + #endif + + if (setsockopt(sock->common.handle, SOL_SOCKET, is_recv ? SO_RCVTIMEO : SO_SNDTIMEO, timeout_pointer, sizeof(timeout)) < 0) { + SDL_SetError("Error setting send/receive timeout"); + return false; + } + + return true; +} -int SDLNet_WaitUntilInputAvailable(void **vsockets, int numsockets, int timeoutms) +int SDLNet_WaitUntilInputAvailable(SDLNet_GenericSocket **sockets, int numsockets, int timeoutms) { - SDLNet_GenericSocket **sockets = (SDLNet_GenericSocket **) vsockets; if (!sockets) { return SDL_InvalidParamError("sockets"); } else if (numsockets == 0) { From ceafc888964ae27731715e7475b0f2d9229e4bda Mon Sep 17 00:00:00 2001 From: Ivan Stojanovski <154389969+egnima@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:19:16 -0600 Subject: [PATCH 3/8] Update SDL_net.h --- include/SDL3_net/SDL_net.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/SDL3_net/SDL_net.h b/include/SDL3_net/SDL_net.h index 4332850..5ee0976 100644 --- a/include/SDL3_net/SDL_net.h +++ b/include/SDL3_net/SDL_net.h @@ -1286,7 +1286,7 @@ extern SDL_DECLSPEC void SDLCALL SDLNet_DestroyDatagramSocket(SDLNet_DatagramSoc * timeout is reached, this returns zero. On error, this returns -1. * * \param vsockets an array of pointers to various objects that can be waited - * on, each cast to a void pointer. + * on, each cast to union members. * \param numsockets the number of pointers in the `vsockets` array. * \param timeout Number of milliseconds to wait for new input to become * available. -1 to wait indefinitely, 0 to check once without @@ -1304,7 +1304,7 @@ extern SDL_DECLSPEC void SDLCALL SDLNet_DestroyDatagramSocket(SDLNet_DatagramSoc * \sa SDLNet_SendDatagram * \sa SDLNet_ReceiveDatagram */ -extern SDL_DECLSPEC int SDLCALL SDLNet_WaitUntilInputAvailable(void **vsockets, int numsockets, Sint32 timeout); +extern SDL_DECLSPEC int SDLCALL SDLNet_WaitUntilInputAvailable(SDLNet_GenericSocket **vsockets, int numsockets, Sint32 timeout); /* Ends C function definitions when using C++ */ #ifdef __cplusplus From 143e65bc5933a2c101ccfe1ca12a392256d95fb1 Mon Sep 17 00:00:00 2001 From: Ivan Stojanovski <154389969+egnima@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:21:37 -0600 Subject: [PATCH 4/8] Update SDL_net.sym --- src/SDL_net.sym | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/SDL_net.sym b/src/SDL_net.sym index 4dec23f..e73b067 100644 --- a/src/SDL_net.sym +++ b/src/SDL_net.sym @@ -14,6 +14,7 @@ SDL3_net_0.0.0 { SDLNet_GetAddressString; SDLNet_GetConnectionStatus; SDLNet_GetLocalAddresses; + SDLNet_GetSocketBlocking; SDLNet_GetStreamSocketAddress; SDLNet_GetStreamSocketPendingWrites; SDLNet_Init; @@ -23,6 +24,8 @@ SDL3_net_0.0.0 { SDLNet_RefAddress; SDLNet_ResolveHostname; SDLNet_SendDatagram; + SDLNet_SetBlockingSocketTimeout; + SDLNet_SetSocketBlocking; SDLNet_SimulateAddressResolutionLoss; SDLNet_SimulateDatagramPacketLoss; SDLNet_SimulateStreamPacketLoss; From cbe2f4f63d731800e1bd4663311e03c40acd3d05 Mon Sep 17 00:00:00 2001 From: Ivan Stojanovski <154389969+egnima@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:30:35 -0600 Subject: [PATCH 5/8] Update simple-http-get.c --- examples/simple-http-get.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/simple-http-get.c b/examples/simple-http-get.c index 151d723..9007e7a 100644 --- a/examples/simple-http-get.c +++ b/examples/simple-http-get.c @@ -32,12 +32,14 @@ int main(int argc, char **argv) SDL_Log("Out of memory!"); } else if (!sock) { SDL_Log("Failed to create stream socket to %s: %s\n", argv[i], SDL_GetError()); + } else if (!SDLNet_SetSocketBlocking((SDLNet_GenericSocket*)sock, true)) { + SDL_Log("Enabling blocking mode failed!"); + } else if (!SDLNet_SetBlockingSocketTimeout((SDLNet_GenericSocket*) sock, 2000, true)) { + SDL_Log("Setting receive timeout failed!"); } else if (SDLNet_WaitUntilConnected(sock, -1) < 0) { SDL_Log("Failed to connect to %s: %s", argv[i], SDL_GetError()); } else if (!SDLNet_WriteToStreamSocket(sock, req, SDL_strlen(req))) { SDL_Log("Failed to write to %s: %s", argv[i], SDL_GetError()); - } else if (SDLNet_WaitUntilStreamSocketDrained(sock, -1) < 0) { - SDL_Log("Failed to finish write to %s: %s", argv[i], SDL_GetError()); } else { char buf[512]; int br; From db185e062b7b60bf5621283be125dd43e6cbe299 Mon Sep 17 00:00:00 2001 From: Ivan Stojanovski <154389969+egnima@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:31:34 -0600 Subject: [PATCH 6/8] Update SDL_net.h --- include/SDL3_net/SDL_net.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/SDL3_net/SDL_net.h b/include/SDL3_net/SDL_net.h index 5ee0976..bdbc738 100644 --- a/include/SDL3_net/SDL_net.h +++ b/include/SDL3_net/SDL_net.h @@ -453,7 +453,6 @@ typedef struct SDLNet_CommonSocket SDLNet_CommonSocket; /**< the base socket. * * \since This function is available since SDL_Net 3.0.0. * * \sa SDLNet_GetSocketBlocking - * \sa SDLNet_SetBlockingSocketReceiveTimeout * \sa SDLNet_SetBlockingSocketTimeout */ extern SDL_DECLSPEC bool SDLCALL SDLNet_SetSocketBlocking(SDLNet_GenericSocket *sock, bool blocking); From 23864aea30a282d8873b8a578516e3e01f6987a3 Mon Sep 17 00:00:00 2001 From: Ivan Stojanovski <154389969+egnima@users.noreply.github.com> Date: Tue, 4 Feb 2025 18:38:08 -0600 Subject: [PATCH 7/8] Fix union redefinition error --- src/SDL_net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SDL_net.c b/src/SDL_net.c index aabee98..a9ea40d 100644 --- a/src/SDL_net.c +++ b/src/SDL_net.c @@ -1603,7 +1603,7 @@ void SDLNet_DestroyDatagramSocket(SDLNet_DatagramSocket *sock) } } -typedef union SDLNet_GenericSocket +union SDLNet_GenericSocket { SDLNet_SocketType socktype; SDLNet_CommonSocket common; From e0370b0916ae20691dcf9ffc508d8145005cff05 Mon Sep 17 00:00:00 2001 From: Ivan Stojanovski <154389969+egnima@users.noreply.github.com> Date: Tue, 4 Feb 2025 18:44:05 -0600 Subject: [PATCH 8/8] ACTUALLY fix union redefinition error --- src/SDL_net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SDL_net.c b/src/SDL_net.c index a9ea40d..75f2fc7 100644 --- a/src/SDL_net.c +++ b/src/SDL_net.c @@ -1610,7 +1610,7 @@ union SDLNet_GenericSocket SDLNet_StreamSocket stream; SDLNet_DatagramSocket dgram; SDLNet_Server server; -} SDLNet_GenericSocket; +}; bool SDLNet_SetSocketBlocking(SDLNet_GenericSocket *sock, bool blocking) {