feat(tcp): connect over IPv4 and IPv6, and say why a connect failed
- ClientTCP resolves with AF_UNSPEC and tries every address the name resolves to, in getaddrinfo's order, the way Python's socket.create_connection does. It used to ask for AF_INET only and keep the first answer, so `localhost` could not reach a server listening on ::1 alone, which is what a dev server on a recent Node does. The connect timeout bounds each address on its own; when none connects, what the last one failed with is thrown. - A failed resolve, socket() or connect throws SocketErrorException: the std::runtime_error it always was, with the same message, plus the errno (SocketErrorKind::System) or the getaddrinfo code (SocketErrorKind::Resolver). A caller that reports the cause in its own words, such as a Python-style "[Errno 111] Connection refused", no longer has to parse it back out of the text. - The move constructor carries the resolved addresses along. New: ShouldConnectDualStack, ShouldReportConnectErrors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3a58564422
commit
78853033e8
6 changed files with 383 additions and 51 deletions
|
|
@ -37,14 +37,44 @@ namespace Crafter {
|
|||
}
|
||||
};
|
||||
|
||||
// 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. A refused connect
|
||||
// fails at once either way.
|
||||
// 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();
|
||||
|
|
@ -83,7 +113,13 @@ namespace Crafter {
|
|||
// Into `buffer`, which must outlive the receive; the callback gets the count.
|
||||
void RecieveAsync(std::uint32_t bufferSize, std::function<void(std::uint32_t)> recieveCallback, char* buffer, std::function<void(std::exception_ptr)> onError) const;
|
||||
private:
|
||||
sockaddr_in serv_addr;
|
||||
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<Address> addresses;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
Loading…
Reference in a new issue