From 1342de938ab5d67922d8ff7decbd303e4eb5af6d Mon Sep 17 00:00:00 2001 From: Foo Date: Sun, 25 Feb 2024 01:26:21 +0100 Subject: [PATCH] non blocking semantics for receiving soeckts and the tcp acceptor tests updated documentation updated and improved --- README.md | 127 +++++++--- samples/README.cpp | 76 +++++- samples/tcp/CMakeLists.txt | 4 + samples/tcp/README.md | 32 +++ .../tcp/Sample04_server_nn_block_2_clients | 3 + samples/tcp/TcpClient.cpp | 14 +- samples/tcp/TcpRepeater.cpp | 19 +- samples/tcp/TcpServer.cpp | 15 +- samples/tcp/TcpServerNonBlocking.cpp | 94 ++++++++ samples/udp/CMakeLists.txt | 4 + samples/udp/README.md | 25 ++ .../Sample04_2_askers_2_nn_block_responders | 3 + samples/udp/UdpAsker.cpp | 16 +- samples/udp/UdpResponder.cpp | 11 +- samples/udp/UdpResponderNonBlocking.cpp | 71 ++++++ samples/udp/UdpScriptsGenerator.cpp | 79 ------ samples/utils/Args.cpp | 16 -- samples/utils/Args.h | 79 ++++-- samples/utils/Ask.h | 42 +++- samples/utils/Names.cpp | 10 +- samples/utils/Names.h | 20 +- samples/utils/Pollables.cpp | 34 +++ samples/utils/Pollables.h | 29 +++ samples/utils/Respond.h | 43 ++-- src/header/MinimalSocket/Error.h | 6 +- src/header/MinimalSocket/core/Address.h | 26 +- src/header/MinimalSocket/core/Definitions.h | 2 +- src/header/MinimalSocket/core/Receiver.h | 187 ++++++++++----- src/header/MinimalSocket/core/Sender.h | 63 +++-- src/header/MinimalSocket/core/Socket.h | 103 +++++--- src/header/MinimalSocket/core/SocketContext.h | 16 +- src/header/MinimalSocket/tcp/TcpClient.h | 51 ++-- src/header/MinimalSocket/tcp/TcpServer.h | 184 ++++++++++---- src/header/MinimalSocket/udp/UdpSocket.h | 213 +++++++++++------ src/src/SocketFunctions.cpp | 46 +++- src/src/SocketFunctions.h | 6 + src/src/SocketHandler.cpp | 5 + src/src/core/Address.cpp | 20 +- src/src/core/Receiver.cpp | 200 ++++++++++------ src/src/core/Sender.cpp | 16 +- src/src/core/Socket.cpp | 64 +++-- src/src/core/SocketContext.cpp | 10 +- src/src/tcp/TcpClient.cpp | 21 +- src/src/tcp/TcpServer.cpp | 156 +++++++----- src/src/udp/UdpSocket.cpp | 98 +++++--- tests/ConnectionsUtils.cpp | 60 ++++- tests/ConnectionsUtils.h | 47 ++-- tests/ParallelSection.cpp | 14 +- tests/PortFactory.cpp | 10 +- tests/PortFactory.h | 10 +- tests/RollingView.cpp | 56 +++++ tests/RollingView.h | 51 ++++ tests/SlicedOps.cpp | 79 ------ tests/SlicedOps.h | 39 --- tests/TestAddress.cpp | 18 +- tests/TestOpenTimeout.cpp | 2 +- tests/TestRobustness.cpp | 107 +++++---- tests/TestTCP.cpp | 224 +++++++++--------- tests/TestUDP.cpp | 193 +++++++-------- 59 files changed, 2061 insertions(+), 1208 deletions(-) create mode 100644 samples/tcp/Sample04_server_nn_block_2_clients create mode 100644 samples/tcp/TcpServerNonBlocking.cpp create mode 100644 samples/udp/Sample04_2_askers_2_nn_block_responders create mode 100644 samples/udp/UdpResponderNonBlocking.cpp delete mode 100644 samples/udp/UdpScriptsGenerator.cpp create mode 100644 samples/utils/Pollables.cpp create mode 100644 samples/utils/Pollables.h create mode 100644 tests/RollingView.cpp create mode 100644 tests/RollingView.h delete mode 100644 tests/SlicedOps.cpp delete mode 100644 tests/SlicedOps.h diff --git a/README.md b/README.md index b07cad53..344ce7c3 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ ## INTRO **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**. +completely platform agnostic way. The supported systems are: **Windows**, any **Linux** distro and **MacOS**. -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**. +The [Features](#features) Section details 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,22 @@ 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 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/). +**MinimalSocket** allows you to build and set up **tcp** and **udp** connections. Messages can be sent and received in terms of both buffer of bytes or strings. Indeed, this is actually the only capability you need for a socket, as more complex messages can be serialized into or internalized from a buffer of bytes 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 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. +These are the most notable characteristics of **MinimalSocket**: +- A modern **C++** object oriented API allowing you to set up and build socket connections. Typically, sockets are represented by the classes part of this library. Any time an object is created, the related socket is generated in a closed state in order to defer the opening at the convenient moment. This allows you to decouple the moments when sockets are created from those when the socket should be actually started and used. At the same time, any connection is automatically closed when the handler object is destroyed (and all relevant information cleaned up). +- 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 system specific modules, functions, linkages (ex. winsock in **Windows**) 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**. 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. +- For any socket type, **MinimalSocket** allows you to choose between a blocking and a non blocking version (see also the table at the end of this Section as well as the [Usage](#usage) Section). In essence, non blocking sockets functions return always instantaneously, with some kind of result when succeeding or an empty result when failing. On the contrary, blocking sockets absorb the caller till the function can be actually completed. At the same time, it is also possible to specify some timeout for blocking socket after which the completion of the function is considered failed. +- **MinimalSocket** is tested to be **thread safe**. However, notice that for a ceratin socket you can still send while receiving from different threads. This allows you to easily create your own asynchronous sockets, building on top of the classes offered by this library. +- Under **Windows**, [**WSAStartup**](https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup) is automatically called before using any relevant functionalities. If you need, it is also possible to specify the Windows Sockets to use. + +This table summarizes the differences between the blocking and the non blocking behaviours of the socket that can be created using **MinimalSocket**: +| | Blocking Behaviour, caller is blocked till completion or timeout is reached (if one was specified) | Non Blocking Behaviour, functions return immediately | +| --- | --- | --- | +| accepting of a new client (tcp only) | caller thread is absorbed till a new client actually asks to connect or timeout is reached (if any was specified) | if a connection request from a client was already queued before calling the accept function, a new connection handler is returned, otherwise a nullopt is returned. | +| receive a new message (tcp and udp) | caller thread is absorbed till a new a message is sent to the socket or timeout is reached (if any was specified) | if a message was sent and already queued in the socket buffer before calling the receive function, that message is returned, otherwise an empty message is returned. | +| send a new message (tcp and udp) | In case the buffer of the socket is not full and can entirely host the message to send, the message is actaully written in the buffer and the function returns almost instantaneously. On the contrary case, caller thread is absorbed until space is done in the buffer (as messages are consumed from the other side) and after that the message is actually written and function completes. | If there is enough space in the buffer of the socket, the message is written and the function returns. In the contrary case, the function returns immediately without actually send the message (the send can be retried later) | ## USAGE @@ -44,13 +48,13 @@ Haven't left a **star** already? Do it now ;)! #### SERVER -To create a **tcp** server you just need to build a **tcp::TcpServer** object: +To create a classic blocking **tcp** server you just need to build a **tcp::TcpServer** object: ```cpp #include MinimalSocket::Port port = 15768; // the port to bind -MinimalSocket::tcp::TcpServer tcp_server(port, - MinimalSocket::AddressFamily::IP_V4); +MinimalSocket::tcp::TcpServer tcp_server( + port, MinimalSocket::AddressFamily::IP_V4); ``` open it: @@ -62,12 +66,12 @@ bool success = tcp_server.open(); and now you are ready to accept new clients: ```cpp // accepts the next client that will ask the connection -MinimalSocket::tcp::TcpConnection accepted_connection = +MinimalSocket::tcp::TcpConnectionBlocking accepted_connection = tcp_server.acceptNewClient(); // blocking till a client actually asks the // connection ``` -you can now receive and send information with the accepted client by simply doing this: +you can now exhange messages with the accepted client by simply doing this: ```cpp // receive a message std::size_t message_max_size = 1000; @@ -78,6 +82,30 @@ std::string accepted_connection.send("a message to send"); ``` +If you instead need a non blocking server, you can create it in a similar way: +```cpp +MinimalSocket::Port port = 15768; // the port to bind +MinimalSocket::tcp::TcpServer tcp_server( + port, MinimalSocket::AddressFamily::IP_V4); +tcp_server.open(); +``` + +This server version will be non blocking, meaning that the accept function will return immediately: +```cpp +// check if a client asked for the connection. If no, the function immediately +// returns a nullopt. On the contrary, the returned optional contains the +// handler to the connected client +std::optional + maybe_accepted_connection = tcp_server.acceptNewClient(); +``` + +Notice that even though the server per se is non blocking, the eventually accepted client handler is blocking. +You can turn it to a non blocking socket too, by transferring the socket to a non blocking handler: +```cpp +MinimalSocket::tcp::TcpConnectionNonBlocking accepted_connection_nn_block = + maybe_accepted_connection->turnToNonBlocking(); +``` + #### CLIENT To create a **tcp** client you just need to build a **tcp::TcpClient** object: @@ -86,16 +114,16 @@ To create a **tcp** client you just need to build a **tcp::TcpClient** object: MinimalSocket::Port server_port = 15768; std::string server_address = "192.168.125.85"; -MinimalSocket::tcp::TcpClient tcp_client( +MinimalSocket::tcp::TcpClient tcp_client( MinimalSocket::Address{server_address, server_port}); ``` open it: ```cpp - // 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 +// 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: @@ -109,14 +137,28 @@ std::string = tcp_client.receive(message_max_size); ``` +If you instead need a non blocking client you can create it ans use it in a similar way: +```cpp +MinimalSocket::Port server_port = 15768; +std::string server_address = "192.168.125.85"; +MinimalSocket::tcp::TcpClient tcp_client( + MinimalSocket::Address{server_address, server_port}); +tcp_client.open(); + +std::size_t message_max_size = 1000; +// non blocking receive: returns immediately with an empty message in case no +// new data were available, or with a non empty message in the contrary case +std::string received_message = tcp_client.receive(message_max_size); +``` + ### UDP -To create a normal **udp** socket you just need to build a **udp::UdpBinded** object: +To create a normal **udp** socket you just need to build a **udp::Udp** object: ```cpp #include MinimalSocket::Port this_socket_port = 15768; -MinimalSocket::udp::UdpBinded udp_socket(this_socket_port, +MinimalSocket::udp::Udp udp_socket(this_socket_port, MinimalSocket::AddressFamily::IP_V6); ``` @@ -126,7 +168,7 @@ open it: bool success = udp_socket.open(); ``` -you can now receive and send information with any other opened **udp** socket: +you can now receive and send information with other **udp** sockets: ```cpp // send a message to another udp MinimalSocket::Address other_recipient_udp = @@ -143,15 +185,17 @@ std::string received_message_content // resized to the nunber of bytes = received_message->received_message; ``` -you can also decide to connect an opened **udp** socket to a specific address. This simply means that messages incoming from other peers will be filtered out, as **udp** sockets are not connection oriented: +you can also decide to "connect" an opened **udp** socket to a specific address. Beware that this simply means that messages incoming from other peers will be filtered out, as **udp** sockets are not connection oriented: ```cpp MinimalSocket::Address permanent_sender_udp = MinimalSocket::Address{"192.168.125.85", 15768}; -MinimalSocket::udp::UdpConnected udp_connected_socket = udp_socket.connect( - permanent_sender_udp); // ownership of the underlying socket is transfered - // from udp_socket to udp_connected_socket, meaning - // that you can't use anymore udp_socket (unless - // you re-open it) +MinimalSocket::udp::UdpConnected udp_connected_socket = + udp_socket.connect( + permanent_sender_udp); // ownership of the underlying socket is + // transfered from udp_socket to + // udp_connected_socket, meaning that you can't + // use anymore udp_socket (unless you re-open + // it) ``` Now you can send and receive data without having to specify the recpient/sender: @@ -165,11 +209,30 @@ std::string udp_connected_socket.send("a message to send"); ``` +You can also create and use non blocking **udp** sockets: +```cpp +MinimalSocket::Port this_socket_port = 15768; +MinimalSocket::udp::Udp udp_socket( + this_socket_port, MinimalSocket::AddressFamily::IP_V6); +udp_socket.open(); + +std::size_t message_max_size = 1000; +// non blocking receive: returns immediately with an empty message in case no +// new data were available, or with a non empty message in the contrary case +// +// struct ReceiveStringResult { +// Address sender; +// std::string received_message; +// }; +std::optional received_message = + udp_socket.receive(message_max_size); +``` + ## SAMPLES Haven't left a **star** already? Do it now ;)! -Instructions about **tcp** samples can be found [here](./samples/tcp/README.md), while **udp** samples are [here](./samples/udp/README.md) discussed. +Examples of usage about **tcp** sockets 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.) diff --git a/samples/README.cpp b/samples/README.cpp index 16288cc3..b2b3d0a3 100644 --- a/samples/README.cpp +++ b/samples/README.cpp @@ -2,15 +2,15 @@ #include int main() { MinimalSocket::Port port = 15768; // the port to bind - MinimalSocket::tcp::TcpServer tcp_server(port, - MinimalSocket::AddressFamily::IP_V4); + MinimalSocket::tcp::TcpServer tcp_server( + port, MinimalSocket::AddressFamily::IP_V4); // Open the server. This will bind the port and the server will start to // listen for connection requests. bool success = tcp_server.open(); // accepts the next client that will ask the connection - MinimalSocket::tcp::TcpConnection accepted_connection = + MinimalSocket::tcp::TcpConnectionBlocking accepted_connection = tcp_server.acceptNewClient(); // blocking till a client actually asks the // connection @@ -28,7 +28,7 @@ int main() { int main() { MinimalSocket::Port server_port = 15768; std::string server_address = "192.168.125.85"; - MinimalSocket::tcp::TcpClient tcp_client( + MinimalSocket::tcp::TcpClient tcp_client( MinimalSocket::Address{server_address, server_port}); // Open the server. Here, the client will ask the connection to specified @@ -49,7 +49,7 @@ int main() { #include int main() { MinimalSocket::Port this_socket_port = 15768; - MinimalSocket::udp::UdpBinded udp_socket(this_socket_port, + MinimalSocket::udp::Udp udp_socket(this_socket_port, MinimalSocket::AddressFamily::IP_V6); // Open the server. This will bind the specified port. @@ -71,11 +71,13 @@ int main() { MinimalSocket::Address permanent_sender_udp = MinimalSocket::Address{"192.168.125.85", 15768}; - MinimalSocket::udp::UdpConnected udp_connected_socket = udp_socket.connect( - permanent_sender_udp); // ownership of the underlying socket is transfered - // from udp_socket to udp_connected_socket, meaning - // that you can't use anymore udp_socket (unless - // you re-open it) + MinimalSocket::udp::UdpConnected udp_connected_socket = + udp_socket.connect( + permanent_sender_udp); // ownership of the underlying socket is + // transfered from udp_socket to + // udp_connected_socket, meaning that you can't + // use anymore udp_socket (unless you re-open + // it) // receive a message std::size_t message_max_size = 1000; @@ -85,3 +87,57 @@ int main() { // send a message udp_connected_socket.send("a message to send"); } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// tcp server, non blocking +int main() { + MinimalSocket::Port port = 15768; // the port to bind + MinimalSocket::tcp::TcpServer tcp_server( + port, MinimalSocket::AddressFamily::IP_V4); + tcp_server.open(); + + // check if a client asked for the connection. If no, the function immediately + // returns a nullopt. On the contrary, the returned optional contains the + // handler to the connected client + std::optional + maybe_accepted_connection = tcp_server.acceptNewClient(); + + MinimalSocket::tcp::TcpConnectionNonBlocking accepted_connection_nn_block = + maybe_accepted_connection->turnToNonBlocking(); +} + +// tcp client, non blocking +int main() { + MinimalSocket::Port server_port = 15768; + std::string server_address = "192.168.125.85"; + MinimalSocket::tcp::TcpClient tcp_client( + MinimalSocket::Address{server_address, server_port}); + tcp_client.open(); + + std::size_t message_max_size = 1000; + // non blocking receive: returns immediately with an empty message in case no + // new data were available, or with a non empty message in the contrary case + std::string received_message = tcp_client.receive(message_max_size); +} + +// udp socket, non blocking +int main() { + MinimalSocket::Port this_socket_port = 15768; + MinimalSocket::udp::Udp udp_socket( + this_socket_port, MinimalSocket::AddressFamily::IP_V6); + udp_socket.open(); + + std::size_t message_max_size = 1000; + // non blocking receive: returns immediately with an empty message in case no + // new data were available, or with a non empty message in the contrary case + // + // struct ReceiveStringResult { + // Address sender; + // std::string received_message; + // }; + std::optional received_message = + udp_socket.receive(message_max_size); +} diff --git a/samples/tcp/CMakeLists.txt b/samples/tcp/CMakeLists.txt index f62c6531..4f5bb5b1 100644 --- a/samples/tcp/CMakeLists.txt +++ b/samples/tcp/CMakeLists.txt @@ -1,5 +1,6 @@ MakeApp(TcpClient) MakeApp(TcpServer) +MakeApp(TcpServerNonBlocking) MakeApp(TcpRepeater) MakeSample(Sample01_server_client Tcp) @@ -10,3 +11,6 @@ add_dependencies(TcpSample02_server_2_clients TcpClient TcpServer) MakeSample(Sample03_chain_with_2_repeaters Tcp) add_dependencies(TcpSample03_chain_with_2_repeaters TcpClient TcpServer TcpRepeater) + +MakeSample(Sample04_server_nn_block_2_clients Tcp) +add_dependencies(TcpSample04_server_nn_block_2_clients TcpClient TcpServerNonBlocking) diff --git a/samples/tcp/README.md b/samples/tcp/README.md index fdb62112..c49e35d4 100644 --- a/samples/tcp/README.md +++ b/samples/tcp/README.md @@ -78,4 +78,36 @@ The above classes of samples can be described as follows: TcpRepeater2->>TcpClient: forawrd response 1 ``` +- **TcpSample04_server_nn_block_2_clients** is an example of non blocking tcp server. The application uses one single thread to spin multiple connections. More in detail: + - related config file is [Sample04_server_nn_block_2_clients](./Sample04_server_nn_block_2_clients) + - runs **TcpServerNonBlocking**, creating a tcp server that binds and listen to a specified port + - runs **TcpClient**, creating a first tcp client that connections to the previous server, exchanging messages with it. + - runs **TcpClient**, creating a second tcp client that connections to the previous server, exchanging messages with it with a different frequency. + - the following sequence diagram summarizes this sample + ```mermaid + sequenceDiagram + TcpServer->>TcpServer: bind a port + TcpClient1->>TcpServer: ask for connection + TcpServer->>TcpClient1: connection done + TcpClient2->>TcpServer: ask for connection + TcpServer->>TcpClient2: connection done + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpClient1->>TcpServer: request 1 + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpServer->>TcpClient1: response 1 + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpClient2->>TcpServer: request 1 + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpServer->>TcpClient2: response 1 + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpClient1->>TcpServer: request 2 + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpServer->>TcpClient1: response 2 + **TcpServer** and **TcpClient** can be also used as stand alone processes, in order to check connections locally or on a different host. diff --git a/samples/tcp/Sample04_server_nn_block_2_clients b/samples/tcp/Sample04_server_nn_block_2_clients new file mode 100644 index 00000000..35cd99ad --- /dev/null +++ b/samples/tcp/Sample04_server_nn_block_2_clients @@ -0,0 +1,3 @@ +TcpServerNonBlocking --port 35998 --clients 2 +TcpClient --port 35998 +TcpClient --port 35998 --rate 400 \ No newline at end of file diff --git a/samples/tcp/TcpClient.cpp b/samples/tcp/TcpClient.cpp index f9f5bde6..777fd77e 100644 --- a/samples/tcp/TcpClient.cpp +++ b/samples/tcp/TcpClient.cpp @@ -22,14 +22,13 @@ int main(const int argc, const char **argv) { cout << "----------------------- Client -----------------------" << endl; PARSE_ARGS - const auto server_host = options->getValue("host", "127.0.0.1"); - const auto server_port = - static_cast(options->getIntValue("port")); - const auto rate = - std::chrono::milliseconds{options->getIntValue<250>("rate")}; + const auto server_host = options->getValue("host", "127.0.0.1"); + const auto server_port = options->getValue("port"); + const auto rate = options->getValue( + "rate", std::chrono::milliseconds{250}); const MinimalSocket::Address server_address(server_host, server_port); - MinimalSocket::tcp::TcpClient client(server_address); + MinimalSocket::tcp::TcpClient client(server_address); cout << "Connecting to " << MinimalSocket::to_string(server_address) << endl; if (!client.open()) { @@ -38,7 +37,8 @@ int main(const int argc, const char **argv) { } cout << "Connected" << endl; - MinimalSocket::samples::ask(client, rate, options->getIntValue<5>("cycles")); + MinimalSocket::samples::ask(client, rate, + options->getValue("cycles", 5)); // the connection will be close when destroying the client object return EXIT_SUCCESS; diff --git a/samples/tcp/TcpRepeater.cpp b/samples/tcp/TcpRepeater.cpp index 912ea8e4..85a99e9f 100644 --- a/samples/tcp/TcpRepeater.cpp +++ b/samples/tcp/TcpRepeater.cpp @@ -21,8 +21,8 @@ #include using namespace std; -void repeat(MinimalSocket::tcp::TcpConnection &preceding, - MinimalSocket::tcp::TcpClient &following) { +void repeat(MinimalSocket::tcp::TcpConnectionBlocking &preceding, + MinimalSocket::tcp::TcpClient &following) { while (true) { auto request = preceding.receive(500, std::chrono::seconds{5}); if (request.empty()) { @@ -45,17 +45,17 @@ int main(const int argc, const char **argv) { cout << "----------------------- Repeater -----------------------" << endl; PARSE_ARGS - const auto following_host = options->getValue("host", "127.0.0.1"); + const auto following_host = + options->getValue("host", "127.0.0.1"); const auto following_port = - static_cast(options->getIntValue("next_port")); + options->getValue("next_port"); MinimalSocket::Address following_address(following_host, following_port); - const auto port_to_reserve = - static_cast(options->getIntValue("port")); + const auto port_to_reserve = options->getValue("port"); // reserve port - MinimalSocket::tcp::TcpServer acceptor(port_to_reserve, - following_address.getFamily()); + MinimalSocket::tcp::TcpServer acceptor(port_to_reserve, + following_address.getFamily()); if (!acceptor.open()) { cerr << "Failed to bind and listen to specified port" << endl; return EXIT_FAILURE; @@ -63,7 +63,8 @@ int main(const int argc, const char **argv) { cout << "Listening on port " << port_to_reserve << endl; // ask connection to follower - MinimalSocket::tcp::TcpClient connection_to_following(following_address); + MinimalSocket::tcp::TcpClient connection_to_following( + following_address); cout << "Connecting to next on chain at " << MinimalSocket::to_string(following_address) << endl; if (!connection_to_following.open()) { diff --git a/samples/tcp/TcpServer.cpp b/samples/tcp/TcpServer.cpp index 89a11133..3128360f 100644 --- a/samples/tcp/TcpServer.cpp +++ b/samples/tcp/TcpServer.cpp @@ -20,8 +20,8 @@ #include using namespace std; -std::thread accept_new_client(MinimalSocket::tcp::TcpServer &server) { - MinimalSocket::tcp::TcpConnection accepted_connection = +std::thread accept_new_client(MinimalSocket::tcp::TcpServer &server) { + MinimalSocket::tcp::TcpConnectionBlocking accepted_connection = server.acceptNewClient(); cout << "New client accepted" << endl; return std::thread([connection = std::move(accepted_connection)]() mutable { @@ -33,13 +33,12 @@ int main(const int argc, const char **argv) { cout << "----------------------- Server -----------------------" << endl; PARSE_ARGS - const auto server_port = - static_cast(options->getIntValue("port")); - const auto max_clients = options->getIntValue("clients"); - const auto family = - MinimalSocket::samples::to_family(options->getValue("family", "v4")); + const auto server_port = options->getValue("port"); + const auto max_clients = options->getValue("clients", 0); + const auto family = options->getValue( + "family", MinimalSocket::AddressFamily::IP_V4); - MinimalSocket::tcp::TcpServer server(server_port, family); + MinimalSocket::tcp::TcpServer server(server_port, family); if (!server.open()) { cerr << "Failed to bind and listen to specified port" << endl; diff --git a/samples/tcp/TcpServerNonBlocking.cpp b/samples/tcp/TcpServerNonBlocking.cpp new file mode 100644 index 00000000..6229866e --- /dev/null +++ b/samples/tcp/TcpServerNonBlocking.cpp @@ -0,0 +1,94 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +/////////////////////////////////////////////////////////////////////////// +// Have a look to README.md // +/////////////////////////////////////////////////////////////////////////// + +// elements from the MinimalSocket library +#include + +// just a bunch of utilities +#include +#include +#include +#include + +#include +#include +using namespace std; + +int main(const int argc, const char **argv) { + cout << "----------------------- Server -----------------------" << endl; + PARSE_ARGS + + const auto server_port = options->getValue("port"); + const auto max_clients = options->getValue("clients", 0); + const auto family = options->getValue( + "family", MinimalSocket::AddressFamily::IP_V4); + + MinimalSocket::tcp::TcpServer server(server_port, family); + + if (!server.open()) { + cerr << "Failed to bind and listen to specified port" << endl; + return EXIT_FAILURE; + } + cout << "Listening for new clients on port " << server_port << endl; + + std::size_t connected = 0; + std::list connections; + MinimalSocket::samples::Pollables pollables; + + auto create_pollable_connection = + [&](MinimalSocket::tcp::TcpConnectionNonBlocking &&connection) { + auto &conn = connections.emplace_back( + std::forward( + connection)); + return [conn = &conn]() { + // poll the connection by doing a non blocking receive + try { + auto request = conn->receive(500); + if (request.empty()) { + return MinimalSocket::samples::PollableStatus::NOT_ADVANCED; + } + const auto &response = + MinimalSocket::samples::NamesCircularIterator::NAMES_SURNAMES + .find(request) + ->second; + cout << MinimalSocket::samples::TimeOfDay{} + << " received: " << request << " ; sending: " << response + << endl; + conn->send(response); + } catch (const MinimalSocket::SocketError &) { + // if here the connection was closed + return MinimalSocket::samples::PollableStatus::COMPLETED; + } + return MinimalSocket::samples::PollableStatus::ADVANCED; + }; + }; + + pollables.emplace([&]() { + // poll the acceptor by trying to accept a new client + auto maybe_new_connection = server.acceptNewNonBlockingClient(); + if (maybe_new_connection.has_value()) { + cout << MinimalSocket::samples::TimeOfDay{} + << " connected a new client from " + << MinimalSocket::to_string(maybe_new_connection->getRemoteAddress()) + << endl; + pollables.emplace( + create_pollable_connection(std::move(maybe_new_connection.value()))); + return (max_clients != 0 && ++connected == max_clients) + ? MinimalSocket::samples::PollableStatus::COMPLETED + : MinimalSocket::samples::PollableStatus::ADVANCED; + } + return MinimalSocket::samples::PollableStatus::NOT_ADVANCED; + }); + + pollables.loop(std::chrono::seconds{5}); + + return EXIT_SUCCESS; +} diff --git a/samples/udp/CMakeLists.txt b/samples/udp/CMakeLists.txt index 0b3086f9..83e6de20 100644 --- a/samples/udp/CMakeLists.txt +++ b/samples/udp/CMakeLists.txt @@ -1,5 +1,6 @@ MakeApp(UdpAsker) MakeApp(UdpResponder) +MakeApp(UdpResponderNonBlocking) MakeSample(Sample01_asker_responder Udp) add_dependencies(UdpSample01_asker_responder UdpAsker UdpResponder) @@ -9,3 +10,6 @@ add_dependencies(UdpSample02_asker_connected_responer UdpAsker UdpResponder) MakeSample(Sample03_2_askers_responder Udp) add_dependencies(UdpSample03_2_askers_responder UdpAsker UdpResponder) + +MakeSample(Sample04_2_askers_2_nn_block_responders Udp) +add_dependencies(UdpSample04_2_askers_2_nn_block_responders UdpAsker UdpResponderNonBlocking) diff --git a/samples/udp/README.md b/samples/udp/README.md index 111b8777..637f38f7 100644 --- a/samples/udp/README.md +++ b/samples/udp/README.md @@ -65,5 +65,30 @@ The above classes of samples can be described as follows: UdpResponder->>UdpAsker1: response 2 ``` +- **UdpSample04_2_askers_2_nn_block_responders**: is an example of non blocking udp. The application uses one single thread to spin multiple connections. More in detail: + - related config file is [Sample04_2_askers_2_nn_block_responders](./Sample04_2_askers_2_nn_block_responders) + - runs **UdpResponderNonBlocking**, creating two non blocking udp sockets that binds two distinct specified ports. Then, a single thread is used to spin such sockets, checking, at each iteration and one socket at a time, if something was received and eventually respond + - runs **UdpAsker**, creating a udp socket that binds another port and exchanges messages with one of the two udp spawned in **UdpResponderNonBlocking** + - runs another **UdpAsker**, creating a udp socket that binds another port and exchanges messages with the other udp spawned in **UdpResponderNonBlocking** + - the following sequence diagram summarizes this sample + ```mermaid + sequenceDiagram + UdpResponder->>UdpResponder: bind port_A + UdpResponder->>UdpResponder: bind port_B + UdpAsker->>UdpAsker: bind a port + UdpResponder->>UdpResponder: was a message delivered to the first udp? if so send response + UdpResponder->>UdpResponder: was a message delivered to the second udp? if so send response + UdpAsker->>UdpResponder: request 1 + UdpResponder->>UdpResponder: was a message delivered to the first udp? if so send response + UdpResponder->>UdpResponder: was a message delivered to the second udp? if so send response + UdpResponder->>UdpAsker: response 1 + UdpResponder->>UdpResponder: was a message delivered to the first udp? if so send response + UdpResponder->>UdpResponder: was a message delivered to the second udp? if so send response + UdpAsker->>UdpResponder: request 2 + UdpResponder->>UdpResponder: was a message delivered to the first udp? if so send response + UdpResponder->>UdpResponder: was a message delivered to the second udp? if so send response + UdpResponder->>UdpAsker: response 2 + ``` + **UdpAsker** and **UdpResponder** can be also used as stand alone processes, in order to check connections on local processes or the ones stored in a different host. Check the sources (or the scripts generated by **UdpScriptsGenerator**) for the syntax of the accepted arguments. diff --git a/samples/udp/Sample04_2_askers_2_nn_block_responders b/samples/udp/Sample04_2_askers_2_nn_block_responders new file mode 100644 index 00000000..9beff1d6 --- /dev/null +++ b/samples/udp/Sample04_2_askers_2_nn_block_responders @@ -0,0 +1,3 @@ +UdpResponderNonBlocking --port_A 36995 --port_B 36996 +UdpAsker --port 36995 --port_this 37005 +UdpAsker --port 36996 --port_this 37015 \ No newline at end of file diff --git a/samples/udp/UdpAsker.cpp b/samples/udp/UdpAsker.cpp index 7c79a2e0..db3e5dc6 100644 --- a/samples/udp/UdpAsker.cpp +++ b/samples/udp/UdpAsker.cpp @@ -22,16 +22,14 @@ int main(const int argc, const char **argv) { cout << "----------------------- Udp asker -----------------------" << endl; PARSE_ARGS - const auto remote_host = options->getValue("host", "127.0.0.1"); - const auto remote_port = - static_cast(options->getIntValue("port")); - const auto port_this = - static_cast(options->getIntValue("port_this")); - const auto rate = - std::chrono::milliseconds{options->getIntValue<250>("rate")}; + const auto remote_host = options->getValue("host", "127.0.0.1"); + const auto remote_port = options->getValue("port"); + const auto port_this = options->getValue("port_this"); + const auto rate = options->getValue( + "rate", std::chrono::milliseconds{250}); const MinimalSocket::Address remote_address(remote_host, remote_port); - MinimalSocket::udp::UdpBinded asker(port_this, remote_address.getFamily()); + MinimalSocket::udp::Udp asker(port_this, remote_address.getFamily()); std::this_thread::sleep_for( std::chrono::seconds{1}); // just to be sure the responder has already @@ -43,7 +41,7 @@ int main(const int argc, const char **argv) { cout << "Port successfully reserved" << endl; MinimalSocket::samples::ask(asker, remote_address, rate, - options->getIntValue<5>("cycles")); + options->getValue("cycles", 5)); return EXIT_SUCCESS; } diff --git a/samples/udp/UdpResponder.cpp b/samples/udp/UdpResponder.cpp index 926db3e6..01aceae0 100644 --- a/samples/udp/UdpResponder.cpp +++ b/samples/udp/UdpResponder.cpp @@ -22,13 +22,12 @@ int main(const int argc, const char **argv) { << endl; PARSE_ARGS - const auto port_this = - static_cast(options->getIntValue("port_this")); - const auto family = - MinimalSocket::samples::to_family(options->getValue("family", "v4")); - const bool connect = options->getValue("connect", "no") == "yes"; + const auto port_this = options->getValue("port_this"); + const auto family = options->getValue( + "family", MinimalSocket::AddressFamily::IP_V4); + const bool connect = options->getValue("connect", false); - MinimalSocket::udp::UdpBinded responder(port_this, family); + MinimalSocket::udp::Udp responder(port_this, family); if (!responder.open()) { cerr << "Failed to reserve specified port" << endl; diff --git a/samples/udp/UdpResponderNonBlocking.cpp b/samples/udp/UdpResponderNonBlocking.cpp new file mode 100644 index 00000000..58a23903 --- /dev/null +++ b/samples/udp/UdpResponderNonBlocking.cpp @@ -0,0 +1,71 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +/////////////////////////////////////////////////////////////////////////// +// Have a look to README.md // +/////////////////////////////////////////////////////////////////////////// + +// elements from the MinimalSocket library +#include + +// just a bunch of utilities +#include +#include +#include +#include +using namespace std; + +int main(const int argc, const char **argv) { + cout << "----------------------- Udp responder -----------------------" + << endl; + PARSE_ARGS + + const auto port_A = options->getValue("port_A"); + const auto port_B = options->getValue("port_B"); + const auto family = options->getValue( + "family", MinimalSocket::AddressFamily::IP_V4); + + vector> responders; + responders.emplace_back(port_A, family); + responders.emplace_back(port_B, family); + for (auto &socket : responders) { + if (!socket.open()) { + cerr << "Failed to reserve one of the port" << endl; + return EXIT_FAILURE; + } + } + cout << "Ports successfully reserved" << endl; + + MinimalSocket::samples::Pollables pollables; + + auto make_pollable_responder = [](MinimalSocket::udp::Udp &responder) { + return [&responder]() { + auto request = responder.receive(500); + if (!request.has_value()) { + return MinimalSocket::samples::PollableStatus::NOT_ADVANCED; + } + const auto &response = + MinimalSocket::samples::NamesCircularIterator::NAMES_SURNAMES + .find(request->received_message) + ->second; + cout << MinimalSocket::samples::TimeOfDay{} + << " received: " << request->received_message + << " from: " << MinimalSocket::to_string(request->sender) + << " ; sending: " << response << endl; + responder.sendTo(response, request->sender); + return MinimalSocket::samples::PollableStatus::ADVANCED; + }; + }; + + for (auto &socket : responders) { + pollables.emplace(make_pollable_responder(socket)); + } + + pollables.loop(std::chrono::seconds{5}); + + return EXIT_SUCCESS; +} diff --git a/samples/udp/UdpScriptsGenerator.cpp b/samples/udp/UdpScriptsGenerator.cpp deleted file mode 100644 index 09e7e057..00000000 --- a/samples/udp/UdpScriptsGenerator.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -/////////////////////////////////////////////////////////////////////////// -// Have a look to README.md // -/////////////////////////////////////////////////////////////////////////// - -#include - -#include -using namespace std; - -int main() { - { - // 1 responder 1 asker - const std::string sample_name = "udp01_responder_asker"; - MinimalSocket::samples::ScriptGenerator generator; - - const std::size_t port_asker = 36995; - const std::size_t port_responder = port_asker + 10; - - generator.add("UdpResponder", - {{"port_this", std::to_string(port_responder)}}); - - generator.add("UdpAsker", {{"port", std::to_string(port_responder)}, - {"port_this", std::to_string(port_asker)}}); - - cout << "generating " << sample_name << endl; - generator.generate(sample_name); - } - - { - // 1 connecting responder 1 asker - const std::string sample_name = "udp02_connecting_responder_asker"; - MinimalSocket::samples::ScriptGenerator generator; - - const std::size_t port_asker = 36995; - const std::size_t port_responder = port_asker + 10; - - generator.add( - "UdpResponder", - {{"port_this", std::to_string(port_responder)}, {"connect", "yes"}}); - - generator.add("UdpAsker", {{"port", std::to_string(port_responder)}, - {"port_this", std::to_string(port_asker)}}); - - cout << "generating " << sample_name << endl; - generator.generate(sample_name); - } - - { - // 1 responder 2 askers - const std::string sample_name = "udp03_responder_2_askers"; - MinimalSocket::samples::ScriptGenerator generator; - - const std::size_t port_responder = 36995; - const std::size_t port_asker_1 = port_responder + 10; - const std::size_t port_asker_2 = port_responder + 20; - - generator.add("UdpResponder", - {{"port_this", std::to_string(port_responder)}}); - - generator.add("UdpAsker", {{"port", std::to_string(port_responder)}, - {"port_this", std::to_string(port_asker_1)}}); - - generator.add("UdpAsker", {{"port", std::to_string(port_responder)}, - {"port_this", std::to_string(port_asker_2)}, - {"rate", "800"}}); - - cout << "generating " << sample_name << endl; - generator.generate(sample_name); - } - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/samples/utils/Args.cpp b/samples/utils/Args.cpp index 827162cb..9db79eaa 100644 --- a/samples/utils/Args.cpp +++ b/samples/utils/Args.cpp @@ -49,22 +49,6 @@ Args::Args(const int argc, const char **argv) { std::cout << std::endl; } -std::string Args::getValue(const std::string &argument_name, - const std::string &default_value) const { - auto args_it = arguments_map.find(argument_name); - return (args_it == arguments_map.end()) ? default_value : args_it->second; -} - -std::string Args::getValue(const std::string &argument_name) const { - auto args_it = arguments_map.find(argument_name); - if (args_it == arguments_map.end()) { - std::stringstream stream; - stream << "--" << argument_name << " was not specififed"; - throw std::runtime_error{stream.str()}; - } - return args_it->second; -} - MinimalSocket::AddressFamily to_family(const std::string &family_as_string) { if (family_as_string == "v4") { return MinimalSocket::AddressFamily::IP_V4; diff --git a/samples/utils/Args.h b/samples/utils/Args.h index 22dc9dec..c5d9f543 100644 --- a/samples/utils/Args.h +++ b/samples/utils/Args.h @@ -9,51 +9,80 @@ #include +#include #include #include #include #include namespace MinimalSocket::samples { -using ArgsMap = std::unordered_map; +template struct Convert {}; -// Group the passed args into an ordered table -class Args { -public: - Args(Args &&) = default; +template <> struct Convert { + static std::string convert(const std::string &val) { return val; } +}; - static std::optional parse(const int argc, const char **argv); +template <> struct Convert { + static bool convert(const std::string &val) { + if (val == "1" || val == "yes" || val == "true") { + return true; + } + if (val == "0" || val == "no" || val == "false") { + return false; + } + throw std::runtime_error{"Unrecognized boolean value"}; + } +}; - // default value is returned in case the argument name is not found among the - // parsed ones - std::string getValue(const std::string &argument_name, - const std::string &default_value) const; +template <> struct Convert { + static int convert(const std::string &val) { return std::atoi(val.c_str()); } +}; - // throw if this option does not exists - std::string getValue(const std::string &argument_name) const; +template <> struct Convert { + static std::chrono::milliseconds convert(const std::string &val) { + return std::chrono::milliseconds{std::atoi(val.c_str())}; + } +}; - template - int getIntValue(const std::string &argument_name) const { - auto temp = getValue(argument_name, ""); - if (temp.empty()) { - return DefaultValue; - } - return std::atoi(temp.c_str()); +template <> struct Convert { + static MinimalSocket::Port convert(const std::string &val) { + return static_cast(std::atoi(val.c_str())); } +}; - int getIntValue(const std::string &argument_name) const { - auto temp = getValue(argument_name); - return std::atoi(temp.c_str()); +MinimalSocket::AddressFamily to_family(const std::string &family_as_string); + +template <> struct Convert { + static MinimalSocket::AddressFamily convert(const std::string &val) { + return to_family(val); + } +}; + +// Group the passed args into an ordered table +class Args { +public: + static std::optional parse(const int argc, const char **argv); + + template + T getValue(const std::string &argument_name, + const std::optional &default_value = std::nullopt) const { + auto it = arguments_map.find(argument_name); + if (it == arguments_map.end()) { + if (default_value == std::nullopt) { + std::string msg = "Unable to find '" + argument_name + "'"; + throw std::runtime_error{msg}; + } + return default_value.value(); + } + return Convert::convert(it->second); } private: Args(const int argc, const char **argv); - ArgsMap arguments_map; + std::unordered_map arguments_map; }; -MinimalSocket::AddressFamily to_family(const std::string &family_as_string); - #define PARSE_ARGS \ auto options = MinimalSocket::samples::Args::parse(argc, argv); \ if (std::nullopt == options) { \ diff --git a/samples/utils/Ask.h b/samples/utils/Ask.h index ef0fcb37..5231646a 100644 --- a/samples/utils/Ask.h +++ b/samples/utils/Ask.h @@ -7,7 +7,8 @@ #pragma once -#include +#include +#include #include #include @@ -17,34 +18,36 @@ #include namespace MinimalSocket::samples { -template -void ask(SocketT &channel, const std::chrono::milliseconds &rate, - std::size_t cycles) { +void ask_connected(ReceiverBlocking &receiver, Sender &sender, + const std::chrono::milliseconds &rate, std::size_t cycles) { NamesCircularIterator iterator; - for (std::size_t k = 0; k < cycles * NamesCircularIterator::size(); ++k) { + for (std::size_t k = 0; + k < cycles * NamesCircularIterator::NAMES_SURNAMES.size(); ++k) { // send name of this person std::cout << TimeOfDay{} << "Sending: " << iterator.current()->first << std::endl; - channel.send(iterator.current()->first); + sender.send(iterator.current()->first); // expect to get back the corresponding surname - auto response = channel.receive(500); + auto response = receiver.receive(500); std::cout << TimeOfDay{} << "Got response: " << response << std::endl; iterator.next(); std::this_thread::sleep_for(rate); } } -void ask(MinimalSocket::udp::UdpBinded &channel, - const MinimalSocket::Address &target, - const std::chrono::milliseconds &rate, std::size_t cycles) { +void ask_disconnected(ReceiverUnkownSenderBlocking &receiver, SenderTo &sender, + const MinimalSocket::Address &target, + const std::chrono::milliseconds &rate, + std::size_t cycles) { NamesCircularIterator iterator; - for (std::size_t k = 0; k < cycles * NamesCircularIterator::size(); ++k) { + for (std::size_t k = 0; + k < cycles * NamesCircularIterator::NAMES_SURNAMES.size(); ++k) { // send name of this person std::cout << TimeOfDay{} << "Sending: " << iterator.current()->first << std::endl; - channel.sendTo(iterator.current()->first, target); + sender.sendTo(iterator.current()->first, target); // expect to get back the corresponding surname - auto response = channel.receive(500); + auto response = receiver.receive(500); std::cout << TimeOfDay{} << "From " << MinimalSocket::to_string(response->sender) << " , got as response: " << response->received_message @@ -53,4 +56,17 @@ void ask(MinimalSocket::udp::UdpBinded &channel, std::this_thread::sleep_for(rate); } } + +template +void ask(SocketT &socket, const std::chrono::milliseconds &rate, + std::size_t cycles) { + ask_connected(socket, socket, rate, cycles); +} + +template +void ask(SocketT &socket, const MinimalSocket::Address &target, + const std::chrono::milliseconds &rate, std::size_t cycles) { + ask_disconnected(socket, socket, target, rate, cycles); +} + } // namespace MinimalSocket::samples diff --git a/samples/utils/Names.cpp b/samples/utils/Names.cpp index 159012db..b3052e36 100644 --- a/samples/utils/Names.cpp +++ b/samples/utils/Names.cpp @@ -8,16 +8,8 @@ #include namespace MinimalSocket::samples { -const Names NamesCircularIterator::NAMES_SURNAMES = - Names{{"Luciano", "Pavarotti"}, - {"Gengis", "Khan"}, - {"Giulio", "Cesare"}, - {"Theodor", "Roosvelt"}, - {"Immanuel", "Kant"}}; - void NamesCircularIterator::next() { - ++current_; - if (current_ == NAMES_SURNAMES.end()) { + if (++current_ == NAMES_SURNAMES.end()) { current_ = NAMES_SURNAMES.begin(); } } diff --git a/samples/utils/Names.h b/samples/utils/Names.h index 6e795733..0a125a76 100644 --- a/samples/utils/Names.h +++ b/samples/utils/Names.h @@ -7,30 +7,28 @@ #pragma once -#include #include #include namespace MinimalSocket::samples { -using Names = std::unordered_map; - -using NamesIterator = Names::const_iterator; class NamesCircularIterator { public: - static const Names NAMES_SURNAMES; + using Names = std::unordered_map; + using NamesIterator = Names::const_iterator; + static inline Names NAMES_SURNAMES = {{"Luciano", "Pavarotti"}, + {"Gengis", "Khan"}, + {"Giulio", "Cesare"}, + {"Theodor", "Roosvelt"}, + {"Immanuel", "Kant"}}; - NamesCircularIterator() : current_(NAMES_SURNAMES.begin()){}; + NamesCircularIterator() = default; const NamesIterator ¤t() { return current_; }; void next(); - static std::size_t size() { - return NamesCircularIterator::NAMES_SURNAMES.size(); - } - private: - NamesIterator current_; + NamesIterator current_ = NAMES_SURNAMES.begin(); }; } // namespace MinimalSocket::samples diff --git a/samples/utils/Pollables.cpp b/samples/utils/Pollables.cpp new file mode 100644 index 00000000..5630e1ec --- /dev/null +++ b/samples/utils/Pollables.cpp @@ -0,0 +1,34 @@ +#include "Pollables.h" + +namespace MinimalSocket::samples { +void Pollables::loop(const std::chrono::seconds &timeout) { + auto last_notable_event = std::chrono::high_resolution_clock::now(); + while (!pollables_.empty()) { + auto it = pollables_.begin(); + while (it != pollables_.end()) { + auto stat = (*it)(); + switch (stat) { + case PollableStatus::NOT_ADVANCED: + ++it; + break; + case PollableStatus::ADVANCED: + last_notable_event = std::chrono::high_resolution_clock::now(); + ++it; + break; + case PollableStatus::COMPLETED: + last_notable_event = std::chrono::high_resolution_clock::now(); + it = pollables_.erase(it); + break; + } + } + + auto elapsed_since_last_notable = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - last_notable_event); + if (timeout < elapsed_since_last_notable) { + break; + } + } +} + +} // namespace MinimalSocket::samples diff --git a/samples/utils/Pollables.h b/samples/utils/Pollables.h new file mode 100644 index 00000000..8ca3745f --- /dev/null +++ b/samples/utils/Pollables.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +namespace MinimalSocket::samples { +enum class PollableStatus { NOT_ADVANCED, ADVANCED, COMPLETED }; + +using Pollable = std::function; + +class Pollables { +public: + Pollables() = default; + + template void emplace(Pred &&pred) { + pollables_.emplace_back(std::forward(pred)); + } + + // At each iteration, it polls one by one all the pollables. + // If there is no progress for a time period higher than the specified + // timeout, the function completes and return to the caller. + void loop(const std::chrono::seconds &timeout); + +private: + std::list pollables_; +}; + +} // namespace MinimalSocket::samples diff --git a/samples/utils/Respond.h b/samples/utils/Respond.h index 0d627861..53a05a2b 100644 --- a/samples/utils/Respond.h +++ b/samples/utils/Respond.h @@ -7,31 +7,38 @@ #pragma once -#include +#include +#include +#include #include namespace MinimalSocket::samples { template void respond(SocketT &channel) { while (true) { - // receive name to search - auto request = channel.receive(500, std::chrono::seconds{5}); - // respond with corresponding surname - if constexpr (std::is_same::value) { - if (!request.has_value()) { - break; + try { + // receive name to search + auto request = channel.receive(500, std::chrono::seconds{5}); + // respond with corresponding surname + if constexpr (std::is_base_of_v) { + if (!request.has_value()) { + break; + } + const auto &response = NamesCircularIterator::NAMES_SURNAMES + .find(request->received_message) + ->second; + channel.sendTo(response, request->sender); + } else { + if (request.empty()) { + break; + } + const auto &response = + NamesCircularIterator::NAMES_SURNAMES.find(request)->second; + channel.send(response); } - const auto &response = - NamesCircularIterator::NAMES_SURNAMES.find(request->received_message) - ->second; - channel.sendTo(response, request->sender); - } else { - if (request.empty()) { - break; - } - const auto &response = - NamesCircularIterator::NAMES_SURNAMES.find(request)->second; - channel.send(response); + } catch (const MinimalSocket::SocketError &) { + // if here the connection was shut down + break; } } } diff --git a/src/header/MinimalSocket/Error.h b/src/header/MinimalSocket/Error.h index 0a568db4..7aa96374 100644 --- a/src/header/MinimalSocket/Error.h +++ b/src/header/MinimalSocket/Error.h @@ -45,8 +45,8 @@ class ErrorCodeHolder { class SocketError : public ErrorCodeHolder, public Error { public: /** - * @brief last error code raised by the socket API is automatically retrieved - * and appended to error message + * @brief The last raised error code is automatically retrieved + * and included in the error message */ SocketError(const std::string &what); @@ -56,6 +56,6 @@ class SocketError : public ErrorCodeHolder, public Error { class TimeOutError : public Error { public: - TimeOutError() : Error("Timeout"){}; + TimeOutError() : Error("Timeout reached"){}; }; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index 310951fb..9c4f7e02 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -21,23 +21,20 @@ enum class AddressFamily { IP_V4, IP_V6 }; using Port = std::uint16_t; /** - * @brief Passing this value as Port implies to ask the system to reserve a - * random port. + * @brief Used to indicate a random port that can be assigned by the os. */ static constexpr Port ANY_PORT = 0; class Address { public: /** - * @brief Internally the AddressFamily is deduced according to the - * hostIp content. - * In case of invalid host, the object is built but left empty (i.e. *this == - * nullptr would be true) + * @brief The AddressFamily is deduced on the basis of the hostIp. + * @throw In case of an invalid hostIp */ Address(const std::string &hostIp, Port port); /** - * @brief Local host address is asumed. + * @brief Local host on the specified port is assumed as address. */ Address(Port port, AddressFamily family = AddressFamily::IP_V4); @@ -47,12 +44,6 @@ class Address { 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; @@ -62,20 +53,17 @@ class Address { }; /** - * @return "host:port" into a string. + * @return "host:port" */ std::string to_string(const Address &subject); /** * @brief Tries to deduce the family from the host. - * @return nullopt in case the host is invalid, otherwise the deduced family - * value is returned. + * @return nullopt in case the host is invalid, otherwise the family + * conrresponding to the passed address */ std::optional deduceAddressFamily(const std::string &host_address); -bool operator==(std::nullptr_t, const Address &subject); -bool operator==(const Address &subject, std::nullptr_t); - bool isValidHost(const std::string &host_address); } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Definitions.h b/src/header/MinimalSocket/core/Definitions.h index 708b2b4f..68226a66 100644 --- a/src/header/MinimalSocket/core/Definitions.h +++ b/src/header/MinimalSocket/core/Definitions.h @@ -22,7 +22,7 @@ struct BufferView { void clear(BufferView &subject); /** - * @param subject the string buffer to convert + * @param subject the string to convert * @return a buffer pointing to the first element of the subject, and a lenght * equal to the current size of subject */ diff --git a/src/header/MinimalSocket/core/Receiver.h b/src/header/MinimalSocket/core/Receiver.h index b6fffef9..307ca17d 100644 --- a/src/header/MinimalSocket/core/Receiver.h +++ b/src/header/MinimalSocket/core/Receiver.h @@ -13,102 +13,179 @@ #include namespace MinimalSocket { -class ReceiverBase : public virtual Socket { +class ReceiverWithTimeout : public virtual Socket { protected: - 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; +private: Timeout receive_timeout = NULL_TIMEOUT; }; -/** - * @brief Typically associated to a connected socket, whose remote peer - * exchanging messages is known. - * 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. - */ -class Receiver : public ReceiverBase { +class ReceiverBlocking : public ReceiverWithTimeout { public: /** - * @param message the buffer that will store the received bytes. - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to - * begin a blocking receive. - * @return the number of received bytes actually received and copied into - * message. It can be also lower then buffer size, as less bytes might be - * received. + * @param the buffer that will store the received message. + * @param optional timeout after which the receive is considered failed. A + * NULL_TIMEOUT means actually to begin a blocking receive. + * @return the number of actually received bytes. It can be also lower then + * the message cacapity, as that represents the maxium number of bytes + * expected to be received. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ std::size_t receive(BufferView message, const Timeout &timeout = NULL_TIMEOUT); /** - * @brief Similar to Receiver::receive(Buffer &, const Timeout &), but - * internally building the recipient buffer which is converted into a string - * before returning it. - * - * @param expected_max_bytes maximum number of bytes to receive - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to - * begin a blocking receive. + * @brief Similar to Receiver::receive(BufferView &, const Timeout &), but + * returning a string as a buffer containing the received message. + * @param maximum number of bytes to receive + * @param optional timeout after which the receive is considered failed. A + * NULL_TIMEOUT means actually to begin a blocking receive. * @return the received sequence of bytes as a string. The size of the result - * might be less than expected_max_bytes, in case less bytes were actually - * received. + * might be less than expected_max_bytes, as that represents the maxium number + * of bytes expected to be received. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ std::string receive(std::size_t expected_max_bytes, const Timeout &timeout = NULL_TIMEOUT); + +private: + std::mutex recv_mtx; }; -/** - * @brief Typically associated to a non connected socket, whose remote peer that - * 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. - */ -class ReceiverUnkownSender : public ReceiverBase { +class ReceiverNonBlocking : public virtual Socket { public: - struct ReceiveResult { - Address sender; - std::size_t received_bytes; - }; /** - * @param message the buffer that will store the received bytes. - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to + * @brief Notice this is a non blocking operation, meaning that in case no new + * bytes were actually received at the time of calling this method, 0 will be + * immediately returned. + * @param the buffer that will store the received bytes. + * @return the number of actually received bytes. It can be also lower then + * the message cacapity, as that represents the maxium number of bytes + * expected to be received. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. + */ + std::size_t receive(BufferView message); + + /** + * @brief Similar to Receiver::receive(BufferView), but + * returning a string as a buffer containing the received message. + * Notice that this is a non blocking operation, meaning that in case no new + * bytes were actually received at the time of calling this method, an empty + * string will be immediately returned. + * + * @param expected_max_bytes maximum number of bytes to receive + * @return the received sequence of bytes as a string. The size of the result + * might be less than expected_max_bytes, as that represents the maxium number + * of bytes expected to be received. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. + */ + std::string receive(std::size_t expected_max_bytes); + +private: + std::mutex recv_mtx; +}; + +template class Receiver {}; +template <> class Receiver : public ReceiverBlocking {}; +template <> class Receiver : public ReceiverNonBlocking {}; + +struct ReceiveResult { + Address sender; + std::size_t received_bytes; +}; + +struct ReceiveStringResult { + Address sender; + std::string received_message; +}; + +class ReceiverUnkownSenderBlocking : public ReceiverWithTimeout { +public: + /** + * @param the buffer that will store the received bytes. + * @param the timeout to consider. A NULL_TIMEOUT means actually to * begin a blocking receive. * @return the number of received bytes actually received and copied into * message, together with the address of the sender. The received bytes can be * also lower then buffer size, as less bytes might be received. * In case no bytes were received within the timeout, a nullopt is returned. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ std::optional receive(BufferView message, const Timeout &timeout = NULL_TIMEOUT); - struct ReceiveStringResult { - Address sender; - std::string received_message; - }; /** - * @brief Similar to ReceiverUnkownSender::receive(Buffer &, const Timeout &), - * but internally building the recipient buffer which is converted into a + * @brief Similar to ReceiverUnkownSender::receive(BufferView &, const Timeout + * &), but internally building the recipient buffer which is converted into a * string before returning it. * - * @param expected_max_bytes maximum number of bytes to receive - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to + * @param maximum number of bytes to receive + * @param the timeout to consider. A NULL_TIMEOUT means actually to * begin a blocking receive. * @return the received sequence of bytes as a string, together with the * address of the sender. The size of the result might be less than * expected_max_bytes, in case less bytes were actually received. * In case no bytes were received within the timeout, a nullopt is returned. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ std::optional receive(std::size_t expected_max_bytes, const Timeout &timeout = NULL_TIMEOUT); + +private: + std::mutex recv_mtx; }; + +class ReceiverUnkownSenderNonBlocking : public virtual Socket { +public: + /** + * @brief Notice this is a non blocking operation, meaning that in case no new + * bytes were actually received at the time of calling this method, a nullopt + * will be actually returned. + * @param the buffer that will store the received bytes. + * @return the number of received bytes actually received and copied into + * message, together with the address of the sender. The received bytes can be + * also lower then buffer size, as less bytes might be received. + * In case no bytes were received within the timeout, a nullopt is returned. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. + */ + std::optional receive(BufferView message); + + /** + * @brief Similar to ReceiverUnkownSender::receive(BufferView &, const Timeout + * &), but internally building the recipient buffer which is converted into a + * string before returning it. + * Notice this is a non blocking operation, meaning that in case no new + * bytes were actually received at the time of calling this method, a nullopt + * will be actually returned. + * + * @param maximum number of bytes to receive + * @param the timeout to consider. A NULL_TIMEOUT means actually to + * begin a blocking receive. + * @return the received sequence of bytes as a string, together with the + * address of the sender. The size of the result might be less than + * expected_max_bytes, in case less bytes were actually received. + * In case no bytes were received within the timeout, a nullopt is returned. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. + */ + std::optional receive(std::size_t expected_max_bytes); + +private: + std::mutex recv_mtx; +}; + +template class ReceiverUnkownSender {}; +template <> +class ReceiverUnkownSender : public ReceiverUnkownSenderBlocking {}; +template <> +class ReceiverUnkownSender : public ReceiverUnkownSenderNonBlocking {}; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Sender.h b/src/header/MinimalSocket/core/Sender.h index 161a07c8..3849858e 100644 --- a/src/header/MinimalSocket/core/Sender.h +++ b/src/header/MinimalSocket/core/Sender.h @@ -14,24 +14,31 @@ #include namespace MinimalSocket { -/** - * @brief Typically associated to a connected socket, whose remote peer - * exchanging messages is known. - * Attention!! Even when calling from different threads some simultaneously - * send, they will be satisfited one at a time, as an internal mutex must be - * locked before starting to receive. - */ class Sender : public virtual Socket { public: /** - * @param message the buffer storing the bytes to send - * @return true in case all the bytes were successfully sent + * @param the buffer storing the bytes to send + * @return true in case all the bytes were successfully sent. In case the + * socket is non blocking, false is returned when the buffer is full and no + * further bytes can be inserted. In such case, the send fails but does not + * throw. + * On the opposite, a blocking socket will wait (absorbing the calling thread) + * until the buffer has enough space to proceed with the send. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ bool send(const BufferViewConst &message); /** - * @param message the buffer storing the bytes to send as a string - * @return true in case all the bytes were successfully sent + * @param the buffer storing the bytes to send as a string + * @return true in case all the bytes were successfully sent. In case the + * socket is non blocking, false is returned when the buffer is full and no + * further bytes can be inserted. In such case, the send fails but does not + * throw. + * On the opposite, a blocking socket will wait (absorbing the calling thread) + * until the buffer has enough space to proceed with the send. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ bool send(const std::string &message); @@ -39,29 +46,35 @@ class Sender : public virtual Socket { std::mutex send_mtx; }; -/** - * @brief Typically associated to a non connected socket, whose remote peer that - * 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 - * same recipient, sendTo request will be queued and executed one at a time. - */ class SenderTo : public virtual Socket { public: /** - * @param message the buffer storing the bytes to send - * @param recipient the recpient of the message + * @param the buffer storing the bytes to send + * @param the recpient of the message * @return true in case all the bytes were successfully sent to the specified - * recipient + * recipient. In case the + * socket is non blocking, false is returned when the buffer is full and no + * further bytes can be inserted. In such case, the send fails but does not + * throw. + * On the opposite, a blocking socket will wait (absorbing the calling thread) + * until the buffer has enough space to proceed with the send. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ bool sendTo(const BufferViewConst &message, const Address &recipient); /** - * @param message the buffer storing the bytes to send as a string - * @param recipient the recpient of the message + * @param the buffer storing the bytes to send as a string + * @param the recpient of the message * @return true in case all the bytes were successfully sent to the specified - * recipient + * recipient. In case the + * socket is non blocking, false is returned when the buffer is full and no + * further bytes can be inserted. In such case, the send fails but does not + * throw. + * On the opposite, a blocking socket will wait (absorbing the calling thread) + * until the buffer has enough space to proceed with the send. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ bool sendTo(const std::string &message, const Address &recipient); diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 9bf52c11..d790889a 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -23,18 +23,18 @@ using WSAVersion = std::array; /** * @brief Refer to * https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup - * In windows, any application using sockets, need to initialize the WSA data - * structure. When doing this, the version to use must be specified. This will - * be internally handled by this library, before calling any fucntion that - * requires the WSA data to be already initialized. - * The WSA data will be automatically cleaned up when the process terminates. + * When operating under Windows, any application using sockets need to + * initialize the WSA data structure. When doing this, the version to use must + * be specified. This is internally handled, before using any relevant + * functionality. The WSA data will be automatically cleaned up when the process + * terminates. * - * This object allows you specify the WSA version used to initialize the WSA - * data. When calling setWsaVersion(...) you can change the WSA version and such - * information will be used next time the WSA data will be (automatically and - * internally) initialized. Clearly, in case the WSA data was already - * initialized, as a consequence of creating any kind of sockets or using one of - * the features defined in Address.h, setWsaVersion has actually no effect. + * This object allows you to specify the WSA version to use. When calling + * setWsaVersion(...) you can change the WSA version, as this information will + * be used next time the WSA data will be (automatically and internally) + * initialized. Clearly, in case the WSA data was already initialized, as a + * consequence of creating any kind of sockets or using one of the features + * defined in Address.h, setWsaVersion has actually no effect. */ class WSAManager { public: @@ -50,7 +50,7 @@ class WSAManager { class SocketHandler; /** - * @brief The base onject of any kind of socket. + * @brief The base object for any socket. */ class Socket { public: @@ -60,12 +60,12 @@ class Socket { Socket &operator=(const Socket &) = delete; /** - * @return the socket descriptor associated to this object. + * @return the file descriptor associated to this socket. * - * This might be: + * This could be: * * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket - * in windows is the SOCKET (cast as in integer) data used to identify the + * in Windows is the SOCKET (cast as an integer) used to identify the * underlying socket and returned by ::socket(...) * * https://man7.org/linux/man-pages/man2/socket.2.html @@ -74,13 +74,21 @@ class Socket { * * Normally, all the operations involving the socket should be handled by the * functions provided by this library. Therefore, you shouldn't need to - * access this number However, it might happen that sometimes you want to - * specify additional socket option, and in order to do that you need this - * number. Beware that you should really know what you are doing when using - * this number. + * access this value. However, it might happen that you want to + * specify additional options, and in order to do so you need to access such + * value. Beware that you should really know what you are doing when using + * this value. */ int getSocketDescriptor() const; + /** + * @return true in case the socket is blocking, i.e. all possible operations + * absorb the calling thread till completion. Otherwise false, for a + * socket configured to be non blocking, i.e. operations immediately + * succeed or not. + */ + bool isBlocking() const { return isBlocking_; } + protected: Socket(); @@ -91,37 +99,58 @@ class Socket { SocketHandler &getHandler(); void resetHandler(); + void setNonBlocking() { isBlocking_ = false; } + void setUp(); + private: + bool isBlocking_ = true; std::unique_ptr socket_id_wrapper; }; -class Openable : public virtual Socket { +class OpenableBase : public virtual Socket { public: - virtual ~Openable() = default; + virtual ~OpenableBase() = default; bool wasOpened() const { return opened; } - /** - * @brief Tries to do all the steps required to open this socket. In case - * potentially expected expections are raised, they are interally catched and - asserted. - * On the opposite, unexpected expections are passed to the caller. - * In both cases, the object is inernally closed and left in a state for which - * open may be tried again. - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to - * begin a blocking open. - */ - bool open(const Timeout &timeout = NULL_TIMEOUT); - protected: - Openable() = default; + OpenableBase() = default; - void steal(Openable &giver); // Socket::steal(...) is also called - void transfer(Openable &recipient) { recipient.steal(*this); } + void steal(OpenableBase &giver); // Socket::steal(...) is also called + void transfer(OpenableBase &recipient) { recipient.steal(*this); } virtual void open_() = 0; -private: std::mutex open_procedure_mtx; std::atomic_bool opened = false; }; + +class Openable : public OpenableBase { +public: + /** + * @brief Tries to execute all required steps in order to open this socket. + * Expections (but actually only those extending the Error class) thrown will + * doing so, are interally catched, terminating the opening phase and + * consequently leaving the socket closed. On the opposite, unexpected + * expections are passed to the caller. In both cases, the object is inernally + * closed and left in a state for which open may be tried again. + */ + bool open(); +}; + +class OpenableWithTimeout : public OpenableBase { +public: + /** + * @brief Tries to execute all required steps in order to open this socket. + * Expections (but actually only those extending the Error class) thrown will + * doing so, are interally catched, terminating the opening phase and + * consequently leaving the socket closed. On the opposite, unexpected + * expections are passed to the caller. In both cases, the object is inernally + * closed and left in a state for which open may be tried again. + * @param the timeout to consider. A NULL_TIMEOUT means actually to + * begin a blocking open. On the contrary, in case the open steps are not + * completely within the specified timeout, function returns to the caller and + * leave the socket closed. + */ + bool open(const Timeout &timeout = NULL_TIMEOUT); +}; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/SocketContext.h b/src/header/MinimalSocket/core/SocketContext.h index d6614f35..25b118b3 100644 --- a/src/header/MinimalSocket/core/SocketContext.h +++ b/src/header/MinimalSocket/core/SocketContext.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include @@ -29,10 +30,6 @@ class RemoteAddressAware { Address getRemoteAddress() const; protected: - /** - * @throw in case the passed address is invalid (i.e. address == nullptr is - * true). - */ RemoteAddressAware(const Address &address); private: @@ -52,16 +49,14 @@ class PortToBindAware { } /** - * @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. + * @return the port to reserve. */ Port getPortToBind() const { return port_to_bind; } /** - * @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 - * behaviour is the opposite: you don't call this function the port will be - * possibly re-used. + * @brief Used to specify that the port should be actually free when trying to + * open this socket. Beware that the default behaviour is the opposite: until + * you don't call this function the port will be re-used. */ void mustBeFreePort() { must_be_free_port = true; }; bool shallBeFreePort() const { return must_be_free_port; } @@ -98,4 +93,5 @@ class RemoteAddressFamilyAware { private: std::atomic remote_address_family; }; + } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/tcp/TcpClient.h b/src/header/MinimalSocket/tcp/TcpClient.h index 4e35d618..7a246fa9 100644 --- a/src/header/MinimalSocket/tcp/TcpClient.h +++ b/src/header/MinimalSocket/tcp/TcpClient.h @@ -13,30 +13,47 @@ #include namespace MinimalSocket::tcp { -class TcpClient : public NonCopiable, - public Openable, - public Sender, - public Receiver, - public RemoteAddressAware { -public: - TcpClient(TcpClient &&o); - TcpClient &operator=(TcpClient &&o); +class TcpClientBase : public NonCopiable, + public OpenableWithTimeout, + public Sender, + public RemoteAddressAware { +protected: + TcpClientBase(TcpClientBase &&o); + + void stealBase(TcpClientBase &o); + TcpClientBase(const Address &server_address, bool block_mode); + + void open_() override; +}; + +template +class TcpClient : public TcpClientBase, public Receiver { +public: /** - * @brief The connection to the server is not asked in this c'tor which - * simply initialize this object. Such a connection is tried to be established - * when calling open(...) + * @brief After construction, the socket is left closed. Indeed, the + * connection to the server is actually asked when opening the client. * @param server_address the server to reach when opening this socket */ - TcpClient(const Address &server_address); + TcpClient(const Address &server_address) + : TcpClientBase{server_address, BlockMode} {} -protected: - void open_() override; + TcpClient(TcpClient &&o) + : TcpClientBase{std::forward(o)} {} + TcpClient &operator=(TcpClient &&o) { + this->stealBase(o); + return *this; + } }; /** - * @return a client ready to ask the connection to the same server. - * Beware that a closed socket is returned, which can be later opened. + * @return a client ready to ask the connection to the same server targeted by + * the passed client. Beware that a closed socket is returned, which can be + * later opened. */ -TcpClient clone(const TcpClient &o); +template +TcpClient clone(const TcpClient &o) { + return TcpClient{o.getRemoteAddress()}; +} + } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index 62d0da1c..57854118 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -16,79 +16,171 @@ #include namespace MinimalSocket::tcp { -class TcpServer; +class TcpServerBase; +class TcpConnectionNonBlocking; + /** - * @brief Handler of an already established connection with a client, on the - * server side. - * An istance of this object is created calling TcpServer::acceptNewClient(). + * @brief Handler of an already established connection. */ -class TcpConnection : public NonCopiable, - public Sender, - public Receiver, - public RemoteAddressAware { - friend class TcpServer; +class TcpConnectionBlocking : public NonCopiable, + public Sender, + public Receiver, + public RemoteAddressAware { + friend class TcpServerBase; public: - TcpConnection(TcpConnection &&o); - TcpConnection &operator=(TcpConnection &&o); + TcpConnectionBlocking(TcpConnectionBlocking &&o); + TcpConnectionBlocking &operator=(TcpConnectionBlocking &&o); + + /** + * @brief Beware that the giver objet is left empty. + */ + TcpConnectionNonBlocking turnToNonBlocking(); private: - TcpConnection(const Address &remote_address); + TcpConnectionBlocking(const Address &remote_address); }; -class TcpServer : public NonCopiable, - public PortToBindAware, - public RemoteAddressFamilyAware, - public Openable { +/** + * @brief Similar to TcpConnectionBlocking, but representing the non blocking + * version. + */ +class TcpConnectionNonBlocking : public NonCopiable, + public Sender, + public Receiver, + public RemoteAddressAware { public: - TcpServer(TcpServer &&o); - TcpServer &operator=(TcpServer &&o); + TcpConnectionNonBlocking(TcpConnectionNonBlocking &&o); + TcpConnectionNonBlocking &operator=(TcpConnectionNonBlocking &&o); + + TcpConnectionNonBlocking(TcpConnectionBlocking &&connection); +}; + +class TcpServerBase : public NonCopiable, + public PortToBindAware, + public RemoteAddressFamilyAware, + public Openable { +protected: + TcpServerBase(TcpServerBase &&o); + + void stealBase(TcpServerBase &o); + TcpServerBase(Port port_to_bind, AddressFamily accepted_client_family, + bool block_mode); + +public: /** - * @brief The port is not reserved in this c'tor which - * simply initialize this object. Such a thing is done when - * when calling open(...) which does: - * bind to the specified port this server - * start listening for clients on the same port - * @param port_to_bind the port that will be reserved when opening this server - * @param accepted_client_family family of the client that will ask the - * connection to this server + * @param the maximum size of the queue of clients waiting to establish a + * connection with this server. + * https://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/023/2333/2333s2.html#:~:text=TCP%20listen()%20Backlog,queue%20of%20partially%20open%20connections. + * (valid also for Windows) + * @throw in case the server was already opened. */ - TcpServer(Port port_to_bind = ANY_PORT, - AddressFamily accepted_client_family = AddressFamily::IP_V4); + void setClientQueueSize(std::size_t queue_size); + +protected: + void open_() override; + + struct AcceptedSocket; + void acceptClient_(AcceptedSocket &recipient); + static TcpConnectionBlocking makeClient(const AcceptedSocket &acceptedSocket); + +private: + std::atomic client_queue_size = 50; +}; + +class AcceptorBlocking : public TcpServerBase { +public: /** * @brief Wait till accepting the connection from a new client. This is a * blocking operation. */ - TcpConnection acceptNewClient(); // blocking + TcpConnectionBlocking acceptNewClient(); /** - * @brief Wait till accepting the connection from a new client. In case such a - * connection is not asked within the specified timeout, a nullopt is - * returned. - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to - * begin a blocking accept. + * @brief Similar to AcceptorBlocking::acceptNewClient, but returning a non + * blocking socket after the connection is established. + * Notice that in any case, this operation is blocking. */ - std::optional acceptNewClient(const Timeout &timeout); + TcpConnectionNonBlocking acceptNewNonBlockingClient() { + return acceptNewClient().turnToNonBlocking(); + } +protected: + template + AcceptorBlocking(Args &&...args) + : TcpServerBase{std::forward(args)...} {} + +private: + std::mutex accept_mtx; +}; + +class AcceptorNonBlocking : public TcpServerBase { +public: /** - * @param queue_size the backlog size to assume when the server will be - * opened, refer also to - * https://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/023/2333/2333s2.html#:~:text=TCP%20listen()%20Backlog,queue%20of%20partially%20open%20connections. - * (valid also for Windows) - * @throw in case the server was already opened. + * @brief Check if a new client tried to connect and eventually immediately + * return the object handling the connection. This is a non blocking + * operation: in case no client actually asked for the connection, the method + * immeditely returns a nullopt. + * + * Notice that even though this operation is non + * blocking, a blocking socket is returned. */ - void setClientQueueSize(const std::size_t queue_size); + std::optional acceptNewClient(); + + /** + * @brief Similar to AcceptorNonBlocking::acceptNewClient, but returning a non + * blocking socket. + */ + std::optional acceptNewNonBlockingClient() { + auto client = acceptNewClient(); + if (client.has_value()) { + return client->turnToNonBlocking(); + } + return std::nullopt; + } protected: - void open_() override; + template + AcceptorNonBlocking(Args &&...args) + : TcpServerBase{std::forward(args)...} {} private: - // maximum number of clients waiting for the connection to be - // accepted - std::atomic client_queue_size = 50; - std::mutex accept_mtx; }; + +template class Acceptor {}; +template <> class Acceptor : public AcceptorBlocking { +protected: + template + Acceptor(Args &&...args) : AcceptorBlocking{std::forward(args)...} {} +}; +template <> class Acceptor : public AcceptorNonBlocking { +protected: + template + Acceptor(Args &&...args) : AcceptorNonBlocking{std::forward(args)...} {} +}; + +template class TcpServer : public Acceptor { +public: + /** + * @brief After construction, the socket is left closed. Only after calling + * open(...), the port is binded and the server starts to listen for + * connection request on that port. + * @param the port that will be reserved when opening this server + * @param family of the client that will ask the + * connection to this server + */ + TcpServer(Port port_to_bind = 0, + AddressFamily accepted_client_family = AddressFamily::IP_V4) + : Acceptor{port_to_bind, accepted_client_family, BlockMode} {} + + TcpServer(TcpServer &&o) + : Acceptor{std::forward>(o)} {} + TcpServer &operator=(TcpServer &&o) { + this->stealBase(o); + return *this; + } +}; } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 2d3bd86e..d800fde3 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -19,55 +19,44 @@ namespace MinimalSocket::udp { */ static constexpr std::size_t MAX_UDP_RECV_MESSAGE = 65507; -class UdpConnected; +template class UdpConnected; -/** - * @brief This kind of udp is agnostic of the remote address (which can also - * change over the time) exchanging messages with socket. - * This udp can be reached by any other udp, on the port reserved when opening - * this socket. - * At the same time, this udp can send messages to any other non connected udp - * sockets. - */ -class UdpBinded : public NonCopiable, - public SenderTo, - public ReceiverUnkownSender, - public PortToBindAware, - public RemoteAddressFamilyAware, - public Openable { -public: - UdpBinded(UdpBinded &&o); - UdpBinded &operator=(UdpBinded &&o); +class UdpBase : public NonCopiable, + public SenderTo, + public PortToBindAware, + public RemoteAddressFamilyAware, + public Openable { +protected: + UdpBase(UdpBase &&o); - /** - * @brief The port is not reserved in this c'tor which - * simply initialize this object. Such a thing is done when - * when opening this socket - * @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(Port port_to_bind = ANY_PORT, - AddressFamily accepted_connection_family = AddressFamily::IP_V4); + void stealBase(UdpBase &o); + + UdpBase(Port port_to_bind, AddressFamily accepted_connection_family, + bool blockMode); + void open_() override; +}; + +class UdpBlocking : public UdpBase, public ReceiverUnkownSender { +public: /** - * @brief Connects the udo socket to the specified remote address. + * @brief Connects the udp socket to the specified remote address. * This leads to transfer the ownership of the underlying socket to the * returned object while this socket is left empty and closed. * @param remote_address the address to use for connecting the socket * @return a socket connected to the passed remote address */ - UdpConnected connect(const Address &remote_address); + UdpConnected connect(const Address &remote_address); /** * @brief similar to connect(const Address &). Here, the remote address is not - * explicitly specified, but is assumed to be equal to the first udp sending a - * message to this one. The first sent message is not lost, and can be moved - * into initial_message if not set to nullptr. - * This is a blocking operation. - * @param initial_message the initial message sent from the remote peer to - * detect its address. + * explicitly specified, but is assumed to be equal to the one of the first + * socket sending a message to this one. The first sent message is not lost, + * and can be moved into initial_message if not set to nullptr. + * Notice that this is a blocking operation. + * @param initial_message the initial message sent from the remote peer. */ - UdpConnected connect(std::string *initial_message = nullptr); + UdpConnected connect(std::string *initial_message = nullptr); /** * @brief similar to connect(std::string *initial_message), but non blocking. @@ -78,54 +67,131 @@ class UdpBinded : public NonCopiable, * @param initial_message the initial message sent from the remote peer to * detect its address. */ - std::optional connect(const Timeout &timeout, - std::string *initial_message = nullptr); + std::optional> + connect(const Timeout &timeout, std::string *initial_message = nullptr); protected: - void open_() override; + template + UdpBlocking(Args &&...args) : UdpBase{std::forward(args)...} {} }; -/** - * @brief A udp that is permanently connected to a specific remote address. - * Messages that are sent to this udp from different remote peer are ignored. - * Attention!! Differently from tcp connections, udp socket are not connection - * oriented. - * The attribute "connected" for this socket simply means that messages - * 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 NonCopiable, - public Sender, - public Receiver, - public PortToBindAware, - public RemoteAddressAware, - public Openable { +class UdpNonBlocking : public UdpBase, public ReceiverUnkownSender { public: - UdpConnected(UdpConnected &&o); - UdpConnected &operator=(UdpConnected &&o); + /** + * @brief Connects the udp socket to the specified remote address. + * This leads to transfer the ownership of the underlying socket to the + * returned object while this socket is left empty and closed. + * @param remote_address the address to use for connecting the socket + * @return a socket connected to the passed remote address + */ + UdpConnected connect(const Address &remote_address); /** - * @brief The connection to the remote address is not done in this c'tor which - * simply initialize this object. Such a thing is done when - * when opening this socket. - * @param remote_address remote address of the peer - * @param port the port to reserve by this udp + * @brief similar to connect(const Address &). Here, the remote address is not + * explicitly specified, but is assumed to be equal to the first socket + * sending a message to this one. The first sent message is not lost, and can + * be moved into initial_message if not set to nullptr. + * Notice that this is a non blocking operation, meaning that if no message + * was sent to this socket before calling this method, a nullopt is + * immediately returned. + * @param initial_message the initial message sent from the remote peer to + * detect its address. */ - UdpConnected(const Address &remote_address, Port port = ANY_PORT); + std::optional> + connect(std::string *initial_message = nullptr); + +protected: + template + UdpNonBlocking(Args &&...args) : UdpBase{std::forward(args)...} {} +}; +template class Udp_ {}; +template <> class Udp_ : public UdpBlocking { +protected: + template + Udp_(Args &&...args) : UdpBlocking{std::forward(args)...} {} +}; +template <> class Udp_ : public UdpNonBlocking { +protected: + template + Udp_(Args &&...args) : UdpNonBlocking{std::forward(args)...} {} +}; + +/** + * @brief This kind of udp is agnostic of the remote address (which can also + * change over the time) of the socket(s) exchanging messages with this one. + * This udp can be reached by any other udp, on the port reserved when opening + * this socket. + * At the same time, this udp can send messages to any other udp + * sockets. + */ +template class Udp : public Udp_ { +public: /** - * @brief disconnect the underlying socket, generating an unbinded udp that - * reserves the same port reserved by this one. This leaves this onbject - * empty and closed. + * @brief After construction, the socket is left closed. + * The port is actually reserved after opening the socket. + * @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 disconnect(); + Udp(Port port_to_bind = ANY_PORT, + AddressFamily accepted_connection_family = AddressFamily::IP_V4) + : Udp_{port_to_bind, accepted_connection_family, BlockMode} {} + + Udp(Udp &&o) : Udp_{std::forward>(o)} {} + Udp &operator=(Udp &&o) { + this->stealBase(o); + return *this; + } +}; +class UdpConnectedBase : public NonCopiable, + public Sender, + public PortToBindAware, + public RemoteAddressAware, + public Openable { protected: + UdpConnectedBase(UdpConnectedBase &&o); + + void stealBase(UdpConnectedBase &o); + + UdpConnectedBase(const Address &remote_address, Port port, bool blockMode); + void open_() override; }; /** - * @brief builds from 0 a connected udp socket. The connection is done to the + * @brief A udp that is permanently "connected" to a specific remote address. + * Messages sent from senders with an address different from the specified + * remote ones are ignored. + * + * Attention!! Differently from tcp connections, udp + * socket are not connection oriented. The attribute "connected" for this socket + * simply means that the os filter out message incoming from peers different + * from the specified one. At the same time, the "connected" remote peer + * might also not exist at the time of opening or using the socket. + */ +template +class UdpConnected : public UdpConnectedBase, public Receiver { +public: + /** + * @brief After construction, the socket is left closed. + * The port is actually reserved after opening the socket. + * @param remote_address remote address of the peer + * @param port the port to reserve by this udp + */ + UdpConnected(const Address &remote_address, Port port_to_bind = ANY_PORT) + : UdpConnectedBase{remote_address, port_to_bind, BlockMode} {} + + UdpConnected(UdpConnected &&o) + : UdpConnectedBase{std::forward(o)} {} + UdpConnected &operator=(UdpConnected &&o) { + this->stealBase(o); + return *this; + } +}; + +/** + * @brief builds from zero a connected udp socket. The connection is done to the * first udp sending a message on the specified port. * This is a blocking operation. * @param port the port to reserve by the udp to build @@ -134,17 +200,8 @@ class UdpConnected : public NonCopiable, * @param initial_message the message sent from the remote peer to detect its * address */ -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 +UdpConnected makeUdpConnectedToUnknown(Port port, AddressFamily accepted_connection_family, - const Timeout &timeout, std::string *initial_message = nullptr); + } // namespace MinimalSocket::udp diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index f51d6bdd..2a272ea0 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -11,11 +11,15 @@ #include "SocketFunctions.h" #include "Utils.h" +#if defined(__unix__) || defined(__APPLE__) +#include +#endif + namespace MinimalSocket { namespace { #ifdef _WIN32 #define REBIND_OPTION SO_REUSEADDR -#else +#elif defined(__unix__) || defined(__APPLE__) #define REBIND_OPTION SO_REUSEPORT #endif } // namespace @@ -133,4 +137,44 @@ void connect(SocketID socket_id, const Address &remote_address) { } }); } + +void turnToNonBlocking(SocketID socket_id) { +#ifdef _WIN32 + // https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-ioctlsocket + u_long iMode = 1; + if (ioctlsocket(socket_id, FIONBIO, &iMode) != NO_ERROR) { + throw Error{"Unable to set up the non blocking mode"}; + } +#elif defined(__unix__) || defined(__APPLE__) + // https://jameshfisher.com/2017/04/05/set_socket_nonblocking/ + int flags = ::fcntl(socket_id, F_GETFL); + if (::fcntl(socket_id, F_SETFL, flags | O_NONBLOCK) == -1) { + throw Error{"Unable to set up the non blocking mode"}; + } +#endif +} + +int isTimeoutErrorCode(int code) { + return +#ifdef _WIN32 + code == WSAETIMEDOUT || code == WSAEWOULDBLOCK; +#elif defined(__unix__) + code == EAGAIN || code == EWOULDBLOCK; +#elif defined(__APPLE__) + code == EAGAIN; +#endif +} + +bool checkResult(int value, int errorValue, const std::string &error_msg, + bool canBeTimedOut) { + if (value != errorValue) { + return false; + } + SocketError maybe_error(error_msg); + if (canBeTimedOut && isTimeoutErrorCode(maybe_error.getErrorCode())) { + // just out of time: tolerable + return true; + } + throw maybe_error; +} } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/SocketFunctions.h b/src/src/SocketFunctions.h index eaed8e76..1586cfa2 100644 --- a/src/src/SocketFunctions.h +++ b/src/src/SocketFunctions.h @@ -17,4 +17,10 @@ Port bind(SocketID socket_id, AddressFamily family, Port port, void listen(SocketID socket_id, std::size_t backlog_size); void connect(SocketID socket_id, const Address &remote_address); + +void turnToNonBlocking(SocketID socket_id); + +// return true in case the timeout was reached +bool checkResult(int value, int errorValue, const std::string &error_msg, + bool canBeTimedOut); } // namespace MinimalSocket diff --git a/src/src/SocketHandler.cpp b/src/src/SocketHandler.cpp index 5e75091f..724582c4 100644 --- a/src/src/SocketHandler.cpp +++ b/src/src/SocketHandler.cpp @@ -10,6 +10,10 @@ #include "SocketHandler.h" #include "Utils.h" +#if defined(__unix__) || defined(__APPLE__) +#include +#endif + namespace MinimalSocket { #ifdef _WIN32 WSALazyInitializer::WSALazyInitializer(const WSAVersion &version) @@ -129,4 +133,5 @@ void SocketHandler::reset(SocketType type, AddressFamily family) { throw err; } } + } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/core/Address.cpp b/src/src/core/Address.cpp index a850b46d..a184d5de 100644 --- a/src/src/core/Address.cpp +++ b/src/src/core/Address.cpp @@ -27,7 +27,7 @@ Address::Address(const std::string &hostIp, Port port) return; } - this->host.clear(); + throw Error{'\'', hostIp, "' is an invalid host ip "}; } namespace { @@ -48,14 +48,6 @@ bool Address::operator==(const Address &o) const { (this->family == o.family); } -bool operator==(std::nullptr_t, const Address &subject) { - return subject.getHost().empty(); -} - -bool operator==(const Address &subject, std::nullptr_t) { - return subject.getHost().empty(); -} - std::string to_string(const Address &subject) { std::stringstream stream; stream << subject.getHost() << ':' << subject.getPort(); @@ -64,11 +56,13 @@ std::string to_string(const Address &subject) { std::optional deduceAddressFamily(const std::string &host_address) { - Address temp(host_address, 0); - if (nullptr == temp) { - return std::nullopt; + std::optional res; + try { + Address temp(host_address, 0); + res = temp.getFamily(); + } catch (const Error &) { } - return temp.getFamily(); + return res; } bool isValidAddress(const std::string &host_address) { diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 6871c2e8..742e25e8 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -9,12 +9,13 @@ #include #include "../SocketAddress.h" +#include "../SocketFunctions.h" #ifndef _WIN32 #include #endif namespace MinimalSocket { -void ReceiverBase::updateTimeout_(const Timeout &timeout) { +void ReceiverWithTimeout::updateTimeout_(const Timeout &timeout) { if (timeout == receive_timeout) { return; } @@ -44,103 +45,154 @@ void ReceiverBase::updateTimeout_(const Timeout &timeout) { } } -namespace { -#ifdef _WIN32 -static constexpr int TIMEOUT_CODE = 10060; -#else -static constexpr int TIMEOUT_CODE = EAGAIN; -#endif - -void check_received_bytes(int &recvBytes, const Timeout &timeout) { - if (recvBytes != SCK_SOCKET_ERROR) { - return; - } - SocketError error_with_code("receive failed"); - recvBytes = 0; - if ((error_with_code.getErrorCode() == TIMEOUT_CODE) && - (timeout != NULL_TIMEOUT)) { - // just out of time: tolerable - return; +std::size_t ReceiverBlocking::receive(BufferView message, + const Timeout &timeout) { + std::scoped_lock lock{recv_mtx}; + updateTimeout_(timeout); + clear(message); + + int recvBytes = ::recv(getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0); + if (checkResult(recvBytes, SCK_SOCKET_ERROR, "receive failed", + timeout != NULL_TIMEOUT) || + recvBytes > message.buffer_size) { + return 0; } - throw error_with_code; + return static_cast(recvBytes); } -} // namespace - -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); +std::size_t ReceiverNonBlocking::receive(BufferView message) { + std::scoped_lock lock{recv_mtx}; + 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; + int recvBytes = ::recv(getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0); + if (checkResult(recvBytes, SCK_SOCKET_ERROR, "receive failed", true) || + recvBytes > message.buffer_size) { + return 0; + } + return static_cast(recvBytes); } -std::string Receiver::receive(std::size_t expected_max_bytes, - const Timeout &timeout) { +namespace { +template +std::string receive_into_string(std::size_t expected_max_bytes, Pred pred, + const Args &...args) { std::string buffer; buffer.resize(expected_max_bytes); auto buffer_temp = makeBufferView(buffer); - auto recvBytes = receive(buffer_temp, timeout); + auto recvBytes = pred(buffer_temp, args...); buffer.resize(recvBytes); return buffer; } +} // namespace + +std::string ReceiverBlocking::receive(std::size_t expected_max_bytes, + const Timeout &timeout) { + return receive_into_string( + expected_max_bytes, + [this](BufferView message, const Timeout &timeout) { + return this->receive(message, timeout); + }, + timeout); +} -std::optional -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)}; - }); +std::string ReceiverNonBlocking::receive(std::size_t expected_max_bytes) { + return receive_into_string(expected_max_bytes, [this](BufferView message) { + return this->receive(message); + }); +} + +std::optional +ReceiverUnkownSenderBlocking::receive(BufferView message, + const Timeout &timeout) { + std::scoped_lock lock{recv_mtx}; + updateTimeout_(timeout); + clear(message); + + std::optional res; + + 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); + if (checkResult(recvBytes, SCK_SOCKET_ERROR, "receive failed", + timeout != NULL_TIMEOUT) || + recvBytes > message.buffer_size) { + return std::nullopt; + } + + res = ReceiveResult{ + toAddress(reinterpret_cast(sender_address)), + static_cast(recvBytes)}; return res; } -std::optional -ReceiverUnkownSender::receive(std::size_t expected_max_bytes, - const Timeout &timeout) { +std::optional +ReceiverUnkownSenderNonBlocking::receive(BufferView message) { + std::scoped_lock lock{recv_mtx}; + clear(message); + + std::optional res; + + 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); + if (checkResult(recvBytes, SCK_SOCKET_ERROR, "receive failed", true) || + recvBytes > message.buffer_size) { + return std::nullopt; + } + + res = ReceiveResult{ + toAddress(reinterpret_cast(sender_address)), + static_cast(recvBytes)}; + + return res; +} + +namespace { +template +std::optional +receive_unknown_into_string(std::size_t expected_max_bytes, Pred pred, + const Args &...args) { std::string buffer; buffer.resize(expected_max_bytes); auto buffer_temp = makeBufferView(buffer); - auto result = receive(buffer_temp, timeout); + auto result = pred(buffer_temp, args...); if (!result) { return std::nullopt; } buffer.resize(result->received_bytes); return ReceiveStringResult{std::move(result->sender), std::move(buffer)}; } +} // namespace + +std::optional +ReceiverUnkownSenderBlocking::receive(std::size_t expected_max_bytes, + const Timeout &timeout) { + return receive_unknown_into_string( + expected_max_bytes, + [this](BufferView message, const Timeout &timeout) { + return this->receive(message, timeout); + }, + timeout); +} + +std::optional +ReceiverUnkownSenderNonBlocking::receive(std::size_t expected_max_bytes) { + return receive_unknown_into_string( + expected_max_bytes, + [this](BufferView message) { return this->receive(message); }); +} + } // namespace MinimalSocket diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index 80ec5eab..ef5c2c31 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -9,6 +9,7 @@ #include #include "../SocketAddress.h" +#include "../SocketFunctions.h" #include "../Utils.h" namespace MinimalSocket { @@ -16,11 +17,10 @@ bool Sender::send(const BufferViewConst &message) { std::scoped_lock lock(send_mtx); int sentBytes = ::send(getHandler().accessId(), message.buffer, static_cast(message.buffer_size), 0); - if (sentBytes == SCK_SOCKET_ERROR) { - sentBytes = 0; - throw SocketError{"send failed"}; + if (checkResult(sentBytes, SCK_SOCKET_ERROR, "send failed", !isBlocking())) { + return false; } - return (sentBytes == static_cast(message.buffer_size)); + return true; } bool Sender::send(const std::string &message) { @@ -62,12 +62,10 @@ bool SenderTo::sendTo(const BufferViewConst &message, sizeof(SocketAddressIpv6)); }); } - if (sentBytes == SCK_SOCKET_ERROR) { - sentBytes = 0; - auto err = SocketError{"sendto failed"}; - throw err; + if (checkResult(sentBytes, SCK_SOCKET_ERROR, "send failed", !isBlocking())) { + return false; } - return (sentBytes == static_cast(message.buffer_size)); + return true; } bool SenderTo::sendTo(const std::string &message, const Address &recipient) { diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 37155b74..50678045 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -8,6 +8,7 @@ #include #include +#include "../SocketFunctions.h" #include "../SocketHandler.h" #include "../Utils.h" @@ -38,6 +39,7 @@ int Socket::getSocketDescriptor() const { void Socket::steal(Socket &giver) { this->socket_id_wrapper = std::move(giver.socket_id_wrapper); giver.resetHandler(); + isBlocking_ = giver.isBlocking_; } const SocketHandler &Socket::getHandler() const { return *socket_id_wrapper; } @@ -47,19 +49,31 @@ void Socket::resetHandler() { socket_id_wrapper = std::make_unique(); } -bool Openable::open(const Timeout &timeout) { +void Socket::setUp() { + if (!isBlocking_) { + turnToNonBlocking(getHandler().accessId()); + } +} + +void OpenableBase::steal(OpenableBase &giver) { + std::scoped_lock lock(this->open_procedure_mtx, giver.open_procedure_mtx); + const bool o_value = giver.opened; + this->opened = o_value; + giver.opened = false; + this->Socket::steal(giver); +} + +namespace { +template +std::unique_ptr doOpen(std::mutex &mtx, std::atomic_bool &opened, + Pred pred) { if (opened) { throw Error{"Already opened"}; } - std::scoped_lock lock(open_procedure_mtx); + std::scoped_lock lock(mtx); std::unique_ptr exception; try { - if (NULL_TIMEOUT == timeout) { - this->open_(); - } else { - try_within_timeout([this]() { this->open_(); }, - [this]() { this->resetHandler(); }, timeout); - } + pred(); opened = true; } catch (const SocketError &e) { exception = std::make_unique(e); @@ -70,19 +84,33 @@ bool Openable::open(const Timeout &timeout) { } catch (...) { exception = std::make_unique("Not opened for an unknown reason"); } - if (nullptr != exception) { - this->resetHandler(); - throw *exception; - } + return exception; +} +} // namespace + +bool Openable::open() { + auto excpt = doOpen(open_procedure_mtx, opened, [this]() { this->open_(); }); + if (excpt != nullptr) { + resetHandler(); + throw *excpt; + }; return opened; } -void Openable::steal(Openable &giver) { - std::scoped_lock lock(this->open_procedure_mtx, giver.open_procedure_mtx); - const bool o_value = giver.opened; - this->opened = o_value; - giver.opened = false; - this->Socket::steal(giver); +bool OpenableWithTimeout::open(const Timeout &timeout) { + auto excpt = doOpen(open_procedure_mtx, opened, [&]() { + if (NULL_TIMEOUT == timeout) { + this->open_(); + } else { + try_within_timeout([this]() { this->open_(); }, + [this]() { this->resetHandler(); }, timeout); + } + }); + if (excpt != nullptr) { + resetHandler(); + throw *excpt; + }; + return opened; } } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/core/SocketContext.cpp b/src/src/core/SocketContext.cpp index 6f016a2a..4b84def0 100644 --- a/src/src/core/SocketContext.cpp +++ b/src/src/core/SocketContext.cpp @@ -8,6 +8,9 @@ #include #include +#include "../SocketFunctions.h" +#include "../SocketHandler.h" + namespace MinimalSocket { Address RemoteAddressAware::getRemoteAddress() const { std::scoped_lock lock(remote_address_mtx); @@ -15,10 +18,5 @@ Address RemoteAddressAware::getRemoteAddress() const { } RemoteAddressAware::RemoteAddressAware(const Address &address) - : remote_address(address) { - if (nullptr == getRemoteAddress()) { - throw Error{"Invalid address"}; - } -} - + : remote_address(address) {} } // namespace MinimalSocket diff --git a/src/src/tcp/TcpClient.cpp b/src/src/tcp/TcpClient.cpp index 740e17df..91472d0a 100644 --- a/src/src/tcp/TcpClient.cpp +++ b/src/src/tcp/TcpClient.cpp @@ -12,22 +12,27 @@ #include "../Utils.h" namespace MinimalSocket::tcp { -TcpClient::TcpClient(TcpClient &&o) : RemoteAddressAware(o) { this->steal(o); } -TcpClient &TcpClient::operator=(TcpClient &&o) { +TcpClientBase::TcpClientBase(TcpClientBase &&o) : RemoteAddressAware(o) { + this->steal(o); +} + +void TcpClientBase::stealBase(TcpClientBase &o) { this->steal(o); copy_as(*this, o); - return *this; } -TcpClient::TcpClient(const Address &server_address) - : RemoteAddressAware(server_address) {} +TcpClientBase::TcpClientBase(const Address &server_address, bool block_mode) + : RemoteAddressAware(server_address) { + if (!block_mode) { + setNonBlocking(); + } +} -void TcpClient::open_() { +void TcpClientBase::open_() { auto &socket = getHandler(); const auto remote_address = getRemoteAddress(); socket.reset(SocketType::TCP, remote_address.getFamily()); MinimalSocket::connect(socket.accessId(), remote_address); + this->Socket::setUp(); } - -TcpClient clone(const TcpClient &o) { return TcpClient{o.getRemoteAddress()}; } } // namespace MinimalSocket::tcp diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index cb22217e..2a8cee89 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -13,22 +13,66 @@ #include "../Utils.h" namespace MinimalSocket::tcp { -TcpServer::TcpServer(TcpServer &&o) +TcpConnectionBlocking::TcpConnectionBlocking(const Address &remote_address) + : RemoteAddressAware{remote_address} {} + +TcpConnectionBlocking::TcpConnectionBlocking(TcpConnectionBlocking &&o) + : RemoteAddressAware(o) { + this->steal(o); +} + +TcpConnectionBlocking & +TcpConnectionBlocking::operator=(TcpConnectionBlocking &&o) { + this->steal(o); + copy_as(*this, o); + return *this; +} + +TcpConnectionNonBlocking TcpConnectionBlocking::turnToNonBlocking() { + return TcpConnectionNonBlocking{std::move(*this)}; +} + +TcpConnectionNonBlocking::TcpConnectionNonBlocking(TcpConnectionNonBlocking &&o) + : RemoteAddressAware{o} { + this->steal(o); +} + +TcpConnectionNonBlocking & +TcpConnectionNonBlocking::operator=(TcpConnectionNonBlocking &&o) { + this->steal(o); + copy_as(*this, o); + return *this; +} + +TcpConnectionNonBlocking::TcpConnectionNonBlocking( + TcpConnectionBlocking &&connection) + : RemoteAddressAware{connection} { + this->steal(connection); + turnToNonBlocking(getHandler().accessId()); +} + +TcpServerBase::TcpServerBase(TcpServerBase &&o) : PortToBindAware(o), RemoteAddressFamilyAware(o) { this->steal(o); } -TcpServer &TcpServer::operator=(TcpServer &&o) { + +void TcpServerBase::stealBase(TcpServerBase &o) { this->steal(o); copy_as(*this, o); copy_as(*this, o); - return *this; } -TcpServer::TcpServer(Port port_to_bind, AddressFamily accepted_client_family) +TcpServerBase::TcpServerBase(Port port_to_bind, + AddressFamily accepted_client_family, + bool block_mode) : PortToBindAware(port_to_bind), - RemoteAddressFamilyAware(accepted_client_family) {} + RemoteAddressFamilyAware(accepted_client_family) { + if (!block_mode) { + setNonBlocking(); + } +} -void TcpServer::open_() { +void TcpServerBase::open_() { auto &socket = getHandler(); const auto port = getPortToBind(); const auto family = getRemoteAddressFamily(); @@ -36,79 +80,71 @@ void TcpServer::open_() { auto binded_port = MinimalSocket::bind(socket.accessId(), family, port, shallBeFreePort()); setPort(binded_port); + this->Socket::setUp(); MinimalSocket::listen(socket.accessId(), client_queue_size); } -void TcpServer::setClientQueueSize(const std::size_t queue_size) { +void TcpServerBase::setClientQueueSize(const std::size_t queue_size) { if (wasOpened()) { throw Error{"Can't set client queue size of an alrady opened tcp server"}; } client_queue_size = queue_size; } -TcpConnection TcpServer::acceptNewClient() { - auto temp = acceptNewClient(NULL_TIMEOUT); - return std::move(temp.value()); +struct TcpServerBase::AcceptedSocket { + SocketID fd = SCK_INVALID_SOCKET; + SocketAddressLength address_length = MAX_POSSIBLE_ADDRESS_SIZE; + char address[MAX_POSSIBLE_ADDRESS_SIZE]; +}; + +void TcpServerBase::acceptClient_(AcceptedSocket &recipient) { + auto &[accepted_client_socket_id, acceptedClientAddress_length, + acceptedClientAddress] = recipient; + + // accept: wait for a client to call connect and hit this server and get a + // pointer to this client. + accepted_client_socket_id = + ::accept(getHandler().accessId(), + reinterpret_cast(&acceptedClientAddress[0]), + &acceptedClientAddress_length); + checkResult(static_cast(accepted_client_socket_id), SCK_INVALID_SOCKET, + "accepting a new client", !isBlocking()); +} + +TcpConnectionBlocking +TcpServerBase::makeClient(const AcceptedSocket &acceptedSocket) { + auto accepted_client_parsed_address = toAddress( + reinterpret_cast(acceptedSocket.address)); + TcpConnectionBlocking result{accepted_client_parsed_address}; + result.getHandler().reset(acceptedSocket.fd); + return result; } -std::optional -TcpServer::acceptNewClient(const Timeout &timeout) { +TcpConnectionBlocking AcceptorBlocking::acceptNewClient() { std::scoped_lock lock(accept_mtx); if (!this->wasOpened()) { throw Error("Tcp server was not opened before starting to accept clients"); } - char acceptedClientAddress[MAX_POSSIBLE_ADDRESS_SIZE]; - SocketAddressLength acceptedClientAddress_length = MAX_POSSIBLE_ADDRESS_SIZE; - SocketID accepted_client_socket_id = SCK_INVALID_SOCKET; - - auto accept_client = [&]() { - // accept: wait for a client to call connect and hit this server and get a - // pointer to this client. - accepted_client_socket_id = - ::accept(getHandler().accessId(), - reinterpret_cast(&acceptedClientAddress[0]), - &acceptedClientAddress_length); - if (accepted_client_socket_id == SCK_INVALID_SOCKET) { - auto err = SocketError{"accepting a new client"}; - throw err; - } - }; - - try { - if (NULL_TIMEOUT == timeout) { - accept_client(); - } else { - try_within_timeout([&]() { accept_client(); }, - [this]() { this->resetHandler(); }, timeout); - } - } catch (const TimeOutError &) { - TcpServer reopened = TcpServer{getPortToBind(), getRemoteAddressFamily()}; - reopened.open(); - *this = std::move(reopened); - return std::nullopt; - } catch (...) { - std::rethrow_exception(std::current_exception()); - } + AcceptedSocket acceptedSocket; + acceptClient_(acceptedSocket); - auto accepted_client_parsed_address = - toAddress(reinterpret_cast(acceptedClientAddress)); - std::optional result; - auto &accepted = - result.emplace(TcpConnection{accepted_client_parsed_address}); - accepted.getHandler().reset(accepted_client_socket_id); - return result; + return makeClient(acceptedSocket); } -TcpConnection::TcpConnection(const Address &remote_address) - : RemoteAddressAware(remote_address) {} +std::optional AcceptorNonBlocking::acceptNewClient() { + std::scoped_lock lock(accept_mtx); + if (!this->wasOpened()) { + throw Error("Tcp server was not opened before starting to accept clients"); + } -TcpConnection::TcpConnection(TcpConnection &&o) : RemoteAddressAware(o) { - this->steal(o); -} -TcpConnection &TcpConnection::operator=(TcpConnection &&o) { - copy_as(*this, o); - this->steal(o); - return *this; + AcceptedSocket acceptedSocket; + acceptClient_(acceptedSocket); + + if (acceptedSocket.fd == SCK_INVALID_SOCKET) { + return std::nullopt; + } + return makeClient(acceptedSocket); } + } // namespace MinimalSocket::tcp diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index 91ddfe78..ee654a47 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -12,49 +12,54 @@ #include "../Utils.h" namespace MinimalSocket::udp { -UdpBinded::UdpBinded(Port port_to_bind, - AddressFamily accepted_connection_family) +UdpBase::UdpBase(Port port_to_bind, AddressFamily accepted_connection_family, + bool blockMode) : PortToBindAware(port_to_bind), - RemoteAddressFamilyAware(accepted_connection_family) {} + RemoteAddressFamilyAware(accepted_connection_family) { + if (!blockMode) { + setNonBlocking(); + } +} -UdpBinded::UdpBinded(UdpBinded &&o) +UdpBase::UdpBase(UdpBase &&o) : PortToBindAware(o), RemoteAddressFamilyAware(o) { this->steal(o); } -UdpBinded &UdpBinded::operator=(UdpBinded &&o) { + +void UdpBase::stealBase(UdpBase &o) { copy_as(*this, o); copy_as(*this, o); this->steal(o); - return *this; } -void UdpBinded::open_() { +void UdpBase::open_() { getHandler().reset(SocketType::UDP, getRemoteAddressFamily()); auto binded_port = MinimalSocket::bind(getHandler().accessId(), getRemoteAddressFamily(), getPortToBind(), shallBeFreePort()); setPort(binded_port); + this->Socket::setUp(); } -UdpConnected UdpBinded::connect(const Address &remote_address) { +UdpConnected UdpBlocking::connect(const Address &remote_address) { if (remote_address.getFamily() != getRemoteAddressFamily()) { throw Error{"Passed address has invalid family"}; } - UdpConnected result(remote_address, getPortToBind()); + UdpConnected result(remote_address, getPortToBind()); if (wasOpened()) { MinimalSocket::connect(getHandler().accessId(), remote_address); } this->transfer(result); - return std::move(result); + return result; } -UdpConnected UdpBinded::connect(std::string *initial_message) { +UdpConnected UdpBlocking::connect(std::string *initial_message) { auto result = this->connect(NULL_TIMEOUT, initial_message); return std::move(result.value()); } -std::optional UdpBinded::connect(const Timeout &timeout, - std::string *initial_message) { +std::optional> +UdpBlocking::connect(const Timeout &timeout, std::string *initial_message) { auto maybe_received = this->receive(MAX_UDP_RECV_MESSAGE, timeout); if (!maybe_received) { return std::nullopt; @@ -65,54 +70,69 @@ std::optional UdpBinded::connect(const Timeout &timeout, return connect(maybe_received->sender); } -UdpConnected::UdpConnected(const Address &remote_address, Port port) - : PortToBindAware(port), RemoteAddressAware(remote_address) {} +UdpConnected UdpNonBlocking::connect(const Address &remote_address) { + if (remote_address.getFamily() != getRemoteAddressFamily()) { + throw Error{"Passed address has invalid family"}; + } + UdpConnected result(remote_address, getPortToBind()); + if (wasOpened()) { + MinimalSocket::connect(getHandler().accessId(), remote_address); + } + this->transfer(result); + return result; +} -UdpConnected::UdpConnected(UdpConnected &&o) +std::optional> +UdpNonBlocking::connect(std::string *initial_message) { + auto maybe_received = this->receive(MAX_UDP_RECV_MESSAGE); + if (!maybe_received) { + return std::nullopt; + } + if (nullptr != initial_message) { + *initial_message = std::move(maybe_received->received_message); + } + return connect(maybe_received->sender); +} + +UdpConnectedBase::UdpConnectedBase(const Address &remote_address, Port port, + bool blockMode) + : PortToBindAware(port), RemoteAddressAware(remote_address) { + if (!blockMode) { + setNonBlocking(); + } +} + +UdpConnectedBase::UdpConnectedBase(UdpConnectedBase &&o) : PortToBindAware(o), RemoteAddressAware(o) { this->steal(o); } -UdpConnected &UdpConnected::operator=(UdpConnected &&o) { + +void UdpConnectedBase::stealBase(UdpConnectedBase &o) { copy_as(*this, o); copy_as(*this, o); this->steal(o); - return *this; } -void UdpConnected::open_() { +void UdpConnectedBase::open_() { const auto &remote_address = getRemoteAddress(); 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); + this->Socket::setUp(); MinimalSocket::connect(socket_id, remote_address); } -UdpBinded UdpConnected::disconnect() { - resetHandler(); - UdpBinded result(getPortToBind(), getRemoteAddress().getFamily()); - result.open(); - return std::move(result); -} - -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 +UdpConnected makeUdpConnectedToUnknown(Port port, AddressFamily accepted_connection_family, - const Timeout &timeout, std::string *initial_message) { - UdpBinded primal_socket(port, accepted_connection_family); + Udp primal_socket(port, accepted_connection_family); auto success = primal_socket.open(); if (!success) { - return std::nullopt; + throw Error{"Unable to open the primal upd socket"}; } - return primal_socket.connect(timeout, initial_message); + return primal_socket.connect(initial_message); } + } // namespace MinimalSocket::udp diff --git a/tests/ConnectionsUtils.cpp b/tests/ConnectionsUtils.cpp index e6dfdaf5..21ebd868 100644 --- a/tests/ConnectionsUtils.cpp +++ b/tests/ConnectionsUtils.cpp @@ -16,11 +16,11 @@ TcpPeers::TcpPeers(const Port &port, const AddressFamily &family) ParallelSection::biSection( [&](Barrier &br) { // server - tcp::TcpServer server(port, family); + tcp::TcpServer server(port, family); REQUIRE(server.open()); br.arrive_and_wait(); auto accepted = server.acceptNewClient(); - server_side = std::make_unique(std::move(accepted)); + server_side.emplace(std::move(accepted)); }, [&](Barrier &br) { // client @@ -29,10 +29,62 @@ TcpPeers::TcpPeers(const Port &port, const AddressFamily &family) }); } -UdpPeers::UdpPeers(const Port &port_a, const Port &port_b, - const AddressFamily &family) +template <> +UdpPeers>::UdpPeers(const Port &port_a, const Port &port_b, + const AddressFamily &family) : peer_a(port_a, family), peer_b(port_b, family) { REQUIRE(peer_a.open()); REQUIRE(peer_b.open()); } + +template <> +Address +UdpPeers>::extractRemoteAddress(const udp::Udp &subject) { + return Address{subject.getPortToBind(), subject.getRemoteAddressFamily()}; +} + +template <> +UdpPeers>::UdpPeers(const Port &port_a, const Port &port_b, + const AddressFamily &family) + : peer_a(port_a, family), peer_b(port_b, family) { + REQUIRE(peer_a.open()); + REQUIRE(peer_b.open()); +} + +template <> +Address UdpPeers>::extractRemoteAddress( + const udp::Udp &subject) { + return Address{subject.getPortToBind(), subject.getRemoteAddressFamily()}; +} + +template <> +UdpPeers>::UdpPeers(const Port &port_a, + const Port &port_b, + const AddressFamily &family) + : peer_a(Address{port_b, family}, port_a), + peer_b(Address{port_a, family}, port_b) { + REQUIRE(peer_a.open()); + REQUIRE(peer_b.open()); +} +template <> +Address UdpPeers>::extractRemoteAddress( + const udp::UdpConnected &subject) { + return subject.getRemoteAddress(); +} + +template <> +UdpPeers>::UdpPeers(const Port &port_a, + const Port &port_b, + const AddressFamily &family) + : peer_a(Address{port_b, family}, port_a), + peer_b(Address{port_a, family}, port_b) { + REQUIRE(peer_a.open()); + REQUIRE(peer_b.open()); +} +template <> +Address UdpPeers>::extractRemoteAddress( + const udp::UdpConnected &subject) { + return subject.getRemoteAddress(); +} + } // namespace MinimalSocket::test diff --git a/tests/ConnectionsUtils.h b/tests/ConnectionsUtils.h index 4204f3a5..361e0883 100644 --- a/tests/ConnectionsUtils.h +++ b/tests/ConnectionsUtils.h @@ -11,42 +11,45 @@ #include #include +#include +#include + namespace MinimalSocket::test { class TcpPeers { public: TcpPeers(const Port &port, const AddressFamily &family); - tcp::TcpConnection &getServerSide() { return *server_side; } - tcp::TcpClient &getClientSide() { return client_side; } + std::pair *> get() { + return std::make_pair(&server_side.value(), &client_side); + } private: - std::unique_ptr server_side; - tcp::TcpClient client_side; + std::optional server_side; + tcp::TcpClient client_side; }; -class UdpPeers { +template class UdpPeers { public: UdpPeers(const Port &port_a, const Port &port_b, const AddressFamily &family); - udp::UdpBinded &getPeerA() { return peer_a; } - udp::UdpBinded &getPeerB() { return peer_b; } - - Address addressPeerA() const { - return Address{peer_a.getPortToBind(), peer_a.getRemoteAddressFamily()}; - }; - Address addressPeerB() const { - return Address{peer_b.getPortToBind(), peer_b.getRemoteAddressFamily()}; - }; + std::tuple get() { + return std::make_tuple(&peer_a, extractRemoteAddress(peer_a), &peer_b, + extractRemoteAddress(peer_b)); + } private: - udp::UdpBinded peer_a; - udp::UdpBinded peer_b; + static Address extractRemoteAddress(const Udp_ &subject); + + Udp_ peer_a; + Udp_ peer_b; }; -#define UDP_PEERS(PORT_A, PORT_B, FAMILY) \ - UdpPeers peers(PORT_A, PORT_B, FAMILY); \ - auto &requester = peers.getPeerA(); \ - const auto requester_address = peers.addressPeerA(); \ - auto &responder = peers.getPeerB(); \ - const auto responder_address = peers.addressPeerB(); +#define UDP_PEERS(TYPE, FAMILY) \ + UdpPeers peers{PortFactory::get().makePort(), \ + PortFactory::get().makePort(), family}; \ + auto tmp = peers.get(); \ + auto *requester = std::get<0>(tmp); \ + auto &requester_address = std::get<1>(tmp); \ + auto *responder = std::get<2>(tmp); \ + auto &responder_address = std::get<3>(tmp); } // namespace MinimalSocket::test diff --git a/tests/ParallelSection.cpp b/tests/ParallelSection.cpp index 64002b86..04f40655 100644 --- a/tests/ParallelSection.cpp +++ b/tests/ParallelSection.cpp @@ -6,11 +6,13 @@ **/ #include "ParallelSection.h" + #include +#include namespace MinimalSocket::test { namespace { -std::function make_thread(Barrier &br, const Task &task) { +std::function make_task(Barrier &br, const Task &task) { return [&task = task, &br = br]() mutable { br.arrive_and_wait(); task(br); @@ -22,12 +24,12 @@ void ParallelSection::run() { if (tasks.size() < 2) { throw std::runtime_error{"invalid number of tasks for parallel region"}; } - barrier.emplace(tasks.size()); + auto &br = barrier.emplace(tasks.size()); std::vector spinners; - for (auto it = tasks.begin(); it != tasks.end() - 1; ++it) { - spinners.emplace_back(make_thread(barrier.value(), *it)); - } - spinners.emplace_back(make_thread(barrier.value(), tasks.back())); + std::for_each(tasks.begin() + 1, tasks.end(), [&](const Task &t) { + spinners.emplace_back(make_task(br, t)); + }); + make_task(br, tasks.front())(); for (auto &sp : spinners) { sp.join(); } diff --git a/tests/PortFactory.cpp b/tests/PortFactory.cpp index ad8eead1..9c9971ad 100644 --- a/tests/PortFactory.cpp +++ b/tests/PortFactory.cpp @@ -8,18 +8,22 @@ #include "PortFactory.h" namespace MinimalSocket::test { +PortFactory &PortFactory::get() { + static PortFactory res = PortFactory{}; + return res; +} + namespace { static constexpr std::uint16_t INITIAL_PORT = 9999; static constexpr std::uint16_t DELTA_PORT = 10; } // namespace -std::mutex PortFactory::port_mtx = std::mutex{}; -Port PortFactory::port = INITIAL_PORT; - Port PortFactory::makePort() { std::lock_guard lock(port_mtx); auto result = port; port += DELTA_PORT; return result; } + +PortFactory::PortFactory() : port{INITIAL_PORT} {} } // namespace MinimalSocket::test diff --git a/tests/PortFactory.h b/tests/PortFactory.h index 3d50d126..3fdec065 100644 --- a/tests/PortFactory.h +++ b/tests/PortFactory.h @@ -13,10 +13,14 @@ namespace MinimalSocket::test { class PortFactory { public: - static Port makePort(); + static PortFactory &get(); + + Port makePort(); private: - static std::mutex port_mtx; - static Port port; + PortFactory(); + + std::mutex port_mtx; + Port port; }; } // namespace MinimalSocket::test diff --git a/tests/RollingView.cpp b/tests/RollingView.cpp new file mode 100644 index 00000000..6a0900e4 --- /dev/null +++ b/tests/RollingView.cpp @@ -0,0 +1,56 @@ +#include "RollingView.h" + +namespace MinimalSocket::test { +RollingView::RollingView(const std::string &buff) : buffer{buff} {} + +RollingView::RollingView(std::size_t buff_size) { buffer.resize(buff_size); } + +void sliced_send(Sender &subject, const std::string &to_send, + std::size_t delta_send) { + RollingView buff{to_send}; + buff.forEachView(delta_send, [&](const std::string_view &view) { + bool ok = subject.send(BufferViewConst{view.data(), view.size()}); + if (!ok) { + throw std::runtime_error{"wasn't able to send all data"}; + } + return delta_send; + }); +} + +void sliced_send(SenderTo &subject, const std::string &to_send, + const Address &to_send_address, std::size_t delta_send) { + RollingView buff{to_send}; + buff.forEachView(delta_send, [&](const std::string_view &view) { + bool ok = subject.sendTo(BufferViewConst{view.data(), view.size()}, + to_send_address); + if (!ok) { + throw std::runtime_error{"wasn't able to send all data"}; + } + return delta_send; + }); +} + +std::string sliced_receive(Receiver &subject, std::size_t to_receive, + std::size_t delta_receive) { + RollingView buff{to_receive}; + buff.forEachView(delta_receive, [&](const std::string_view &view) { + BufferView buff{const_cast(view.data()), view.size()}; + return subject.receive(buff); + }); + return buff.getBuffer(); +} + +std::string sliced_receive(ReceiverUnkownSender &subject, + std::size_t to_receive, std::size_t delta_receive) { + RollingView buff{to_receive}; + buff.forEachView(delta_receive, [&](const std::string_view &view) { + BufferView buff{const_cast(view.data()), view.size()}; + auto maybe_bytes_received = subject.receive(buff); + if (!maybe_bytes_received.has_value()) { + throw std::runtime_error{"wasn'table tor receive the data"}; + } + return maybe_bytes_received->received_bytes; + }); + return buff.getBuffer(); +} +} // namespace MinimalSocket::test diff --git a/tests/RollingView.h b/tests/RollingView.h new file mode 100644 index 00000000..ae698b64 --- /dev/null +++ b/tests/RollingView.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include +#include + +namespace MinimalSocket::test { +class RollingView { +public: + RollingView(const std::string &buff); + RollingView(std::size_t buff_size); + + template + void forEachView(std::size_t offset, Pred pred) const { + std::size_t view_begin = 0; + std::size_t view_end = std::min(offset, buffer.size()); + while (true) { + std::size_t processed = pred( + std::string_view{buffer.data() + view_begin, view_end - view_begin}); + if (processed != offset) { + throw std::runtime_error{"Wrong number of bytes were processed"}; + } + if (view_end == buffer.size()) { + break; + } + view_begin = view_end; + view_end += offset; + view_end = std::min(view_end, buffer.size()); + } + } + + const auto &getBuffer() const { return buffer; } + +private: + std::string buffer; +}; + +void sliced_send(Sender &subject, const std::string &to_send, + std::size_t delta_send); + +void sliced_send(SenderTo &subject, const std::string &to_send, + const Address &to_send_address, std::size_t delta_send); + +std::string sliced_receive(Receiver &subject, std::size_t to_receive, + std::size_t delta_receive); + +std::string sliced_receive(ReceiverUnkownSender &subject, + std::size_t to_receive, std::size_t delta_receive); +} // namespace MinimalSocket::test diff --git a/tests/SlicedOps.cpp b/tests/SlicedOps.cpp deleted file mode 100644 index 2ae73b1a..00000000 --- a/tests/SlicedOps.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "SlicedOps.h" - -namespace MinimalSocket::test { -MovingPointerBuffer::MovingPointerBuffer(const std::string &buff) - : buffer(buff) { - init(); -} - -MovingPointerBuffer::MovingPointerBuffer(const std::size_t buff_size) { - buffer.resize(buff_size); - init(); -} - -std::size_t MovingPointerBuffer::remainingBytes() const { - return buffer.size() - buffer_cursor; -} - -void MovingPointerBuffer::shift(const std::size_t stride) { - buffer_cursor += stride; - buffer_pointer += stride; -} - -void MovingPointerBuffer::init() { - buffer_cursor = 0; - buffer_pointer = buffer.data(); -} - -void sliced_send(Sender &subject, const std::string &to_send, - const std::size_t delta_send) { - MovingPointerBuffer buffer(to_send); - while (buffer.remainingBytes() != 0) { - std::size_t bytes_to_send = - std::min(delta_send, buffer.remainingBytes()); - subject.send(BufferViewConst{buffer.data(), bytes_to_send}); - buffer.shift(bytes_to_send); - } -} - -void sliced_send(SenderTo &subject, const std::string &to_send, - const Address &to_send_address, const std::size_t delta_send) { - MovingPointerBuffer buffer(to_send); - while (buffer.remainingBytes() != 0) { - std::size_t bytes_to_send = - std::min(delta_send, buffer.remainingBytes()); - subject.sendTo(BufferViewConst{buffer.data(), bytes_to_send}, - to_send_address); - buffer.shift(bytes_to_send); - } -} - -std::string sliced_receive(Receiver &subject, const std::size_t to_receive, - const std::size_t delta_receive) { - MovingPointerBuffer buffer(to_receive); - while (buffer.remainingBytes() != 0) { - std::size_t bytes_to_receive = - std::min(delta_receive, buffer.remainingBytes()); - auto bytes_received = - subject.receive(BufferView{buffer.data(), bytes_to_receive}); - buffer.shift(bytes_received); - } - return buffer.asString(); -} - -std::string sliced_receive(ReceiverUnkownSender &subject, - const std::size_t to_receive, - const std::size_t delta_receive) { - MovingPointerBuffer buffer(to_receive); - while (buffer.remainingBytes() != 0) { - std::size_t bytes_to_receive = - std::min(delta_receive, buffer.remainingBytes()); - auto maybe_bytes_received = - subject.receive(BufferView{buffer.data(), bytes_to_receive}); - if (maybe_bytes_received) { - buffer.shift(maybe_bytes_received->received_bytes); - } - } - return buffer.asString(); -} -} // namespace MinimalSocket::test diff --git a/tests/SlicedOps.h b/tests/SlicedOps.h deleted file mode 100644 index 4b15de0f..00000000 --- a/tests/SlicedOps.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include - -namespace MinimalSocket::test { -class MovingPointerBuffer { -public: - MovingPointerBuffer(const std::string &buff); - MovingPointerBuffer(const std::size_t buff_size); - - const std::string &asString() const { return buffer; } - char *data() { return buffer_pointer; } - const char *data() const { return buffer_pointer; } - - std::size_t remainingBytes() const; - void shift(const std::size_t stride); - -private: - void init(); - - std::string buffer; - std::size_t buffer_cursor; - char *buffer_pointer; -}; - -void sliced_send(Sender &subject, const std::string &to_send, - const std::size_t delta_send); - -void sliced_send(SenderTo &subject, const std::string &to_send, - const Address &to_send_address, const std::size_t delta_send); - -std::string sliced_receive(Receiver &subject, const std::size_t to_receive, - const std::size_t delta_receive); - -std::string sliced_receive(ReceiverUnkownSender &subject, - const std::size_t to_receive, - const std::size_t delta_receive); -} // namespace MinimalSocket::test diff --git a/tests/TestAddress.cpp b/tests/TestAddress.cpp index 503960d0..708ed90d 100644 --- a/tests/TestAddress.cpp +++ b/tests/TestAddress.cpp @@ -2,29 +2,27 @@ #include #include -#include #include +#include using namespace MinimalSocket; namespace { - static constexpr Port TEST_PORT = 100; +static constexpr Port TEST_PORT = 100; } #ifdef _WIN32 TEST_CASE("Invalid WSA version", "[address]") { - WSAManager::setWsaVersion({ 0,0 }); - CHECK_THROWS_AS(Address("127.0.0.1", TEST_PORT), Error); - WSAManager::setWsaVersion({ 2,2 }); - CHECK_NOTHROW(Address("127.0.0.1", TEST_PORT)); + WSAManager::setWsaVersion({0, 0}); + CHECK_THROWS_AS(Address("127.0.0.1", TEST_PORT), Error); + WSAManager::setWsaVersion({2, 2}); + CHECK_NOTHROW(Address("127.0.0.1", TEST_PORT)); } #endif - TEST_CASE("parse valid ipv4 hosts", "[address]") { auto host = GENERATE("192.168.125.34", "127.0.0.1", "0.0.0.0"); Address converted(host, TEST_PORT); - CHECK_FALSE(nullptr == converted); CHECK(converted.getFamily() == AddressFamily::IP_V4); CHECK(converted.getHost() == host); CHECK(converted.getPort() == TEST_PORT); @@ -35,7 +33,6 @@ TEST_CASE("parse valid ipv6 hosts", "[address]") { GENERATE("2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8::1:0", "0000:0000:0000:0000:0000:0000:0000:0001", "::1"); Address converted(host, TEST_PORT); - CHECK_FALSE(nullptr == converted); CHECK(converted.getFamily() == AddressFamily::IP_V6); CHECK(converted.getHost() == host); CHECK(converted.getPort() == TEST_PORT); @@ -43,6 +40,5 @@ TEST_CASE("parse valid ipv6 hosts", "[address]") { TEST_CASE("parse invalid hosts", "[address]") { auto host = GENERATE("192.125.34.34.34", "10000.0.0.1", "192.125.db8::1:0"); - Address converted(host, TEST_PORT); - CHECK(nullptr == converted); + CHECK_THROWS_AS(Address(host, TEST_PORT), Error); } diff --git a/tests/TestOpenTimeout.cpp b/tests/TestOpenTimeout.cpp index 01227dad..5cb36b0f 100644 --- a/tests/TestOpenTimeout.cpp +++ b/tests/TestOpenTimeout.cpp @@ -8,7 +8,7 @@ using namespace MinimalSocket; namespace { -class OpenableTest : public Openable { +class OpenableTest : public OpenableWithTimeout { public: OpenableTest(const Timeout &duration) : open_duration(duration) {} diff --git a/tests/TestRobustness.cpp b/tests/TestRobustness.cpp index ab0b1713..5732e358 100644 --- a/tests/TestRobustness.cpp +++ b/tests/TestRobustness.cpp @@ -29,40 +29,59 @@ static const std::string MESSAGE = "A simple message"; template void close(SocketT &subject) { SocketT{std::move(subject)}; } + +struct ThrownOrReceivedNothing { + template ThrownOrReceivedNothing(Pred pred) { + try { + received_nothing = pred(); + } catch (const SocketError &) { + throwned = true; + } + } + + operator bool() const { return throwned || received_nothing; } + +private: + bool throwned = false; + bool received_nothing = false; +}; } // namespace TEST_CASE("Thread safe d'tor tcp case", "[robustness]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); SECTION("on connected sockets") { test::TcpPeers peers(port, family); + auto [server_side, client_side] = peers.get(); SECTION("close client while receiving") { ParallelSection::biSection( - [&peers](auto &) { - CHECK(peers.getClientSide().receive(500).empty()); + [&client_side = client_side](auto &) { + CHECK(ThrownOrReceivedNothing{ + [&]() { return client_side->receive(500).empty(); }}); }, - [&peers](auto &) { - std::this_thread::sleep_for(std::chrono::milliseconds{50}); - close(peers.getClientSide()); + [&client_side = client_side](auto &) { + std::this_thread::sleep_for(std::chrono::milliseconds{200}); + close(*client_side); }); } SECTION("close server side while receiving") { ParallelSection::biSection( - [&peers](auto &) { - CHECK(peers.getClientSide().receive(500).empty()); + [&server_side = server_side](auto &) { + CHECK(ThrownOrReceivedNothing{ + [&]() { return server_side->receive(500).empty(); }}); }, - [&peers](auto &) { - std::this_thread::sleep_for(std::chrono::milliseconds{50}); - close(peers.getServerSide()); + [&server_side = server_side](auto &) { + std::this_thread::sleep_for(std::chrono::milliseconds{200}); + close(*server_side); }); } } SECTION("close while accepting client") { - tcp::TcpServer server(port, family); + tcp::TcpServer server(port, family); REQUIRE(server.open()); ParallelSection::biSection( [&server](auto &) { @@ -76,21 +95,20 @@ TEST_CASE("Thread safe d'tor tcp case", "[robustness]") { } TEST_CASE("Receive from multiple threads tcp case", "[robustness]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); test::TcpPeers peers(port, family); - auto &server_side = peers.getServerSide(); - auto &client_side = peers.getClientSide(); + auto [server_side, client_side] = peers.get(); const std::size_t threads = 3; ParallelSection sections; - sections.add([&](auto &) { - client_side.send(make_repeated_message(MESSAGE, threads)); + sections.add([&client_side = client_side](auto &) { + client_side->send(make_repeated_message(MESSAGE, threads)); }); for (std::size_t t = 0; t < threads; ++t) { - sections.add([&](auto &) { - const auto received_request = server_side.receive(MESSAGE.size()); + sections.add([&server_side = server_side](auto &) { + const auto received_request = server_side->receive(MESSAGE.size()); CHECK(received_request == MESSAGE); }); } @@ -98,21 +116,21 @@ TEST_CASE("Receive from multiple threads tcp case", "[robustness]") { } TEST_CASE("Send from multiple threads tcp case", "[robustness]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); test::TcpPeers peers(port, family); - auto &server_side = peers.getServerSide(); - auto &client_side = peers.getClientSide(); + auto [server_side, client_side] = peers.get(); const std::size_t threads = 3; ParallelSection sections; for (std::size_t t = 0; t < threads; ++t) { - sections.add([&](auto &) { client_side.send(MESSAGE); }); + sections.add( + [&client_side = client_side](auto &) { client_side->send(MESSAGE); }); } - sections.add([&](auto &) { + sections.add([&server_side = server_side](auto &) { for (std::size_t t = 0; t < threads; ++t) { - const auto received_request = server_side.receive(MESSAGE.size()); + const auto received_request = server_side->receive(MESSAGE.size()); CHECK(received_request == MESSAGE); } }); @@ -122,35 +140,41 @@ TEST_CASE("Send from multiple threads tcp case", "[robustness]") { TEST_CASE("Thread safe d'tor udp case", "[robustness]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - udp::UdpBinded connection(PortFactory::makePort()); + udp::Udp connection(PortFactory::get().makePort()); ParallelSection::biSection( - [&](auto &) { CHECK_THROWS_AS(connection.receive(500), Error); }, [&](auto &) { - std::this_thread::sleep_for(std::chrono::milliseconds{50}); + CHECK(ThrownOrReceivedNothing{[&]() { + auto res = connection.receive(500); + // if here it did not thrown, but 0 bytes are expected to have been + // actually received + REQUIRE(res.has_value()); + return res->received_message.empty(); + }}); + }, + [&](auto &) { + std::this_thread::sleep_for(std::chrono::milliseconds{200}); close(connection); }); } -/* - TEST_CASE("Receive from multiple threads udp case", "[robustness]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family) + UDP_PEERS(udp::Udp, family); const std::size_t threads = 3; ParallelSection sections; sections.add([&](Barrier &br) { for (std::size_t t = 0; t < threads; ++t) { - requester.sendTo(MESSAGE, responder_address); + requester->sendTo(MESSAGE, responder_address); } br.arrive_and_wait(); }); for (std::size_t t = 0; t < threads; ++t) { sections.add([&](Barrier &br) { br.arrive_and_wait(); - const auto received_request = responder.receive(MESSAGE.size()); + const auto received_request = responder->receive(MESSAGE.size()); CHECK(received_request); CHECK(received_request->received_message == MESSAGE); }); @@ -161,39 +185,38 @@ TEST_CASE("Receive from multiple threads udp case", "[robustness]") { TEST_CASE("Send from multiple threads udp case", "[robustness]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family) + UDP_PEERS(udp::Udp, family); const std::size_t threads = 3; ParallelSection sections; for (std::size_t t = 0; t < threads; ++t) { sections.add([&](Barrier &br) { - requester.sendTo(MESSAGE, responder_address); + requester->sendTo(MESSAGE, responder_address); br.arrive_and_wait(); }); } sections.add([&](Barrier &br) { br.arrive_and_wait(); for (std::size_t t = 0; t < threads; ++t) { - const auto received_request = responder.receive(MESSAGE.size()); + const auto received_request = responder->receive(MESSAGE.size()); CHECK(received_request); CHECK(received_request->received_message == MESSAGE); } }); sections.run(); } -*/ TEST_CASE("Use tcp socket before opening it", "[robustness]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); SECTION("server") { - tcp::TcpServer socket(port, family); + tcp::TcpServer socket(port, family); CHECK_THROWS_AS(socket.acceptNewClient(), Error); } SECTION("client") { - tcp::TcpClient socket(Address{port, family}); + tcp::TcpClient socket(Address{port, family}); CHECK_THROWS_AS(socket.receive(500), SocketError); CHECK_THROWS_AS(socket.send("dummy"), SocketError); } @@ -202,10 +225,10 @@ TEST_CASE("Use tcp socket before opening it", "[robustness]") { TEST_CASE("Use udp socket before opening it", "[robustness]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - udp::UdpBinded socket(PortFactory::makePort(), family); + udp::Udp socket(PortFactory::get().makePort(), family); CHECK_THROWS_AS(socket.receive(500), SocketError); CHECK_THROWS_AS( - socket.sendTo("dummy", Address{PortFactory::makePort(), family}), + socket.sendTo("dummy", Address{PortFactory::get().makePort(), family}), SocketError); CHECK_THROWS_AS(socket.connect(), SocketError); } diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index f09f7c0a..aed9058b 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -9,7 +9,7 @@ #include "ConnectionsUtils.h" #include "ParallelSection.h" #include "PortFactory.h" -#include "SlicedOps.h" +#include "RollingView.h" using namespace MinimalSocket; using namespace MinimalSocket::tcp; @@ -21,11 +21,11 @@ static const std::string response = "Welcome"; struct SenderReceiver { Sender &sender; - Receiver &receiver; + Receiver &receiver; }; template SenderReceiver makeSenderReceiver(T &subject) { Sender &as_sender = subject; - Receiver &as_receiver = subject; + Receiver &as_receiver = subject; return SenderReceiver{as_sender, as_receiver}; } @@ -52,12 +52,12 @@ void send_response(const SenderReceiver &requester, } // namespace TEST_CASE("Establish tcp connection", "[tcp]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); #if !defined(_WIN32) SECTION("expected failure") { - TcpClient client(Address(port, family)); + TcpClient client(Address(port, family)); CHECK_THROWS_AS(client.open(), Error); CHECK_FALSE(client.wasOpened()); } @@ -65,43 +65,40 @@ TEST_CASE("Establish tcp connection", "[tcp]") { SECTION("expected success") { test::TcpPeers peers(port, family); - auto &server_side = peers.getServerSide(); - auto &client_side = peers.getClientSide(); + auto [server_side, client_side] = peers.get(); - REQUIRE(client_side.wasOpened()); + REQUIRE(client_side->wasOpened()); const std::size_t cycles = 5; - const std::string request = "Hello"; - const std::string response = "Welcome"; SECTION("client send, server respond") { - send_response(makeSenderReceiver(client_side), - makeSenderReceiver(server_side)); + send_response(makeSenderReceiver(*client_side), + makeSenderReceiver(*server_side)); } SECTION("server send, client respond") { - send_response(makeSenderReceiver(server_side), - makeSenderReceiver(client_side)); + send_response(makeSenderReceiver(*server_side), + makeSenderReceiver(*client_side)); } SECTION("receive with timeout") { const auto timeout = Timeout{500}; SECTION("expect fail within timeout") { - auto received_request = server_side.receive(request.size(), timeout); + auto received_request = server_side->receive(request.size(), timeout); CHECK(received_request.empty()); } SECTION("expect success within timeout") { const auto wait = Timeout{250}; ParallelSection::biSection( - [&](auto &) { + [&, client_side = client_side](auto &) { std::this_thread::sleep_for(wait); - client_side.send(request); + client_side->send(request); }, - [&](auto &) { + [&, server_side = server_side](auto &) { auto received_request = - server_side.receive(request.size(), timeout); + server_side->receive(request.size(), timeout); CHECK(received_request == request); }); } @@ -110,17 +107,17 @@ TEST_CASE("Establish tcp connection", "[tcp]") { } TEST_CASE("Establish many tcp connections to same server", "[tcp]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - TcpServer server(port, family); + TcpServer server(port, family); server.open(); const std::size_t clients_numb = 5; SECTION("sequencial connnections") { - std::list accepted_clients; - std::list clients; + std::list accepted_clients; + std::list> clients; ParallelSection::biSection( [&](auto &) { for (std::size_t c = 0; c < clients_numb; ++c) { @@ -143,7 +140,7 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { } }); Task ask_connection = [&](auto &) { - TcpClient client(Address(port, family)); + TcpClient client(Address(port, family)); CHECK(client.open()); }; for (std::size_t c = 0; c < clients_numb; ++c) { @@ -154,15 +151,15 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { } TEST_CASE("Open multiple times tcp clients", "[tcp]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - TcpServer server(port, family); + TcpServer server(port, family); server.open(); std::size_t cycles = 5; - TcpClient client(Address(port, family)); + TcpClient client(Address(port, family)); for (std::size_t c = 0; c < cycles; ++c) { ParallelSection::biSection([&](auto &) { server.acceptNewClient(); }, @@ -175,33 +172,32 @@ TEST_CASE("Open multiple times tcp clients", "[tcp]") { } TEST_CASE("Open tcp client with timeout", "[tcp]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const auto timeout = Timeout{500}; - TcpClient client(Address(port, family)); + TcpClient client(Address(port, family)); SECTION("expect fail within timeout") { #ifdef _WIN32 CHECK_FALSE(client.open(timeout)); #else - CHECK_THROWS_AS( - client.open(timeout), - Error); // linux throw if no server tcp were previously created, while - // windows seems to does not have this check + // linux throw if no server tcp were previously created, while windows seems + // to does not have this check + CHECK_THROWS_AS(client.open(timeout), Error); #endif CHECK_FALSE(client.wasOpened()); } SECTION("expect success within timeout") { const auto wait = Timeout{250}; - TcpServer server(port, family); + TcpServer server(port, family); REQUIRE(server.open()); ParallelSection::biSection( [&](auto &) { std::this_thread::sleep_for(wait); - TcpConnection conn = server.acceptNewClient(); + auto conn = server.acceptNewClient(); auto received_request = conn.receive(request.size()); CHECK(received_request == request); }, @@ -215,7 +211,7 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { TEST_CASE("Reserve random port for tcp server", "[tcp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - TcpServer server(ANY_PORT, family); + TcpServer server(ANY_PORT, family); REQUIRE(server.open()); const auto port = server.getPortToBind(); REQUIRE(port != 0); @@ -229,7 +225,7 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { }, [&](Barrier &br) { // client - TcpClient client(Address(port, family)); + TcpClient client(Address(port, family)); br.arrive_and_wait(); REQUIRE(client.open()); REQUIRE(client.wasOpened()); @@ -237,109 +233,105 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { }); } -TEST_CASE("Accept client with timeout", "[tcp]") { +#if !defined(__APPLE__) +TEST_CASE("Send Receive messages split into multiple pieces (tcp)", "[tcp]") { + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - const auto port = PortFactory::makePort(); - TcpServer server(port, family); - REQUIRE(server.open()); - const auto server_address = Address(port, family); - - const auto timeout = Timeout{500}; - - SECTION("expect fail within timeout") { - // connect first client - TcpClient client_first = TcpClient{server_address}; - std::unique_ptr server_side_first; - ParallelSection::biSection([&](auto &) { CHECK(client_first.open()); }, - [&](auto &) { - auto accepted = server.acceptNewClient(); - server_side_first = - std::make_unique( - std::move(accepted)); - }); + TcpPeers peers(port, family); + auto [server_side, client_side] = peers.get(); - // expect second accept to fail - CHECK_FALSE(server.acceptNewClient(timeout)); - CHECK(server.wasOpened()); + const std::string request = "This is a simulated long message"; - // check first accepted connection is still valid - ParallelSection::biSection( - [&](auto &) { - auto received_request = server_side_first->receive(request.size()); - CHECK(received_request == request); - }, - [&](auto &) { - // client - client_first.send(request); - }); + const std::size_t delta = 4; - // connect second client after accept unsuccess and check they can exchange - // messages + SECTION("split receive") { ParallelSection::biSection( - [&](Barrier &br) { - TcpClient client_second = TcpClient{server_address}; - br.arrive_and_wait(); - CHECK(client_second.open()); - client_second.send(request); - }, - [&](Barrier &br) { - br.arrive_and_wait(); - auto server_side_second = server.acceptNewClient(); - auto received_request = server_side_second.receive(request.size()); + [&, client_side = client_side](auto &) { client_side->send(request); }, + [&, server_side = server_side](auto &) { + auto received_request = + sliced_receive(*server_side, request.size(), 4); CHECK(received_request == request); }); } - SECTION("expect success within timeout") { - const auto wait = Timeout{250}; + SECTION("split send") { ParallelSection::biSection( - [&](Barrier &br) { - TcpClient client = TcpClient{server_address}; + [&, client_side = client_side](Barrier &br) { + sliced_send(*client_side, request, 4); br.arrive_and_wait(); - std::this_thread::sleep_for(wait); - CHECK(client.open()); }, - [&](Barrier &br) { + [&, server_side = server_side](Barrier &br) { br.arrive_and_wait(); - CHECK(server.acceptNewClient(timeout)); + auto received_request = server_side->receive(request.size()); + CHECK(received_request == request); }); } } +#endif -#if !defined(__APPLE__) -TEST_CASE("Send Receive messages split into multiple pieces (tcp)", "[tcp]") { - const auto port = PortFactory::makePort(); +TEST_CASE("Establish tcp connection non blocking", "[tcp]") { + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - TcpPeers peers(port, family); - auto &server_side = peers.getServerSide(); - auto &client_side = peers.getClientSide(); + tcp::TcpServer server{port, family}; + REQUIRE(server.open()); - const std::string request = "This is a simulated long message"; + ParallelSection::biSection( + [&](Barrier &br) { + CHECK_FALSE(server.acceptNewClient().has_value()); + br.arrive_and_wait(); + std::this_thread::sleep_for(std::chrono::milliseconds{500}); + auto accepted = server.acceptNewClient(); + REQUIRE(accepted.has_value()); + auto received_request = accepted->receive(request.size()); + CHECK(received_request == request); + }, + [&](Barrier &br) { + br.arrive_and_wait(); + TcpClient client{Address{port, family}}; + client.open(); + REQUIRE(client.wasOpened()); + client.send(request); + }); +} - const std::size_t delta = 4; +TEST_CASE("Receive non blocking (tcp)", "[tcp]") { + const auto port = PortFactory::get().makePort(); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - SECTION("split receive") { - ParallelSection::biSection([&](auto &) { client_side.send(request); }, - [&](auto &) { - auto received_request = sliced_receive( - server_side, request.size(), 4); - CHECK(received_request == request); - }); + std::optional server_side; + tcp::TcpClient client_side{Address{port, family}}; + ParallelSection::biSection( + [&](Barrier &br) { + tcp::TcpServer server{port, family}; + REQUIRE(server.open()); + br.arrive_and_wait(); + auto accepted = server.acceptNewClient(); + server_side.emplace(accepted.turnToNonBlocking()); + }, + [&](Barrier &br) { + br.arrive_and_wait(); + REQUIRE(client_side.open()); + }); + + SECTION("client side non blocking receive") { + CHECK(client_side.receive(request.size()).empty()); + server_side->send(request); +#if defined(__APPLE__) + std::this_thread::sleep_for(std::chrono::seconds{3}); +#endif + auto received_request = client_side.receive(request.size()); + CHECK(received_request == request); } - SECTION("split send") { - ParallelSection::biSection( - [&](Barrier &br) { - sliced_send(client_side, request, 4); - br.arrive_and_wait(); - }, - [&](Barrier &br) { - br.arrive_and_wait(); - auto received_request = server_side.receive(request.size()); - CHECK(received_request == request); - }); + SECTION("server side non blocking receive") { + CHECK(server_side->receive(request.size()).empty()); + client_side.send(request); +#if defined(__APPLE__) + std::this_thread::sleep_for(std::chrono::seconds{3}); +#endif + auto received_request = server_side->receive(request.size()); + CHECK(received_request == request); } } -#endif diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index c393ab43..96db2ad8 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -6,7 +6,7 @@ #include "ConnectionsUtils.h" #include "ParallelSection.h" #include "PortFactory.h" -#include "SlicedOps.h" +#include "RollingView.h" using namespace MinimalSocket; using namespace MinimalSocket::udp; @@ -20,21 +20,22 @@ bool are_same(const Address &a, const Address &b, const AddressFamily &family) { return (family == AddressFamily::IP_V4) ? (a == b) : (a.getPort() == b.getPort()); } -} // namespace + +}; // namespace TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const std::size_t cycles = 5; - UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family); + UDP_PEERS(udp::Udp, family); ParallelSection::biSection( [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { - CHECK(requester.sendTo(request, responder_address)); + CHECK(requester->sendTo(request, responder_address)); br.arrive_and_wait(); br.arrive_and_wait(); - auto received_response = requester.receive(response.size()); + auto received_response = requester->receive(response.size()); REQUIRE(received_response); CHECK(received_response->received_message == response); CHECK(are_same(received_response->sender, responder_address, family)); @@ -43,11 +44,11 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); + auto received_request = responder->receive(request.size()); REQUIRE(received_request); CHECK(received_request->received_message == request); CHECK(are_same(received_request->sender, requester_address, family)); - responder.sendTo(response, requester_address); + responder->sendTo(response, requester_address); br.arrive_and_wait(); } }); @@ -56,7 +57,7 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { const auto timeout = Timeout{500}; SECTION("expect fail within timeout") { - auto received_request = responder.receive(request.size(), timeout); + auto received_request = responder->receive(request.size(), timeout); CHECK_FALSE(received_request); } @@ -65,10 +66,10 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { ParallelSection::biSection( [&](auto &) { std::this_thread::sleep_for(wait); - requester.sendTo(request, responder_address); + requester->sendTo(request, responder_address); }, [&](auto &) { - auto received_request = responder.receive(request.size(), timeout); + auto received_request = responder->receive(request.size(), timeout); REQUIRE(received_request); CHECK(received_request->received_message == request); CHECK( @@ -82,33 +83,24 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const std::size_t cycles = 5; - const auto requester_port = PortFactory::makePort(); - const Address requester_address = Address(requester_port, family); - - const auto responder_port = PortFactory::makePort(); - const Address responder_address = Address(responder_port, family); - - UdpConnected requester(responder_address, requester_port); - REQUIRE(requester.open()); - UdpConnected responder(requester_address, responder_port); - REQUIRE(responder.open()); + UDP_PEERS(udp::UdpConnected, family); ParallelSection::biSection( [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { - CHECK(requester.send(request)); + CHECK(requester->send(request)); br.arrive_and_wait(); br.arrive_and_wait(); - auto received_response = requester.receive(response.size()); + auto received_response = requester->receive(response.size()); CHECK(received_response == response); } }, [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); + auto received_request = responder->receive(request.size()); CHECK(received_request == request); - responder.send(response); + responder->send(response); br.arrive_and_wait(); } }); @@ -117,7 +109,7 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { const auto timeout = Timeout{500}; SECTION("expect fail within timeout") { - auto received_request = responder.receive(request.size(), timeout); + auto received_request = responder->receive(request.size(), timeout); CHECK(received_request.empty()); } @@ -126,10 +118,10 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { ParallelSection::biSection( [&](auto &) { std::this_thread::sleep_for(wait); - requester.send(request); + requester->send(request); }, [&](auto &) { - auto received_request = responder.receive(request.size(), timeout); + auto received_request = responder->receive(request.size(), timeout); CHECK(received_request == request); }); } @@ -141,47 +133,38 @@ TEST_CASE( "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - const auto requester_port = PortFactory::makePort(); - const Address requester_address = Address(requester_port, family); - - const auto responder_port = PortFactory::makePort(); - const Address responder_address = Address(responder_port, family); - - UdpConnected requester(responder_address, requester_port); - REQUIRE(requester.open()); - UdpConnected responder(requester_address, responder_port); - REQUIRE(responder.open()); + UDP_PEERS(udp::UdpConnected, family); auto exchange_messages_before = GENERATE(true, false); if (exchange_messages_before) { ParallelSection::biSection( [&](Barrier &br) { - CHECK(requester.send(request)); + CHECK(requester->send(request)); br.arrive_and_wait(); br.arrive_and_wait(); - auto received_response = requester.receive(response.size()); + auto received_response = requester->receive(response.size()); CHECK(received_response == response); }, [&](Barrier &br) { br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); + auto received_request = responder->receive(request.size()); CHECK(received_request == request); - responder.send(response); + responder->send(response); br.arrive_and_wait(); }); } - UdpBinded second_requester(PortFactory::makePort(), family); + udp::Udp second_requester(PortFactory::get().makePort(), family); REQUIRE(second_requester.open()); const auto timeout = Timeout{500}; const auto wait = Timeout{250}; ParallelSection::biSection( [&](auto &) { std::this_thread::sleep_for(wait); - second_requester.sendTo(request, Address(responder_port, family)); + second_requester.sendTo(request, responder_address); }, [&](auto &) { - auto received_request = responder.receive(request.size(), timeout); + auto received_request = responder->receive(request.size(), timeout); CHECK(received_request.empty()); }); } @@ -190,38 +173,26 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const std::size_t cycles = 5; - const auto requester_port = PortFactory::makePort(); - const Address requester_address = Address(requester_port, family); - const auto responder_port = PortFactory::makePort(); - const Address responder_address = Address(responder_port, family); - - UdpBinded responder(responder_port, family); - REQUIRE(responder.open()); - - std::unique_ptr requester_only_bind = - std::make_unique(requester_port, family); - REQUIRE(requester_only_bind->open()); + UDP_PEERS(udp::Udp, family); // connect requester to responder auto deduce_sender = GENERATE(true, false); - std::unique_ptr requester_connected; + std::optional> requester_connected; if (deduce_sender) { ParallelSection::biSection( [&](Barrier &br) { - responder.sendTo("1", requester_address); + responder->sendTo("1", requester_address); br.arrive_and_wait(); }, [&](Barrier &br) { br.arrive_and_wait(); - auto socket_connected = requester_only_bind->connect(); + auto socket_connected = requester->connect(); CHECK(are_same(socket_connected.getRemoteAddress(), responder_address, family)); - requester_connected = - std::make_unique(std::move(socket_connected)); + requester_connected.emplace(std::move(socket_connected)); }); } else { - requester_connected = std::make_unique( - requester_only_bind->connect(responder_address)); + requester_connected.emplace(requester->connect(responder_address)); } REQUIRE(requester_connected->wasOpened()); @@ -240,39 +211,10 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); - REQUIRE(received_request); - CHECK(received_request->received_message == request); - responder.sendTo(response, requester_address); - br.arrive_and_wait(); - } - }); - - // try to disconnect requester - requester_only_bind = - std::make_unique(requester_connected->disconnect()); - REQUIRE(requester_only_bind->wasOpened()); - - // try message exchange - ParallelSection::biSection( - [&](Barrier &br) { - for (std::size_t c = 0; c < cycles; ++c) { - CHECK(requester_only_bind->sendTo(request, responder_address)); - br.arrive_and_wait(); - br.arrive_and_wait(); - auto received_response = - requester_only_bind->receive(response.size()); - REQUIRE(received_response); - CHECK(received_response->received_message == response); - } - }, - [&](Barrier &br) { - for (std::size_t c = 0; c < cycles; ++c) { - br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); + auto received_request = responder->receive(request.size()); REQUIRE(received_request); CHECK(received_request->received_message == request); - responder.sendTo(response, requester_address); + responder->sendTo(response, requester_address); br.arrive_and_wait(); } }); @@ -281,12 +223,12 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { TEST_CASE("Open connection with timeout", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family); + UDP_PEERS(udp::Udp, family); const auto timeout = Timeout{500}; SECTION("expect fail within timeout") { - CHECK_FALSE(requester.connect(timeout)); + CHECK_FALSE(requester->connect(timeout)); } SECTION("expect success within timeout") { @@ -294,10 +236,10 @@ TEST_CASE("Open connection with timeout", "[udp]") { ParallelSection::biSection( [&](auto &) { std::this_thread::sleep_for(wait); - responder.sendTo("1", requester_address); + responder->sendTo("1", requester_address); }, [&](auto &) { - auto connected_result = requester.connect(timeout); + auto connected_result = requester->connect(timeout); REQUIRE(connected_result); CHECK(are_same(connected_result->getRemoteAddress(), responder_address, family)); @@ -309,13 +251,13 @@ TEST_CASE("Reserve random port for udp connection", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); auto requester_port = ANY_PORT; - UdpBinded requester(requester_port, family); + udp::Udp requester(requester_port, family); REQUIRE(requester.open()); requester_port = requester.getPortToBind(); const Address requester_address = Address(requester_port, family); - auto responder_port = GENERATE(PortFactory::makePort(), ANY_PORT); - UdpBinded responder(responder_port, family); + auto responder_port = GENERATE(PortFactory::get().makePort(), ANY_PORT); + udp::Udp responder(responder_port, family); REQUIRE(responder.open()); responder_port = responder.getPortToBind(); const Address responder_address = Address(responder_port, family); @@ -342,12 +284,10 @@ TEST_CASE("Reserve random port for udp connection", "[udp]") { } /* - -TEST_CASE("Send Receive messages split into multiple pieces (udp)", - "[udp][!mayfail]") { +TEST_CASE("Send Receive messages split into multiple pieces (udp)", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family); + UDP_PEERS(udp::Udp, family); const std::string request = "This is a simulated long message"; @@ -357,13 +297,13 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", SECTION("split receive") { ParallelSection::biSection( [&](Barrier &br) { - requester.sendTo(request, responder_address); + requester->sendTo(request, responder_address); br.arrive_and_wait(); }, [&](Barrier &br) { br.arrive_and_wait(); auto received_request = - sliced_receive(responder, request.size(), 4); + sliced_receive(*responder, request.size(), 4); CHECK(received_request == request); }); } @@ -371,12 +311,12 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", SECTION("split send") { ParallelSection::biSection( [&](Barrier &br) { - sliced_send(requester, request, responder_address, 4); + sliced_send(*requester, request, responder_address, 4); br.arrive_and_wait(); }, [&](Barrier &br) { br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); + auto received_request = responder->receive(request.size()); CHECK(received_request); CHECK(received_request->received_message == request); }); @@ -384,8 +324,8 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", } SECTION("connected") { - auto requester_conn = requester.connect(responder_address); - auto responder_conn = responder.connect(requester_address); + auto requester_conn = requester->connect(responder_address); + auto responder_conn = responder->connect(requester_address); SECTION("split receive") { ParallelSection::biSection( [&](Barrier &br) { @@ -414,5 +354,36 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", } } } - */ + +TEST_CASE("Receive from unknown non blocking", "[udp]") { + const auto port = PortFactory::get().makePort(); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); + + UDP_PEERS(udp::Udp, family); + + CHECK_FALSE(responder->receive(request.size()).has_value()); + requester->sendTo(request, responder_address); +#if defined(__APPLE__) + std::this_thread::sleep_for(std::chrono::seconds{3}); +#endif + auto received_request = responder->receive(request.size()); + REQUIRE(received_request.has_value()); + CHECK(received_request->received_message == request); + CHECK(received_request->sender == requester_address); +} + +TEST_CASE("Receive non blocking (udp)", "[udp]") { + const auto port = PortFactory::get().makePort(); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); + + UDP_PEERS(udp::UdpConnected, family); + + CHECK(responder->receive(request.size()).empty()); + requester->send(request); +#if defined(__APPLE__) + std::this_thread::sleep_for(std::chrono::seconds{3}); +#endif + auto received_request = responder->receive(request.size()); + CHECK(received_request == request); +}