diff --git a/coresdk/src/backend/network_driver.cpp b/coresdk/src/backend/network_driver.cpp index cdff16ad..0bf45845 100644 --- a/coresdk/src/backend/network_driver.cpp +++ b/coresdk/src/backend/network_driver.cpp @@ -22,6 +22,30 @@ #include #include + +// Platform-specific includes for non-blocking socket operations +#ifdef _WIN32 + #include + #include + #define SOCKET_ERRNO WSAGetLastError() + #define SOCKET_WOULDBLOCK WSAEWOULDBLOCK + #define SOCKET_INPROGRESS WSAEINPROGRESS +#else + #include + #include + #include + #include + #include + #include + #include + #define SOCKET_ERRNO errno + #define SOCKET_WOULDBLOCK EWOULDBLOCK + #define SOCKET_INPROGRESS EINPROGRESS +#endif + +// Default connection timeout in seconds +#define SK_CONNECTION_TIMEOUT_SECONDS 5 + namespace splashkit_lib { // This set keeps track of all of the sockets to see if there is activity @@ -67,29 +91,191 @@ namespace splashkit_lib return result; } + /** + * Helper function to connect with timeout using native sockets. + * Returns the connected socket fd on success, -1 on failure. + */ + static int _connect_with_timeout(const char *host, unsigned short port, int timeout_seconds) + { + struct sockaddr_in server_addr; + int sock_fd; + + // Create socket + sock_fd = socket(AF_INET, SOCK_STREAM, 0); + if (sock_fd < 0) + { + LOG(ERROR) << "Failed to create socket: " << SOCKET_ERRNO; + return -1; + } + + // Set up server address + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + + // Convert host to IP address + if (inet_pton(AF_INET, host, &server_addr.sin_addr) <= 0) + { + // Try to resolve hostname using SDLNet + IPaddress addr; + if (SDLNet_ResolveHost(&addr, host, port) < 0) + { + LOG(ERROR) << "Failed to resolve host: " << host; +#ifdef _WIN32 + closesocket(sock_fd); +#else + close(sock_fd); +#endif + return -1; + } + server_addr.sin_addr.s_addr = addr.host; + } + + // Set socket to non-blocking mode +#ifdef _WIN32 + unsigned long mode = 1; + ioctlsocket(sock_fd, FIONBIO, &mode); +#else + int flags = fcntl(sock_fd, F_GETFL, 0); + fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK); +#endif + + // Initiate connection (returns immediately for non-blocking socket) + int connect_result = connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)); + + if (connect_result < 0) + { + int err = SOCKET_ERRNO; + // Check if connection is in progress (expected for non-blocking) + if (err != SOCKET_INPROGRESS && err != SOCKET_WOULDBLOCK) + { + LOG(ERROR) << "Connect failed immediately: " << err; +#ifdef _WIN32 + closesocket(sock_fd); +#else + close(sock_fd); +#endif + return -1; + } + + // Wait for connection with timeout using select() + fd_set write_fds, except_fds; + struct timeval timeout; + + FD_ZERO(&write_fds); + FD_ZERO(&except_fds); + FD_SET(sock_fd, &write_fds); + FD_SET(sock_fd, &except_fds); + + timeout.tv_sec = timeout_seconds; + timeout.tv_usec = 0; + + int select_result = select(sock_fd + 1, NULL, &write_fds, &except_fds, &timeout); + + if (select_result <= 0) + { + if (select_result == 0) + { + LOG(ERROR) << "Connection timeout after " << timeout_seconds << " seconds"; + } + else + { + LOG(ERROR) << "Select failed: " << SOCKET_ERRNO; + } +#ifdef _WIN32 + closesocket(sock_fd); +#else + close(sock_fd); +#endif + return -1; + } + + // Check if connection was successful + int so_error = 0; + socklen_t len = sizeof(so_error); + if (getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, (char*)&so_error, &len) < 0 || so_error != 0) + { + LOG(ERROR) << "Connection failed: " << (so_error ? so_error : SOCKET_ERRNO); +#ifdef _WIN32 + closesocket(sock_fd); +#else + close(sock_fd); +#endif + return -1; + } + } + + // Set socket back to blocking mode for normal operation +#ifdef _WIN32 + mode = 0; + ioctlsocket(sock_fd, FIONBIO, &mode); +#else + int flags_blocking = fcntl(sock_fd, F_GETFL, 0); + fcntl(sock_fd, F_SETFL, flags_blocking & ~O_NONBLOCK); +#endif + + return sock_fd; + } + sk_network_connection sk_open_tcp_connection(const char *host, unsigned short port) { internal_sk_init(); - IPaddress addr; - TCPsocket client; - sk_network_connection result; result.id = NETWORK_CONNECTION_PTR; result.kind = UNKNOWN; result._socket = nullptr; + // If host is null, we're creating a server socket - use the original blocking method + if (host == nullptr) + { + IPaddress addr; + if (SDLNet_ResolveHost(&addr, nullptr, port) < 0) + return result; + + TCPsocket server = SDLNet_TCP_Open(&addr); + if (server) + { + result.kind = TCP; + result._socket = server; + } + else + { + LOG(ERROR) << "SDLNet_TCP_Open (server): " << SDLNet_GetError(); + } + return result; + } + + // For client connections, use non-blocking connect with timeout + int sock_fd = _connect_with_timeout(host, port, SK_CONNECTION_TIMEOUT_SECONDS); + + if (sock_fd < 0) + { + return result; // Connection failed + } + + // Successfully connected! Now we need to wrap this in SDL_net's TCPsocket + // Unfortunately, SDL_net doesn't provide a way to create a TCPsocket from a raw fd. + // So we close the test connection and use SDL_net for the actual connection. + // Since we verified the connection works, SDLNet_TCP_Open should succeed quickly. +#ifdef _WIN32 + closesocket(sock_fd); +#else + close(sock_fd); +#endif + + // Now use SDL_net - the connection should succeed immediately since we verified connectivity + IPaddress addr; if (SDLNet_ResolveHost(&addr, host, port) < 0) return result; - client = SDLNet_TCP_Open(&addr); + TCPsocket client = SDLNet_TCP_Open(&addr); if (client) { result.kind = TCP; result._socket = client; - if (host) - SDLNet_TCP_AddSocket(_sockets, client); + SDLNet_TCP_AddSocket(_sockets, client); } else {