diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/core/StarSocket.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/core/StarSocket.cpp')
-rw-r--r-- | source/core/StarSocket.cpp | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/source/core/StarSocket.cpp b/source/core/StarSocket.cpp new file mode 100644 index 0000000..95ffe7c --- /dev/null +++ b/source/core/StarSocket.cpp @@ -0,0 +1,272 @@ +#include "StarSocket.hpp" +#include "StarLogging.hpp" +#include "StarNetImpl.hpp" + +namespace Star { + +Maybe<SocketPollResult> Socket::poll(SocketPollQuery const& query, unsigned timeout) { + if (query.empty()) + return {}; + + // Prevent close from being called on any socket during this call. + LinkedList<ReadLocker> readLockers; + for (auto const& p : query) + readLockers.emplaceAppend(p.first->m_mutex); + + // If any sockets are already closed, then this is an "event" according to + // this api but we cannot call poll on a closed socket, so just poll the rest + // of the sockets with no wait. + SocketPollResult result; + for (auto const& p : query) { + if (!p.first->isOpen()) { + result[p.first].exception = true; + timeout = 0; + } + } + +#ifdef STAR_SYSTEM_FAMILY_WINDOWS + fd_set readfs; + fd_set writefs; + fd_set exceptfs; + + FD_ZERO(&readfs); + FD_ZERO(&writefs); + FD_ZERO(&exceptfs); + + int ret; + for (auto const& p : query) { + if (p.first->isOpen()) { + if (p.second.readable) + FD_SET(p.first->m_impl->socketDesc, &readfs); + if (p.second.writable) + FD_SET(p.first->m_impl->socketDesc, &writefs); + FD_SET(p.first->m_impl->socketDesc, &exceptfs); + } + } + timeval time; + time.tv_usec = (timeout % 1000) * 1000; + time.tv_sec = timeout - timeout % 1000; + ret = ::select(0, &readfs, &writefs, &exceptfs, &time); + + if (ret < 0) + throw NetworkException::format("Error during call to select, '%s'", netErrorString()); + + if (ret == 0) + return {}; + + for (auto const& p : query) { + if (p.first->isOpen()) { + auto& r = result[p.first]; + r.readable = FD_ISSET(p.first->m_impl->socketDesc, &readfs); + r.writable = FD_ISSET(p.first->m_impl->socketDesc, &writefs); + r.exception = FD_ISSET(p.first->m_impl->socketDesc, &exceptfs); + if (r.exception) + p.first->doShutdown(); + } + } + +#else + unique_ptr<pollfd[]> pollfds(new pollfd[query.size()]); + int ret = 0; + for (auto p : enumerateIterator(query)) { + if (p.first.first->isOpen()) { + auto& pfd = pollfds[p.second]; + pfd.fd = p.first.first->m_impl->socketDesc; + pfd.events = 0; + if (p.first.second.readable) + pfd.events |= POLLIN; + if (p.first.second.writable) + pfd.events |= POLLOUT; + } + } + ret = ::poll(pollfds.get(), query.size(), timeout); + + if (ret < 0) + throw NetworkException::format("Error during call to poll, '%s'", netErrorString()); + + if (ret == 0) + return {}; + + for (auto p : enumerateIterator(query)) { + if (p.first.first->isOpen()) { + auto& pfd = pollfds[p.second]; + SocketPollResultEntry pr; + pr.readable = pfd.revents & POLLIN; + pr.writable = pfd.revents & POLLOUT; + pr.exception = pfd.revents & POLLHUP || pfd.revents & POLLNVAL || pfd.revents & POLLERR; + if (pfd.revents & POLLHUP) + p.first.first->doShutdown(); + result.add(p.first.first, move(pr)); + } + } +#endif + + readLockers.clear(); + + return result; +} + +Socket::~Socket() { + close(); +} + +void Socket::bind(HostAddressWithPort const& addressWithPort) { + WriteLocker locker(m_mutex); + checkOpen("Socket::bind"); + + struct sockaddr_storage sockAddr; + socklen_t sockAddrLen; + + if (addressWithPort.address().mode() != m_networkMode) + throw NetworkException("Bind address does not match socket mode"); + + // Ensure quick restarts don't prevent us binding + int set = 1; + m_impl->setSockOpt(SOL_SOCKET, SO_REUSEADDR, (void*)&set, sizeof(int)); + + m_localAddress = addressWithPort; + setNativeFromAddress(m_localAddress, &sockAddr, &sockAddrLen); + if (::bind(m_impl->socketDesc, (struct sockaddr*)&sockAddr, sockAddrLen) < 0) + throw NetworkException(strf("Cannot bind socket to %s: %s", m_localAddress, netErrorString())); + + m_socketMode = SocketMode::Bound; + + Logger::debug("bind %s (%d)", addressWithPort, m_impl->socketDesc); +} + +void Socket::listen(int backlog) { + WriteLocker locker(m_mutex); + + if (::listen(m_impl->socketDesc, backlog) != 0) + throw NetworkException(strf("Could not listen on socket: '%s'", netErrorString())); +} + +void Socket::setTimeout(unsigned timeout) { + ReadLocker locker(m_mutex); + checkOpen("Socket::setTimeout"); + + void* val; + socklen_t size; +#ifdef STAR_SYSTEM_FAMILY_WINDOWS + val = &timeout; + size = sizeof(timeout); +#else + struct timeval tv; + tv.tv_sec = timeout - timeout % 1000; + tv.tv_usec = (timeout % 1000) * 1000; + val = &tv; + size = sizeof(tv); +#endif + + m_impl->setSockOpt(SOL_SOCKET, SO_RCVTIMEO, val, size); + m_impl->setSockOpt(SOL_SOCKET, SO_SNDTIMEO, val, size); +} + +void Socket::setNonBlocking(bool nonBlocking) { + ReadLocker locker(m_mutex); + checkOpen("Socket::setNonBlocking"); +#ifdef WIN32 + unsigned long mode = nonBlocking ? 1 : 0; + if (ioctlsocket(m_impl->socketDesc, FIONBIO, &mode) != 0) + throw NetworkException::format("Cannot set socket non-blocking mode: %s", netErrorString()); +#else + int flags = fcntl(m_impl->socketDesc, F_GETFL, 0); + if (flags < 0) + throw NetworkException::format("fcntl failure getting socket flags: %s", netErrorString()); + flags = nonBlocking ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK); + if (fcntl(m_impl->socketDesc, F_SETFL, flags) != 0) + throw NetworkException::format("fcntl failure setting non-blocking mode: %s", netErrorString()); +#endif +} + +NetworkMode Socket::networkMode() const { + ReadLocker locker(m_mutex); + return m_networkMode; +} + +SocketMode Socket::socketMode() const { + ReadLocker locker(m_mutex); + return m_socketMode; +} + +bool Socket::isActive() const { + return m_socketMode > SocketMode::Shutdown; +} + +bool Socket::isOpen() const { + return m_socketMode != SocketMode::Closed; +} + +void Socket::shutdown() { + ReadLocker locker(m_mutex); + doShutdown(); +} + +void Socket::close() { + WriteLocker locker(m_mutex); + doShutdown(); + doClose(); +} + +Socket::Socket(SocketType type, NetworkMode networkMode) + : m_networkMode(networkMode), m_impl(make_shared<SocketImpl>()), m_socketMode(SocketMode::Closed) { + if (m_networkMode == NetworkMode::IPv4) + m_impl->socketDesc = ::socket(AF_INET, type == SocketType::Tcp ? SOCK_STREAM : SOCK_DGRAM, 0); + else + m_impl->socketDesc = ::socket(AF_INET6, type == SocketType::Tcp ? SOCK_STREAM : SOCK_DGRAM, 0); + + if (invalidSocketDescriptor(m_impl->socketDesc)) + throw NetworkException(strf("cannot create socket: %s", netErrorString())); + + m_socketMode = SocketMode::Shutdown; + setTimeout(60000); + setNonBlocking(false); +} + +Socket::Socket(NetworkMode networkMode, SocketImplPtr impl, SocketMode socketMode) + : m_networkMode(networkMode), m_impl(impl), m_socketMode(socketMode) { + setTimeout(60000); + setNonBlocking(false); +} + +void Socket::checkOpen(char const* methodName) const { + if (m_socketMode == SocketMode::Closed) + throw SocketClosedException::format("Socket not open in %s", methodName); +} + +void Socket::doShutdown() { + if (m_socketMode <= SocketMode::Shutdown) + return; + + // Set socket mode first so that if this causes an exception the exception + // handlers know the socket is being shut down. + m_socketMode = SocketMode::Shutdown; + + if (m_impl->socketDesc > 0) { +#ifdef STAR_SYSTEM_FAMILY_WINDOWS + ::shutdown(m_impl->socketDesc, SD_BOTH); +#else + ::shutdown(m_impl->socketDesc, SHUT_RDWR); +#endif + } +} + +void Socket::doClose() { + if (m_socketMode == SocketMode::Closed) + return; + + // Set socket mode first so that if this causes an exception the exception + // handlers know the socket is being closed. + m_socketMode = SocketMode::Closed; + + if (m_impl->socketDesc > 0) { +#ifdef STAR_SYSTEM_FAMILY_WINDOWS + ::closesocket(m_impl->socketDesc); +#else + ::close(m_impl->socketDesc); +#endif + m_impl->socketDesc = 0; + } +} + +} |