20#include <system_error>
33namespace morph::net::detail {
36enum class PollOutcome : std::uint8_t {
45 PollOutcome outcome = PollOutcome::kTimedOut;
72inline PollResult pollUntil(
int descriptor,
short events, std::chrono::steady_clock::time_point deadline) {
79 auto const remaining =
80 std::chrono::ceil<std::chrono::milliseconds>(deadline - std::chrono::steady_clock::now());
81 if (remaining.count() <= 0) {
82 return {.outcome = PollOutcome::kTimedOut};
84 auto const waitMs =
static_cast<int>(
85 std::min<std::chrono::milliseconds::rep>(remaining.count(), std::numeric_limits<int>::max()));
86 int const ready = ::poll(&pfd, 1, waitMs);
88 return {.outcome = PollOutcome::kReady};
91 int const err = errno;
93 return {.outcome = PollOutcome::kFailed, .error = err};
108 TcpSocket() =
default;
132 explicit TcpSocket(
int rawFd) : _fd{rawFd} {
133 applyNoSigPipe(rawFd);
134 clearNonBlocking(rawFd);
137 TcpSocket(
const TcpSocket&) =
delete;
138 TcpSocket& operator=(
const TcpSocket&) =
delete;
142 TcpSocket(TcpSocket&& other) noexcept : _fd{other._fd} { other._fd = -1; }
147 TcpSocket& operator=(TcpSocket&& other)
noexcept {
148 if (
this != &other) {
157 ~TcpSocket() { closeNow(); }
167 static TcpSocket connect(
const std::string& host, std::uint16_t port, std::chrono::milliseconds timeout) {
169 hints.ai_family = AF_UNSPEC;
170 hints.ai_socktype = SOCK_STREAM;
175 std::string
const portStr = std::to_string(
static_cast<unsigned>(port));
176 int const rc = ::getaddrinfo(
host.c_str(), portStr.c_str(), &hints, &resolved);
177 if (rc != 0 || resolved ==
nullptr) {
206 throw std::runtime_error(
"TcpSocket::connect: getaddrinfo failed for " + host +
": " + ::gai_strerror(rc));
208 struct AddrInfoGuard {
210 ~AddrInfoGuard() { ::freeaddrinfo(p); }
219 auto const deadline = std::chrono::steady_clock::now() + timeout;
221 for (addrinfo* rp = resolved; rp !=
nullptr; rp = rp->ai_next) {
222 int fd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
226 int const flags = ::fcntl(fd, F_GETFL, 0);
227 ::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
228 int const connectRc = ::connect(fd, rp->ai_addr, rp->ai_addrlen);
229 if (connectRc == 0) {
230 ::fcntl(fd, F_SETFL, flags);
231 return TcpSocket{fd};
233 if (errno != EINPROGRESS) {
240 if (pollUntil(fd, POLLOUT, deadline).outcome != PollOutcome::kReady) {
245 socklen_t soErrLen =
sizeof(soErr);
248 if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &soErr, &soErrLen) != 0 || soErr != 0) {
252 ::fcntl(fd, F_SETFL, flags);
253 return TcpSocket{fd};
255 throw std::runtime_error(
"TcpSocket::connect: could not connect to " + host +
":" + portStr);
263 static TcpSocket listen(std::uint16_t port,
int backlog = 64) {
264 int fd = ::socket(AF_INET, SOCK_STREAM, 0);
266 throw std::runtime_error(
"TcpSocket::listen: socket() failed: " + errnoMessage(errno));
269 ::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse,
sizeof(reuse));
271 addr.sin_family = AF_INET;
272 addr.sin_port = htons(port);
273 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
274 if (::bind(fd,
reinterpret_cast<sockaddr*
>(&addr),
sizeof(addr)) != 0) {
275 int const err = errno;
277 throw std::runtime_error(
"TcpSocket::listen: bind() failed: " + errnoMessage(err));
279 if (::listen(fd, backlog) != 0) {
280 int const err = errno;
282 throw std::runtime_error(
"TcpSocket::listen: listen() failed: " + errnoMessage(err));
284 return TcpSocket{fd};
289 [[nodiscard]] std::uint16_t boundPort()
const {
294 socklen_t len =
sizeof(addr);
295 ::getsockname(_fd,
reinterpret_cast<sockaddr*
>(&addr), &len);
296 return ntohs(addr.sin_port);
314 int const clientFd = ::accept(_fd,
nullptr,
nullptr);
316 return TcpSocket{clientFd};
340 if (errno == EINTR) {
343 throw std::runtime_error(
"TcpSocket::accept: " + errnoMessage(errno));
361 [[nodiscard]]
bool setNonBlocking() noexcept {
365 int const flags = ::fcntl(_fd, F_GETFL, 0);
367 return flags >= 0 && ::fcntl(_fd, F_SETFL, flags | O_NONBLOCK) == 0;
385 std::optional<TcpSocket> tryAccept() {
387 int const clientFd = ::accept(_fd,
nullptr,
nullptr);
389 return TcpSocket{clientFd};
391 int const err = errno;
395 if (wouldBlock(err)) {
398 throw std::runtime_error(
"TcpSocket::tryAccept: " + errnoMessage(err));
409 std::size_t recvSome(
char* buf, std::size_t len) {
411 ssize_t
const n = ::recv(_fd, buf, len, 0);
413 if (errno == EINTR) {
416 if (errno == ECONNRESET) {
419 throw std::runtime_error(
"TcpSocket::recvSome: " + errnoMessage(errno));
421 return static_cast<std::size_t
>(n);
429 void sendAll(
const char* data, std::size_t len) {
430 std::size_t sent = 0;
432 ssize_t
const n = ::send(_fd, data + sent, len - sent, MSG_NOSIGNAL);
434 if (errno == EINTR) {
437 throw std::runtime_error(
"TcpSocket::sendAll: " + errnoMessage(errno));
439 sent +=
static_cast<std::size_t
>(n);
457 [[nodiscard]]
bool setSendTimeout(std::chrono::milliseconds timeout)
const noexcept {
463 timeval timeoutVal{};
464 timeoutVal.tv_sec = timeout.count() / 1000;
465 timeoutVal.tv_usec = (timeout.count() % 1000) * 1000;
466 return ::setsockopt(_fd, SOL_SOCKET, SO_SNDTIMEO, &timeoutVal,
sizeof(timeoutVal)) == 0;
485 [[nodiscard]]
bool setRecvTimeout(std::chrono::milliseconds timeout)
const noexcept {
486 timeval timeoutVal{};
487 timeoutVal.tv_sec = timeout.count() / 1000;
488 timeoutVal.tv_usec = (timeout.count() % 1000) * 1000;
489 return ::setsockopt(_fd, SOL_SOCKET, SO_RCVTIMEO, &timeoutVal,
sizeof(timeoutVal)) == 0;
498 void shutdownBoth() noexcept {
500 ::shutdown(_fd, SHUT_RDWR);
506 [[nodiscard]]
int nativeHandle() const noexcept {
return _fd; }
513 [[nodiscard]]
int release() noexcept {
return std::exchange(_fd, -1); }
517 [[nodiscard]]
bool valid() const noexcept {
return _fd >= 0; }
538 static std::string errnoMessage(
int err) {
return std::system_category().message(err); }
545 static bool wouldBlock(
int err)
noexcept {
549 return err == EWOULDBLOCK;
554 static void clearNonBlocking(
int rawFd)
noexcept {
556 int const flags = ::fcntl(rawFd, F_GETFL, 0);
557 if (flags < 0 || (flags & O_NONBLOCK) == 0) {
561 static_cast<void>(::fcntl(rawFd, F_SETFL, flags & ~O_NONBLOCK));
564 static void applyNoSigPipe(
int fd) {
565#if defined(SO_NOSIGPIPE)
567 ::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one,
sizeof(one));
573 void closeNow() noexcept {
Command host(std::string const &name, AnyHandle const &args={})
host: a command the application registered with the client.
Resolved const & resolved()
The process registries, matched and validated on the first call.