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

Add support for blocking sockets #116

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions examples/simple-http-get.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
61 changes: 59 additions & 2 deletions include/SDL3_net/SDL_net.h
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,63 @@ 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_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. */
Expand Down Expand Up @@ -1228,7 +1285,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
Expand All @@ -1246,7 +1303,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
Expand Down
110 changes: 97 additions & 13 deletions src/SDL_net.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1577,18 +1603,76 @@ void SDLNet_DestroyDatagramSocket(SDLNet_DatagramSocket *sock)
}
}

typedef union SDLNet_GenericSocket
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) {
Expand Down
3 changes: 3 additions & 0 deletions src/SDL_net.sym
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ SDL3_net_0.0.0 {
SDLNet_GetAddressString;
SDLNet_GetConnectionStatus;
SDLNet_GetLocalAddresses;
SDLNet_GetSocketBlocking;
SDLNet_GetStreamSocketAddress;
SDLNet_GetStreamSocketPendingWrites;
SDLNet_Init;
Expand All @@ -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;
Expand Down