See More

module; #ifdef _WIN32 #include #include #pragma comment(lib, "ws2_32.lib") #else #include #include #include #include #include #include #include #endif export module mcpplibs.tinyhttps:socket; import std; namespace mcpplibs::tinyhttps { #ifdef _WIN32 using SocketHandle = SOCKET; constexpr SocketHandle INVALID_SOCKET_FD = INVALID_SOCKET; #else using SocketHandle = int; constexpr SocketHandle INVALID_SOCKET_FD = -1; #endif export class Socket { public: Socket() = default; ~Socket() { close(); } // Non-copyable Socket(const Socket&) = delete; Socket& operator=(const Socket&) = delete; // Move constructor Socket(Socket&& other) noexcept : fd_(other.fd_) { other.fd_ = INVALID_SOCKET_FD; } // Move assignment Socket& operator=(Socket&& other) noexcept { if (this != &other) { close(); fd_ = other.fd_; other.fd_ = INVALID_SOCKET_FD; } return *this; } [[nodiscard]] bool is_valid() const { return fd_ != INVALID_SOCKET_FD; } bool connect(const char* host, int port, int timeoutMs) { // Close existing connection if any if (is_valid()) { close(); } // Resolve address struct addrinfo hints{}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; auto portStr = std::to_string(port); struct addrinfo* result = nullptr; int rc = ::getaddrinfo(host, portStr.c_str(), &hints, &result); if (rc != 0 || result == nullptr) { return false; } // Try each address for (auto* rp = result; rp != nullptr; rp = rp->ai_next) { SocketHandle fd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (fd == INVALID_SOCKET_FD) { continue; } // Set non-blocking if (!set_non_blocking(fd, true)) { close_handle(fd); continue; } rc = ::connect(fd, rp->ai_addr, static_cast(rp->ai_addrlen)); bool connected = false; if (rc == 0) { connected = true; } else { #ifdef _WIN32 if (WSAGetLastError() == WSAEWOULDBLOCK) { #else if (errno == EINPROGRESS) { #endif // Wait for connection with timeout if (poll_fd(fd, timeoutMs, false)) { int err = 0; socklen_t len = sizeof(err); if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&err), &len) == 0 && err == 0) { connected = true; } } } } if (connected) { // Restore blocking mode set_non_blocking(fd, false); fd_ = fd; ::freeaddrinfo(result); return true; } close_handle(fd); } ::freeaddrinfo(result); return false; } int read(char* buf, int len) { if (!is_valid()) return -1; return static_cast(::recv(fd_, buf, len, 0)); } int write(const char* buf, int len) { if (!is_valid()) return -1; return static_cast(::send(fd_, buf, len, 0)); } bool wait_readable(int timeoutMs) { if (!is_valid()) return false; return poll_fd(fd_, timeoutMs, true); } bool wait_writable(int timeoutMs) { if (!is_valid()) return false; return poll_fd(fd_, timeoutMs, false); } [[nodiscard]] SocketHandle native_handle() const { return fd_; } void close() { if (is_valid()) { close_handle(fd_); fd_ = INVALID_SOCKET_FD; } } static void platform_init() { #ifdef _WIN32 WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); #endif } static void platform_cleanup() { #ifdef _WIN32 WSACleanup(); #endif } private: SocketHandle fd_ = INVALID_SOCKET_FD; static bool set_non_blocking(SocketHandle fd, bool nonBlocking) { #ifdef _WIN32 u_long mode = nonBlocking ? 1 : 0; return ioctlsocket(fd, FIONBIO, &mode) == 0; #else int flags = ::fcntl(fd, F_GETFL, 0); if (flags == -1) return false; if (nonBlocking) { flags |= O_NONBLOCK; } else { flags &= ~O_NONBLOCK; } return ::fcntl(fd, F_SETFL, flags) == 0; #endif } static bool poll_fd(SocketHandle fd, int timeoutMs, bool forRead) { #ifdef _WIN32 WSAPOLLFD pfd{}; pfd.fd = fd; pfd.events = forRead ? POLLIN : POLLOUT; int ret = WSAPoll(&pfd, 1, timeoutMs); return ret > 0 && (pfd.revents & (pfd.events | POLLERR | POLLHUP)); #else struct pollfd pfd{}; pfd.fd = fd; pfd.events = forRead ? POLLIN : POLLOUT; int ret = ::poll(&pfd, 1, timeoutMs); return ret > 0 && (pfd.revents & (pfd.events | POLLERR | POLLHUP)); #endif } static void close_handle(SocketHandle fd) { #ifdef _WIN32 ::closesocket(fd); #else ::close(fd); #endif } }; } // namespace mcpplibs::tinyhttps