diff --git a/inc/tcpstream.hpp b/inc/tcpstream.hpp index 63ff1fcf0c6bde66baf88f8ee0554e244fc517eb..fb3c5f4f2ba5de00a1a16c3eca3398a3068b11ca 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 7ed5df1f0cfb82ef483dd9112c607091c672f1c6..83d03a5aa17bc746afd2865627e8e66ea587882e 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 630c928dd1affdda475ec7e9f4f954a70375c3e7..c08d40345a016cf686b0098cdc686d204aa78ff1 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 895fabc409c6431361825d99bd5ae768a39e6d4e..91744c2c7cbfa75ec256f0bbc55451bbaea15cc6 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)