diff --git a/implementations/Crafter.Network-ClientTCP.cpp b/implementations/Crafter.Network-ClientTCP.cpp index dc2d8a9..31270e0 100755 --- a/implementations/Crafter.Network-ClientTCP.cpp +++ b/implementations/Crafter.Network-ClientTCP.cpp @@ -31,11 +31,22 @@ ClientTCP::ClientTCP(int socketid) : socketid(socketid) ClientTCP::ClientTCP(const char* hostName, std::uint16_t port) { - host = gethostbyname(hostName); - serv_addr.sin_family = AF_INET; - serv_addr.sin_port = htons(port); - serv_addr.sin_addr = *((struct in_addr *)host->h_addr); - bzero(&(serv_addr.sin_zero),8); + // getaddrinfo rather than gethostbyname: the latter is not thread safe, + // and it signals failure by returning null — which was then dereferenced + // straight into a crash on any unresolvable host. + addrinfo hints{}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + addrinfo* resolved = nullptr; + const std::string service = std::to_string(port); + const int status = getaddrinfo(hostName, service.c_str(), &hints, &resolved); + if (status != 0 || resolved == nullptr) { + throw std::runtime_error(std::string("Could not resolve host '") + hostName + "': " + + gai_strerror(status)); + } + host = nullptr; + serv_addr = *reinterpret_cast(resolved->ai_addr); + freeaddrinfo(resolved); Connect(); } @@ -45,17 +56,16 @@ ClientTCP::ClientTCP(std::string hostName, std::uint16_t port): ClientTCP(hostNa } +// The moved-from object gives up ownership; the socket itself stays open — +// closing it here (as this used to) meant moving a ClientTCP silently +// dropped the connection it was carrying. ClientTCP::ClientTCP(ClientTCP&& other) noexcept : socketid(other.socketid) { - if(socketid != 1) { - shutdown(socketid, SHUT_RDWR); - close(socketid); - } other.socketid = -1; } ClientTCP::~ClientTCP() { - if(socketid != 1) { + if(socketid != -1) { shutdown(socketid, SHUT_RDWR); close(socketid); } @@ -63,11 +73,17 @@ ClientTCP::~ClientTCP() void ClientTCP::Connect() { if((socketid = socket(AF_INET, SOCK_STREAM, 0)) == -1){ - std::cerr << "Could not open socket" << std::endl; + throw std::runtime_error(std::string("Could not open socket: ") + std::strerror(errno)); } if(connect(socketid,(sockaddr*)&serv_addr, sizeof(sockaddr)) == -1){ - std::cerr << "Could not connect to server" << std::endl; + // Report the failure instead of handing back a socket that is not + // connected to anything — every later send/recv on it would fail + // with a far less obvious error. + const std::string reason = std::strerror(errno); + close(socketid); + socketid = -1; + throw std::runtime_error("Could not connect to server: " + reason); } } @@ -78,12 +94,20 @@ void ClientTCP::Stop() { } void ClientTCP::Send(const void* buffer, std::uint32_t size) const { - int status = send(socketid, reinterpret_cast(buffer), size, 0); - - if (status == 0) { - throw SocketClosedException(); - } else if (status < 0) { - throw std::runtime_error(std::strerror(errno)); + // send() is free to accept less than it was offered, and does so + // routinely once a buffer outgrows the socket's send buffer — a + // multi-megabyte HTTP body, say. Loop until it is all handed over. + const char* data = reinterpret_cast(buffer); + std::uint32_t sent = 0; + while (sent < size) { + const auto status = send(socketid, data + sent, size - sent, MSG_NOSIGNAL); + if (status == 0) { + throw SocketClosedException(); + } else if (status < 0) { + if (errno == EINTR) continue; + throw std::runtime_error(std::strerror(errno)); + } + sent += static_cast(status); } } std::vector ClientTCP::RecieveSync(std::uint32_t bufferSize) const { diff --git a/implementations/Crafter.Network-ListenerTCP.cpp b/implementations/Crafter.Network-ListenerTCP.cpp index 584aafc..57e956f 100755 --- a/implementations/Crafter.Network-ListenerTCP.cpp +++ b/implementations/Crafter.Network-ListenerTCP.cpp @@ -1,114 +1,135 @@ -//SPDX-License-Identifier: LGPL-3.0-only -//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® - -module; - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -module Crafter.Network:ListenerTCP_impl; -import :ListenerTCP; -import std; -import Crafter.Thread; - -using namespace Crafter; - -ListenerTCP::ListenerTCP(std::uint16_t port, std::function connectCallback, std::uint32_t concurrentClientLimit, std::uint32_t totalClientLimit) : connectCallback(connectCallback), concurrentClientLimit(concurrentClientLimit), totalClientLimit(totalClientLimit) { - sockaddr_in servAddr; - bzero((char*)&servAddr, sizeof(servAddr)); - servAddr.sin_family = AF_INET; - servAddr.sin_addr.s_addr = htonl(INADDR_ANY); - servAddr.sin_port = htons(port); - - s = socket(AF_INET, SOCK_STREAM, 0); - if(s < 0) - { - throw std::runtime_error("Error establishing the server socket"); - } - int bindStatus = bind(s, (struct sockaddr*) &servAddr, sizeof(servAddr)); - listen(s, 5); -} - -void ListenerTCP::Stop() { - running = false; - shutdown(s, SHUT_RDWR); - close(s); - s = -1; -} - -void ListenerTCP::ListenSyncSync() { - while (running && totalClientCounter < totalClientLimit) { - sockaddr_in newSockAddr; - socklen_t newSockAddrSize = sizeof(newSockAddr); - int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); - if (client > 0) { - connectCallback(new ClientTCP(client)); - this->totalClientCounter++; - } - else { - std::cerr << "Error accepting request from client!" << std::endl; - } - } -} - -void ListenerTCP::ListenSyncAsync() { - while (running && totalClientCounter < totalClientLimit) { - sockaddr_in newSockAddr; - socklen_t newSockAddrSize = sizeof(newSockAddr); - int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); - if (client > 0) { - ThreadPool::Enqueue([this, client]() {connectCallback(new ClientTCP(client)); }); - this->totalClientCounter++; - } - else { - std::cerr << "Error accepting request from client!" << std::endl; - } - } -} - -void ListenerTCP::ListenAsyncSync() { - ThreadPool::Enqueue([this]() { - while (running && totalClientCounter < totalClientLimit) { - sockaddr_in newSockAddr; - socklen_t newSockAddrSize = sizeof(newSockAddr); - int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); - if (client > 0) { - connectCallback(new ClientTCP(client)); - this->totalClientCounter++; - } - else { - std::cerr << "Error accepting request from client!" << std::endl; - } - } - }); -} - -void ListenerTCP::ListenAsyncAsync() { - ThreadPool::Enqueue([this]() { - while (running && totalClientCounter < totalClientLimit) { - sockaddr_in newSockAddr; - socklen_t newSockAddrSize = sizeof(newSockAddr); - int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); - if (client > 0) { - ThreadPool::Enqueue([this, client]() {connectCallback(new ClientTCP(client)); }); - this->totalClientCounter++; - } - else { - std::cerr << "Error accepting request from client!" << std::endl; - } - } - }); -} - -ListenerTCP::~ListenerTCP() { - if(s != -1) { - close(s); - } +//SPDX-License-Identifier: LGPL-3.0-only +//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +module; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +module Crafter.Network:ListenerTCP_impl; +import :ListenerTCP; +import std; +import Crafter.Thread; + +using namespace Crafter; + +ListenerTCP::ListenerTCP(std::uint16_t port, std::function connectCallback, std::uint32_t concurrentClientLimit, std::uint32_t totalClientLimit) : connectCallback(connectCallback), concurrentClientLimit(concurrentClientLimit), totalClientLimit(totalClientLimit) { + sockaddr_in servAddr; + bzero((char*)&servAddr, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_addr.s_addr = htonl(INADDR_ANY); + servAddr.sin_port = htons(port); + + s = socket(AF_INET, SOCK_STREAM, 0); + if(s < 0) + { + throw std::runtime_error("Error establishing the server socket"); + } + // Without SO_REUSEADDR the port stays unbindable for the length of + // TIME_WAIT after a restart, which turns "restart the server" into a + // minute of failed binds. + int reuse = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); + + if(bind(s, (struct sockaddr*) &servAddr, sizeof(servAddr)) < 0) { + // Ignoring this left the listener silently accepting nothing at + // all, with no hint as to why. + const std::string reason = std::strerror(errno); + close(s); + s = -1; + throw std::runtime_error("Could not bind port " + std::to_string(port) + ": " + reason); + } + if(listen(s, 128) < 0) { + const std::string reason = std::strerror(errno); + close(s); + s = -1; + throw std::runtime_error("Could not listen on port " + std::to_string(port) + ": " + reason); + } +} + +void ListenerTCP::Stop() { + running = false; + shutdown(s, SHUT_RDWR); + close(s); + s = -1; +} + +void ListenerTCP::ListenSyncSync() { + while (running && totalClientCounter < totalClientLimit) { + sockaddr_in newSockAddr; + socklen_t newSockAddrSize = sizeof(newSockAddr); + int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); + if (client > 0) { + connectCallback(new ClientTCP(client)); + this->totalClientCounter++; + } + else if (running) { + // accept() also fails once on the way out, when Stop() closes + // the listening socket — that one is not worth reporting. + std::cerr << "Error accepting request from client!" << std::endl; + } + } +} + +void ListenerTCP::ListenSyncAsync() { + while (running && totalClientCounter < totalClientLimit) { + sockaddr_in newSockAddr; + socklen_t newSockAddrSize = sizeof(newSockAddr); + int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); + if (client > 0) { + ThreadPool::Enqueue([this, client]() {connectCallback(new ClientTCP(client)); }); + this->totalClientCounter++; + } + else if (running) { + std::cerr << "Error accepting request from client!" << std::endl; + } + } +} + +void ListenerTCP::ListenAsyncSync() { + ThreadPool::Enqueue([this]() { + while (running && totalClientCounter < totalClientLimit) { + sockaddr_in newSockAddr; + socklen_t newSockAddrSize = sizeof(newSockAddr); + int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); + if (client > 0) { + connectCallback(new ClientTCP(client)); + this->totalClientCounter++; + } + else if (running) { + std::cerr << "Error accepting request from client!" << std::endl; + } + } + }); +} + +void ListenerTCP::ListenAsyncAsync() { + ThreadPool::Enqueue([this]() { + while (running && totalClientCounter < totalClientLimit) { + sockaddr_in newSockAddr; + socklen_t newSockAddrSize = sizeof(newSockAddr); + int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); + if (client > 0) { + ThreadPool::Enqueue([this, client]() {connectCallback(new ClientTCP(client)); }); + this->totalClientCounter++; + } + else if (running) { + std::cerr << "Error accepting request from client!" << std::endl; + } + } + }); +} + +ListenerTCP::~ListenerTCP() { + if(s != -1) { + close(s); + } } \ No newline at end of file