From e065c44808a10c2c1e6db17c1827a021de628d52 Mon Sep 17 00:00:00 2001 From: Daniel M <daniel.q.mueller@stud.h-da.de> Date: Wed, 12 May 2021 00:54:02 +0200 Subject: [PATCH] Add read functions with timeout - Added timeout read for TcpStream and UdpSocket --- inc/tcpstream.hpp | 40 +++++++++++++++++++++++++ inc/udpsocket.hpp | 36 ++++++++++++++++++++-- src/tcpstream.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++++++ src/udpsocket.cpp | 38 ++++++++++++++++++++++-- 4 files changed, 186 insertions(+), 4 deletions(-) diff --git a/inc/tcpstream.hpp b/inc/tcpstream.hpp index 63ff1fc..fb3c5f4 100644 --- a/inc/tcpstream.hpp +++ b/inc/tcpstream.hpp @@ -167,6 +167,46 @@ public: */ ssize_t readAll(void *data, size_t len); + /** + * @brief Same as TcpStream::read but with a millisecond timeout. If the + * timeout is reached without receiving data, 0 is returned. + * + * If receiving fails, an exception is thrown. + * + * @param data Pointer to at least len bytes where the data received over + * the tcp connection will be stored. + * @param len The maxiumum number of bytes that will be received over the + * tcp connection. + * @param timeoutMs The number of milliseconds before a timeout occurs. + * + * @return The number of bytes that were actually received over the + * connection. This can be less than len. If a timeout occurs 0 is + * returned. + */ + ssize_t readTimeout(void *data, size_t len, int timeoutMs); + + /** + * @brief Same as TcpStream::readAll but with a millisecond timeout. If + * the timeout is reached without receiving any data, 0 is returned. If + * the timout occurs after receiving some data, the number of bytes + * received before the timeout is returned as NEGATIVE. The timeout + * starts from the begining if partial data is received. + * + * If receiving failed an exception is thrown. + * + * @param data Pointer to at least len bytes where the data received over + * the tcp connection will be stored. + * @param len The number of bytes that will be received over the tcp + * connection. + * @param timeoutMs The number of milliseconds before a timeout occurs. + * + * @return The number of bytes that were actually received over the + * connection. This can be less than len if the connection is closed. + * If a timout occurs after some data was already read, the number of + * bytes is returned as negative. + */ + ssize_t readAllTimeout(void *data, size_t len, int timeoutMs); + /** * @brief Get the socket address of the connection target. * diff --git a/inc/udpsocket.hpp b/inc/udpsocket.hpp index 7ed5df1..83d03a5 100644 --- a/inc/udpsocket.hpp +++ b/inc/udpsocket.hpp @@ -138,7 +138,7 @@ public: * @param remote A reference to a SockAddr that is used to store the origin * of the UDP packet. * - * @return The numbe of bytes that were actually copied. + * @return The number of bytes that were actually copied. */ ssize_t receive(void *data, size_t len, SockAddr &remote); @@ -150,10 +150,42 @@ public: * will be copied. * @param len The maximum number of bytes that can be copied into data. * - * @return The numbe of bytes that were actually copied. + * @return The number of bytes that were actually copied. */ ssize_t receive(void *data, size_t len); + /** + * @brief Receive a UDP packet and copy a maximum number of len bytes from + * the packet payload into data. Store the senders origin socket address + * into remote. If the timeout occurs, 0 is returned. + * + * @param data Pointer to at least len bytes of data in which the payload + * will be copied. + * @param len The maximum number of bytes that can be copied into data. + * @param remote A reference to a SockAddr that is used to store the origin + * of the UDP packet. + * @param timeoutMs The number of milliseconds before a timeout occurs. + * + * @return The number of bytes that were actually copied, or 0 if a timeout + * occured. + */ + ssize_t receiveTimeout(void *data, size_t len, SockAddr &remote, int timeoutMs); + + /** + * @brief Receive a UDP packet and copy a maximum number of len bytes from + * the packet payload into data. The packets origin is not stored. If the + * timeout occurs, 0 is returned. + * + * @param data Pointer to at least len bytes of data in which the payload + * will be copied. + * @param len The maximum number of bytes that can be copied into data. + * @param timeoutMs The number of milliseconds before a timeout occurs. + * + * @return The number of bytes that were actually copied, or 0 if a timeout + * occured. + */ + ssize_t receiveTimeout(void *data, size_t len, int timeoutMs); + /** * @brief Check if the socket is closed or open. Open in this case means * bound and ready to send / receive. diff --git a/src/tcpstream.cpp b/src/tcpstream.cpp index 630c928..c08d403 100644 --- a/src/tcpstream.cpp +++ b/src/tcpstream.cpp @@ -5,6 +5,7 @@ #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> +#include <poll.h> TcpStream::TcpStream(SockAddr _remote) : remote{_remote}, sockfd{0} @@ -154,6 +155,81 @@ ssize_t TcpStream::readAll(void *data, size_t len) return bytesReadTotal; } +ssize_t TcpStream::readTimeout(void *data, size_t len, int timeoutMs) +{ + if (timeoutMs <= 0) return read(data, len); + + if (sockfd == 0) + throw std::runtime_error("Can't read from closed socket"); + + pollfd pfd = {0}; + pfd.fd = sockfd; + pfd.events = POLLIN; + + // block until data is available or the timeout is reached + int res = poll(&pfd, 1, timeoutMs); + + // a timout occured + if (res == 0) return 0; + + // a poll error occured + if (res < 0) + { + close(); + throw std::runtime_error("Error while reading from socket"); + } + + ssize_t bytes_read = ::read(sockfd, data, len); + if (bytes_read < 0) + { + close(); + throw std::runtime_error("Error while reading from socket"); + } + return bytes_read; +} + +ssize_t TcpStream::readAllTimeout(void *data, size_t len, int timeoutMs) +{ + if (timeoutMs <= 0) return readAll(data, len); + + if (sockfd == 0) + throw std::runtime_error("Can't read from closed socket"); + + pollfd pfd = {0}; + pfd.fd = sockfd; + pfd.events = POLLIN; + + + size_t bytesReadTotal = 0; + while (true) + { + // block until data is available or the timeout is reached + int res = poll(&pfd, 1, timeoutMs); + + // a timout occured + if (res == 0) return -1 * bytesReadTotal; + + // a poll error occured + if (res < 0) + { + close(); + throw std::runtime_error("Error while reading from socket"); + } + + ssize_t bytesRead = ::read(sockfd, (uint8_t*)data + bytesReadTotal, len-bytesReadTotal); + + if (bytesRead == 0) break; + if (bytesRead < 0) + { + close(); + throw std::runtime_error("Error while reading from socket"); + } + + bytesReadTotal += bytesRead; + } + return bytesReadTotal; +} + const SockAddr & TcpStream::getRemoteAddr() const { return remote; diff --git a/src/udpsocket.cpp b/src/udpsocket.cpp index 895fabc..91744c2 100644 --- a/src/udpsocket.cpp +++ b/src/udpsocket.cpp @@ -4,6 +4,7 @@ #include <unistd.h> #include <sys/socket.h> +#include <poll.h> UdpSocket::UdpSocket(SockAddr _local) @@ -93,9 +94,10 @@ ssize_t UdpSocket::sendTo(const std::string &remoteAddrPort, const void *data, s ssize_t UdpSocket::receive(void *data, size_t len, SockAddr &remote) { SockAddr::RawSockAddr remote_raw_saddr = {0}; + socklen_t remote_raw_socklen = raw_socklen; // TODO: Lookup flags - ssize_t bytes_read = ::recvfrom(sockfd, data, len, 0, &remote_raw_saddr.generic, &raw_socklen); + ssize_t bytes_read = ::recvfrom(sockfd, data, len, 0, &remote_raw_saddr.generic, &remote_raw_socklen); if (bytes_read < 0) { @@ -108,20 +110,52 @@ ssize_t UdpSocket::receive(void *data, size_t len, SockAddr &remote) } ssize_t UdpSocket::receive(void *data, size_t len) +{ + SockAddr saddr; + return receive(data, len, saddr); +} + +ssize_t UdpSocket::receiveTimeout(void *data, size_t len, SockAddr &remote, int timeoutMs) { SockAddr::RawSockAddr remote_raw_saddr = {0}; + socklen_t remote_raw_socklen = raw_socklen; + + pollfd pfd = {0}; + pfd.fd = sockfd; + pfd.events = POLLIN; + + // block until data is available or the timeout is reached + int res = poll(&pfd, 1, timeoutMs); + + // a timout occured + if (res == 0) return 0; + + // a poll error occured + if (res < 0) + { + close(); + throw std::runtime_error("Error while reading from socket"); + } // TODO: Lookup flags - ssize_t bytes_read = ::recvfrom(sockfd, data, len, 0, &remote_raw_saddr.generic, &raw_socklen); + ssize_t bytes_read = ::recvfrom(sockfd, data, len, 0, &remote_raw_saddr.generic, &remote_raw_socklen); if (bytes_read < 0) { throw std::runtime_error("Error while reading from socket"); } + remote = SockAddr(&remote_raw_saddr.generic, local.address.type); + return bytes_read; } +ssize_t UdpSocket::receiveTimeout(void *data, size_t len, int timeoutMs) +{ + SockAddr saddr; + return receiveTimeout(data, len, saddr, timeoutMs); +} + void UdpSocket::close() { if (sockfd != 0) -- GitLab