diff options
author | B. Stack <bgstack15@gmail.com> | 2023-01-22 13:56:55 -0500 |
---|---|---|
committer | B. Stack <bgstack15@gmail.com> | 2023-01-22 13:56:55 -0500 |
commit | 75e05bc441382db69c842a64c562738cb749214e (patch) | |
tree | 698b60b3b4b914bf7958cf1174d0373909bf1e8f /zen/socket.h | |
parent | add upstream 11.29 (diff) | |
download | FreeFileSync-75e05bc441382db69c842a64c562738cb749214e.tar.gz FreeFileSync-75e05bc441382db69c842a64c562738cb749214e.tar.bz2 FreeFileSync-75e05bc441382db69c842a64c562738cb749214e.zip |
add upstream 12.0
Diffstat (limited to 'zen/socket.h')
-rw-r--r-- | zen/socket.h | 78 |
1 files changed, 71 insertions, 7 deletions
diff --git a/zen/socket.h b/zen/socket.h index 8e92b616..df8b768b 100644 --- a/zen/socket.h +++ b/zen/socket.h @@ -13,7 +13,6 @@ #include <netdb.h> //getaddrinfo - namespace zen { #define THROW_LAST_SYS_ERROR_WSA(functionName) \ @@ -26,14 +25,23 @@ const SocketType invalidSocket = -1; inline void closeSocket(SocketType s) { ::close(s); } warn_static("log on error!") +void setNonBlocking(SocketType socket, bool value); //throw SysError + //Winsock needs to be initialized before calling any of these functions! (WSAStartup/WSACleanup) + + class Socket //throw SysError { public: - Socket(const Zstring& server, const Zstring& serviceName) //throw SysError + Socket(const Zstring& server, const Zstring& serviceName, int timeoutSec) //throw SysError { + //GetAddrInfo(): "If the pNodeName parameter contains an empty string, all registered addresses on the local computer are returned." + // "If the pNodeName parameter points to a string equal to "localhost", all loopback addresses on the local computer are returned." + if (trimCpy(server).empty()) + throw SysError(_("Server name must not be empty.")); + const addrinfo hints { .ai_flags = AI_ADDRCONFIG, //save a AAAA lookup on machines that can't use the returned data anyhow @@ -49,23 +57,62 @@ public: if (!servinfo) throw SysError(formatSystemError("getaddrinfo", L"", L"Empty server info.")); - const auto getConnectedSocket = [](const auto& /*::addrinfo*/ ai) + const auto getConnectedSocket = [timeoutSec](const auto& /*addrinfo*/ ai) { SocketType testSocket = ::socket(ai.ai_family, //int socket_family - SOCK_CLOEXEC | + SOCK_CLOEXEC | SOCK_NONBLOCK | ai.ai_socktype, //int socket_type ai.ai_protocol); //int protocol if (testSocket == invalidSocket) THROW_LAST_SYS_ERROR_WSA("socket"); ZEN_ON_SCOPE_FAIL(closeSocket(testSocket)); - warn_static("support timeout! https://stackoverflow.com/questions/2597608/c-socket-connection-timeout") - if (::connect(testSocket, ai.ai_addr, static_cast<int>(ai.ai_addrlen)) != 0) - THROW_LAST_SYS_ERROR_WSA("connect"); + + if (::connect(testSocket, ai.ai_addr, static_cast<int>(ai.ai_addrlen)) != 0) //0 or SOCKET_ERROR(-1) + { + if (errno != EINPROGRESS) + THROW_LAST_SYS_ERROR_WSA("connect"); + + fd_set writefds{}; + fd_set exceptfds{}; //mostly only relevant for connect() + FD_SET(testSocket, &writefds); + FD_SET(testSocket, &exceptfds); + + /*const*/ timeval tv{.tv_sec = timeoutSec}; + + const int rv = ::select( + testSocket + 1, //int nfds = "highest-numbered file descriptor in any of the three sets, plus 1" + nullptr, //fd_set* readfds + &writefds, //fd_set* writefds + &exceptfds, //fd_set* exceptfds + &tv); //const timeval* timeout + if (rv < 0) + THROW_LAST_SYS_ERROR_WSA("select"); + + if (rv == 0) //time-out! + throw SysError(formatSystemError("select, " + utfTo<std::string>(_P("1 sec", "%x sec", timeoutSec)), ETIMEDOUT)); + int error = 0; + socklen_t optLen = sizeof(error); + if (::getsockopt(testSocket, //[in] SOCKET s + SOL_SOCKET, //[in] int level + SO_ERROR, //[in] int optname + reinterpret_cast<char*>(&error), //[out] char* optval + &optLen) //[in, out] socklen_t* optlen + != 0) + THROW_LAST_SYS_ERROR_WSA("getsockopt(SO_ERROR)"); + + if (error != 0) + throw SysError(formatSystemError("connect, SO_ERROR", static_cast<ErrorCode>(error))/*== system error code, apparently!?*/); + } + + setNonBlocking(testSocket, false); //throw SysError return testSocket; }; + /* getAddrInfo() often returns only one ai_family == AF_INET address, but more items are possible: + facebook.com: 1 x AF_INET6, 3 x AF_INET + microsoft.com: 5 x AF_INET => server not allowing connection: hanging for 5x timeoutSec :( */ std::optional<SysError> firstError; for (const auto* /*::addrinfo*/ si = servinfo; si; si = si->ai_next) try @@ -152,6 +199,23 @@ void shutdownSocketSend(SocketType socket) //throw SysError if (::shutdown(socket, SHUT_WR) != 0) THROW_LAST_SYS_ERROR_WSA("shutdown"); } + + +inline +void setNonBlocking(SocketType socket, bool nonBlocking) //throw SysError +{ + int flags = ::fcntl(socket, F_GETFL); + if (flags == -1) + THROW_LAST_SYS_ERROR("fcntl(F_GETFL)"); + + if (nonBlocking) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if (::fcntl(socket, F_SETFL, flags) != 0) + THROW_LAST_SYS_ERROR(nonBlocking ? "fcntl(F_SETFL, O_NONBLOCK)" : "fcntl(F_SETFL, ~O_NONBLOCK)"); +} } #endif //SOCKET_H_23498325972583947678456437 |