From cca045cdea48d37e877803d5d0e85478229b6ba9 Mon Sep 17 00:00:00 2001 From: Foo Date: Sat, 24 Feb 2024 17:22:42 +0100 Subject: [PATCH] Refactoring + doc improved --- .gitignore | 1 + README.md | 48 ++++---- samples/README.cpp | 17 +-- samples/tcp/TcpServer.cpp | 1 + src/header/MinimalSocket/Error.h | 38 +++--- src/header/MinimalSocket/NonCopiable.h | 19 +++ src/header/MinimalSocket/core/Address.h | 15 ++- src/header/MinimalSocket/core/Definitions.h | 14 +-- src/header/MinimalSocket/core/Receiver.h | 16 ++- src/header/MinimalSocket/core/Sender.h | 24 ++-- src/header/MinimalSocket/core/Socket.h | 23 ++-- src/header/MinimalSocket/core/SocketContext.h | 38 ++++-- src/header/MinimalSocket/tcp/TcpClient.h | 4 +- src/header/MinimalSocket/tcp/TcpServer.h | 24 ++-- src/header/MinimalSocket/udp/UdpSocket.h | 28 ++--- src/src/Error.cpp | 13 ++- src/src/SocketAddress.cpp | 20 ++-- src/src/SocketAddress.h | 6 +- src/src/SocketFunctions.cpp | 8 +- src/src/SocketFunctions.h | 10 +- src/src/{SocketId.cpp => SocketHandler.cpp} | 15 +-- src/src/{SocketId.h => SocketHandler.h} | 24 ++-- src/src/Utils.cpp | 51 --------- src/src/Utils.h | 61 +++++++--- src/src/core/Address.cpp | 7 +- src/src/core/Definitions.cpp | 12 +- src/src/core/Receiver.cpp | 108 ++++++++++-------- src/src/core/Sender.cpp | 92 ++++++--------- src/src/core/Socket.cpp | 38 +++--- src/src/core/SocketContext.cpp | 26 ----- src/src/tcp/TcpClient.cpp | 8 +- src/src/tcp/TcpServer.cpp | 19 ++- src/src/udp/UdpSocket.cpp | 42 +++---- tests/SlicedOps.cpp | 9 +- 34 files changed, 436 insertions(+), 443 deletions(-) create mode 100644 src/header/MinimalSocket/NonCopiable.h rename src/src/{SocketId.cpp => SocketHandler.cpp} (87%) rename src/src/{SocketId.h => SocketHandler.h} (72%) delete mode 100644 src/src/Utils.cpp diff --git a/.gitignore b/.gitignore index ffb44a60..1e59ac21 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build .vscode *.log test.html +TODO diff --git a/README.md b/README.md index 42d62e14..b07cad53 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ ## INTRO -**MinimalSocket** gives you a modern **C++** interface to create, connect and handle **tcp** and **udp** sockets, in a -completely platform independent way. The supported systems are: **Windows**, any **Linux** distro and **MacOS**. +**MinimalSocket** gives you a modern **C++** library to set up and create **tcp** and **udp** socket connections, in a +completely platform agnostic way. The supported platforms are: **Windows**, any **Linux** distro and **MacOS**. -Check [Features](#features) to see details about the various features of **MinimalSocket**. You can refer to [Usage](#usage) and [Samples](#samples) to see how to use **MinimalSocket**. +Check [Features](#features) to see details about the various features of **MinimalSocket**. Read [Usage](#usage) and [Samples](#samples) to see how easy is to use **MinimalSocket**. This is a **CMake** project, check [CMake support](#cmake-support) to see how this library can be integrated. @@ -23,18 +23,18 @@ Remember to leave a **star** in case you have found this library useful. Haven't left a **star** already? Do it now ;)! -**MinimalSocket** allows you to build and set up **tcp** and **udp** connections. Messages can be sent and received in terms of both low level buffer of chars or high level string. Indeed, this is actually the only capability you need for a socket, as more complex messages can be encoded and decoded using among the others approaches like [Google Protocol Buffers](https://developers.google.com/protocol-buffers/docs/cpptutorial) or [NanoPb](https://jpa.kapsi.fi/nanopb/). +**MinimalSocket** allows you to build and set up **tcp** and **udp** connections. Messages can be sent and received in terms of both low level buffer of chars or high level string. Indeed, this is actually the only capability you need for a socket, as more complex messages can be serialized to a string or internalized from a string using, among the others, approaches like [Google Protocol Buffers](https://developers.google.com/protocol-buffers/docs/cpptutorial) or [NanoPb](https://jpa.kapsi.fi/nanopb/). -This are the most notable properties of **MinimalSocket**: -- A modern **C++** interface allows you to set up and build connections in terms of objects. Sockets are not opened as soon as the wrapping object is created, but you after calling a proper method, allowing you to decouple socket creation from socket opening. Sockets are automatically closed (and all relevant information cleaned after destroying the wrapping object). -- You don't need to access low level functions from system modules: let **MinimalSocket** do it for you. Actually, all the system specific modules, functions, linkages are kept completely private. -- **AF_INET** (**ip v4**) and **AF_INET6** (**ip v6**), refer to [this](https://www.ibm.com/docs/en/i/7.1?topic=characteristics-socket-address-family) link, are both supported -- Many sockets operations are by default blocking. However, **MinimalSocket** allows you also to opt for non-blocking versions off such operations, specifying a **timeout** to use, after which the operation terminates in any case. In particular, the operations allowing for such possibility are: - - non blocking receive (send are always intrinsically non blocking) +This are the most notable characteristics of **MinimalSocket**: +- A modern **C++** object oriented API allowing you to set up and build socket connections. Typically, socket handlers are represented by the classes part of this library. Any time an object is created, the related socket is closed in order to defer the opening at the convenient moment. This allows you to decouple the moments when sockets are created from those where they are actually connected. Any connection is automatically closed when the handler object is destroyed (and all relevant information cleaned up after destroying the wrapping object). +- Prevent you from handling low level socket programming, abstracting from the particular platform hosting your application(s): let **MinimalSocket** do all the work for you. Morevoer, all the platform specific modules, functions, linkages are not exposed. +- **AF_INET** (**ip v4**) and **AF_INET6** (**ip v6**) addresses, refer to [this](https://www.ibm.com/docs/en/i/7.1?topic=characteristics-socket-address-family) link, are both supported +- Many sockets operations are by default blocking. However, **MinimalSocket** allows you also to use specify **timeout**(s) to use, after which the operation terminates in any case giving the control back to the caller. In particular, the operations allowing for such possibility are: + - receive (send are always intrinsically non blocking) - acceptance of a new client from the tcp server side -- **MinimalSocket** is tested to be **thread safe**. Morevoer, you can also send while receiving in different dedicated threads. This allows you to easily create your own asynchronous sockets, building upon the classes offered by this library. -- **Udp** sockets can be used both as un-connected or connected, check [here](./samples/udp/README.md) for further details. Moreover, the same **udp** socket can be connected or sconnected during its lifetime. -- Under **Windows** systems, [**WSAStartup**](https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup) is automatically called before using any functionalities. From the outside, you can specify the Windows Sockets specification version. +- **MinimalSocket** is tested to be **thread safe**. However, notice that you can send while receiving for a certain socket, but from different threads. This allows you to easily create your own asynchronous sockets, building on top of the classes offered by this library. +- **Udp** sockets can be used both as un-connected or connected, check [here](./samples/udp/README.md) for further details. Moreover, the same **udp** socket can be connected or disconnected during its lifetime. +- Under **Windows** systems, [**WSAStartup**](https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup) is automatically called before using any functionalities. From the outside, you can specify the Windows Sockets version if you need. ## USAGE @@ -48,7 +48,7 @@ To create a **tcp** server you just need to build a **tcp::TcpServer** object: ```cpp #include -MinimalSocket::Port port = 15768; // port the server needs to bind +MinimalSocket::Port port = 15768; // the port to bind MinimalSocket::tcp::TcpServer tcp_server(port, MinimalSocket::AddressFamily::IP_V4); ``` @@ -61,9 +61,9 @@ bool success = tcp_server.open(); and now you are ready to accept new clients: ```cpp -// accepts next client asking connection +// accepts the next client that will ask the connection MinimalSocket::tcp::TcpConnection accepted_connection = - tcp_server.acceptNewClient(); // blocing till a client actually asks the + tcp_server.acceptNewClient(); // blocking till a client actually asks the // connection ``` @@ -92,8 +92,10 @@ MinimalSocket::tcp::TcpClient tcp_client( open it: ```cpp -// open the client: asks connection to server -bool success = tcp_client.open(); + // Open the server. Here, the client will ask the connection to specified + // server. After that, the client will be actually connected. + bool success = + tcp_client.open(); // blocking till the connection is actually established ``` you can now receive and send information with the remote server by simply doing this: @@ -120,7 +122,7 @@ MinimalSocket::udp::UdpBinded udp_socket(this_socket_port, open it: ```cpp -// open the client: reserve port for this cocket +// Open the server. This will bind the specified port. bool success = udp_socket.open(); ``` @@ -167,7 +169,7 @@ udp_connected_socket.send("a message to send"); Haven't left a **star** already? Do it now ;)! -Instructions about the **tcp** samples are contained [here](./samples/tcp/README.md), while [here](./samples/udp/README.md) the **udp** samples are explained. +Instructions about **tcp** samples can be found [here](./samples/tcp/README.md), while **udp** samples are [here](./samples/udp/README.md) discussed. ATTENTION!!! The Samples execution might be blocked the first time by your firewall: set up properly your firewall or run the samples with the [administrator privileges](https://www.techopedia.com/definition/4961/administrative-privileges#:~:text=Administrative%20privileges%20are%20the%20ability,as%20a%20database%20management%20system.) @@ -175,7 +177,7 @@ ATTENTION!!! The Samples execution might be blocked the first time by your firew Haven't left a **star** already? Do it now ;)! -To consume this library you can rely on [CMake](https://cmake.org). +In order to consume this library you can rely on [CMake](https://cmake.org). More precisely, You can fetch this package and link to the **MinimalSocket** library: ```cmake include(FetchContent) @@ -195,5 +197,5 @@ target_link_libraries(${TARGET_NAME} ) ``` -All the system specific modules are internally inlcluded and don't exposed to the outside. -Moreover, under **Windows**, **wsock32** and **ws2_32** are privately linked and you don't need to link them again when integrating **MinimalSocket**. +All the system specific modules are internally inlcluded and are not exposed. +Moreover, under **Windows**, **wsock32** and **ws2_32** are privately linked and you don't need to link them again when consuming **MinimalSocket**. diff --git a/samples/README.cpp b/samples/README.cpp index 8b234360..16288cc3 100644 --- a/samples/README.cpp +++ b/samples/README.cpp @@ -1,16 +1,17 @@ // tcp server #include int main() { - MinimalSocket::Port port = 15768; // port the server needs to bind + MinimalSocket::Port port = 15768; // the port to bind MinimalSocket::tcp::TcpServer tcp_server(port, MinimalSocket::AddressFamily::IP_V4); - // open the server: binds the port and start to listen on the port + // Open the server. This will bind the port and the server will start to + // listen for connection requests. bool success = tcp_server.open(); - // accepts next client asking connection + // accepts the next client that will ask the connection MinimalSocket::tcp::TcpConnection accepted_connection = - tcp_server.acceptNewClient(); // blocing till a client actually asks the + tcp_server.acceptNewClient(); // blocking till a client actually asks the // connection // receive a message @@ -30,8 +31,10 @@ int main() { MinimalSocket::tcp::TcpClient tcp_client( MinimalSocket::Address{server_address, server_port}); - // open the client: asks connection to server - bool success = tcp_client.open(); + // Open the server. Here, the client will ask the connection to specified + // server. After that, the client will be actually connected. + bool success = + tcp_client.open(); // blocking till the connection is actually established // send a message tcp_client.send("a message to send"); @@ -49,7 +52,7 @@ int main() { MinimalSocket::udp::UdpBinded udp_socket(this_socket_port, MinimalSocket::AddressFamily::IP_V6); - // open the client: reserve port for this cocket + // Open the server. This will bind the specified port. bool success = udp_socket.open(); // send a message to another udp diff --git a/samples/tcp/TcpServer.cpp b/samples/tcp/TcpServer.cpp index 419c1d55..89a11133 100644 --- a/samples/tcp/TcpServer.cpp +++ b/samples/tcp/TcpServer.cpp @@ -16,6 +16,7 @@ #include #include +#include #include using namespace std; diff --git a/src/header/MinimalSocket/Error.h b/src/header/MinimalSocket/Error.h index 733c84a8..0a568db4 100644 --- a/src/header/MinimalSocket/Error.h +++ b/src/header/MinimalSocket/Error.h @@ -16,46 +16,46 @@ class Error : public std::runtime_error { public: Error(const std::string &what) : std::runtime_error(what){}; - template Error(Args... args) : Error(merge(args...)) {} + template + Error(const Args &...args) : Error(merge(args...)) {} protected: - template static std::string merge(Args... args) { + template static std::string merge(const Args &...args) { std::stringstream stream; - merge(stream, args...); + (merge_(stream, args), ...); return stream.str(); }; - template - static void merge(std::stringstream &stream, const T ¤t, - Args... remaining) { - stream << current; - merge(stream, remaining...); - }; - - template - static void merge(std::stringstream &stream, const T &back) { - stream << back; + template + static void merge_(std::stringstream &stream, const T &arg) { + stream << arg; }; }; -class ErrorCodeAware { +class ErrorCodeHolder { public: - int getErrorCode() const { return error_code; } + ErrorCodeHolder(); -protected: - ErrorCodeAware(); + int getErrorCode() const { return errorCode; } private: - int error_code; + int errorCode; }; -class SocketError : public ErrorCodeAware, public Error { + +class SocketError : public ErrorCodeHolder, public Error { public: /** * @brief last error code raised by the socket API is automatically retrieved + * and appended to error message */ SocketError(const std::string &what); template SocketError(const Args &...args) : SocketError{merge(args...)} {}; }; + +class TimeOutError : public Error { +public: + TimeOutError() : Error("Timeout"){}; +}; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/NonCopiable.h b/src/header/MinimalSocket/NonCopiable.h new file mode 100644 index 00000000..4d683e02 --- /dev/null +++ b/src/header/MinimalSocket/NonCopiable.h @@ -0,0 +1,19 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +namespace MinimalSocket { +class NonCopiable { +public: + NonCopiable(const NonCopiable &) = delete; + NonCopiable &operator=(const NonCopiable &) = delete; + +protected: + NonCopiable() = default; +}; +} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index e6ca3aca..310951fb 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -34,22 +34,25 @@ class Address { * In case of invalid host, the object is built but left empty (i.e. *this == * nullptr would be true) */ - Address(const std::string &hostIp, const Port &port); + Address(const std::string &hostIp, Port port); /** - * @brief A representation of a local host address is created. + * @brief Local host address is asumed. */ - Address(const Port &port, const AddressFamily &family = AddressFamily::IP_V4); + Address(Port port, AddressFamily family = AddressFamily::IP_V4); const std::string &getHost() const { return this->host; }; - const Port &getPort() const { return this->port; }; - const AddressFamily &getFamily() const { return this->family; }; + Port getPort() const { return this->port; }; + AddressFamily getFamily() const { return this->family; }; bool operator==(const Address &o) const; Address(const Address &) = default; Address &operator=(const Address &) = default; + Address(Address &&) = default; + Address &operator=(Address &&) = default; + private: Address() = default; @@ -59,7 +62,7 @@ class Address { }; /** - * @return host:port into a string. + * @return "host:port" into a string. */ std::string to_string(const Address &subject); diff --git a/src/header/MinimalSocket/core/Definitions.h b/src/header/MinimalSocket/core/Definitions.h index e4ad3e37..708b2b4f 100644 --- a/src/header/MinimalSocket/core/Definitions.h +++ b/src/header/MinimalSocket/core/Definitions.h @@ -11,26 +11,26 @@ #include namespace MinimalSocket { -struct Buffer { +struct BufferView { char *buffer; - const std::size_t buffer_size; + std::size_t buffer_size; }; /** * @brief sets all values inside the passed buffer to 0 */ -void clear(const Buffer &subject); +void clear(BufferView &subject); /** * @param subject the string buffer to convert * @return a buffer pointing to the first element of the subject, and a lenght * equal to the current size of subject */ -Buffer makeStringBuffer(std::string &subject); +BufferView makeBufferView(std::string &subject); -struct ConstBuffer { +struct BufferViewConst { const char *buffer; - const std::size_t buffer_size; + std::size_t buffer_size; }; /** @@ -38,7 +38,7 @@ struct ConstBuffer { * @return an immutable buffer pointing to the first element of the subject, and * a lenght equal to the current size of subject */ -ConstBuffer makeStringConstBuffer(const std::string &subject); +BufferViewConst makeBufferViewConst(const std::string &subject); enum class SocketType { UDP, TCP }; diff --git a/src/header/MinimalSocket/core/Receiver.h b/src/header/MinimalSocket/core/Receiver.h index caeeaf61..b6fffef9 100644 --- a/src/header/MinimalSocket/core/Receiver.h +++ b/src/header/MinimalSocket/core/Receiver.h @@ -15,10 +15,16 @@ namespace MinimalSocket { class ReceiverBase : public virtual Socket { protected: - std::unique_ptr> - lazyUpdateReceiveTimeout(const Timeout &timeout); + template + void lazyUpdateAndUseTimeout(const Timeout &to, Pred what) { + std::scoped_lock lock{receive_mtx}; + updateTimeout_(to); + what(receive_timeout); + } private: + void updateTimeout_(const Timeout &timeout); + std::mutex receive_mtx; Timeout receive_timeout = NULL_TIMEOUT; }; @@ -40,7 +46,7 @@ class Receiver : public ReceiverBase { * message. It can be also lower then buffer size, as less bytes might be * received. */ - std::size_t receive(const Buffer &message, + std::size_t receive(BufferView message, const Timeout &timeout = NULL_TIMEOUT); /** @@ -61,7 +67,7 @@ class Receiver : public ReceiverBase { /** * @brief Typically associated to a non connected socket, whose remote peer that - * sends bytes is known and may change over the time. + * sends bytes is not fixed. * Attention!! Even when calling from different threads some simultaneously * receive, they will be satisfited one at a time, as an internal mutex must be * locked before starting to receive. @@ -81,7 +87,7 @@ class ReceiverUnkownSender : public ReceiverBase { * also lower then buffer size, as less bytes might be received. * In case no bytes were received within the timeout, a nullopt is returned. */ - std::optional receive(const Buffer &message, + std::optional receive(BufferView message, const Timeout &timeout = NULL_TIMEOUT); struct ReceiveStringResult { diff --git a/src/header/MinimalSocket/core/Sender.h b/src/header/MinimalSocket/core/Sender.h index 125adb06..161a07c8 100644 --- a/src/header/MinimalSocket/core/Sender.h +++ b/src/header/MinimalSocket/core/Sender.h @@ -10,8 +10,6 @@ #include #include -#include -#include #include #include @@ -29,7 +27,7 @@ class Sender : public virtual Socket { * @param message the buffer storing the bytes to send * @return true in case all the bytes were successfully sent */ - bool send(const ConstBuffer &message); + bool send(const BufferViewConst &message); /** * @param message the buffer storing the bytes to send as a string @@ -43,7 +41,7 @@ class Sender : public virtual Socket { /** * @brief Typically associated to a non connected socket, whose remote peer that - * sends bytes is known and may change over the time. + * sends bytes is not fixed. * Attention!! It is thread safe to simultaneously send messages from different * threads to many different recipients. * However, be aware that in case 2 or more threads are sending a message to the @@ -57,7 +55,7 @@ class SenderTo : public virtual Socket { * @return true in case all the bytes were successfully sent to the specified * recipient */ - bool sendTo(const ConstBuffer &message, const Address &recipient); + bool sendTo(const BufferViewConst &message, const Address &recipient); /** * @param message the buffer storing the bytes to send as a string @@ -68,20 +66,20 @@ class SenderTo : public virtual Socket { bool sendTo(const std::string &message, const Address &recipient); private: - std::future reserveAddress(const Address &to_reserve); - void freeAddress(const Address &to_reserve); + std::mutex &getRecipientMtx(const Address &recipient); std::mutex recipients_register_mtx; - struct AddressHasher { - std::hash string_hasher; - std::size_t operator()(const Address &subject) const { - return string_hasher(to_string(subject)); + return getHasher()(to_string(subject)); + } + + static std::hash &getHasher() { + static std::hash res = std::hash{}; + return res; } }; - using WaitingToSendQueue = std::list>; - std::unordered_map + std::unordered_map, AddressHasher> recipients_register; }; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index d96aaada..9bf52c11 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -15,7 +15,6 @@ #include #include #include -#include namespace MinimalSocket { #ifdef _WIN32 @@ -48,7 +47,7 @@ class WSAManager { }; #endif -class SocketIdWrapper; +class SocketHandler; /** * @brief The base onject of any kind of socket. @@ -61,7 +60,7 @@ class Socket { Socket &operator=(const Socket &) = delete; /** - * @return the socket id associated to this object. + * @return the socket descriptor associated to this object. * * This might be: * @@ -80,23 +79,25 @@ class Socket { * number. Beware that you should really know what you are doing when using * this number. */ - int accessSocketID() const; + int getSocketDescriptor() const; protected: Socket(); - static void transfer(Socket &receiver, Socket &giver); + void steal(Socket &giver); + void transfer(Socket &recipient) { recipient.steal(*this); } - const SocketIdWrapper &getIDWrapper() const; - SocketIdWrapper &getIDWrapper(); - void resetIDWrapper(); + const SocketHandler &getHandler() const; + SocketHandler &getHandler(); + void resetHandler(); private: - std::unique_ptr socket_id_wrapper; + std::unique_ptr socket_id_wrapper; }; class Openable : public virtual Socket { public: + virtual ~Openable() = default; bool wasOpened() const { return opened; } /** @@ -114,8 +115,8 @@ class Openable : public virtual Socket { protected: Openable() = default; - static void transfer(Openable &receiver, - Openable &giver); // Socket::transfer(...) is also called + void steal(Openable &giver); // Socket::steal(...) is also called + void transfer(Openable &recipient) { recipient.steal(*this); } virtual void open_() = 0; diff --git a/src/header/MinimalSocket/core/SocketContext.h b/src/header/MinimalSocket/core/SocketContext.h index f931a6b8..d6614f35 100644 --- a/src/header/MinimalSocket/core/SocketContext.h +++ b/src/header/MinimalSocket/core/SocketContext.h @@ -15,15 +15,19 @@ namespace MinimalSocket { class RemoteAddressAware { public: + RemoteAddressAware(const RemoteAddressAware &o) + : remote_address{o.remote_address} {} + RemoteAddressAware &operator=(const RemoteAddressAware &o) { + remote_address = o.remote_address; + return *this; + } + /** * @return the address of the peer that can exchange messages with this * socket. */ Address getRemoteAddress() const; - RemoteAddressAware(const RemoteAddressAware &); - RemoteAddressAware &operator=(const RemoteAddressAware &); - protected: /** * @throw in case the passed address is invalid (i.e. address == nullptr is @@ -38,15 +42,21 @@ class RemoteAddressAware { class PortToBindAware { public: + PortToBindAware(const PortToBindAware &o) + : port_to_bind{o.port_to_bind.load()}, must_be_free_port{ + o.must_be_free_port.load()} {} + PortToBindAware &operator=(const PortToBindAware &o) { + port_to_bind = o.port_to_bind.load(); + must_be_free_port = o.must_be_free_port.load(); + return *this; + } + /** * @return the port that will be reserved, in case the socket was not already * opened, or the port actually reserved when the socket was opened. */ Port getPortToBind() const { return port_to_bind; } - PortToBindAware(const PortToBindAware &); - PortToBindAware &operator=(const PortToBindAware &); - /** * @brief Used to enforce the fact that this port should be not previously * binded by anyone else when opening the socket. Beware that the default @@ -57,9 +67,9 @@ class PortToBindAware { bool shallBeFreePort() const { return must_be_free_port; } protected: - PortToBindAware(const Port &port) : port_to_bind(port){}; + PortToBindAware(Port port) : port_to_bind(port){}; - void setPort(const Port &port) { port_to_bind = port; }; + void setPort(Port port) { port_to_bind = port; }; private: std::atomic port_to_bind; @@ -68,17 +78,21 @@ class PortToBindAware { class RemoteAddressFamilyAware { public: + RemoteAddressFamilyAware(const RemoteAddressFamilyAware &o) + : remote_address_family{o.remote_address_family.load()} {} + RemoteAddressFamilyAware &operator=(const RemoteAddressFamilyAware &o) { + remote_address_family = o.remote_address_family.load(); + return *this; + } + /** * @return the address family of the peer that can exchange messages with this * socket. */ AddressFamily getRemoteAddressFamily() const { return remote_address_family; } - RemoteAddressFamilyAware(const RemoteAddressFamilyAware &); - RemoteAddressFamilyAware &operator=(const RemoteAddressFamilyAware &); - protected: - RemoteAddressFamilyAware(const AddressFamily &family) + RemoteAddressFamilyAware(AddressFamily family) : remote_address_family(family){}; private: diff --git a/src/header/MinimalSocket/tcp/TcpClient.h b/src/header/MinimalSocket/tcp/TcpClient.h index e39d1e54..4e35d618 100644 --- a/src/header/MinimalSocket/tcp/TcpClient.h +++ b/src/header/MinimalSocket/tcp/TcpClient.h @@ -7,12 +7,14 @@ #pragma once +#include #include #include #include namespace MinimalSocket::tcp { -class TcpClient : public Openable, +class TcpClient : public NonCopiable, + public Openable, public Sender, public Receiver, public RemoteAddressAware { diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index 99adc837..62d0da1c 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -17,11 +18,12 @@ namespace MinimalSocket::tcp { class TcpServer; /** - * @brief An already accepted connection to a client. - * An istance of this object can be built by before creating a TcpServer, open - * it and call acceptNewClient(). + * @brief Handler of an already established connection with a client, on the + * server side. + * An istance of this object is created calling TcpServer::acceptNewClient(). */ -class TcpConnection : public Sender, +class TcpConnection : public NonCopiable, + public Sender, public Receiver, public RemoteAddressAware { friend class TcpServer; @@ -34,9 +36,9 @@ class TcpConnection : public Sender, TcpConnection(const Address &remote_address); }; -class TcpServer : public PortToBindAware, +class TcpServer : public NonCopiable, + public PortToBindAware, public RemoteAddressFamilyAware, - public virtual Socket, public Openable { public: TcpServer(TcpServer &&o); @@ -52,8 +54,8 @@ class TcpServer : public PortToBindAware, * @param accepted_client_family family of the client that will ask the * connection to this server */ - TcpServer(const Port port_to_bind = ANY_PORT, - const AddressFamily &accepted_client_family = AddressFamily::IP_V4); + TcpServer(Port port_to_bind = ANY_PORT, + AddressFamily accepted_client_family = AddressFamily::IP_V4); /** * @brief Wait till accepting the connection from a new client. This is a @@ -83,9 +85,9 @@ class TcpServer : public PortToBindAware, void open_() override; private: - std::atomic client_queue_size = - 50; // maximum number of clients put in the queue wiating for connection - // to be accepted + // maximum number of clients waiting for the connection to be + // accepted + std::atomic client_queue_size = 50; std::mutex accept_mtx; }; diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index eb785175..2d3bd86e 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -28,7 +29,8 @@ class UdpConnected; * At the same time, this udp can send messages to any other non connected udp * sockets. */ -class UdpBinded : public SenderTo, +class UdpBinded : public NonCopiable, + public SenderTo, public ReceiverUnkownSender, public PortToBindAware, public RemoteAddressFamilyAware, @@ -44,9 +46,8 @@ class UdpBinded : public SenderTo, * @param port_to_bind the port to reserve by this udp * @param accepted_connection_family the kind of udp that can reach this one */ - UdpBinded( - const Port port_to_bind = ANY_PORT, - const AddressFamily &accepted_connection_family = AddressFamily::IP_V4); + UdpBinded(Port port_to_bind = ANY_PORT, + AddressFamily accepted_connection_family = AddressFamily::IP_V4); /** * @brief Connects the udo socket to the specified remote address. @@ -93,7 +94,8 @@ class UdpBinded : public SenderTo, * incoming from udp sockets different from the remote address are filtered out. * At the same time, the remote address might also not exists at all. */ -class UdpConnected : public Sender, +class UdpConnected : public NonCopiable, + public Sender, public Receiver, public PortToBindAware, public RemoteAddressAware, @@ -109,7 +111,7 @@ class UdpConnected : public Sender, * @param remote_address remote address of the peer * @param port the port to reserve by this udp */ - UdpConnected(const Address &remote_address, const Port &port = ANY_PORT); + UdpConnected(const Address &remote_address, Port port = ANY_PORT); /** * @brief disconnect the underlying socket, generating an unbinded udp that @@ -132,17 +134,17 @@ class UdpConnected : public Sender, * @param initial_message the message sent from the remote peer to detect its * address */ -UdpConnected -makeUdpConnectedToUnknown(const Port &port, - const AddressFamily &accepted_connection_family, - std::string *initial_message = nullptr); +UdpConnected makeUdpConnectedToUnknown(Port port, + AddressFamily accepted_connection_family, + std::string *initial_message = nullptr); /** * @brief non blocking version of makeUdpConnectedToUnknown(const Port &, const * AddressFamily &, std::string *). In case no remote peer sends at least 1 byte * within the timeout, a nullopt is returned. */ -std::optional makeUdpConnectedToUnknown( - const Port &port, const AddressFamily &accepted_connection_family, - const Timeout &timeout, std::string *initial_message = nullptr); +std::optional +makeUdpConnectedToUnknown(Port port, AddressFamily accepted_connection_family, + const Timeout &timeout, + std::string *initial_message = nullptr); } // namespace MinimalSocket::udp diff --git a/src/src/Error.cpp b/src/src/Error.cpp index 2143b12a..eb2a4264 100644 --- a/src/src/Error.cpp +++ b/src/src/Error.cpp @@ -7,18 +7,23 @@ #include -#include "SocketId.h" +#include "../src/SocketHandler.h" namespace MinimalSocket { -ErrorCodeAware::ErrorCodeAware() { - error_code = +namespace { +int getLastErrorCode() { + int res = #ifdef _WIN32 WSAGetLastError(); #else static_cast(errno); #endif + return res; } +} // namespace + +ErrorCodeHolder::ErrorCodeHolder() : errorCode{getLastErrorCode()} {} SocketError::SocketError(const std::string &what) - : ErrorCodeAware(), Error(what, " , error code: ", getErrorCode()) {} + : ErrorCodeHolder{}, Error(what, " , error code: ", getErrorCode()) {} } // namespace MinimalSocket diff --git a/src/src/SocketAddress.cpp b/src/src/SocketAddress.cpp index 29f42ade..bb3f6cb7 100644 --- a/src/src/SocketAddress.cpp +++ b/src/src/SocketAddress.cpp @@ -17,7 +17,7 @@ namespace MinimalSocket { std::optional toSocketAddressIpv4(const std::string &host, - const Port &port) { + Port port) { #ifdef _WIN32 WSALazyInitializer::lazyInit(); #endif @@ -33,14 +33,14 @@ std::optional toSocketAddressIpv4(const std::string &host, #ifdef _WIN32 in_addr ia; if (1 == ::inet_pton(AF_INET, host.c_str(), &ia)) { - ::memcpy(&result->sin_addr, &ia, sizeof(in_addr)); - return result; + ::memcpy(&result->sin_addr, &ia, sizeof(in_addr)); + return result; } #else in_addr ia; if (1 == ::inet_pton(AF_INET, host.c_str(), &ia)) { - result->sin_addr.s_addr = ia.s_addr; - return result; + result->sin_addr.s_addr = ia.s_addr; + return result; } #endif @@ -60,14 +60,14 @@ std::optional toSocketAddressIpv4(const std::string &host, return std::nullopt; } - const auto* ipv4 = reinterpret_cast(res->ai_addr); + const auto *ipv4 = reinterpret_cast(res->ai_addr); result->sin_addr.s_addr = ipv4->sin_addr.s_addr; ::freeaddrinfo(res); return result; } std::optional toSocketAddressIpv6(const std::string &host, - const Port &port) { + Port port) { #ifdef _WIN32 WSALazyInitializer::lazyInit(); #endif @@ -84,8 +84,8 @@ std::optional toSocketAddressIpv6(const std::string &host, #ifdef _WIN32 in6_addr ia; if (1 == ::inet_pton(AF_INET6, host.c_str(), &ia)) { - ::memcpy(&result->sin6_addr, &ia, sizeof(in6_addr)); - return result; + ::memcpy(&result->sin6_addr, &ia, sizeof(in6_addr)); + return result; } #else in6_addr ia; @@ -111,7 +111,7 @@ std::optional toSocketAddressIpv6(const std::string &host, return std::nullopt; } - const auto* ipv6 = reinterpret_cast(res->ai_addr); + const auto *ipv6 = reinterpret_cast(res->ai_addr); result->sin6_addr = ipv6->sin6_addr; ::freeaddrinfo(res); return result; diff --git a/src/src/SocketAddress.h b/src/src/SocketAddress.h index 240f112d..417a3a5d 100644 --- a/src/src/SocketAddress.h +++ b/src/src/SocketAddress.h @@ -7,7 +7,7 @@ #pragma once -#include "SocketId.h" +#include "SocketHandler.h" namespace MinimalSocket { /** @@ -51,14 +51,14 @@ static constexpr std::size_t MAX_POSSIBLE_ADDRESS_SIZE = * of the address */ std::optional toSocketAddressIpv4(const std::string &host, - const Port &port); + Port port); /** * @brief checks the address syntax and in case * it's valid as an ipv6, creates the socket API representation * of the address */ std::optional toSocketAddressIpv6(const std::string &host, - const Port &port); + Port port); std::optional toPort(const SocketAddress &address); diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index fe5a5cb8..f51d6bdd 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -20,8 +20,8 @@ namespace { #endif } // namespace -Port bind(const SocketID &socket_id, const AddressFamily &family, - const Port &port, const bool must_be_free_port) { +Port bind(SocketID socket_id, AddressFamily family, Port port, + bool must_be_free_port) { if (!must_be_free_port) { int reusePortOptVal = 1; ::setsockopt(socket_id, SOL_SOCKET, REBIND_OPTION, @@ -94,14 +94,14 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, return binded_port; } -void listen(const SocketID &socket_id, const std::size_t backlog_size) { +void listen(SocketID socket_id, std::size_t backlog_size) { if (::listen(socket_id, static_cast(backlog_size)) == SCK_SOCKET_ERROR) { auto err = SocketError{"Error: listening on reserved port"}; throw err; } } -void connect(const SocketID &socket_id, const Address &remote_address) { +void connect(SocketID socket_id, const Address &remote_address) { visitAddress( remote_address.getFamily(), [&]() { diff --git a/src/src/SocketFunctions.h b/src/src/SocketFunctions.h index 026b9f4e..eaed8e76 100644 --- a/src/src/SocketFunctions.h +++ b/src/src/SocketFunctions.h @@ -7,14 +7,14 @@ #pragma once -#include "SocketId.h" +#include "SocketHandler.h" namespace MinimalSocket { // return port actually binded (as you could pass to the function also AnyPort) -Port bind(const SocketID &socket_id, const AddressFamily &family, - const Port &port, const bool must_be_free_port); +Port bind(SocketID socket_id, AddressFamily family, Port port, + bool must_be_free_port); -void listen(const SocketID &socket_id, const std::size_t backlog_size); +void listen(SocketID socket_id, std::size_t backlog_size); -void connect(const SocketID &socket_id, const Address &remote_address); +void connect(SocketID socket_id, const Address &remote_address); } // namespace MinimalSocket diff --git a/src/src/SocketId.cpp b/src/src/SocketHandler.cpp similarity index 87% rename from src/src/SocketId.cpp rename to src/src/SocketHandler.cpp index 9c6c7641..5e75091f 100644 --- a/src/src/SocketId.cpp +++ b/src/src/SocketHandler.cpp @@ -7,11 +7,12 @@ #include +#include "SocketHandler.h" #include "Utils.h" namespace MinimalSocket { #ifdef _WIN32 -WSALazyInitializer::WSALazyInitializer(const WSAVersion& version) +WSALazyInitializer::WSALazyInitializer(const WSAVersion &version) : configured_version(version) { WSADATA wsa; const BYTE version_major = static_cast(version[0]); @@ -55,8 +56,9 @@ std::unique_ptr WSALazyInitializer::lazy_proxy = nullptr; void WSALazyInitializer::lazyInit() { auto version = WSAManager::getWsaVersion(); std::scoped_lock lock(WSALazyInitializer::lazy_proxy_mtx); - if ((nullptr != WSALazyInitializer::lazy_proxy) && (WSALazyInitializer::lazy_proxy->configured_version == version)) { - return; + if ((nullptr != WSALazyInitializer::lazy_proxy) && + (WSALazyInitializer::lazy_proxy->configured_version == version)) { + return; } try { WSALazyInitializer::lazy_proxy.reset(new WSALazyInitializer{version}); @@ -83,9 +85,9 @@ void close(SocketID &socket_id) { } } // namespace -SocketIdWrapper::~SocketIdWrapper() { MinimalSocket::close(socket_id); } +SocketHandler::~SocketHandler() { MinimalSocket::close(socket_id); } -void SocketIdWrapper::reset(const SocketID &hndl) { +void SocketHandler::reset(SocketID hndl) { if (socket_id != SCK_INVALID_SOCKET) { MinimalSocket::close(socket_id); } @@ -102,8 +104,7 @@ int domain_number(const AddressFamily &family) { } } // namespace -void SocketIdWrapper::reset(const SocketType &type, - const AddressFamily &family) { +void SocketHandler::reset(SocketType type, AddressFamily family) { if (socket_id != SCK_INVALID_SOCKET) { MinimalSocket::close(socket_id); } diff --git a/src/src/SocketId.h b/src/src/SocketHandler.h similarity index 72% rename from src/src/SocketId.h rename to src/src/SocketHandler.h index fe8a458f..3f347b23 100644 --- a/src/src/SocketId.h +++ b/src/src/SocketHandler.h @@ -48,33 +48,35 @@ using SocketID = int; * An object storing a socket API handler and containing the minimal * functionalities for interacting with it. */ -class SocketIdWrapper { +class SocketHandler { public: - SocketIdWrapper(const SocketIdWrapper &) = delete; - SocketIdWrapper &operator=(const SocketIdWrapper &) = delete; + SocketHandler(const SocketHandler &) = delete; + SocketHandler &operator=(const SocketHandler &) = delete; + SocketHandler(SocketHandler &&) = delete; + SocketHandler &operator=(SocketHandler &&) = delete; - const SocketID &accessId() const { return socket_id; }; + auto accessId() const { return socket_id; }; /** * @brief an invalid socket id is created */ - SocketIdWrapper() = default; + SocketHandler() = default; /** * @brief close and shutdown the current socket */ - ~SocketIdWrapper(); + ~SocketHandler(); /** - * @brief internally creates a new socket + * @brief regenerates the socket descriptor, i.e. creates a new socket */ - void reset(const SocketType &type, const AddressFamily &family); + void reset(SocketType type, AddressFamily family); /** - * @brief the passed handler should be already created externally - * by the socket api + * @brief the passed handler should be already externally created and setup + * (for blocking or non blocking node). */ - void reset(const SocketID &hndl); + void reset(SocketID hndl); private: SocketID socket_id = SCK_INVALID_SOCKET; diff --git a/src/src/Utils.cpp b/src/src/Utils.cpp deleted file mode 100644 index 399c01b4..00000000 --- a/src/src/Utils.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -#include "Utils.h" - -#include - -namespace MinimalSocket { -void visitAddress(const AddressFamily &family, - const std::function &ipv4_case, - const std::function &ipv6_case) { - switch (family) { - case AddressFamily::IP_V4: - ipv4_case(); - break; - case AddressFamily::IP_V6: - ipv6_case(); - break; - default: - throw Error{"Unrecognized AddressFamily"}; - break; - } -} - -void try_within_timeout(const std::function &action_to_try, - const std::function &action_to_abort, - const Timeout &timeout) { - if (NULL_TIMEOUT == timeout) { - throw Error{"Invalid timeout"}; - } - auto open_task = std::async([&]() { action_to_try(); }); - auto open_task_status = open_task.wait_for(timeout); - if (open_task_status == std::future_status::ready) { - open_task.get(); // will throw if ready because an exception throwned - // before timeout - } else { - try { - action_to_abort(); - open_task.get(); - } catch (...) { - } - throw TimeOutError{}; - } -} -} // namespace MinimalSocket diff --git a/src/src/Utils.h b/src/src/Utils.h index 5befe359..c7d9f37b 100644 --- a/src/src/Utils.h +++ b/src/src/Utils.h @@ -7,31 +7,54 @@ #pragma once +#include +#include #include -#include "SocketId.h" - -#include +#include namespace MinimalSocket { -void visitAddress(const AddressFamily &family, - const std::function &ipv4_case, - const std::function &ipv6_case); - -template void copy_as(U &receiver, const U &giver) { - T &receiver_ref = receiver; - const T &giver_ref = giver; - receiver_ref = giver_ref; +template +void visitAddress(AddressFamily family, Ipv4Pred ipv4_case, + Ipv6Pred ipv6_case) { + switch (family) { + case AddressFamily::IP_V4: + ipv4_case(); + break; + case AddressFamily::IP_V6: + ipv6_case(); + break; + default: + throw Error{"Unrecognized AddressFamily"}; + break; + } } -class TimeOutError : public Error { -public: - TimeOutError() : Error("Timeout"){}; -}; - // rethrow exception if happens // throw timeout excpetion if timeout reached -void try_within_timeout(const std::function &action_to_try, - const std::function &action_to_abort, - const Timeout &timeout); +template +void try_within_timeout(TryAction action_to_try, + RecoverAction action_to_recover, + const Timeout &timeout) { + if (NULL_TIMEOUT == timeout) { + throw Error{"Invalid timeout"}; + } + auto task = std::async(action_to_try); + auto task_status = task.wait_for(timeout); + if (task_status == std::future_status::ready) { + task.get(); // will throw if ready because an exception throwned + // before timeout + } else { + try { + action_to_recover(); + task.get(); + } catch (...) { + } + throw TimeOutError{}; + } +} + +template void copy_as(T &recipient, const T &giver) { + recipient = giver; +} } // namespace MinimalSocket diff --git a/src/src/core/Address.cpp b/src/src/core/Address.cpp index 731df6cf..a850b46d 100644 --- a/src/src/core/Address.cpp +++ b/src/src/core/Address.cpp @@ -14,9 +14,8 @@ #include namespace MinimalSocket { -Address::Address(const std::string &hostIp, const Port &port) { - this->host = hostIp; - this->port = port; +Address::Address(const std::string &hostIp, Port port) + : host{hostIp}, port{port} { if (std::nullopt != toSocketAddressIpv4(hostIp, port)) { this->family = AddressFamily::IP_V4; @@ -36,7 +35,7 @@ static const std::string LOCALHOST_IPv4 = "127.0.0.1"; static const std::string LOCALHOST_IPv6 = "::1"; } // namespace -Address::Address(const std::uint16_t &port, const AddressFamily &family) { +Address::Address(std::uint16_t port, AddressFamily family) { this->port = port; this->family = family; visitAddress( diff --git a/src/src/core/Definitions.cpp b/src/src/core/Definitions.cpp index b5556011..800e10ca 100644 --- a/src/src/core/Definitions.cpp +++ b/src/src/core/Definitions.cpp @@ -7,18 +7,18 @@ #include -#include "../SocketId.h" +#include "../SocketHandler.h" namespace MinimalSocket { -void clear(const Buffer &subject) { +void clear(BufferView &subject) { ::memset(subject.buffer, 0, subject.buffer_size); } -Buffer makeStringBuffer(std::string &subject) { - return Buffer{subject.data(), subject.size()}; +BufferView makeBufferView(std::string &subject) { + return BufferView{subject.data(), subject.size()}; } -ConstBuffer makeStringConstBuffer(const std::string &subject) { - return ConstBuffer{subject.data(), subject.size()}; +BufferViewConst makeBufferViewConst(const std::string &subject) { + return BufferViewConst{subject.data(), subject.size()}; } } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 7cffbd1c..6871c2e8 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -12,19 +12,17 @@ #ifndef _WIN32 #include #endif + namespace MinimalSocket { -std::unique_ptr> -ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { - std::unique_ptr> lock = - std::make_unique>(receive_mtx); +void ReceiverBase::updateTimeout_(const Timeout &timeout) { if (timeout == receive_timeout) { - return lock; + return; } receive_timeout = timeout; // set new timeout #ifdef _WIN32 auto tv = DWORD(this->receive_timeout.count()); - if (setsockopt(getIDWrapper().accessId(), SOL_SOCKET, SO_RCVTIMEO, + if (setsockopt(getHandler().accessId(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { #else @@ -38,14 +36,12 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { std::chrono::duration_cast(receive_timeout) .count(); } - if (::setsockopt(getIDWrapper().accessId(), SOL_SOCKET, SO_RCVTIMEO, + if (::setsockopt(getHandler().accessId(), SOL_SOCKET, SO_RCVTIMEO, static_cast(&tv), sizeof(struct timeval)) != 0) { #endif - auto err = SocketError{"can't set timeout"}; - throw err; + throw SocketError{"can't set timeout"}; } - return lock; } namespace { @@ -63,61 +59,75 @@ void check_received_bytes(int &recvBytes, const Timeout &timeout) { recvBytes = 0; if ((error_with_code.getErrorCode() == TIMEOUT_CODE) && (timeout != NULL_TIMEOUT)) { - // just out of time: tolerate + // just out of time: tolerable return; } throw error_with_code; } } // namespace -std::size_t Receiver::receive(const Buffer &message, const Timeout &timeout) { - auto lock = lazyUpdateReceiveTimeout(timeout); - clear(message); - int recvBytes = ::recv(getIDWrapper().accessId(), message.buffer, - static_cast(message.buffer_size), 0); - check_received_bytes(recvBytes, timeout); - if (recvBytes > message.buffer_size) { - // if here, the message received is probably corrupted - recvBytes = 0; - } - return static_cast(recvBytes); +std::size_t Receiver::receive(BufferView message, const Timeout &timeout) { + std::size_t res = 0; + + lazyUpdateAndUseTimeout( + timeout, [&message, &res, this](const Timeout &timeout) { + clear(message); + + int recvBytes = ::recv(getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0); + check_received_bytes(recvBytes, timeout); + if (recvBytes > message.buffer_size) { + // if here, the message received is probably corrupted + recvBytes = 0; + } + res = static_cast(recvBytes); + }); + + return res; } std::string Receiver::receive(std::size_t expected_max_bytes, const Timeout &timeout) { std::string buffer; buffer.resize(expected_max_bytes); - auto buffer_temp = makeStringBuffer(buffer); + auto buffer_temp = makeBufferView(buffer); auto recvBytes = receive(buffer_temp, timeout); buffer.resize(recvBytes); return buffer; } std::optional -ReceiverUnkownSender::receive(const Buffer &message, const Timeout &timeout) { - auto lock = lazyUpdateReceiveTimeout(timeout); - clear(message); - - char sender_address[MAX_POSSIBLE_ADDRESS_SIZE]; - SocketAddressLength sender_address_length = MAX_POSSIBLE_ADDRESS_SIZE; - - int recvBytes = - ::recvfrom(getIDWrapper().accessId(), message.buffer, - static_cast(message.buffer_size), 0, - reinterpret_cast(&sender_address[0]), - &sender_address_length); - check_received_bytes(recvBytes, timeout); - if (recvBytes > message.buffer_size) { - // if here, the message received is probably corrupted - return std::nullopt; - } - if (0 == recvBytes) { - // if here, timeout was reached - return std::nullopt; - } - return ReceiveResult{ - toAddress(reinterpret_cast(sender_address)), - static_cast(recvBytes)}; +ReceiverUnkownSender::receive(BufferView message, const Timeout &timeout) { + std::optional res; + + lazyUpdateAndUseTimeout( + timeout, [&message, &res, this](const Timeout &timeout) { + clear(message); + + char sender_address[MAX_POSSIBLE_ADDRESS_SIZE]; + SocketAddressLength sender_address_length = MAX_POSSIBLE_ADDRESS_SIZE; + + int recvBytes = + ::recvfrom(getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0, + reinterpret_cast(&sender_address[0]), + &sender_address_length); + check_received_bytes(recvBytes, timeout); + if (recvBytes > message.buffer_size) { + // if here, the message received is probably corrupted + return; + } + if (0 == recvBytes) { + // if here, timeout was reached + return; + } + + res = ReceiveResult{ + toAddress(reinterpret_cast(sender_address)), + static_cast(recvBytes)}; + }); + + return res; } std::optional @@ -125,12 +135,12 @@ ReceiverUnkownSender::receive(std::size_t expected_max_bytes, const Timeout &timeout) { std::string buffer; buffer.resize(expected_max_bytes); - auto buffer_temp = makeStringBuffer(buffer); + auto buffer_temp = makeBufferView(buffer); auto result = receive(buffer_temp, timeout); if (!result) { return std::nullopt; } buffer.resize(result->received_bytes); - return ReceiveStringResult{result->sender, std::move(buffer)}; + return ReceiveStringResult{std::move(result->sender), std::move(buffer)}; } } // namespace MinimalSocket diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index 77f5945b..80ec5eab 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -12,76 +12,56 @@ #include "../Utils.h" namespace MinimalSocket { -bool Sender::send(const ConstBuffer &message) { +bool Sender::send(const BufferViewConst &message) { std::scoped_lock lock(send_mtx); - int sentBytes = ::send(getIDWrapper().accessId(), message.buffer, + int sentBytes = ::send(getHandler().accessId(), message.buffer, static_cast(message.buffer_size), 0); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; - auto err = SocketError{"send failed"}; - throw err; + throw SocketError{"send failed"}; } return (sentBytes == static_cast(message.buffer_size)); } bool Sender::send(const std::string &message) { - return send(makeStringConstBuffer(message)); + return send(makeBufferViewConst(message)); } -std::future SenderTo::reserveAddress(const Address &to_reserve) { - std::scoped_lock lock(recipients_register_mtx); - auto it = recipients_register.find(to_reserve); - if (it == recipients_register.end()) { - auto &promises = recipients_register[to_reserve]; - promises.emplace_back(); - auto &promise = promises.back(); - auto result = promise.get_future(); - promise.set_value(); - return result; +std::mutex &SenderTo::getRecipientMtx(const Address &recipient) { + std::scoped_lock lock{recipients_register_mtx}; + auto &res = recipients_register[recipient]; + if (res == nullptr) { + res = std::make_unique(); } - auto &promises = it->second; - promises.emplace_back(); - auto &promise = promises.back(); - return promise.get_future(); + return *res; } -void SenderTo::freeAddress(const Address &to_reserve) { - std::scoped_lock lock(recipients_register_mtx); - auto it = recipients_register.find(to_reserve); - auto &promises = it->second; - if (1 == promises.size()) { - recipients_register.erase(it); - } else { - promises.pop_front(); - promises.front().set_value(); - } -} - -bool SenderTo::sendTo(const ConstBuffer &message, const Address &recipient) { - auto send_allowed = reserveAddress(recipient); - send_allowed.wait(); +bool SenderTo::sendTo(const BufferViewConst &message, + const Address &recipient) { int sentBytes; - visitAddress( - recipient.getFamily(), - [&]() { - auto socketIp4 = - toSocketAddressIpv4(recipient.getHost(), recipient.getPort()); - sentBytes = ::sendto( - getIDWrapper().accessId(), message.buffer, - static_cast(message.buffer_size), 0, - reinterpret_cast(&socketIp4.value()), - sizeof(SocketAddressIpv4)); - }, - [&]() { - auto socketIp6 = - toSocketAddressIpv6(recipient.getHost(), recipient.getPort()); - sentBytes = ::sendto( - getIDWrapper().accessId(), message.buffer, - static_cast(message.buffer_size), 0, - reinterpret_cast(&socketIp6.value()), - sizeof(SocketAddressIpv6)); - }); - freeAddress(recipient); + { + std::scoped_lock lock{getRecipientMtx(recipient)}; + visitAddress( + recipient.getFamily(), + [&]() { + auto socketIp4 = + toSocketAddressIpv4(recipient.getHost(), recipient.getPort()); + sentBytes = ::sendto( + getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0, + reinterpret_cast(&socketIp4.value()), + sizeof(SocketAddressIpv4)); + }, + [&]() { + auto socketIp6 = + toSocketAddressIpv6(recipient.getHost(), recipient.getPort()); + sentBytes = ::sendto( + getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0, + reinterpret_cast(&socketIp6.value()), + sizeof(SocketAddressIpv6)); + }); + } if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; auto err = SocketError{"sendto failed"}; @@ -91,6 +71,6 @@ bool SenderTo::sendTo(const ConstBuffer &message, const Address &recipient) { } bool SenderTo::sendTo(const std::string &message, const Address &recipient) { - return sendTo(makeStringConstBuffer(message), recipient); + return sendTo(makeBufferViewConst(message), recipient); } } // namespace MinimalSocket diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index f628e24f..37155b74 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -8,7 +8,7 @@ #include #include -#include "../SocketId.h" +#include "../SocketHandler.h" #include "../Utils.h" namespace MinimalSocket { @@ -29,24 +29,22 @@ WSAVersion WSAManager::getWsaVersion() { Socket::~Socket() = default; -Socket::Socket() { resetIDWrapper(); } +Socket::Socket() { resetHandler(); } -int Socket::accessSocketID() const { - return static_cast(getIDWrapper().accessId()); +int Socket::getSocketDescriptor() const { + return static_cast(getHandler().accessId()); } -void Socket::transfer(Socket &receiver, Socket &giver) { - receiver.socket_id_wrapper = std::move(giver.socket_id_wrapper); - giver.resetIDWrapper(); +void Socket::steal(Socket &giver) { + this->socket_id_wrapper = std::move(giver.socket_id_wrapper); + giver.resetHandler(); } -const SocketIdWrapper &Socket::getIDWrapper() const { - return *socket_id_wrapper; -} -SocketIdWrapper &Socket::getIDWrapper() { return *socket_id_wrapper; } +const SocketHandler &Socket::getHandler() const { return *socket_id_wrapper; } +SocketHandler &Socket::getHandler() { return *socket_id_wrapper; } -void Socket::resetIDWrapper() { - socket_id_wrapper = std::make_unique(); +void Socket::resetHandler() { + socket_id_wrapper = std::make_unique(); } bool Openable::open(const Timeout &timeout) { @@ -60,7 +58,7 @@ bool Openable::open(const Timeout &timeout) { this->open_(); } else { try_within_timeout([this]() { this->open_(); }, - [this]() { this->resetIDWrapper(); }, timeout); + [this]() { this->resetHandler(); }, timeout); } opened = true; } catch (const SocketError &e) { @@ -70,21 +68,21 @@ bool Openable::open(const Timeout &timeout) { } catch (const Error &e) { exception = std::make_unique(e); } catch (...) { - exception = std::make_unique("Not opened for an unkown reason"); + exception = std::make_unique("Not opened for an unknown reason"); } if (nullptr != exception) { - this->resetIDWrapper(); + this->resetHandler(); throw *exception; } return opened; } -void Openable::transfer(Openable &receiver, Openable &giver) { - std::scoped_lock lock(receiver.open_procedure_mtx, giver.open_procedure_mtx); +void Openable::steal(Openable &giver) { + std::scoped_lock lock(this->open_procedure_mtx, giver.open_procedure_mtx); const bool o_value = giver.opened; - receiver.opened = o_value; + this->opened = o_value; giver.opened = false; - Socket::transfer(receiver, giver); + this->Socket::steal(giver); } } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/core/SocketContext.cpp b/src/src/core/SocketContext.cpp index 49dc30b7..6f016a2a 100644 --- a/src/src/core/SocketContext.cpp +++ b/src/src/core/SocketContext.cpp @@ -14,22 +14,6 @@ Address RemoteAddressAware::getRemoteAddress() const { return remote_address; } -RemoteAddressAware::RemoteAddressAware(const RemoteAddressAware &o) - : remote_address(o.getRemoteAddress()) {} - -RemoteAddressAware &RemoteAddressAware::operator=(const RemoteAddressAware &o) { - this->remote_address = o.getRemoteAddress(); - return *this; -} - -PortToBindAware::PortToBindAware(const PortToBindAware &o) { *this = o; } - -PortToBindAware &PortToBindAware::operator=(const PortToBindAware &o) { - this->port_to_bind = o.getPortToBind(); - this->must_be_free_port = o.shallBeFreePort(); - return *this; -} - RemoteAddressAware::RemoteAddressAware(const Address &address) : remote_address(address) { if (nullptr == getRemoteAddress()) { @@ -37,14 +21,4 @@ RemoteAddressAware::RemoteAddressAware(const Address &address) } } -RemoteAddressFamilyAware::RemoteAddressFamilyAware( - const RemoteAddressFamilyAware &o) { - *this = o; -} - -RemoteAddressFamilyAware & -RemoteAddressFamilyAware::operator=(const RemoteAddressFamilyAware &o) { - this->remote_address_family = o.getRemoteAddressFamily(); - return *this; -} } // namespace MinimalSocket diff --git a/src/src/tcp/TcpClient.cpp b/src/src/tcp/TcpClient.cpp index 0ae01de0..740e17df 100644 --- a/src/src/tcp/TcpClient.cpp +++ b/src/src/tcp/TcpClient.cpp @@ -12,11 +12,9 @@ #include "../Utils.h" namespace MinimalSocket::tcp { -TcpClient::TcpClient(TcpClient &&o) : RemoteAddressAware(o) { - Openable::transfer(*this, o); -} +TcpClient::TcpClient(TcpClient &&o) : RemoteAddressAware(o) { this->steal(o); } TcpClient &TcpClient::operator=(TcpClient &&o) { - Openable::transfer(*this, o); + this->steal(o); copy_as(*this, o); return *this; } @@ -25,7 +23,7 @@ TcpClient::TcpClient(const Address &server_address) : RemoteAddressAware(server_address) {} void TcpClient::open_() { - auto &socket = getIDWrapper(); + auto &socket = getHandler(); const auto remote_address = getRemoteAddress(); socket.reset(SocketType::TCP, remote_address.getFamily()); MinimalSocket::connect(socket.accessId(), remote_address); diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 04fdd02c..cb22217e 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -15,22 +15,21 @@ namespace MinimalSocket::tcp { TcpServer::TcpServer(TcpServer &&o) : PortToBindAware(o), RemoteAddressFamilyAware(o) { - Openable::transfer(*this, o); + this->steal(o); } TcpServer &TcpServer::operator=(TcpServer &&o) { - Openable::transfer(*this, o); + this->steal(o); copy_as(*this, o); copy_as(*this, o); return *this; } -TcpServer::TcpServer(const Port port_to_bind, - const AddressFamily &accepted_client_family) +TcpServer::TcpServer(Port port_to_bind, AddressFamily accepted_client_family) : PortToBindAware(port_to_bind), RemoteAddressFamilyAware(accepted_client_family) {} void TcpServer::open_() { - auto &socket = getIDWrapper(); + auto &socket = getHandler(); const auto port = getPortToBind(); const auto family = getRemoteAddressFamily(); socket.reset(SocketType::TCP, family); @@ -67,7 +66,7 @@ TcpServer::acceptNewClient(const Timeout &timeout) { // accept: wait for a client to call connect and hit this server and get a // pointer to this client. accepted_client_socket_id = - ::accept(getIDWrapper().accessId(), + ::accept(getHandler().accessId(), reinterpret_cast(&acceptedClientAddress[0]), &acceptedClientAddress_length); if (accepted_client_socket_id == SCK_INVALID_SOCKET) { @@ -81,7 +80,7 @@ TcpServer::acceptNewClient(const Timeout &timeout) { accept_client(); } else { try_within_timeout([&]() { accept_client(); }, - [this]() { this->resetIDWrapper(); }, timeout); + [this]() { this->resetHandler(); }, timeout); } } catch (const TimeOutError &) { TcpServer reopened = TcpServer{getPortToBind(), getRemoteAddressFamily()}; @@ -97,7 +96,7 @@ TcpServer::acceptNewClient(const Timeout &timeout) { std::optional result; auto &accepted = result.emplace(TcpConnection{accepted_client_parsed_address}); - accepted.getIDWrapper().reset(accepted_client_socket_id); + accepted.getHandler().reset(accepted_client_socket_id); return result; } @@ -105,11 +104,11 @@ TcpConnection::TcpConnection(const Address &remote_address) : RemoteAddressAware(remote_address) {} TcpConnection::TcpConnection(TcpConnection &&o) : RemoteAddressAware(o) { - Socket::transfer(*this, o); + this->steal(o); } TcpConnection &TcpConnection::operator=(TcpConnection &&o) { copy_as(*this, o); - Socket::transfer(*this, o); + this->steal(o); return *this; } } // namespace MinimalSocket::tcp diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index e3512120..91ddfe78 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -12,26 +12,26 @@ #include "../Utils.h" namespace MinimalSocket::udp { -UdpBinded::UdpBinded(const Port port_to_bind, - const AddressFamily &accepted_connection_family) +UdpBinded::UdpBinded(Port port_to_bind, + AddressFamily accepted_connection_family) : PortToBindAware(port_to_bind), RemoteAddressFamilyAware(accepted_connection_family) {} UdpBinded::UdpBinded(UdpBinded &&o) : PortToBindAware(o), RemoteAddressFamilyAware(o) { - Openable::transfer(*this, o); + this->steal(o); } UdpBinded &UdpBinded::operator=(UdpBinded &&o) { copy_as(*this, o); copy_as(*this, o); - Openable::transfer(*this, o); + this->steal(o); return *this; } void UdpBinded::open_() { - getIDWrapper().reset(SocketType::UDP, getRemoteAddressFamily()); + getHandler().reset(SocketType::UDP, getRemoteAddressFamily()); auto binded_port = - MinimalSocket::bind(getIDWrapper().accessId(), getRemoteAddressFamily(), + MinimalSocket::bind(getHandler().accessId(), getRemoteAddressFamily(), getPortToBind(), shallBeFreePort()); setPort(binded_port); } @@ -42,9 +42,9 @@ UdpConnected UdpBinded::connect(const Address &remote_address) { } UdpConnected result(remote_address, getPortToBind()); if (wasOpened()) { - MinimalSocket::connect(getIDWrapper().accessId(), remote_address); + MinimalSocket::connect(getHandler().accessId(), remote_address); } - Openable::transfer(result, *this); + this->transfer(result); return std::move(result); } @@ -65,24 +65,24 @@ std::optional UdpBinded::connect(const Timeout &timeout, return connect(maybe_received->sender); } -UdpConnected::UdpConnected(const Address &remote_address, const Port &port) +UdpConnected::UdpConnected(const Address &remote_address, Port port) : PortToBindAware(port), RemoteAddressAware(remote_address) {} UdpConnected::UdpConnected(UdpConnected &&o) : PortToBindAware(o), RemoteAddressAware(o) { - Openable::transfer(*this, o); + this->steal(o); } UdpConnected &UdpConnected::operator=(UdpConnected &&o) { copy_as(*this, o); copy_as(*this, o); - Openable::transfer(*this, o); + this->steal(o); return *this; } void UdpConnected::open_() { - const auto &socket_id = getIDWrapper().accessId(); const auto &remote_address = getRemoteAddress(); - getIDWrapper().reset(SocketType::UDP, remote_address.getFamily()); + getHandler().reset(SocketType::UDP, remote_address.getFamily()); + auto socket_id = getHandler().accessId(); auto binded_port = MinimalSocket::bind(socket_id, remote_address.getFamily(), getPortToBind(), shallBeFreePort()); setPort(binded_port); @@ -90,24 +90,24 @@ void UdpConnected::open_() { } UdpBinded UdpConnected::disconnect() { - resetIDWrapper(); + resetHandler(); UdpBinded result(getPortToBind(), getRemoteAddress().getFamily()); result.open(); return std::move(result); } -UdpConnected -makeUdpConnectedToUnknown(const Port &port, - const AddressFamily &accepted_connection_family, - std::string *initial_message) { +UdpConnected makeUdpConnectedToUnknown(Port port, + AddressFamily accepted_connection_family, + std::string *initial_message) { auto result = makeUdpConnectedToUnknown(port, accepted_connection_family, NULL_TIMEOUT, initial_message); return std::move(result.value()); } -std::optional makeUdpConnectedToUnknown( - const Port &port, const AddressFamily &accepted_connection_family, - const Timeout &timeout, std::string *initial_message) { +std::optional +makeUdpConnectedToUnknown(Port port, AddressFamily accepted_connection_family, + const Timeout &timeout, + std::string *initial_message) { UdpBinded primal_socket(port, accepted_connection_family); auto success = primal_socket.open(); if (!success) { diff --git a/tests/SlicedOps.cpp b/tests/SlicedOps.cpp index 31232eb6..2ae73b1a 100644 --- a/tests/SlicedOps.cpp +++ b/tests/SlicedOps.cpp @@ -31,7 +31,7 @@ void sliced_send(Sender &subject, const std::string &to_send, while (buffer.remainingBytes() != 0) { std::size_t bytes_to_send = std::min(delta_send, buffer.remainingBytes()); - subject.send(ConstBuffer{buffer.data(), bytes_to_send}); + subject.send(BufferViewConst{buffer.data(), bytes_to_send}); buffer.shift(bytes_to_send); } } @@ -42,7 +42,8 @@ void sliced_send(SenderTo &subject, const std::string &to_send, while (buffer.remainingBytes() != 0) { std::size_t bytes_to_send = std::min(delta_send, buffer.remainingBytes()); - subject.sendTo(ConstBuffer{buffer.data(), bytes_to_send}, to_send_address); + subject.sendTo(BufferViewConst{buffer.data(), bytes_to_send}, + to_send_address); buffer.shift(bytes_to_send); } } @@ -54,7 +55,7 @@ std::string sliced_receive(Receiver &subject, const std::size_t to_receive, std::size_t bytes_to_receive = std::min(delta_receive, buffer.remainingBytes()); auto bytes_received = - subject.receive(Buffer{buffer.data(), bytes_to_receive}); + subject.receive(BufferView{buffer.data(), bytes_to_receive}); buffer.shift(bytes_received); } return buffer.asString(); @@ -68,7 +69,7 @@ std::string sliced_receive(ReceiverUnkownSender &subject, std::size_t bytes_to_receive = std::min(delta_receive, buffer.remainingBytes()); auto maybe_bytes_received = - subject.receive(Buffer{buffer.data(), bytes_to_receive}); + subject.receive(BufferView{buffer.data(), bytes_to_receive}); if (maybe_bytes_received) { buffer.shift(maybe_bytes_received->received_bytes); }