//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® module; #ifndef CRAFTER_NETWORK_BROWSER #include #include #include #include #include #include #include #include #include #include #include #include #include #endif export module Crafter.Network:ClientTCP; import std; #ifndef CRAFTER_NETWORK_BROWSER namespace Crafter { export class SocketClosedException : public std::exception { public: const char* what() const noexcept override { return "Socket closed"; } }; // A connect or a receive waited longer than its timeout. export class SocketTimeoutException : public std::exception { public: const char* what() const noexcept override { return "Socket timed out"; } }; // Where the code of a SocketErrorException comes from. export enum class SocketErrorKind { // An errno: ECONNREFUSED, ENETUNREACH, EMFILE, ... System, // A getaddrinfo result: EAI_NONAME, EAI_AGAIN, ... Resolver, }; // A resolve, a socket() or a connect that failed, with the code it failed // with. what() is the text ClientTCP has always thrown, and catching // std::runtime_error still catches it; the code is for callers that // report the cause in their own words — a Python-style // "[Errno 111] Connection refused", say — and would otherwise have to // parse it back out of the message. export class SocketErrorException : public std::runtime_error { public: SocketErrorException(SocketErrorKind kind, std::int32_t code, const std::string& message) : std::runtime_error(message), kind(kind), code(code) {} SocketErrorKind kind; std::int32_t code; }; export class ClientTCP { public: int socketid; ClientTCP(int socket); // The host is resolved for IPv4 and IPv6 alike and every address it // resolves to is tried in turn, the way Python's // socket.create_connection does: `localhost` reaches a server that // listens on ::1 only, as a dev server on a recent Node does. The // first address that connects wins; when none does, what the last // one failed with is thrown — SocketErrorException, or // SocketTimeoutException for a timeout. // // A non-zero connectTimeout makes a connect nobody answers throw // SocketTimeoutException after that long, where it would otherwise // wait out the kernel's retries, about two minutes. It bounds each // address on its own. A refused connect fails at once either way. ClientTCP(const char* host, std::uint16_t port, std::chrono::milliseconds connectTimeout = std::chrono::milliseconds::zero()); ClientTCP(std::string host, std::uint16_t port, std::chrono::milliseconds connectTimeout = std::chrono::milliseconds::zero()); ~ClientTCP(); ClientTCP(const ClientTCP&) = delete; ClientTCP(ClientTCP&& other) noexcept; void Connect(std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()); void Stop(); // Turns Nagle's algorithm off (TCP_NODELAY), so a small write goes // out at once instead of waiting for the ACK of the one before it. // For small messages that must arrive promptly, such as a stream of // frames. void SetNoDelay(bool enabled); // A receive that waits longer than this throws // SocketTimeoutException, losing what a whole-buffer receive had read // so far. Zero, the default, waits for good. void SetReceiveTimeout(std::chrono::milliseconds timeout); void Send(const void* buffer, std::uint32_t size) const; // The receives return what arrived, at least one byte; asked for none, // none, at once, without looking at the socket. They throw // SocketClosedException once the peer has closed, and // SocketTimeoutException when a receive timeout runs out. std::vector RecieveSync() const; std::vector RecieveUntilCloseSync() const; std::vector RecieveUntilFullSync(std::uint32_t bufferSize) const; std::vector RecieveSync(std::uint32_t bufferSize) const; // Into `buffer`; returns how many bytes it put there. std::uint32_t RecieveSync(std::uint32_t bufferSize, void* buffer) const; // The same receives on Crafter.Thread's ThreadPool. What arrived goes // to the callback; what a receive throws goes to onError instead (an // empty one drops it), as an exception left to escape a pool task // would end the program. void RecieveAsync(std::function)> recieveCallback, std::function onError) const; void RecieveUntilCloseAsync(std::function)> recieveCallback, std::function onError) const; void RecieveAsync(std::uint32_t bufferSize, std::function)> recieveCallback, std::function onError) const; void RecieveUntilFullAsync(std::uint32_t bufferSize, std::function)> recieveCallback, std::function onError) const; // Into `buffer`, which must outlive the receive; the callback gets the count. void RecieveAsync(std::uint32_t bufferSize, std::function recieveCallback, char* buffer, std::function onError) const; private: struct Address { sockaddr_storage storage; socklen_t length; }; // What the host resolved to, in getaddrinfo's order; empty for a // socket handed over by accept(). std::vector
addresses; }; } #endif