From 053286648e180b685b67a547089dd72e13da9959 Mon Sep 17 00:00:00 2001 From: andrea Date: Sun, 22 Nov 2020 17:49:31 +0100 Subject: [PATCH 001/228] description updated --- README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3968d55d..704b0596 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,8 @@ -The classes contained in ./StreamSocket allows you to create server-client tcp connection from C++ code. +The classes contained in ./CrossSocket allows to create tcp or udp connections from C++ code. The library is cross platform: let CMake handle the dependencies. -Check the 4 examples provided in ./Samples for the details, in particular: - --Sample_01 is a simple handshake between a server and a client --Sample_02 implements a service toward two clients from the same server, which responds to the client in a single thread --Sample_03 is similar to Sample_02, but the server use two different threads to manage the requests of the two different clients --Sample_04 in this example there is a client comunicating with a repeater that in turns forwards client's requests to the server - +Check the examples provided in ./Samples for the details. Check also the README.svg (use your favourite browser to open it) contained in the samples folders. ---------------------------------------------------------------------------------- -to run each example, compile the corresponding Launcher and use it +to run each example, compile and run the corresponding Launcher. From ae81518eb2333b819265fb01fa700c4573b85e9b Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Mon, 28 Dec 2020 22:45:43 +0100 Subject: [PATCH 002/228] Update README.md --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 704b0596..75761faa 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,16 @@ -The classes contained in ./CrossSocket allows to create tcp or udp connections from C++ code. -The library is cross platform: let CMake handle the dependencies. -Check the examples provided in ./Samples for the details. -Check also the README.svg (use your favourite browser to open it) contained in the samples folders. +This repository contains the minimal functionalities to create and use from **C++** both **tcp** and **udp** sockets in a +completely platform independent way. ----------------------------------------------------------------------------------- +**Content** -to run each example, compile and run the corresponding Launcher. + * The libarary is contained in the ./CrossSocket folder + * Samples showing the library usage are contained in the ./Samples folder + * Refer also to the README.svg files contained in the sample folders (use your favourite browser to open it) + +**Build and Install** + +Use [CMake](https://cmake.org) to configure and compile the library and the examples. + +**Run the examples** + +You can run the Launcher processes to automatically launch all the process of a certain example. From 91dd84b100b92caa3f8a41caee50bb84b9680169 Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Mon, 28 Dec 2020 22:46:45 +0100 Subject: [PATCH 003/228] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 75761faa..73a078e2 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,4 @@ Use [CMake](https://cmake.org) to configure and compile the library and the exam **Run the examples** -You can run the Launcher processes to automatically launch all the process of a certain example. +You can run the Launchers to automatically launch in the right sequence all the processes pertaining to an example. From 953382861ec8c69781ba0509feb45144a2d729a7 Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Tue, 29 Dec 2020 22:13:06 +0100 Subject: [PATCH 004/228] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 73a078e2..158fa694 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -This repository contains the minimal functionalities to create and use from **C++** both **tcp** and **udp** sockets in a +This repository contains the minimal functionalities to create and use from **C++** both **tcp** and **udp** socket in a completely platform independent way. **Content** From f78d799db3a0b40540c73eb5efee1752f477d25e Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Sat, 2 Jan 2021 01:19:55 +0100 Subject: [PATCH 005/228] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 158fa694..94cb8c13 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,5 @@ Use [CMake](https://cmake.org) to configure and compile the library and the exam **Run the examples** You can run the Launchers to automatically launch in the right sequence all the processes pertaining to an example. + +**IMPORTANT!!** If you are using **Visual Studio** do not launch the Launcher applications from inside VS, but go the folder where the exe are created and launch them from there From 63dec357d2601cb3d61394ea62be056cd77c5714 Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Tue, 5 Jan 2021 00:09:54 +0100 Subject: [PATCH 006/228] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94cb8c13..a2e88a6d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ completely platform independent way. * Samples showing the library usage are contained in the ./Samples folder * Refer also to the README.svg files contained in the sample folders (use your favourite browser to open it) -**Build and Install** +**Build** Use [CMake](https://cmake.org) to configure and compile the library and the examples. From eb80ad320a05554e4b26c48bb9cfa0656d1c800e Mon Sep 17 00:00:00 2001 From: andrea Date: Sat, 30 Jan 2021 00:04:02 +0100 Subject: [PATCH 007/228] async nmspc introduced --- CrossSocket/include/Address.h | 40 +++++---- CrossSocket/include/Error.h | 48 +++++++++++ CrossSocket/include/Socket.h | 19 ++-- CrossSocket/include/SocketClient.h | 21 +++-- CrossSocket/include/SocketServer.h | 11 ++- CrossSocket/include/async/AsyncClient.h | 35 ++++++++ CrossSocket/include/async/AsyncService.h | 62 +++++++++++++ CrossSocket/include/async/AsyncTcpServer.h | 28 ++++++ CrossSocket/include/async/ClientListener.h | 20 +++++ CrossSocket/include/async/ErrorListener.h | 22 +++++ CrossSocket/include/async/MessageListener.h | 20 +++++ CrossSocket/include/async/Service.h | 40 +++++++++ CrossSocket/include/{ => tcp}/TcpClient.h | 10 ++- CrossSocket/include/{ => tcp}/TcpServer.h | 18 ++-- .../include/{ => typed}/StringClient.h | 7 ++ CrossSocket/include/{ => typed}/TypedClient.h | 7 ++ CrossSocket/include/{ => udp}/UdpClient.h | 16 +++- CrossSocket/include/{ => udp}/UdpServer.h | 12 ++- CrossSocket/src/Address.cpp | 64 +++++--------- CrossSocket/src/Socket.cpp | 65 ++++++++++---- CrossSocket/src/SocketClient.cpp | 43 +++++----- CrossSocket/src/SocketHandler.cpp | 86 ++++++++++--------- CrossSocket/src/SocketHandler.h | 14 +-- CrossSocket/src/SocketServer.cpp | 9 +- CrossSocket/src/async/AsyncClient.cpp | 35 ++++++++ CrossSocket/src/async/AsyncTcpServer.cpp | 27 ++++++ CrossSocket/src/async/Service.cpp | 74 ++++++++++++++++ CrossSocket/src/{ => tcp}/TcpClient.cpp | 12 ++- CrossSocket/src/{ => tcp}/TcpServer.cpp | 52 +++++------ CrossSocket/src/{ => typed}/StringClient.cpp | 9 +- CrossSocket/src/{ => udp}/UdpClient.cpp | 18 ++-- CrossSocket/src/{ => udp}/UdpServer.cpp | 19 ++-- TODO | 7 ++ 33 files changed, 759 insertions(+), 211 deletions(-) create mode 100644 CrossSocket/include/Error.h create mode 100644 CrossSocket/include/async/AsyncClient.h create mode 100644 CrossSocket/include/async/AsyncService.h create mode 100644 CrossSocket/include/async/AsyncTcpServer.h create mode 100644 CrossSocket/include/async/ClientListener.h create mode 100644 CrossSocket/include/async/ErrorListener.h create mode 100644 CrossSocket/include/async/MessageListener.h create mode 100644 CrossSocket/include/async/Service.h rename CrossSocket/include/{ => tcp}/TcpClient.h (83%) rename CrossSocket/include/{ => tcp}/TcpServer.h (73%) rename CrossSocket/include/{ => typed}/StringClient.h (83%) rename CrossSocket/include/{ => typed}/TypedClient.h (95%) rename CrossSocket/include/{ => udp}/UdpClient.h (65%) rename CrossSocket/include/{ => udp}/UdpServer.h (76%) create mode 100644 CrossSocket/src/async/AsyncClient.cpp create mode 100644 CrossSocket/src/async/AsyncTcpServer.cpp create mode 100644 CrossSocket/src/async/Service.cpp rename CrossSocket/src/{ => tcp}/TcpClient.cpp (73%) rename CrossSocket/src/{ => tcp}/TcpServer.cpp (61%) rename CrossSocket/src/{ => typed}/StringClient.cpp (75%) rename CrossSocket/src/{ => udp}/UdpClient.cpp (68%) rename CrossSocket/src/{ => udp}/UdpServer.cpp (69%) create mode 100644 TODO diff --git a/CrossSocket/include/Address.h b/CrossSocket/include/Address.h index b6a638c0..d0464751 100644 --- a/CrossSocket/include/Address.h +++ b/CrossSocket/include/Address.h @@ -1,13 +1,24 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + #ifndef _CROSS_SOCKET_ADDRESS_H_ #define _CROSS_SOCKET_ADDRESS_H_ #include #include +#include namespace sck { // refer to https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzab6/address.htm enum Family {IP_V4, IP_V6}; + class Address; + typedef std::unique_ptr
AddressPtr; + /** * @brief representation of a network address */ @@ -15,35 +26,32 @@ namespace sck { public: Address(const Address&) = default; Address& operator=(const Address&) = default; - - /** - * @brief returns an ipv4 or ipv6 address with localhost as host and the passed port - */ - static Address Localhost(const std::uint16_t& port = 0, const Family& protocolType = Family::IP_V4); + Address(Address&&) = delete; + Address& operator=(Address&&) = delete; /** * @brief Internally the protocol Family is deduced according to the host content. - * An exception is thrown if the Ip is invalid + * @return nullptr if the host is invalid, otherwise a smart pointer storing a usable address */ - static Address FromIp(const std::string& host, const std::uint16_t& port); + static AddressPtr create(const std::string& host, const std::uint16_t& port); /** - * @brief check if the passed address is valid and can be used - * by the socket APIs + * @return an ipv4 or ipv6 address with localhost as host and the passed port */ - bool isValid() const; + static AddressPtr createLocalHost(const std::uint16_t& port = 0, const Family& protocolType = Family::IP_V4); + + inline const std::string& getHost() const { return this->host; }; - const std::string& getHost() const; + inline const std::uint16_t& getPort() const { return this->port; }; - const std::uint16_t& getPort() const; + inline Family getFamily() const { return this->family; }; - const Family& getFamily() const; private: Address(const std::string& host, const std::uint16_t& port, const Family& family); - std::string host; - std::uint16_t port; - Family family; + const std::string host; + const std::uint16_t port; + const Family family; }; bool operator==(const Address& lhs, const Address& rhs); diff --git a/CrossSocket/include/Error.h b/CrossSocket/include/Error.h new file mode 100644 index 00000000..fadb4af3 --- /dev/null +++ b/CrossSocket/include/Error.h @@ -0,0 +1,48 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ERROR_H +#define _CROSS_SOCKET_ERROR_H + +#include +#include + +namespace sck { + /** @brief A runtime error that can be raised when using any object in sck:: + */ + class Error : public std::runtime_error { + public: + explicit Error(const std::string& what) : std::runtime_error(what) { + }; + + template + Error(Args ... args) + : Error(merge(args...)) { + } + + private: + template + static std::string merge(Args ... args) { + std::stringstream stream; + merge(stream, args...); + return stream.str(); + }; + + template + static void merge(std::stringstream& stream, const T& current, Args ... remaining) { + stream << current; + merge(stream, remaining...); + }; + + template + static void merge(std::stringstream& stream, const T& back) { + stream << back; + }; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/Socket.h b/CrossSocket/include/Socket.h index 3d72b131..a0ffccb7 100644 --- a/CrossSocket/include/Socket.h +++ b/CrossSocket/include/Socket.h @@ -1,8 +1,16 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + #ifndef _CROSS_SOCKET_SOCKET_H_ #define _CROSS_SOCKET_SOCKET_H_ -#include "Address.h" -#include +#include +#include +#include namespace sck { /** @@ -20,11 +28,12 @@ namespace sck { virtual ~Socket(); - void open(); + void open(const std::chrono::milliseconds& timeout = std::chrono::milliseconds(0)); void close(); - inline bool isConnected() const { return this->connected; } + inline bool isOpen() const { return this->opened; } + protected: Socket(); /** @@ -47,7 +56,7 @@ namespace sck { void bindToPort(const std::uint16_t& port); - bool connected; + std::atomic_bool opened; }; } diff --git a/CrossSocket/include/SocketClient.h b/CrossSocket/include/SocketClient.h index 430c85a7..5c8061c8 100644 --- a/CrossSocket/include/SocketClient.h +++ b/CrossSocket/include/SocketClient.h @@ -1,8 +1,14 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + #ifndef _CROSS_SOCKET_SOCKETCLIENT_H_ #define _CROSS_SOCKET_SOCKETCLIENT_H_ #include "Socket.h" -#include namespace sck { /** @@ -14,7 +20,7 @@ namespace sck { ~SocketClient() override = default; /** - * @brief returns the targeted server address + * @brief returns the address of the remote entity connected with this socket */ const sck::Address& getRemoteAddress() const; @@ -34,6 +40,7 @@ namespace sck { * @param[in] the timeout to consider */ std::size_t receive(char* buffer, const std::size_t& length, const std::chrono::milliseconds& timeout = std::chrono::milliseconds(0)); + protected: /** * @param[in] the address of the server to hit @@ -50,16 +57,12 @@ namespace sck { void openConnection() override; /** - * @brief setter for the timeout used when receiving data - */ - void resetTimeOut(const std::chrono::milliseconds& timeout); - - /** - * @brief address of the server to hit + * @brief address of the server connected to this socket */ sck::Address remoteAddress; + private: - std::chrono::milliseconds actualTimeOut; // setsockopt in windows is not reliable under milliseconds + std::chrono::milliseconds actualTimeOut; }; } diff --git a/CrossSocket/include/SocketServer.h b/CrossSocket/include/SocketServer.h index f83936db..3b314203 100644 --- a/CrossSocket/include/SocketServer.h +++ b/CrossSocket/include/SocketServer.h @@ -1,3 +1,10 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + #ifndef _CROSS_SOCKET_SOCKETSERVER_H_ #define _CROSS_SOCKET_SOCKETSERVER_H_ @@ -12,9 +19,10 @@ namespace sck { public: ~SocketServer() override = default; /** - * @brief returns the port this server is bounded to + * @brief returns the port this server tries to bind when opened */ std::uint16_t getPort() const; + protected: /** * @param[in] the port this server should reserve @@ -23,6 +31,7 @@ namespace sck { SocketServer(const std::uint16_t& port, const sck::Family& family); sck::Family getFamily() final; + private: std::uint16_t port; sck::Family protocol; diff --git a/CrossSocket/include/async/AsyncClient.h b/CrossSocket/include/async/AsyncClient.h new file mode 100644 index 00000000..05ec2026 --- /dev/null +++ b/CrossSocket/include/async/AsyncClient.h @@ -0,0 +1,35 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ASYNCCLIENT_H +#define _CROSS_SOCKET_ASYNCCLIENT_H + +#include +#include +#include +#include + +namespace sck::async { + class AsyncClient : public AsyncDecorator { + public: + AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); + + inline std::size_t send(const char* buffer, const std::size_t& length) { + return this->wrapped->send(buffer, length); + }; + + // std::size_t receive(char* buffer, const std::size_t& length, const std::chrono::milliseconds& timeout = std::chrono::milliseconds(0)); + + private: + std::vector buffer; + class ReceiveService; + + std::unique_ptr make_service() final; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/AsyncService.h b/CrossSocket/include/async/AsyncService.h new file mode 100644 index 00000000..5d31a42a --- /dev/null +++ b/CrossSocket/include/async/AsyncService.h @@ -0,0 +1,62 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ASYNCSERVICE_H +#define _CROSS_SOCKET_ASYNCSERVICE_H + +#include +#include +#include +#include + +namespace sck::async { + template + class AsyncDecorator { + public: + virtual ~AsyncDecorator() { + if(this->wrapped->isOpen()) { + this->close(); + } + }; + + inline void resetListener(Listener* listener) { + std::lock_guard lk(this->listenerMtx); + this->listener = listener; + }; + + inline void resetErrorListener(ErrorListener* listener) { + this->wrapped->resetErrorListener(Listener); + }; + + inline void open(const std::chrono::milliseconds& timeout = std::chrono::milliseconds(0)) { + this->wrapped->open(timeout); + this->service = this->make_service(); + }; + + inline void close() { + this->wrapped->close(); + this->service.reset(); + }; + + inline bool isOpen() const { return this->wrapped->isOpen(); } + + protected: + AsyncDecorator(std::unique_ptr client) + : wrapped(std::move(client)) { + }; + + virtual std::unique_ptr make_service() = 0; + + Listener* listener = nullptr; + std::mutex listenerMtx; + + std::unique_ptr wrapped; + std::unique_ptr service; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/AsyncTcpServer.h b/CrossSocket/include/async/AsyncTcpServer.h new file mode 100644 index 00000000..6096ec05 --- /dev/null +++ b/CrossSocket/include/async/AsyncTcpServer.h @@ -0,0 +1,28 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ASYNCTCPSERVER_H +#define _CROSS_SOCKET_ASYNCTCPSERVER_H + +#include +#include +#include + +namespace sck::async { + class AsyncTcpServer : public AsyncDecorator { + public: + AsyncTcpServer(std::unique_ptr server) + : AsyncDecorator(std::move(server)) {}; + + private: + class AcceptanceService; + + std::unique_ptr make_service() final; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/ClientListener.h b/CrossSocket/include/async/ClientListener.h new file mode 100644 index 00000000..95257cef --- /dev/null +++ b/CrossSocket/include/async/ClientListener.h @@ -0,0 +1,20 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_CLIENTLISTENER_H_ +#define _CROSS_SOCKET_CLIENTLISTENER_H_ + +#include + +namespace sck::async { + class ClientListener { + public: + virtual void handle(std::unique_ptr client) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/ErrorListener.h b/CrossSocket/include/async/ErrorListener.h new file mode 100644 index 00000000..1484fa03 --- /dev/null +++ b/CrossSocket/include/async/ErrorListener.h @@ -0,0 +1,22 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ERRORLISTENER_H_ +#define _CROSS_SOCKET_ERRORLISTENER_H_ + +#include + +namespace sck::async { + class ErrorListener { + public: + virtual void handle(const Error& error) = 0; + + virtual void handle(const std::exception& error) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/MessageListener.h b/CrossSocket/include/async/MessageListener.h new file mode 100644 index 00000000..8f058d17 --- /dev/null +++ b/CrossSocket/include/async/MessageListener.h @@ -0,0 +1,20 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_MESSAGELISTENER_H_ +#define _CROSS_SOCKET_MESSAGELISTENER_H_ + +#include + +namespace sck::async { + class MessageListener { + public: + virtual void handle(const char* message, const std::size_t& messageLenght) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/Service.h b/CrossSocket/include/async/Service.h new file mode 100644 index 00000000..5db351a7 --- /dev/null +++ b/CrossSocket/include/async/Service.h @@ -0,0 +1,40 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_SERVICE_H_ +#define _CROSS_SOCKET_SERVICE_H_ + +#include +#include +#include +#include +#include +#include + +namespace sck::async { + class Service { + public: + // start + Service(const std::function& iterativeAction); + // stop + ~Service(); + + void resetErrorListener(ErrorListener* listener); + + private: + class Barrier; + std::unique_ptr barrier = std::make_unique(); + + ErrorListener* listener; + std::mutex listenerMtx; + + std::atomic_bool loopLife; + std::thread loop; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/TcpClient.h b/CrossSocket/include/tcp/TcpClient.h similarity index 83% rename from CrossSocket/include/TcpClient.h rename to CrossSocket/include/tcp/TcpClient.h index c20a7a0e..b5de5299 100644 --- a/CrossSocket/include/TcpClient.h +++ b/CrossSocket/include/tcp/TcpClient.h @@ -1,9 +1,16 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + #ifndef _CROSS_SOCKET_TCPCLIENT_H_ #define _CROSS_SOCKET_TCPCLIENT_H_ #include "SocketClient.h" -namespace sck { +namespace sck::tcp { /** * @brief interface for a tcp client. * When calling open, the client ask the connection to a tcp server, which must be previously activated, i.e. @@ -18,6 +25,7 @@ namespace sck { explicit TcpClient(const sck::Address& remoteAddress); ~TcpClient() override = default; + protected: explicit TcpClient(const sck::Address& remoteAddress, std::unique_ptr channel); diff --git a/CrossSocket/include/TcpServer.h b/CrossSocket/include/tcp/TcpServer.h similarity index 73% rename from CrossSocket/include/TcpServer.h rename to CrossSocket/include/tcp/TcpServer.h index 213a837d..6d06d46c 100644 --- a/CrossSocket/include/TcpServer.h +++ b/CrossSocket/include/tcp/TcpServer.h @@ -1,10 +1,17 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + #ifndef _CROSS_SOCKET_TCPSERVER_H_ #define _CROSS_SOCKET_TCPSERVER_H_ -#include "SocketServer.h" +#include #include "SocketClient.h" -namespace sck { +namespace sck::tcp { /** * @brief interface for a tcp server. * When calling open, the server binds and listen to the port, in order to be later ready to accept clients @@ -22,16 +29,15 @@ namespace sck { /** * @brief Wait for a new client to ask the connection and after accepting it - * returns an interface to use for sending and receive data with the accepted clients. + * returns an interface to use for exchanging data to and from the accepted clients. * This is a blocking operation. */ - std::unique_ptr acceptNewClient(); + std::unique_ptr acceptClient(); + protected: void initHandle() final; void openConnection() override; - - void doListen(); }; } diff --git a/CrossSocket/include/StringClient.h b/CrossSocket/include/typed/StringClient.h similarity index 83% rename from CrossSocket/include/StringClient.h rename to CrossSocket/include/typed/StringClient.h index ac1a8ed4..17d84e1a 100644 --- a/CrossSocket/include/StringClient.h +++ b/CrossSocket/include/typed/StringClient.h @@ -1,3 +1,10 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + #ifndef _CROSS_SOCKET_STRINGCLIENT_H_ #define _CROSS_SOCKET_STRINGCLIENT_H_ diff --git a/CrossSocket/include/TypedClient.h b/CrossSocket/include/typed/TypedClient.h similarity index 95% rename from CrossSocket/include/TypedClient.h rename to CrossSocket/include/typed/TypedClient.h index 36c534c5..9875b08e 100644 --- a/CrossSocket/include/TypedClient.h +++ b/CrossSocket/include/typed/TypedClient.h @@ -1,3 +1,10 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + #ifndef _CROSS_SOCKET_TYPEDCLIENT_H_ #define _CROSS_SOCKET_TYPEDCLIENT_H_ diff --git a/CrossSocket/include/UdpClient.h b/CrossSocket/include/udp/UdpClient.h similarity index 65% rename from CrossSocket/include/UdpClient.h rename to CrossSocket/include/udp/UdpClient.h index 04fbe5b1..6e825a8c 100644 --- a/CrossSocket/include/UdpClient.h +++ b/CrossSocket/include/udp/UdpClient.h @@ -1,9 +1,20 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + #ifndef _CROSS_SOCKET_UDPCLIENT_H_ #define _CROSS_SOCKET_UDPCLIENT_H_ -#include "SocketClient.h" +#include + +namespace sck::udp { + + // refer to https://en.wikipedia.org/wiki/User_Datagram_Protocol#:~:text=The%20field%20size%20sets%20a,−%2020%20byte%20IP%20header). + constexpr std::size_t MAX_UDP_RECV_MESSAGE = 65507; -namespace sck { /** * @brief interface for a standard udp connection. * A udp may or not reserve a port, in order to be reached by another udp client. @@ -18,6 +29,7 @@ namespace sck { UdpClient(const sck::Address& remoteAddress, const std::uint16_t& localPort = 0); ~UdpClient() override = default; + protected: void initHandle() final; diff --git a/CrossSocket/include/UdpServer.h b/CrossSocket/include/udp/UdpServer.h similarity index 76% rename from CrossSocket/include/UdpServer.h rename to CrossSocket/include/udp/UdpServer.h index 081e1b78..45265f6b 100644 --- a/CrossSocket/include/UdpServer.h +++ b/CrossSocket/include/udp/UdpServer.h @@ -1,15 +1,22 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ #ifndef _CROSS_SOCKET_UDPSERVER_H_ #define _CROSS_SOCKET_UDPSERVER_H_ -#include "UdpClient.h" +#include -namespace sck { +namespace sck::udp { /** * @brief interface for a udp server. * A UdpServer is an UdpClient, with the possibility to deduce the remoteAddress, * by setting as target the first UdpClient that hits this socket, sending * at least 1 byte of data. + * IMPORTANT!!! The first message sent to the server will be lost. */ class UdpServer : public UdpClient { @@ -21,6 +28,7 @@ namespace sck { UdpServer(const std::uint16_t& localPort, const sck::Family& protocol = sck::Family::IP_V4); ~UdpServer() override = default; + protected: void openConnection() final; }; diff --git a/CrossSocket/src/Address.cpp b/CrossSocket/src/Address.cpp index 4c1ada9d..99c8a258 100644 --- a/CrossSocket/src/Address.cpp +++ b/CrossSocket/src/Address.cpp @@ -1,9 +1,14 @@ - -#include "../include/Address.h" +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include #include "SocketHandler.h" namespace sck { - static const inline std::string LOCALHOST = "localhost"; static const inline std::string LOCALHOST_IPv4 = "127.0.0.1"; static const inline std::string LOCALHOST_IPv6 = "::1"; @@ -13,57 +18,30 @@ namespace sck { , family(family) { } - Address Address::FromIp(const std::string& host, const std::uint16_t& port) { + AddressPtr Address::create(const std::string& host, const std::uint16_t& port) { //try to resolve the address as an ipv4 - Address asIpv4(host, port, sck::Family::IP_V4); - if (resolveIPv4(asIpv4)) { - return asIpv4; + AddressPtr addr = std::make_unique
(host, port, sck::Family::IP_V4); + if (resolveIPv4(*addr)) { + return addr; } //try to resolve the address as an ipv6 - Address asIpv6(host, port, sck::Family::IP_V6); - if (resolveIPv6(asIpv6)) { - return asIpv6; + addr = std::make_unique
(host, port, sck::Family::IP_V6); + if (resolveIPv6(*addr)) { + return addr; } - //unknown type - throw std::runtime_error("unknown family"); + return nullptr; } - Address Address::Localhost(const std::uint16_t& port, const Family& protocolType) { + AddressPtr Address::createLocalHost(const std::uint16_t& port, const Family& protocolType) { switch (protocolType) { case Family::IP_V4: - return Address(LOCALHOST_IPv4, port, sck::Family::IP_V4); - case Family::IP_V6: - return Address(LOCALHOST_IPv6, port, sck::Family::IP_V6); - default: - throw std::runtime_error("unknown family"); - } - } - - const std::string& Address::getHost() const { - return this->host; - } - - const std::uint16_t& Address::getPort() const { - return this->port; - } - - const Family& Address::getFamily() const { - return this->family; - } - - bool Address::isValid() const { - switch (this->family) { - case Family::IP_V4: - if (nullptr != resolveIPv4(*this)) { - return true; - } + return std::make_unique
(LOCALHOST_IPv4, port, sck::Family::IP_V4); case Family::IP_V6: - if (nullptr != resolveIPv6(*this)) { - return true; - } + return std::make_unique
(LOCALHOST_IPv6, port, sck::Family::IP_V6); default: - return false; + return nullptr; } + return nullptr; } bool operator==(const Address& lhs, const Address& rhs) { diff --git a/CrossSocket/src/Socket.cpp b/CrossSocket/src/Socket.cpp index 6c88075a..9d274b2f 100644 --- a/CrossSocket/src/Socket.cpp +++ b/CrossSocket/src/Socket.cpp @@ -1,5 +1,14 @@ -#include "../include/Socket.h" +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include #include "SocketHandler.h" +#include +#include #ifdef _WIN32 #define REBIND_OPTION SO_REUSEADDR @@ -10,39 +19,61 @@ namespace sck { Socket::Socket() : channel(std::make_unique()) - , connected(false) { + , opened(false) { } Socket::Socket(std::unique_ptr channel) : channel(std::move(channel)) - , connected(false) { + , opened(false) { } Socket::~Socket() { this->close(); } - void Socket::open() { - if (this->connected) { + void Socket::open(const std::chrono::milliseconds& timeout) { + if (this->opened) { return; } - try { - if (!this->channel->isActive()) { - this->channel.reset(); - this->channel = std::make_unique(); + if (!this->channel->isActive()) { + this->channel.reset(); + this->channel = std::make_unique(); + } + + std::atomic_bool stopWait = false; + auto openSteps = [this, &stopWait]() { + try { + this->initHandle(); + this->openConnection(); + } + catch (...) { + this->close(); + stopWait = true; + return; } - this->initHandle(); - this->openConnection(); + this->opened = true; + stopWait = true; + }; + + if(0 == timeout.count()) { + openSteps(); } - catch (...) { - this->close(); - return; + else { + std::condition_variable notification; + std::mutex notificationMtx; + std::thread opener(openSteps); + + std::unique_lock notificationLck(notificationMtx); + notification.wait_for(notificationLck, timeout, [&stopWait](){ return static_cast(stopWait); }); + if(!this->opened) { + this->close(); + this->opened = false; + } } - this->connected = true; } void Socket::close() { - if (!this->connected) { + if (!this->opened) { return; } try { @@ -50,7 +81,7 @@ namespace sck { } catch (...) { } - this->connected = false; + this->opened = false; } void Socket::bindToPort(const std::uint16_t& port) { diff --git a/CrossSocket/src/SocketClient.cpp b/CrossSocket/src/SocketClient.cpp index 092b7194..765aff43 100644 --- a/CrossSocket/src/SocketClient.cpp +++ b/CrossSocket/src/SocketClient.cpp @@ -1,4 +1,11 @@ -#include "../include/SocketClient.h" +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include #include "SocketHandler.h" namespace sck { @@ -31,21 +38,7 @@ namespace sck { return static_cast(sentBytes); } - std::size_t SocketClient::receive(char* buffer, const std::size_t & bufferMaxSize, const std::chrono::milliseconds& timeout) { - this->resetTimeOut(timeout); - int recvBytes = ::recv(this->channel->handle, buffer, static_cast(bufferMaxSize), 0); - if (recvBytes == SCK_SOCKET_ERROR) { - recvBytes = 0; - throwWithCode("receive failed"); - } - if (recvBytes > bufferMaxSize) { - // if here, the message received is probably corrupted - recvBytes = 0; - } - return static_cast(recvBytes); - } - - void SocketClient::resetTimeOut(const std::chrono::milliseconds& timeout) { + std::size_t SocketClient::receive(char* buffer, const std::size_t & bufferMaxSize, const std::chrono::milliseconds& timeout) { if (timeout.count() != this->actualTimeOut.count()) { //set new timeout this->actualTimeOut = timeout; @@ -65,15 +58,23 @@ namespace sck { throwWithCode("can't set timeout"); } } + + int recvBytes = ::recv(this->channel->handle, buffer, static_cast(bufferMaxSize), 0); + if (recvBytes == SCK_SOCKET_ERROR) { + recvBytes = 0; + throwWithCode("receive failed"); + } + if (recvBytes > bufferMaxSize) { + // if here, the message received is probably corrupted + recvBytes = 0; + } + return static_cast(recvBytes); } void SocketClient::openConnection() { - if (!this->remoteAddress.isValid()) { - throw std::runtime_error(this->remoteAddress.getHost() + ":" + std::to_string(this->remoteAddress.getPort()) + " is an invalid server address"); - } if (sck::Family::IP_V4 == this->getFamily()) { //v4 family - auto addr = resolveIPv4(this->remoteAddress); + auto addr = convertIpv4(this->remoteAddress); if (!addr) { throw std::runtime_error(this->remoteAddress.getHost() + ":" + std::to_string(this->remoteAddress.getPort()) + " is an invalid server address"); } @@ -83,7 +84,7 @@ namespace sck { } else { //v6 family - auto addr = resolveIPv6(this->remoteAddress); + auto addr = convertIpv6(this->remoteAddress); if (!addr) { throw std::runtime_error(this->remoteAddress.getHost() + ":" + std::to_string(this->remoteAddress.getPort()) + " is an invalid server address"); } diff --git a/CrossSocket/src/SocketHandler.cpp b/CrossSocket/src/SocketHandler.cpp index be3643e0..84b35df2 100644 --- a/CrossSocket/src/SocketHandler.cpp +++ b/CrossSocket/src/SocketHandler.cpp @@ -1,4 +1,6 @@ #include "SocketHandler.h" +#include +#include #ifdef _WIN32 #include @@ -65,19 +67,19 @@ namespace sck { return true; }; - std::unique_ptr resolveIPv4(const sck::Address& address) { + std::unique_ptr convertIpv4(const sck::Address& address) { if (sck::Family::IP_V4 == address.getFamily()) { - SocketAddressIn_t resolved; + std::unique_ptr resolved = std::make_unique(); // set everything to 0 first - ::memset(&resolved, 0, sizeof(SocketAddressIn_t)); - resolved.sin_family = AF_INET; - if (!tryConversion(resolved, address)) { - return std::make_unique(); + ::memset(&(*resolved), 0, sizeof(SocketAddressIn_t)); + resolved->sin_family = AF_INET; + if (!tryConversion(*resolved, address)) { + return nullptr; } - resolved.sin_port = ::htons(address.getPort()); - return std::make_unique(resolved); + resolved->sin_port = ::htons(address.getPort()); + return resolved; } - return std::make_unique(); + return nullptr; } /** @@ -115,20 +117,42 @@ namespace sck { return true; }; - std::unique_ptr resolveIPv6(const sck::Address& address) { + std::unique_ptr convertIpv6(const sck::Address& address) { if (sck::Family::IP_V6 == address.getFamily()) { - SocketAddressIn6_t resolved; + std::unique_ptr resolved = std::make_unique(); // set everything to 0 first - ::memset(&resolved, 0, sizeof(SocketAddressIn6_t)); - resolved.sin6_family = AF_INET6; - resolved.sin6_flowinfo = 0; - if (!tryConversion(resolved, address)) { - return std::make_unique(); + ::memset(&(*resolved), 0, sizeof(SocketAddressIn6_t)); + resolved->sin6_family = AF_INET6; + resolved->sin6_flowinfo = 0; + if (!tryConversion(*resolved, address)) { + return nullptr; } - resolved.sin6_port = ::htons(address.getPort()); - return std::make_unique(resolved); + resolved->sin6_port = ::htons(address.getPort()); + return resolved; } - return std::make_unique(); + return nullptr; + } + + AddressPtr convert(SocketAddress_t& address) { + // refer to https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu + std::string ip; + std::uint16_t port; + if (AF_INET == address.sa_family) { + // ipv4 address + // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to an ip that has no sense + ip = std::string(::inet_ntoa(reinterpret_cast(&address)->sin_addr)); + port = ::ntohs(reinterpret_cast(&address)->sin_port); + } + else { + // ipv6 address + char temp[INET6_ADDRSTRLEN]; //this is the longest one + // refer to https://www.gnu.org/software/libc/manual/html_node/Host-Address-Functions.html + ::memset(temp, 0, INET6_ADDRSTRLEN); + ::inet_ntop(address.sa_family, &address, temp, INET6_ADDRSTRLEN); + ip = std::string(temp, INET6_ADDRSTRLEN); + port = ::ntohs(reinterpret_cast(&address)->sin6_port); + } + return sck::Address::create(ip, port); } int castFamily(const sck::Family& family) { @@ -187,31 +211,13 @@ namespace sck { this->handle = SCK_INVALID_SOCKET; } - sck::Address convert(SocketAddress_t& address) { - // refer to https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu - std::uint16_t remotePort; - if (address.sa_family == AF_INET) { - // ipv4 address - SocketAddressIn_t* addressParsed = reinterpret_cast(&address); - remotePort = addressParsed->sin_port; - } - else { - // ipv6 address - SocketAddressIn6_t* addressParsed = reinterpret_cast(&address); - remotePort = addressParsed->sin6_port; - } - // refer to https://www.gnu.org/software/libc/manual/html_node/Host-Address-Functions.html - char temp[INET6_ADDRSTRLEN]; //this is the longest one - ::memset(temp, 0, INET6_ADDRSTRLEN); - ::inet_ntop(address.sa_family, address.sa_data, temp, INET6_ADDRSTRLEN); - return sck::Address::FromIp(std::string(temp), remotePort); - } - bool SocketHandler::isActive() const { return this->active; } void throwWithCode(const std::string& what){ - throw std::runtime_error(what + " , error code: " + std::to_string(getLastError())); + std::stringstream s; + s << what << " , error code: " << getLastError(); + throw Error(s.str()); } } \ No newline at end of file diff --git a/CrossSocket/src/SocketHandler.h b/CrossSocket/src/SocketHandler.h index 98a5cd8d..b500c732 100644 --- a/CrossSocket/src/SocketHandler.h +++ b/CrossSocket/src/SocketHandler.h @@ -68,24 +68,24 @@ namespace sck { * it's valid, creates the socket API representation * of the address */ - std::unique_ptr resolveIPv4(const sck::Address& address); + std::unique_ptr convertIpv4(const sck::Address& address); /** * @brief checks the address syntax and in case * it's valid, creates the socket API representation * of the address */ - std::unique_ptr resolveIPv6(const sck::Address& address); + std::unique_ptr convertIpv6(const sck::Address& address); + + /** + * @brief Convert a SocketAddress_t into an Address + */ + AddressPtr convert(SocketAddress_t& address); /** * @brief the integer representing a family type */ int castFamily(const sck::Family& family); - /** - * @brief Convert a SocketAddress_t into an Address - */ - sck::Address convert(SocketAddress_t& address); - /** * contains the required things to work with the socket API */ diff --git a/CrossSocket/src/SocketServer.cpp b/CrossSocket/src/SocketServer.cpp index 68e65973..1e84ebd1 100644 --- a/CrossSocket/src/SocketServer.cpp +++ b/CrossSocket/src/SocketServer.cpp @@ -1,4 +1,11 @@ -#include "../include/SocketServer.h" +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include #include "SocketHandler.h" namespace sck { diff --git a/CrossSocket/src/async/AsyncClient.cpp b/CrossSocket/src/async/AsyncClient.cpp new file mode 100644 index 00000000..5725699c --- /dev/null +++ b/CrossSocket/src/async/AsyncClient.cpp @@ -0,0 +1,35 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +namespace sck::async { + AsyncClient::AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity) + : AsyncDecorator(std::move(client)) { + this->buffer.resize(bufferCapacity); + }; + + class AsyncClient::ReceiveService : public Service { + public: + ReceiveService(AsyncClient& client) + : Service([&client](){ + std::size_t recvBytes = client.wrapped->receive(client.buffer.data(), client.buffer.capacity()); + if(recvBytes != client.buffer.capacity()) { + client.buffer.resize(recvBytes); + } + std::lock_guard lk(client.listenerMtx); + if(nullptr != client.listener) { + client.listener->handle(client.buffer.data(), recvBytes); + } + }) { + } + }; + + std::unique_ptr AsyncClient::make_service() { + return std::make_unique(*this); + } +} \ No newline at end of file diff --git a/CrossSocket/src/async/AsyncTcpServer.cpp b/CrossSocket/src/async/AsyncTcpServer.cpp new file mode 100644 index 00000000..3e0bc6a4 --- /dev/null +++ b/CrossSocket/src/async/AsyncTcpServer.cpp @@ -0,0 +1,27 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +namespace sck::async { + class AsyncTcpServer::AcceptanceService : public Service { + public: + AcceptanceService(AsyncTcpServer& server) + : Service([&server](){ + auto client = server.wrapped->acceptClient(); + std::lock_guard lk(server.listenerMtx); + if(nullptr != server.listener) { + server.listener->handle(std::move(client)); + } + }) { + } + }; + + std::unique_ptr AsyncTcpServer::make_service() { + return std::make_unique(*this); + } +} diff --git a/CrossSocket/src/async/Service.cpp b/CrossSocket/src/async/Service.cpp new file mode 100644 index 00000000..d17e69d4 --- /dev/null +++ b/CrossSocket/src/async/Service.cpp @@ -0,0 +1,74 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +namespace sck::async { + class Service::Barrier { + public: + Barrier() { this->stopWait = false; }; + + void wait(); + + private: + std::uint8_t queue = 0; + std::mutex queueMtx; + std::condition_variable notification; + std::atomic_bool stopWait; + }; + + void Service::Barrier::wait() { + std::unique_lock lk(this->queueMtx); + ++this->queue; + if(2 == this->queue) { + this->stopWait = true; + this->notification.notify_all(); + } + else { + this->notification.wait(lk, [this](){ return static_cast(this->stopWait); }); + } + } + + Service::Service(const std::function& iterativeAction) + : listener(nullptr) + , loop([this, &iterativeAction](){ + std::function iter(iterativeAction); + this->barrier->wait(); + while (this->loopLife) { + try { + iter(); + } + catch(...) { + std::lock_guard lk(this->listenerMtx); + if(nullptr != this->listener) { + try { + std::rethrow_exception(std::current_exception()); + } + catch(const Error& e) { + this->listener->handle(e); + } + catch(const std::exception& e) { + this->listener->handle(e); + } + } + } + } + }) { + this->loopLife = true; + this->barrier->wait(); + } + + Service::~Service() { + this->loopLife = false; + this->loop.join(); + } + + void Service::resetErrorListener(ErrorListener* listener) { + std::lock_guard lk(this->listenerMtx); + this->listener = listener; + } +} \ No newline at end of file diff --git a/CrossSocket/src/TcpClient.cpp b/CrossSocket/src/tcp/TcpClient.cpp similarity index 73% rename from CrossSocket/src/TcpClient.cpp rename to CrossSocket/src/tcp/TcpClient.cpp index f9c4625b..aa8c1e59 100644 --- a/CrossSocket/src/TcpClient.cpp +++ b/CrossSocket/src/tcp/TcpClient.cpp @@ -1,8 +1,14 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ -#include "../include/TcpClient.h" -#include "SocketHandler.h" +#include +#include "../SocketHandler.h" -namespace sck { +namespace sck::tcp { TcpClient::TcpClient(const sck::Address& remoteAddress) : SocketClient(remoteAddress) { diff --git a/CrossSocket/src/TcpServer.cpp b/CrossSocket/src/tcp/TcpServer.cpp similarity index 61% rename from CrossSocket/src/TcpServer.cpp rename to CrossSocket/src/tcp/TcpServer.cpp index 781dc0d2..232550da 100644 --- a/CrossSocket/src/TcpServer.cpp +++ b/CrossSocket/src/tcp/TcpServer.cpp @@ -1,30 +1,38 @@ -#include "../include/TcpServer.h" -#include "SocketHandler.h" -#include "../include/TcpClient.h" +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ -constexpr std::size_t listenBacklog = 50; +#include +#include +#include "../SocketHandler.h" -namespace sck { +namespace sck::tcp { - class TcpServerClient : public TcpClient { + constexpr std::size_t LISTEN_BACKLOG = 50; + + class ClientHandler : public TcpClient { public: - explicit TcpServerClient(const sck::Address& remoteAddress, std::unique_ptr channel) + explicit ClientHandler(const sck::Address& remoteAddress, std::unique_ptr channel) : TcpClient(remoteAddress, std::move(channel)) { - this->connected = true; - } + this->opened = true; + }; + + ~ClientHandler() override = default; - ~TcpServerClient() final = default; private: void openConnection() final { - throw std::runtime_error("TcpClients from TcpServer not reopenable!"); - } + throw std::runtime_error("ClientHandler from TcpServer are not re-openable!"); + }; }; TcpServer::TcpServer(const std::uint16_t& port, const Family& family) : SocketServer(port, family) { } - std::unique_ptr TcpServer::acceptNewClient() { + std::unique_ptr TcpServer::acceptClient() { SocketAddress_t acceptedClientAddress; std::unique_ptr acceptedClientHandler = std::make_unique(); #ifdef _WIN32 @@ -38,19 +46,12 @@ namespace sck { if (acceptedClientHandler->handle == SCK_INVALID_SOCKET) { throwWithCode("Error: accepting new client"); } - sck::Address remoteAddress = convert(acceptedClientAddress); - if (!remoteAddress.isValid()) { + AddressPtr remoteAddress = convert(acceptedClientAddress); + if (nullptr != remoteAddress) { throw std::runtime_error("accepted client remote address is not resolvable"); } - return std::make_unique(remoteAddress, std::move(acceptedClientHandler)); - } - - void TcpServer::doListen() { - // listen to the port to allow all the following clients acceptance - if (::listen(this->channel->handle, ::listenBacklog) == SCK_SOCKET_ERROR) { - throwWithCode("Error: listening on port: " + std::to_string(this->getPort())); - } + return std::make_unique(*remoteAddress, std::move(acceptedClientHandler)); } void TcpServer::initHandle() { @@ -62,7 +63,10 @@ namespace sck { void TcpServer::openConnection() { this->bindToPort(this->getPort()); - this->doListen(); + // listen to the port to allow all the following clients acceptance + if (::listen(this->channel->handle, LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { + throwWithCode("Error: listening on port: " + std::to_string(this->getPort())); + } } } \ No newline at end of file diff --git a/CrossSocket/src/StringClient.cpp b/CrossSocket/src/typed/StringClient.cpp similarity index 75% rename from CrossSocket/src/StringClient.cpp rename to CrossSocket/src/typed/StringClient.cpp index 54496849..c6ee1d65 100644 --- a/CrossSocket/src/StringClient.cpp +++ b/CrossSocket/src/typed/StringClient.cpp @@ -1,4 +1,11 @@ -#include "../include/StringClient.h" +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include #include namespace sck { diff --git a/CrossSocket/src/UdpClient.cpp b/CrossSocket/src/udp/UdpClient.cpp similarity index 68% rename from CrossSocket/src/UdpClient.cpp rename to CrossSocket/src/udp/UdpClient.cpp index 374e05bc..7aec047d 100644 --- a/CrossSocket/src/UdpClient.cpp +++ b/CrossSocket/src/udp/UdpClient.cpp @@ -1,7 +1,14 @@ -#include "../include/UdpClient.h" -#include "SocketHandler.h" +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ -namespace sck { +#include +#include "../SocketHandler.h" + +namespace sck::udp { UdpClient::UdpClient(const sck::Address& remoteAddress, const std::uint16_t& localPort) : SocketClient(remoteAddress) @@ -16,9 +23,8 @@ namespace sck { } void UdpClient::openConnection() { - if (0 != this->port) { - this->bindToPort(this->port); - } + this->bindToPort(this->port); this->SocketClient::openConnection(); + } } \ No newline at end of file diff --git a/CrossSocket/src/UdpServer.cpp b/CrossSocket/src/udp/UdpServer.cpp similarity index 69% rename from CrossSocket/src/UdpServer.cpp rename to CrossSocket/src/udp/UdpServer.cpp index 207da9d1..476d0f2a 100644 --- a/CrossSocket/src/UdpServer.cpp +++ b/CrossSocket/src/udp/UdpServer.cpp @@ -1,7 +1,14 @@ -#include "../include/UdpServer.h" -#include "SocketHandler.h" +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ -namespace sck { +#include +#include "../SocketHandler.h" + +namespace sck::udp { sck::Address getInitialAddress(const sck::Family& protocol) { if (sck::Family::IP_V6 == protocol) { @@ -17,8 +24,8 @@ namespace sck { void UdpServer::openConnection() { this->bindToPort(this->port); - //listen for a client, launching a receive of 1 byte (no filter will be applied since the socket is opening when arriving here and establishConnection was not already called) - char bf; + // receive a message from the client, that from now on will beccome the recognized one. + char bf[MAX_UDP_RECV_MESSAGE]; SocketAddress_t remoteAddr; #ifdef _WIN32 int @@ -26,7 +33,7 @@ namespace sck { unsigned int #endif remoteAddrLen = sizeof(SocketAddress_t); - if (::recvfrom(this->channel->handle, &bf, 1, 0, &remoteAddr, &remoteAddrLen) == SCK_SOCKET_ERROR) { + if (::recvfrom(this->channel->handle, &bf, MAX_UDP_RECV_MESSAGE, 0, &remoteAddr, &remoteAddrLen) == SCK_SOCKET_ERROR) { throwWithCode("recvfrom failed while identifying the target"); } this->remoteAddress = convert(remoteAddr); diff --git a/TODO b/TODO new file mode 100644 index 00000000..e70ff941 --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ +mettere autorship in header +throw sempre di Error e non runtime generica +make attribute const when possible +inline getter when possible +use #include <> in .h and .cpp files +use ::tcp and ::udp +creare async typed socket From c8c549e9ce11ca264ae7731bcb2b2a255c96f410 Mon Sep 17 00:00:00 2001 From: andrea Date: Sat, 30 Jan 2021 00:50:16 +0100 Subject: [PATCH 008/228] simplifying cmake structure --- CMakeLists.txt | 72 +----------------- CrossSocket/CMakeLists.txt | 60 --------------- CrossSocket/cmake/Cross-SocketConfig.cmake.in | 13 ---- CrossSocket/include/Address.h | 6 +- CrossSocket/include/async/AsyncService.h | 18 +++-- CrossSocket/include/async/Service.h | 17 ++++- CrossSocket/include/typed/StringClient.h | 30 -------- CrossSocket/include/typed/TypedClient.h | 73 ------------------- CrossSocket/include/udp/UdpServer.h | 2 +- CrossSocket/src/Address.cpp | 17 +++-- CrossSocket/src/Socket.cpp | 6 +- CrossSocket/src/SocketHandler.cpp | 10 +-- CrossSocket/src/SocketHandler.h | 6 +- CrossSocket/src/async/Service.cpp | 17 +---- CrossSocket/src/typed/StringClient.cpp | 20 ----- CrossSocket/src/udp/UdpServer.cpp | 12 +-- TODO | 6 +- 17 files changed, 59 insertions(+), 326 deletions(-) delete mode 100644 CrossSocket/cmake/Cross-SocketConfig.cmake.in delete mode 100644 CrossSocket/include/typed/StringClient.h delete mode 100644 CrossSocket/include/typed/TypedClient.h delete mode 100644 CrossSocket/src/typed/StringClient.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index db6d21e4..f5fc020b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,66 +1,15 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) -# project structure by https://rix0r.nl/blog/2015/08/13/cmake-guide/ -# helpful tutorial https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/ -set(VERSION_MAJOR 0) -set(VERSION_MINOR 1) -set(VERSION_PATCH 0) -set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) +project(Cross-Socket-Prj VERSION ${VERSION}) option(LIB_OPT "Compile shared libraries (ON) or static (OFF)" OFF) option(BUILD_SAMPLES "Build the samples showing how to use EFG" ON) -project(Stream-Socket-Prj VERSION ${VERSION}) - -# Must use GNUInstallDirs to install libraries into correct -# locations on all platforms. -include(GNUInstallDirs) -# for version.cmake generator -include(CMakePackageConfigHelpers) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -# Avoid linux warnings: Cannot create package registry file -set(CMAKE_EXPORT_NO_PACKAGE_REGISTRY ON) - -# system information printout -cmake_host_system_information(RESULT NUMBER_OF_PHYSICAL_CORES QUERY NUMBER_OF_PHYSICAL_CORES) -cmake_host_system_information(RESULT OS_PLATFORM QUERY OS_PLATFORM) -cmake_host_system_information(RESULT IS_64BIT QUERY IS_64BIT) -message(STATUS "OS_PLATFORM:${OS_PLATFORM}, NUMBER_OF_PHYSICAL_CORES:${NUMBER_OF_PHYSICAL_CORES} (64BIT=${IS_64BIT})") -cmake_host_system_information(RESULT OS_NAME QUERY OS_NAME) -cmake_host_system_information(RESULT OS_RELEASE QUERY OS_RELEASE) -cmake_host_system_information(RESULT OS_VERSION QUERY OS_VERSION) -message(STATUS "OS:${OS_NAME} ${OS_RELEASE} ${OS_VERSION}") -# end of system information printout - -option(DEBUG "Debug cmake build" ON) -option(DEBUG_LOG "Application Debug Logging" ON) - # only windows specific stuff if(WIN32) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) endif() -if(DEBUG_LOG) -add_definitions( - # This will set the spd preprocessor log level to trace - -DSPDLOG_ACTIVE_LEVEL=0 - # Activate the preprocessor debug and trace logging calls - -DSPDLOG_DEBUG_ON - -DSPDLOG_TRACE_ON -) -endif() - -# allow creation of custom file sinks -add_definitions(-DSPDLOG_NO_FINAL) - -if(NOT WIN32) - # add compiler flags - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas" CACHE STRING "" FORCE) -endif() - # set macro-directory and find scripts set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") @@ -76,22 +25,3 @@ if(BUILD_SAMPLES) add_subdirectory(Samples) endif() -# write a config file with version information for complete framework -set(FRAMEWORK_NAME "CrossSocket") -write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/${FRAMEWORK_NAME}ConfigVersion.cmake - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion -) -set(INSTALL_CONFIGDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${FRAMEWORK_NAME}") -#Install the config, configversion and custom find modules -install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/${FRAMEWORK_NAME}ConfigVersion.cmake - DESTINATION ${INSTALL_CONFIGDIR} -) -install(FILES - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${FRAMEWORK_NAME}Config.cmake.in - DESTINATION ${INSTALL_CONFIGDIR} - RENAME ${FRAMEWORK_NAME}Config.cmake -) - diff --git a/CrossSocket/CMakeLists.txt b/CrossSocket/CMakeLists.txt index 61f002a7..13373cab 100644 --- a/CrossSocket/CMakeLists.txt +++ b/CrossSocket/CMakeLists.txt @@ -31,63 +31,3 @@ if(WIN32) ) endif() -set(INSTALL_CONFIGDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") - -# 'make install' to the correct locations (provided by GNUInstallDirs). -install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}-targets - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${COMPONENT_NAME}_development - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${COMPONENT_NAME}_runtime NAMELINK_SKIP - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${COMPONENT_NAME}_runtime -) - -if(MSVC) - install ( FILES ${CMAKE_CURRENT_BINARY_DIR}/Debug/${PROJECT_NAME}.pdb - DESTINATION ${CMAKE_INSTALL_BINDIR} - CONFIGURATIONS Debug - OPTIONAL -) -endif(MSVC) - -install(DIRECTORY Header/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - COMPONENT ${COMPONENT_NAME}_development - FILES_MATCHING PATTERN "*.h" -) - -#Export the targets to a script -install(EXPORT ${PROJECT_NAME}-targets - FILE - ${PROJECT_NAME}Targets.cmake - DESTINATION - ${INSTALL_CONFIGDIR} -) - -#Create a ConfigVersion.cmake file -write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake - VERSION ${PROJECT_VERSION} - COMPATIBILITY AnyNewerVersion -) -configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/cmake/${PROJECT_NAME}Config.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake - INSTALL_DESTINATION ${INSTALL_CONFIGDIR} -) - -#Install the config, configversion and custom find modules -install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake - DESTINATION ${INSTALL_CONFIGDIR} -) - -############################################## -## Exporting from the build tree - -export(EXPORT ${PROJECT_NAME}-targets - FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake -) - -#Register package in user's package registry -export(PACKAGE ${PROJECT_NAME}) diff --git a/CrossSocket/cmake/Cross-SocketConfig.cmake.in b/CrossSocket/cmake/Cross-SocketConfig.cmake.in deleted file mode 100644 index 4fc16a08..00000000 --- a/CrossSocket/cmake/Cross-SocketConfig.cmake.in +++ /dev/null @@ -1,13 +0,0 @@ -get_filename_component(CROSSSOCK_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) -set(CROSSSOCK_PROJECT_NAME Cross-Socket) -list(APPEND CMAKE_MODULE_PATH ${STREAMSOCK_CMAKE_DIR}) -include(CMakeFindDependencyMacro) - -list(REMOVE_AT CMAKE_MODULE_PATH -1) - -if(NOT TARGET SCK::${CROSSSOCK_PROJECT_NAME}) - include("${CROSSSOCK_CMAKE_DIR}/${CROSSSOCK_PROJECT_NAME}Targets.cmake") - set_property(TARGET SCK::${CROSSSOCK_PROJECT_NAME} APPEND PROPERTY VERSION ${${CROSSSOCK_PROJECT_NAME}_VERSION}) -endif() - -set(${CROSSSOCK_PROJECT_NAME}_LIBRARIES ${CROSSSOCK_PROJECT_NAME}) diff --git a/CrossSocket/include/Address.h b/CrossSocket/include/Address.h index d0464751..50f3aaa6 100644 --- a/CrossSocket/include/Address.h +++ b/CrossSocket/include/Address.h @@ -49,9 +49,9 @@ namespace sck { private: Address(const std::string& host, const std::uint16_t& port, const Family& family); - const std::string host; - const std::uint16_t port; - const Family family; + std::string host; + std::uint16_t port; + Family family; }; bool operator==(const Address& lhs, const Address& rhs); diff --git a/CrossSocket/include/async/AsyncService.h b/CrossSocket/include/async/AsyncService.h index 5d31a42a..fbf0c2b3 100644 --- a/CrossSocket/include/async/AsyncService.h +++ b/CrossSocket/include/async/AsyncService.h @@ -18,9 +18,7 @@ namespace sck::async { class AsyncDecorator { public: virtual ~AsyncDecorator() { - if(this->wrapped->isOpen()) { - this->close(); - } + this->close(); }; inline void resetListener(Listener* listener) { @@ -29,17 +27,21 @@ namespace sck::async { }; inline void resetErrorListener(ErrorListener* listener) { - this->wrapped->resetErrorListener(Listener); + this->wrapped->resetErrorListener(listener); }; inline void open(const std::chrono::milliseconds& timeout = std::chrono::milliseconds(0)) { - this->wrapped->open(timeout); - this->service = this->make_service(); + if(!this->wrapped->isOpen()){ + this->wrapped->open(timeout); + this->service = this->make_service(); + } }; inline void close() { - this->wrapped->close(); - this->service.reset(); + if(this->wrapped->isOpen()){ + this->wrapped->close(); + this->service.reset(); + } }; inline bool isOpen() const { return this->wrapped->isOpen(); } diff --git a/CrossSocket/include/async/Service.h b/CrossSocket/include/async/Service.h index 5db351a7..9f10f155 100644 --- a/CrossSocket/include/async/Service.h +++ b/CrossSocket/include/async/Service.h @@ -25,9 +25,20 @@ namespace sck::async { void resetErrorListener(ErrorListener* listener); - private: - class Barrier; - std::unique_ptr barrier = std::make_unique(); + private: + class Barrier { + public: + Barrier() { this->stopWait = false; }; + + void wait(); + + private: + std::uint8_t queue = 0; + std::mutex queueMtx; + std::condition_variable notification; + std::atomic_bool stopWait; + }; + Barrier barrier = {}; ErrorListener* listener; std::mutex listenerMtx; diff --git a/CrossSocket/include/typed/StringClient.h b/CrossSocket/include/typed/StringClient.h deleted file mode 100644 index 17d84e1a..00000000 --- a/CrossSocket/include/typed/StringClient.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_STRINGCLIENT_H_ -#define _CROSS_SOCKET_STRINGCLIENT_H_ - -#include "TypedClient.h" -#include - -namespace sck { - /** - * @brief A typed client exchanging as messages strings. - */ - class StringClient : public TypedClient { - public: - explicit StringClient(std::unique_ptr client) - : TypedClient(std::move(client)) { - }; - - bool send(const std::string& mex) final; - private: - bool decode(std::string& toParse) final; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/typed/TypedClient.h b/CrossSocket/include/typed/TypedClient.h deleted file mode 100644 index 9875b08e..00000000 --- a/CrossSocket/include/typed/TypedClient.h +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TYPEDCLIENT_H_ -#define _CROSS_SOCKET_TYPEDCLIENT_H_ - -#include "SocketClient.h" -#include - -namespace sck { - /** - * @brief A typed client exchanges typed message with another peer. - * A typed connection consumes a SocketClient, which must be outside created - * and captured in the c'tor. - */ - template - class TypedClient { - public: - TypedClient(const TypedClient&) = delete; - TypedClient& operator=(const TypedClient&) = delete; - - inline void open(){ this->client->open(); }; - - inline void close(){ this->client->close(); }; - - inline bool isConnected() const { return this->client->isConnected(); } - - /** - * @brief returns true if the message was successfully sent. - * @param[in] the typed message to send - */ - virtual bool send(const OutgoingMessage& mex) = 0; - - /** - * @brief receive the required number of bytes, in order to later decode it - * into a typed message. Since the operation does not succeed, an empty - * smart pointer is returned. - * @param[in] the maximal size of the message to receive - * @param[in] the timeout to consider for receiving the message - */ - std::unique_ptr receive(const std::size_t& expectedMaxSize, const std::chrono::milliseconds& timeout = std::chrono::milliseconds(0)){ - this->buffer.resize(expectedMaxSize); - std::size_t recvBytes = this->client->receive(this->buffer.data(), expectedMaxSize); - if(recvBytes != this->buffer.size()){ - this->buffer.resize(recvBytes); - } - std::unique_ptr mex = std::make_unique(); - if(!this->decode(*mex.get())){ - mex.reset(); - } - return mex; - }; - protected: - explicit TypedClient(std::unique_ptr client) - : client(std::move(client)) { - if(nullptr == this->client){ - throw std::runtime_error("client cannot be empty"); - } - }; - - virtual bool decode(IncomingMessage& toParse) = 0; - - std::unique_ptr client; - - std::vector buffer; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/udp/UdpServer.h b/CrossSocket/include/udp/UdpServer.h index 45265f6b..06360e22 100644 --- a/CrossSocket/include/udp/UdpServer.h +++ b/CrossSocket/include/udp/UdpServer.h @@ -8,7 +8,7 @@ #ifndef _CROSS_SOCKET_UDPSERVER_H_ #define _CROSS_SOCKET_UDPSERVER_H_ -#include +#include namespace sck::udp { /** diff --git a/CrossSocket/src/Address.cpp b/CrossSocket/src/Address.cpp index 99c8a258..71a839fe 100644 --- a/CrossSocket/src/Address.cpp +++ b/CrossSocket/src/Address.cpp @@ -9,8 +9,8 @@ #include "SocketHandler.h" namespace sck { - static const inline std::string LOCALHOST_IPv4 = "127.0.0.1"; - static const inline std::string LOCALHOST_IPv6 = "::1"; + static const std::string LOCALHOST_IPv4 = "127.0.0.1"; + static const std::string LOCALHOST_IPv6 = "::1"; Address::Address(const std::string& host, const std::uint16_t& port, const Family& family) : host(host) @@ -20,13 +20,14 @@ namespace sck { AddressPtr Address::create(const std::string& host, const std::uint16_t& port) { //try to resolve the address as an ipv4 - AddressPtr addr = std::make_unique
(host, port, sck::Family::IP_V4); - if (resolveIPv4(*addr)) { + AddressPtr addr; + addr.reset(new Address(host, port, sck::Family::IP_V4)); + if (nullptr == convertIpv4(*addr)) { return addr; } //try to resolve the address as an ipv6 - addr = std::make_unique
(host, port, sck::Family::IP_V6); - if (resolveIPv6(*addr)) { + addr.reset(new Address(host, port, sck::Family::IP_V6)); + if (nullptr == convertIpv6(*addr)) { return addr; } return nullptr; @@ -35,9 +36,9 @@ namespace sck { AddressPtr Address::createLocalHost(const std::uint16_t& port, const Family& protocolType) { switch (protocolType) { case Family::IP_V4: - return std::make_unique
(LOCALHOST_IPv4, port, sck::Family::IP_V4); + return std::unique_ptr
(new Address(LOCALHOST_IPv4, port, sck::Family::IP_V4)); case Family::IP_V6: - return std::make_unique
(LOCALHOST_IPv6, port, sck::Family::IP_V6); + return std::unique_ptr
(new Address(LOCALHOST_IPv6, port, sck::Family::IP_V6)); default: return nullptr; } diff --git a/CrossSocket/src/Socket.cpp b/CrossSocket/src/Socket.cpp index 9d274b2f..56256ca6 100644 --- a/CrossSocket/src/Socket.cpp +++ b/CrossSocket/src/Socket.cpp @@ -40,7 +40,7 @@ namespace sck { this->channel = std::make_unique(); } - std::atomic_bool stopWait = false; + std::atomic_bool stopWait(false); auto openSteps = [this, &stopWait]() { try { this->initHandle(); @@ -100,7 +100,7 @@ namespace sck { SocketAddressIn_t addr; ::memset(&addr, 0, sizeof(SocketAddressIn_t)); addr.sin_family = AF_INET; - addr.sin_port = ::htons(port); + addr.sin_port = htons(port); #ifdef _WIN32 addr.sin_addr.s_addr = ADDR_ANY; #else @@ -117,7 +117,7 @@ namespace sck { addr.sin6_family = AF_INET6; addr.sin6_flowinfo = 0; addr.sin6_addr = IN6ADDR_ANY_INIT; // apparently, there is no such a cross-system define for ipv4 addresses - addr.sin6_port = ::htons(port); + addr.sin6_port = htons(port); if (::bind(this->channel->handle, reinterpret_cast(&addr), sizeof(SocketAddressIn6_t)) == SCK_SOCKET_ERROR) { throwWithCode("can't bind localhost on port: " + std::to_string(port)); } diff --git a/CrossSocket/src/SocketHandler.cpp b/CrossSocket/src/SocketHandler.cpp index 84b35df2..bee04fd1 100644 --- a/CrossSocket/src/SocketHandler.cpp +++ b/CrossSocket/src/SocketHandler.cpp @@ -76,7 +76,7 @@ namespace sck { if (!tryConversion(*resolved, address)) { return nullptr; } - resolved->sin_port = ::htons(address.getPort()); + resolved->sin_port = htons(address.getPort()); return resolved; } return nullptr; @@ -127,13 +127,13 @@ namespace sck { if (!tryConversion(*resolved, address)) { return nullptr; } - resolved->sin6_port = ::htons(address.getPort()); + resolved->sin6_port = htons(address.getPort()); return resolved; } return nullptr; } - AddressPtr convert(SocketAddress_t& address) { + AddressPtr convert(const SocketAddress_t& address) { // refer to https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu std::string ip; std::uint16_t port; @@ -141,7 +141,7 @@ namespace sck { // ipv4 address // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to an ip that has no sense ip = std::string(::inet_ntoa(reinterpret_cast(&address)->sin_addr)); - port = ::ntohs(reinterpret_cast(&address)->sin_port); + port = ntohs(reinterpret_cast(&address)->sin_port); } else { // ipv6 address @@ -150,7 +150,7 @@ namespace sck { ::memset(temp, 0, INET6_ADDRSTRLEN); ::inet_ntop(address.sa_family, &address, temp, INET6_ADDRSTRLEN); ip = std::string(temp, INET6_ADDRSTRLEN); - port = ::ntohs(reinterpret_cast(&address)->sin6_port); + port = ntohs(reinterpret_cast(&address)->sin6_port); } return sck::Address::create(ip, port); } diff --git a/CrossSocket/src/SocketHandler.h b/CrossSocket/src/SocketHandler.h index b500c732..8fe0df2a 100644 --- a/CrossSocket/src/SocketHandler.h +++ b/CrossSocket/src/SocketHandler.h @@ -1,9 +1,7 @@ #ifndef _CROSS_SOCKET_SOCKETHANDLER_H_ #define _CROSS_SOCKET_SOCKETHANDLER_H_ -#include "../include/Address.h" -#include -#include +#include #ifdef _WIN32 #include #include @@ -79,7 +77,7 @@ namespace sck { /** * @brief Convert a SocketAddress_t into an Address */ - AddressPtr convert(SocketAddress_t& address); + AddressPtr convert(const SocketAddress_t& address); /** * @brief the integer representing a family type diff --git a/CrossSocket/src/async/Service.cpp b/CrossSocket/src/async/Service.cpp index d17e69d4..dfa32017 100644 --- a/CrossSocket/src/async/Service.cpp +++ b/CrossSocket/src/async/Service.cpp @@ -8,19 +8,6 @@ #include namespace sck::async { - class Service::Barrier { - public: - Barrier() { this->stopWait = false; }; - - void wait(); - - private: - std::uint8_t queue = 0; - std::mutex queueMtx; - std::condition_variable notification; - std::atomic_bool stopWait; - }; - void Service::Barrier::wait() { std::unique_lock lk(this->queueMtx); ++this->queue; @@ -37,7 +24,7 @@ namespace sck::async { : listener(nullptr) , loop([this, &iterativeAction](){ std::function iter(iterativeAction); - this->barrier->wait(); + this->barrier.wait(); while (this->loopLife) { try { iter(); @@ -59,7 +46,7 @@ namespace sck::async { } }) { this->loopLife = true; - this->barrier->wait(); + this->barrier.wait(); } Service::~Service() { diff --git a/CrossSocket/src/typed/StringClient.cpp b/CrossSocket/src/typed/StringClient.cpp deleted file mode 100644 index c6ee1d65..00000000 --- a/CrossSocket/src/typed/StringClient.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include - -namespace sck { - bool StringClient::send(const std::string& mex) { - return (this->client->send(mex.data(), mex.size()) == mex.size()); // check the # bytes sent is equal to the string lengths - }; - bool StringClient::decode(std::string& toParse) { - toParse.resize(this->buffer.size()); - std::memcpy(toParse.data(), this->buffer.data(), this->buffer.size()); - return true; - }; -} \ No newline at end of file diff --git a/CrossSocket/src/udp/UdpServer.cpp b/CrossSocket/src/udp/UdpServer.cpp index 476d0f2a..f0a7161b 100644 --- a/CrossSocket/src/udp/UdpServer.cpp +++ b/CrossSocket/src/udp/UdpServer.cpp @@ -7,14 +7,15 @@ #include #include "../SocketHandler.h" +#include namespace sck::udp { sck::Address getInitialAddress(const sck::Family& protocol) { if (sck::Family::IP_V6 == protocol) { - return sck::Address::Localhost(0, sck::Family::IP_V6); + return *sck::Address::createLocalHost(0, sck::Family::IP_V6); } - return sck::Address::Localhost(0, sck::Family::IP_V4); + return *sck::Address::createLocalHost(0, sck::Family::IP_V4); } UdpServer::UdpServer(const std::uint16_t& localPort, const sck::Family& protocol) @@ -36,10 +37,11 @@ namespace sck::udp { if (::recvfrom(this->channel->handle, &bf, MAX_UDP_RECV_MESSAGE, 0, &remoteAddr, &remoteAddrLen) == SCK_SOCKET_ERROR) { throwWithCode("recvfrom failed while identifying the target"); } - this->remoteAddress = convert(remoteAddr); - if (!this->remoteAddress.isValid()) { - throw std::runtime_error(this->remoteAddress.getHost() + ":" + std::to_string(this->remoteAddress.getPort()) + " is an invalid address parsed for the target"); + AddressPtr remoteConverted = convert(remoteAddr); + if(nullptr == remoteConverted) { + throw Error(remoteAddr.sa_data, " is an invalid data for udp serer remote address"); } + this->remoteAddress = *remoteConverted; this->SocketClient::openConnection(); } } \ No newline at end of file diff --git a/TODO b/TODO index e70ff941..5b7ba670 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,5 @@ -mettere autorship in header throw sempre di Error e non runtime generica make attribute const when possible inline getter when possible -use #include <> in .h and .cpp files -use ::tcp and ::udp -creare async typed socket +togliere cmake script inutili +rimettere typed connections From 0925feeaf01a451d49e3958dca6433eb37e6bc15 Mon Sep 17 00:00:00 2001 From: andrea Date: Sat, 30 Jan 2021 20:58:07 +0100 Subject: [PATCH 009/228] refactoring --- CrossSocket/include/Messanger.h | 41 +++ CrossSocket/include/MessangerConcrete.h | 40 +++ CrossSocket/include/MessangerDecorator.h | 56 +++++ CrossSocket/include/Socket.h | 40 +-- CrossSocket/include/SocketClient.h | 40 +-- CrossSocket/include/SocketConcrete.h | 55 ++++ CrossSocket/include/SocketDecorator.h | 33 +++ CrossSocket/include/SocketServer.h | 41 --- CrossSocket/include/async/AsyncClient.h | 35 --- CrossSocket/include/async/AsyncService.h | 64 ----- CrossSocket/include/async/AsyncTcpServer.h | 28 --- CrossSocket/include/async/ClientListener.h | 20 -- CrossSocket/include/async/ErrorListener.h | 22 -- CrossSocket/include/async/MessageListener.h | 20 -- CrossSocket/include/async/Service.h | 51 ---- CrossSocket/include/tcp/TcpClient.h | 7 +- CrossSocket/include/tcp/TcpServer.h | 17 +- CrossSocket/include/udp/UdpClient.h | 9 +- CrossSocket/include/udp/UdpServer.h | 6 +- CrossSocket/src/Address.cpp | 2 +- CrossSocket/src/Handler.cpp | 234 ++++++++++++++++++ .../src/{SocketHandler.h => Handler.h} | 83 ++++--- CrossSocket/src/MessangerConcrete.cpp | 61 +++++ CrossSocket/src/SocketClient.cpp | 72 +----- .../src/{Socket.cpp => SocketConcrete.cpp} | 86 +++---- CrossSocket/src/SocketDecorator.cpp | 18 ++ CrossSocket/src/SocketHandler.cpp | 223 ----------------- CrossSocket/src/SocketServer.cpp | 25 -- CrossSocket/src/async/AsyncClient.cpp | 35 --- CrossSocket/src/async/AsyncTcpServer.cpp | 27 -- CrossSocket/src/async/Service.cpp | 61 ----- CrossSocket/src/tcp/TcpClient.cpp | 13 +- CrossSocket/src/tcp/TcpServer.cpp | 38 ++- CrossSocket/src/udp/UdpClient.cpp | 14 +- CrossSocket/src/udp/UdpServer.cpp | 8 +- TODO | 6 + 36 files changed, 699 insertions(+), 932 deletions(-) create mode 100644 CrossSocket/include/Messanger.h create mode 100644 CrossSocket/include/MessangerConcrete.h create mode 100644 CrossSocket/include/MessangerDecorator.h create mode 100644 CrossSocket/include/SocketConcrete.h create mode 100644 CrossSocket/include/SocketDecorator.h delete mode 100644 CrossSocket/include/SocketServer.h delete mode 100644 CrossSocket/include/async/AsyncClient.h delete mode 100644 CrossSocket/include/async/AsyncService.h delete mode 100644 CrossSocket/include/async/AsyncTcpServer.h delete mode 100644 CrossSocket/include/async/ClientListener.h delete mode 100644 CrossSocket/include/async/ErrorListener.h delete mode 100644 CrossSocket/include/async/MessageListener.h delete mode 100644 CrossSocket/include/async/Service.h create mode 100644 CrossSocket/src/Handler.cpp rename CrossSocket/src/{SocketHandler.h => Handler.h} (64%) create mode 100644 CrossSocket/src/MessangerConcrete.cpp rename CrossSocket/src/{Socket.cpp => SocketConcrete.cpp} (59%) create mode 100644 CrossSocket/src/SocketDecorator.cpp delete mode 100644 CrossSocket/src/SocketHandler.cpp delete mode 100644 CrossSocket/src/SocketServer.cpp delete mode 100644 CrossSocket/src/async/AsyncClient.cpp delete mode 100644 CrossSocket/src/async/AsyncTcpServer.cpp delete mode 100644 CrossSocket/src/async/Service.cpp diff --git a/CrossSocket/include/Messanger.h b/CrossSocket/include/Messanger.h new file mode 100644 index 00000000..e981c7f6 --- /dev/null +++ b/CrossSocket/include/Messanger.h @@ -0,0 +1,41 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_MESSANGER_H_ +#define _CROSS_SOCKET_MESSANGER_H_ + +#include + +namespace sck { + /** + * @brief The interface providing functionalities for exchanging data + */ + template + class Messanger { + public: + Messanger() = default; + Messanger(const Messanger&) = delete; + Messanger& operator=(const Messanger&) = delete; + + virtual ~Messanger(); + + /** + * @return true if the message was completely sent + * @param[in] the message to send + */ + virtual bool send(const Send_t& message) = 0; + + /** + * @return the number of received bytes + * @param[in] the recepient + * @param[in] the timeout to consider + */ + virtual std::size_t receive(Recv_t& message, const std::chrono::milliseconds& timeout) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/MessangerConcrete.h b/CrossSocket/include/MessangerConcrete.h new file mode 100644 index 00000000..e0718ce9 --- /dev/null +++ b/CrossSocket/include/MessangerConcrete.h @@ -0,0 +1,40 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_MESSANGERCONCRETE_H_ +#define _CROSS_SOCKET_MESSANGERCONCRETE_H_ + +#include +#include +#include + +namespace sck { + /** + * @brief interface to the socket APIs + */ + class Handler; + + class MessangerConcrete + : public Messanger< + std::pair, + std::pair + > { + private: + bool send(const std::pair& message) final; + + std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; + + std::chrono::milliseconds actualTimeOut = std::chrono::milliseconds(0); + + std::shared_ptr messageChannel; + + protected: + MessangerConcrete(std::shared_ptr messageChannel); + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/MessangerDecorator.h b/CrossSocket/include/MessangerDecorator.h new file mode 100644 index 00000000..971a46bf --- /dev/null +++ b/CrossSocket/include/MessangerDecorator.h @@ -0,0 +1,56 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_MESSANGERDECORATOR_H_ +#define _CROSS_SOCKET_MESSANGERDECORATOR_H_ + +#include +#include + +namespace sck { + /** + * @brief The interface providing functionalities for exchanging data + */ + template + class MessangerDecorator { + private: + bool send(const Send_t& message) final { + this->encode(message); + this->messanger->send({this->sendBuffer.data(), this->sendBuffer.size()}); + }; + + std::size_t receive(Recv_t& message, const std::chrono::milliseconds& timeout) final { + auto recvBytes = this->messanger->receive({this->recvBuffer.data(), this->recvBuffer.capacity()}, timeout); + if(recvBytes != this->recvBuffer.capacity()) { + this->recvBuffer.resize(recvBytes); + } + this->decode(message); + return recvBytes; + }; + + protected: + MessangerDecorator(MessangerConcrete& mess, const std::size_t recvCapacity) { + this->messanger = &mess; + this->recvBuffer.resize(recvCapacity); + }; + MessangerConcrete* messanger; + + /** + * @brief write the message into sendBuffer, as a series of raw bytes + */ + virtual encode(const Send_t& message) = 0; + std::vector sendBuffer; + + /** + * @brief convert the content of recvBuffer as a received message + */ + virtual decode(Recv_t& message) = 0; + std::vector recvBuffer; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/Socket.h b/CrossSocket/include/Socket.h index a0ffccb7..57eea745 100644 --- a/CrossSocket/include/Socket.h +++ b/CrossSocket/include/Socket.h @@ -8,55 +8,25 @@ #ifndef _CROSS_SOCKET_SOCKET_H_ #define _CROSS_SOCKET_SOCKET_H_ -#include -#include #include -namespace sck { - /** - * @brief interface to the socket APIs - */ - class SocketHandler; - +namespace sck { /** * @brief The interface every socket must derive from. */ class Socket { public: + Socket() = default; Socket(const Socket&) = delete; Socket& operator=(const Socket&) = delete; virtual ~Socket(); - void open(const std::chrono::milliseconds& timeout = std::chrono::milliseconds(0)); - - void close(); - - inline bool isOpen() const { return this->opened; } - - protected: - Socket(); - /** - * @param[in] an already created handler to steal - */ - Socket(std::unique_ptr channel); - - virtual void openConnection() = 0; - - virtual void closeConnection(); - - std::unique_ptr channel; - - virtual void initHandle() = 0; - - /** - * @brief The value should be deduced from object to object - */ - virtual sck::Family getFamily() = 0; + virtual void open(const std::chrono::milliseconds& timeout) = 0; - void bindToPort(const std::uint16_t& port); + virtual void close() = 0; - std::atomic_bool opened; + virtual bool isOpen() const = 0; }; } diff --git a/CrossSocket/include/SocketClient.h b/CrossSocket/include/SocketClient.h index 5c8061c8..11e6d029 100644 --- a/CrossSocket/include/SocketClient.h +++ b/CrossSocket/include/SocketClient.h @@ -8,38 +8,21 @@ #ifndef _CROSS_SOCKET_SOCKETCLIENT_H_ #define _CROSS_SOCKET_SOCKETCLIENT_H_ -#include "Socket.h" +#include +#include namespace sck { /** * @brief this is the abstract base class for every implementation of a concrete socket client (tcp or udp) */ class SocketClient - : public Socket { + : public SocketConcrete + , public MessangerConcrete { public: - ~SocketClient() override = default; - /** * @brief returns the address of the remote entity connected with this socket */ - const sck::Address& getRemoteAddress() const; - - /** - * @brief returns the number of bytes actually sent. - * @param[in] the buffer storing the bytes to send - * @param[in] the buffer size - */ - std::size_t send(const char* buffer, const std::size_t& length); - - /** - * @brief returns the number of bytes actually received until the timeout fires. - * When passing a timeout equal to 0, a blocking receive is started, - * otherwise the baytes to receive are waited till timeout - * @param[in] the buffer storing the bytes to receive - * @param[in] the buffer capacity - * @param[in] the timeout to consider - */ - std::size_t receive(char* buffer, const std::size_t& length, const std::chrono::milliseconds& timeout = std::chrono::milliseconds(0)); + inline const sck::Address& getRemoteAddress() const { return this->remoteAddress; }; protected: /** @@ -47,14 +30,15 @@ namespace sck { */ SocketClient(const sck::Address& remoteAddress); /** - * @param[in] the address of the server to hit + * @param[in] the remote address already connected * @param[in] an already created handler to steal */ - SocketClient(const sck::Address& remoteAddress, std::unique_ptr channel); - - sck::Family getFamily() final; + SocketClient(const sck::Address& remoteAddress, std::shared_ptr channel); - void openConnection() override; + /** + * @brief connect is internally called + */ + void openSpecific() override; /** * @brief address of the server connected to this socket @@ -62,7 +46,7 @@ namespace sck { sck::Address remoteAddress; private: - std::chrono::milliseconds actualTimeOut; + inline sck::Family getFamily() const final { return this->remoteAddress.getFamily(); }; }; } diff --git a/CrossSocket/include/SocketConcrete.h b/CrossSocket/include/SocketConcrete.h new file mode 100644 index 00000000..1518f445 --- /dev/null +++ b/CrossSocket/include/SocketConcrete.h @@ -0,0 +1,55 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_SOCKETCONCRETE_H_ +#define _CROSS_SOCKET_SOCKETCONCRETE_H_ + +#include +#include + +namespace sck { + enum Protocol { UDP, TCP }; + + /** + * @brief interface to the socket APIs + */ + class Handler; + + /** + * @brief The interface every socket must derive from. + */ + class SocketConcrete : public Socket { + public: + ~SocketConcrete(); + + private: + void open(const std::chrono::milliseconds& timeout) final; + + void close() final; + + bool isOpen() const; + + protected: + SocketConcrete(std::shared_ptr channel); + + virtual void openSpecific() = 0; + + virtual void closeSpecific(); + + /** + * @brief These values should be internally deduced from object to object + */ + virtual sck::Family getFamily() const = 0; + virtual sck::Protocol getProtocol() const = 0; + + void bindToPort(const std::uint16_t& port); + + std::shared_ptr channel; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/SocketDecorator.h b/CrossSocket/include/SocketDecorator.h new file mode 100644 index 00000000..2f56e9de --- /dev/null +++ b/CrossSocket/include/SocketDecorator.h @@ -0,0 +1,33 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_SOCKETDECORATOR_H_ +#define _CROSS_SOCKET_SOCKETDECORATOR_H_ + +#include +#include + +namespace sck { + /** + * @brief The interface every socket must derive from. + */ + class SocketDecorator : public Socket { + private: + inline void open(const std::chrono::milliseconds& timeout) override { this->wrapped->open(timeout); }; + + inline void close() override { this->wrapped->close(); }; + + inline bool isOpen() const override { return this->wrapped->isOpen(); }; + + protected: + SocketDecorator(std::unique_ptr wrapped); + + std::unique_ptr wrapped; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/SocketServer.h b/CrossSocket/include/SocketServer.h deleted file mode 100644 index 3b314203..00000000 --- a/CrossSocket/include/SocketServer.h +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SOCKETSERVER_H_ -#define _CROSS_SOCKET_SOCKETSERVER_H_ - -#include "Socket.h" - -namespace sck { - /** - @brief this is the abstract base class for every implementation of a socket server - */ - class SocketServer - : public Socket { - public: - ~SocketServer() override = default; - /** - * @brief returns the port this server tries to bind when opened - */ - std::uint16_t getPort() const; - - protected: - /** - * @param[in] the port this server should reserve - * @param[in] the protocol family of the client to accept - */ - SocketServer(const std::uint16_t& port, const sck::Family& family); - - sck::Family getFamily() final; - - private: - std::uint16_t port; - sck::Family protocol; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/async/AsyncClient.h b/CrossSocket/include/async/AsyncClient.h deleted file mode 100644 index 05ec2026..00000000 --- a/CrossSocket/include/async/AsyncClient.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ASYNCCLIENT_H -#define _CROSS_SOCKET_ASYNCCLIENT_H - -#include -#include -#include -#include - -namespace sck::async { - class AsyncClient : public AsyncDecorator { - public: - AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); - - inline std::size_t send(const char* buffer, const std::size_t& length) { - return this->wrapped->send(buffer, length); - }; - - // std::size_t receive(char* buffer, const std::size_t& length, const std::chrono::milliseconds& timeout = std::chrono::milliseconds(0)); - - private: - std::vector buffer; - class ReceiveService; - - std::unique_ptr make_service() final; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/async/AsyncService.h b/CrossSocket/include/async/AsyncService.h deleted file mode 100644 index fbf0c2b3..00000000 --- a/CrossSocket/include/async/AsyncService.h +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ASYNCSERVICE_H -#define _CROSS_SOCKET_ASYNCSERVICE_H - -#include -#include -#include -#include - -namespace sck::async { - template - class AsyncDecorator { - public: - virtual ~AsyncDecorator() { - this->close(); - }; - - inline void resetListener(Listener* listener) { - std::lock_guard lk(this->listenerMtx); - this->listener = listener; - }; - - inline void resetErrorListener(ErrorListener* listener) { - this->wrapped->resetErrorListener(listener); - }; - - inline void open(const std::chrono::milliseconds& timeout = std::chrono::milliseconds(0)) { - if(!this->wrapped->isOpen()){ - this->wrapped->open(timeout); - this->service = this->make_service(); - } - }; - - inline void close() { - if(this->wrapped->isOpen()){ - this->wrapped->close(); - this->service.reset(); - } - }; - - inline bool isOpen() const { return this->wrapped->isOpen(); } - - protected: - AsyncDecorator(std::unique_ptr client) - : wrapped(std::move(client)) { - }; - - virtual std::unique_ptr make_service() = 0; - - Listener* listener = nullptr; - std::mutex listenerMtx; - - std::unique_ptr wrapped; - std::unique_ptr service; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/async/AsyncTcpServer.h b/CrossSocket/include/async/AsyncTcpServer.h deleted file mode 100644 index 6096ec05..00000000 --- a/CrossSocket/include/async/AsyncTcpServer.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ASYNCTCPSERVER_H -#define _CROSS_SOCKET_ASYNCTCPSERVER_H - -#include -#include -#include - -namespace sck::async { - class AsyncTcpServer : public AsyncDecorator { - public: - AsyncTcpServer(std::unique_ptr server) - : AsyncDecorator(std::move(server)) {}; - - private: - class AcceptanceService; - - std::unique_ptr make_service() final; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/async/ClientListener.h b/CrossSocket/include/async/ClientListener.h deleted file mode 100644 index 95257cef..00000000 --- a/CrossSocket/include/async/ClientListener.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_CLIENTLISTENER_H_ -#define _CROSS_SOCKET_CLIENTLISTENER_H_ - -#include - -namespace sck::async { - class ClientListener { - public: - virtual void handle(std::unique_ptr client) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/async/ErrorListener.h b/CrossSocket/include/async/ErrorListener.h deleted file mode 100644 index 1484fa03..00000000 --- a/CrossSocket/include/async/ErrorListener.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ERRORLISTENER_H_ -#define _CROSS_SOCKET_ERRORLISTENER_H_ - -#include - -namespace sck::async { - class ErrorListener { - public: - virtual void handle(const Error& error) = 0; - - virtual void handle(const std::exception& error) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/async/MessageListener.h b/CrossSocket/include/async/MessageListener.h deleted file mode 100644 index 8f058d17..00000000 --- a/CrossSocket/include/async/MessageListener.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_MESSAGELISTENER_H_ -#define _CROSS_SOCKET_MESSAGELISTENER_H_ - -#include - -namespace sck::async { - class MessageListener { - public: - virtual void handle(const char* message, const std::size_t& messageLenght) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/async/Service.h b/CrossSocket/include/async/Service.h deleted file mode 100644 index 9f10f155..00000000 --- a/CrossSocket/include/async/Service.h +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SERVICE_H_ -#define _CROSS_SOCKET_SERVICE_H_ - -#include -#include -#include -#include -#include -#include - -namespace sck::async { - class Service { - public: - // start - Service(const std::function& iterativeAction); - // stop - ~Service(); - - void resetErrorListener(ErrorListener* listener); - - private: - class Barrier { - public: - Barrier() { this->stopWait = false; }; - - void wait(); - - private: - std::uint8_t queue = 0; - std::mutex queueMtx; - std::condition_variable notification; - std::atomic_bool stopWait; - }; - Barrier barrier = {}; - - ErrorListener* listener; - std::mutex listenerMtx; - - std::atomic_bool loopLife; - std::thread loop; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/tcp/TcpClient.h b/CrossSocket/include/tcp/TcpClient.h index b5de5299..940db0c4 100644 --- a/CrossSocket/include/tcp/TcpClient.h +++ b/CrossSocket/include/tcp/TcpClient.h @@ -23,13 +23,12 @@ namespace sck::tcp { * @param[in] the address of the server to reach */ explicit TcpClient(const sck::Address& remoteAddress); - - ~TcpClient() override = default; protected: - explicit TcpClient(const sck::Address& remoteAddress, std::unique_ptr channel); + TcpClient(const sck::Address& remoteAddress, std::shared_ptr channel); - void initHandle() final; + private: + inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; }; } diff --git a/CrossSocket/include/tcp/TcpServer.h b/CrossSocket/include/tcp/TcpServer.h index 6d06d46c..52c887f6 100644 --- a/CrossSocket/include/tcp/TcpServer.h +++ b/CrossSocket/include/tcp/TcpServer.h @@ -8,8 +8,7 @@ #ifndef _CROSS_SOCKET_TCPSERVER_H_ #define _CROSS_SOCKET_TCPSERVER_H_ -#include -#include "SocketClient.h" +#include namespace sck::tcp { /** @@ -17,7 +16,7 @@ namespace sck::tcp { * When calling open, the server binds and listen to the port, in order to be later ready to accept clients */ class TcpServer - : public SocketServer { + : public SocketConcrete { public: /** * @param[in] the port to reserve @@ -25,8 +24,6 @@ namespace sck::tcp { */ explicit TcpServer(const std::uint16_t& port, const Family& family = Family::IP_V4); - ~TcpServer() override = default; - /** * @brief Wait for a new client to ask the connection and after accepting it * returns an interface to use for exchanging data to and from the accepted clients. @@ -34,10 +31,14 @@ namespace sck::tcp { */ std::unique_ptr acceptClient(); - protected: - void initHandle() final; + private: + void openSpecific() override; + + inline sck::Family getFamily() const final { return this->protocol; }; + inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; - void openConnection() override; + std::uint16_t port; + sck::Family protocol; }; } diff --git a/CrossSocket/include/udp/UdpClient.h b/CrossSocket/include/udp/UdpClient.h index 6e825a8c..862cefe7 100644 --- a/CrossSocket/include/udp/UdpClient.h +++ b/CrossSocket/include/udp/UdpClient.h @@ -28,14 +28,13 @@ namespace sck::udp { */ UdpClient(const sck::Address& remoteAddress, const std::uint16_t& localPort = 0); - ~UdpClient() override = default; - protected: - void initHandle() final; + std::uint16_t port; - void openConnection() override; + void openSpecific() override; - std::uint16_t port; + private: + inline sck::Protocol getProtocol() const final { return Protocol::UDP; }; }; } diff --git a/CrossSocket/include/udp/UdpServer.h b/CrossSocket/include/udp/UdpServer.h index 06360e22..d603eb7a 100644 --- a/CrossSocket/include/udp/UdpServer.h +++ b/CrossSocket/include/udp/UdpServer.h @@ -27,10 +27,8 @@ namespace sck::udp { */ UdpServer(const std::uint16_t& localPort, const sck::Family& protocol = sck::Family::IP_V4); - ~UdpServer() override = default; - - protected: - void openConnection() final; + private: + void openSpecific() final; }; } diff --git a/CrossSocket/src/Address.cpp b/CrossSocket/src/Address.cpp index 71a839fe..98b5bad3 100644 --- a/CrossSocket/src/Address.cpp +++ b/CrossSocket/src/Address.cpp @@ -6,7 +6,7 @@ **/ #include -#include "SocketHandler.h" +#include "Handler.h" namespace sck { static const std::string LOCALHOST_IPv4 = "127.0.0.1"; diff --git a/CrossSocket/src/Handler.cpp b/CrossSocket/src/Handler.cpp new file mode 100644 index 00000000..acfecce2 --- /dev/null +++ b/CrossSocket/src/Handler.cpp @@ -0,0 +1,234 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include "Handler.h" +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace sck { +#ifdef _WIN32 + std::shared_ptr SocketHandler::WSAManager::managerInstance; + + std::shared_ptr SocketHandler::WSAManager::getManager() { + if (nullptr == managerInstance) { + managerInstance = std::shared_ptr(new SocketHandler::WSAManager()); //not possible to call make_shared because c'tor is private + } + return managerInstance; + } + + SocketHandler::WSAManager::WSAManager() { + WSADATA wsa; + WSAStartup(MAKEWORD(2, 0), &wsa); + } + + SocketHandler::WSAManager::~WSAManager() { + WSACleanup(); + } +#endif + + int getLastError() { +#ifdef _WIN32 + return WSAGetLastError(); +#else + return static_cast(errno); +#endif + } + + void throwWithCode(const std::string& what){ + std::stringstream s; + s << what << " , error code: " << getLastError(); + throw Error(s.str()); + } + + std::unique_ptr convertIpv4(const sck::Address& address) { + auto tryConversion = [](SocketAddressIn_t& recipient, const sck::Address& address) -> bool { + #if !defined(_WIN32) + in_addr ia; + if (1 == ::inet_pton(AF_INET, address.getHost().c_str(), &ia)) { + recipient.sin_addr.s_addr = ia.s_addr; + return true; + } + #endif + + addrinfo* res, hints = addrinfo{}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + int gai_err = ::getaddrinfo(address.getHost().c_str(), NULL, &hints, &res); + + #if !defined(_WIN32) + if (gai_err == EAI_SYSTEM) { + return false; + } + #endif + if (gai_err != 0) { + return false; + } + + auto ipv4 = reinterpret_cast(res->ai_addr); + ::freeaddrinfo(res); + recipient.sin_addr.s_addr = ipv4->sin_addr.s_addr; + return true; + }; + + if (sck::Family::IP_V4 == address.getFamily()) { + std::unique_ptr resolved = std::make_unique(); + // set everything to 0 first + ::memset(&(*resolved), 0, sizeof(SocketAddressIn_t)); + resolved->sin_family = AF_INET; + if (!tryConversion(*resolved, address)) { + return nullptr; + } + resolved->sin_port = htons(address.getPort()); + return resolved; + } + return nullptr; + } + + std::unique_ptr convertIpv6(const sck::Address& address) { + auto tryConversion = [](SocketAddressIn6_t& recipient, const sck::Address& address) -> bool { + #if !defined(_WIN32) + in6_addr ia; + if (1 == ::inet_pton(AF_INET6, address.getHost().c_str(), &ia)) { + recipient.sin6_addr = ia; + return true; + } + #endif + + addrinfo* res, hints = addrinfo{}; + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + + int gai_err = ::getaddrinfo(address.getHost().c_str(), NULL, &hints, &res); + + #if !defined(_WIN32) + if (gai_err == EAI_SYSTEM) { + return false; + } + #endif + if (gai_err != 0) { + return false; + } + + auto ipv6 = reinterpret_cast(res->ai_addr); + ::freeaddrinfo(res); + recipient.sin6_addr = ipv6->sin6_addr; + return true; + }; + + if (sck::Family::IP_V6 == address.getFamily()) { + std::unique_ptr resolved = std::make_unique(); + // set everything to 0 first + ::memset(&(*resolved), 0, sizeof(SocketAddressIn6_t)); + resolved->sin6_family = AF_INET6; + resolved->sin6_flowinfo = 0; + if (!tryConversion(*resolved, address)) { + return nullptr; + } + resolved->sin6_port = htons(address.getPort()); + return resolved; + } + return nullptr; + } + + AddressPtr convert(const SocketAddress_t& address) { + // refer to https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu + std::string ip; + std::uint16_t port; + if (AF_INET == address.sa_family) { + // ipv4 address + // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to an ip that has no sense + ip = std::string(::inet_ntoa(reinterpret_cast(&address)->sin_addr)); + port = ntohs(reinterpret_cast(&address)->sin_port); + } + else { + // ipv6 address + char temp[INET6_ADDRSTRLEN]; //this is the longest one + // refer to https://www.gnu.org/software/libc/manual/html_node/Host-Address-Functions.html + ::memset(temp, 0, INET6_ADDRSTRLEN); + ::inet_ntop(address.sa_family, &address, temp, INET6_ADDRSTRLEN); + ip = std::string(temp, INET6_ADDRSTRLEN); + port = ntohs(reinterpret_cast(&address)->sin6_port); + } + return sck::Address::create(ip, port); + } + + Handler::~Handler() { + if(this->opened){ + this->close(); + } + } + + Handler::Handler() + : opened(false) + , socketId(SCK_INVALID_SOCKET) { +#ifdef _WIN32 + this->manager = WSAManager::getManager(); +#endif + } + + Handler::Handler(Socket_t extOpendSocket) + : Handler() { + this->opened = true; + this->socketId = extOpendSocket; + } + + void Handler::open(const Protocol& type, const Family& family) { + if(this->opened) return; + + auto toIntFamily = [](const Family& family) -> int { + switch (family) { + case sck::Family::IP_V4: + return AF_INET; + case sck::Family::IP_V6: + return AF_INET6; + default: + throw Error("unknown address family type"); + } + throw Error("unknown address family type"); + }; + + switch (type) { + case Protocol::TCP: + this->socketId = ::socket(toIntFamily(family), SOCK_STREAM, 0); + if (this->socketId == SCK_INVALID_SOCKET) { + this->close(); + throwWithCode("Stream socket could not be created"); + } + break; + case Protocol::UDP: + this->socketId = ::socket(toIntFamily(family), SOCK_DGRAM, 0); + if (this->socketId == SCK_INVALID_SOCKET) { + this->close(); + throwWithCode("DataGram socket could not be created"); + } + default: + throw Error("unknown protocol type"); + } + + this->opened = true; + } + + void Handler::close() { + if (!this->opened) return; +#ifdef _WIN32 + shutdown(this->socketId, 2); + closesocket(this->socketId); +#else + ::shutdown(this->socketId, SHUT_RDWR); + ::close(this->socketId); +#endif + this->opened = false; + this->socketId = SCK_INVALID_SOCKET; + } +} \ No newline at end of file diff --git a/CrossSocket/src/SocketHandler.h b/CrossSocket/src/Handler.h similarity index 64% rename from CrossSocket/src/SocketHandler.h rename to CrossSocket/src/Handler.h index 8fe0df2a..bfb5268e 100644 --- a/CrossSocket/src/SocketHandler.h +++ b/CrossSocket/src/Handler.h @@ -1,7 +1,16 @@ -#ifndef _CROSS_SOCKET_SOCKETHANDLER_H_ -#define _CROSS_SOCKET_SOCKETHANDLER_H_ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_HANDLER_H_ +#define _CROSS_SOCKET_HANDLER_H_ #include +#include +#include #ifdef _WIN32 #include #include @@ -20,14 +29,6 @@ #endif namespace sck { - - /** - * @brief returns the last error code raised by the socket API - */ - int getLastError(); - - void throwWithCode(const std::string& what); - /** * socket handle */ @@ -61,6 +62,13 @@ namespace sck { using SocketAddressIn6_t = sockaddr_in6; #endif + /** + * @brief returns the last error code raised by the socket API + */ + int getLastError(); + + void throwWithCode(const std::string& what); + /** * @brief checks the address syntax and in case * it's valid, creates the socket API representation @@ -73,63 +81,62 @@ namespace sck { * of the address */ std::unique_ptr convertIpv6(const sck::Address& address); - /** * @brief Convert a SocketAddress_t into an Address */ AddressPtr convert(const SocketAddress_t& address); - /** - * @brief the integer representing a family type - */ - int castFamily(const sck::Family& family); - /** * contains the required things to work with the socket API */ - class SocketHandler { + class Handler { public: + Handler(const Handler&) = delete; + Handler& operator=(const Handler&) = delete; + /** - * @brief an empty socket is actually created - * only after a call to SocketConnection::open this socket will be activated + * @brief a closed socket is created */ - SocketHandler(); + Handler(); + /** - * @brief the handle is transferred into the object to create + * @brief the passed socket should be already opened */ - SocketHandler(SocketHandler&& o); + Handler(Socket_t extOpendSocket); + + virtual ~Handler(); + /** - * @brief if the socket was activated, it's shut down and then closed + * @brief internally creates a new socket handler */ - virtual ~SocketHandler(); + void open(const Protocol& type, const Family& family); /** - * @brief When before calling the destroyer invalidates the socket. - * The socket cannot be used in any way after calling this method + * @brief close and shutdown the current socket handler */ void close(); + inline bool isOpen() const { return this->opened; }; - SocketHandler(const SocketHandler&) = delete; - void operator=(const SocketHandler&) = delete; + inline const Socket_t& getSocketId() const { return this->socketId; } - Socket_t handle; - - bool isActive() const; private: - bool active; + std::atomic_bool opened; + Socket_t socketId; + #ifdef _WIN32 /** * @brief When the manager is created WSAStartup is called, * when is destroyed WSACleanup is called */ - class WSAManagerSingleton { + class WSAManager { public: - ~WSAManagerSingleton(); - static std::shared_ptr getManager(); + ~WSAManager(); + static std::shared_ptr getManager(); + private: - WSAManagerSingleton(); - static std::shared_ptr managerInstance; + WSAManager(); + static std::shared_ptr managerInstance; }; - std::shared_ptr manager; + std::shared_ptr manager; #endif }; diff --git a/CrossSocket/src/MessangerConcrete.cpp b/CrossSocket/src/MessangerConcrete.cpp new file mode 100644 index 00000000..c9a73c10 --- /dev/null +++ b/CrossSocket/src/MessangerConcrete.cpp @@ -0,0 +1,61 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include "Handler.h" + +namespace sck { + MessangerConcrete::MessangerConcrete(std::shared_ptr messageChannel) { + if(nullptr == messageChannel) { + throw Error("found null channel when building MessangerConcrete"); + } + this->messageChannel = messageChannel; + } + + bool MessangerConcrete::send(const std::pair& message) { + int sentBytes = ::send(this->messageChannel->getSocketId(), message.first, static_cast(message.second), 0); + if (sentBytes == SCK_SOCKET_ERROR) { + sentBytes = 0; + throwWithCode("send failed"); + } + return (sentBytes == static_cast(message.second)); + } + + std::size_t MessangerConcrete::receive(std::pair& message, const std::chrono::milliseconds& timeout) { + if (timeout.count() != this->actualTimeOut.count()) { + //set new timeout + this->actualTimeOut = timeout; +#ifdef _WIN32 + auto tv = DWORD(this->actualTimeOut.count()); + if (setsockopt(this->messageChannel->getSocketId(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { +#else + struct timeval tv = { 0,0 }; + if (this->actualTimeOut.count() >= 1000) { + tv.tv_sec = std::chrono::duration_cast(this->actualTimeOut).count(); + } + else { + tv.tv_usec = std::chrono::duration_cast(this->actualTimeOut).count(); + } + if (::setsockopt(this->messageChannel->getSocketId(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(struct timeval)) < 0) { +#endif + throwWithCode("can't set timeout"); + } + } + + int recvBytes = ::recv(this->messageChannel->getSocketId(), message.first, static_cast(message.second), 0); + if (recvBytes == SCK_SOCKET_ERROR) { + recvBytes = 0; + throwWithCode("receive failed"); + } + if (recvBytes > message.second) { + // if here, the message received is probably corrupted + recvBytes = 0; + } + return static_cast(recvBytes); + } +} \ No newline at end of file diff --git a/CrossSocket/src/SocketClient.cpp b/CrossSocket/src/SocketClient.cpp index 765aff43..2752c8b1 100644 --- a/CrossSocket/src/SocketClient.cpp +++ b/CrossSocket/src/SocketClient.cpp @@ -6,79 +6,29 @@ **/ #include -#include "SocketHandler.h" +#include "Handler.h" namespace sck { SocketClient::SocketClient(const sck::Address& remoteAddress) - : Socket() - , remoteAddress(remoteAddress) - , actualTimeOut(std::chrono::milliseconds(0)) { + : SocketConcrete(std::make_shared()) + , MessangerConcrete(this->SocketConcrete::channel) + , remoteAddress(remoteAddress) { } - SocketClient::SocketClient(const sck::Address& remoteAddress, std::unique_ptr channel) - : Socket(std::move(channel)) - , remoteAddress(remoteAddress) - , actualTimeOut(std::chrono::milliseconds(0)) { + SocketClient::SocketClient(const sck::Address& remoteAddress, std::shared_ptr channel) + : SocketConcrete(channel) + , MessangerConcrete(this->SocketConcrete::channel) + , remoteAddress(remoteAddress) { } - const sck::Address& SocketClient::getRemoteAddress() const { - return this->remoteAddress; - } - - sck::Family SocketClient::getFamily() { - return this->remoteAddress.getFamily(); - } - - std::size_t SocketClient::send(const char* buffer, const std::size_t& bufferSize) { - int sentBytes = ::send(this->channel->handle, buffer, static_cast(bufferSize), 0); - if (sentBytes == SCK_SOCKET_ERROR) { - sentBytes = 0; - throwWithCode("send failed"); - } - return static_cast(sentBytes); - } - - std::size_t SocketClient::receive(char* buffer, const std::size_t & bufferMaxSize, const std::chrono::milliseconds& timeout) { - if (timeout.count() != this->actualTimeOut.count()) { - //set new timeout - this->actualTimeOut = timeout; -#ifdef _WIN32 - auto tv = DWORD(this->actualTimeOut.count()); - if (setsockopt(this->channel->handle, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { -#else - struct timeval tv = { 0,0 }; - if (this->actualTimeOut.count() >= 1000) { - tv.tv_sec = std::chrono::duration_cast(this->actualTimeOut).count(); - } - else { - tv.tv_usec = std::chrono::duration_cast(this->actualTimeOut).count(); - } - if (::setsockopt(this->channel->handle, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(struct timeval)) < 0) { -#endif - throwWithCode("can't set timeout"); - } - } - - int recvBytes = ::recv(this->channel->handle, buffer, static_cast(bufferMaxSize), 0); - if (recvBytes == SCK_SOCKET_ERROR) { - recvBytes = 0; - throwWithCode("receive failed"); - } - if (recvBytes > bufferMaxSize) { - // if here, the message received is probably corrupted - recvBytes = 0; - } - return static_cast(recvBytes); - } - - void SocketClient::openConnection() { + void SocketClient::openSpecific() { if (sck::Family::IP_V4 == this->getFamily()) { //v4 family auto addr = convertIpv4(this->remoteAddress); if (!addr) { throw std::runtime_error(this->remoteAddress.getHost() + ":" + std::to_string(this->remoteAddress.getPort()) + " is an invalid server address"); } - if (::connect(this->channel->handle, reinterpret_cast(&(*addr)), sizeof(SocketAddressIn_t)) == SCK_SOCKET_ERROR) { + if (::connect(this->channel->getSocketId(), reinterpret_cast(&(*addr)), sizeof(SocketAddressIn_t)) == SCK_SOCKET_ERROR) { throwWithCode("Connection can't be established"); } } @@ -88,7 +38,7 @@ namespace sck { if (!addr) { throw std::runtime_error(this->remoteAddress.getHost() + ":" + std::to_string(this->remoteAddress.getPort()) + " is an invalid server address"); } - if (::connect(this->channel->handle, reinterpret_cast(&(*addr)), sizeof(SocketAddressIn6_t)) == SCK_SOCKET_ERROR) { + if (::connect(this->channel->getSocketId(), reinterpret_cast(&(*addr)), sizeof(SocketAddressIn6_t)) == SCK_SOCKET_ERROR) { throwWithCode("Connection can't be established"); } } diff --git a/CrossSocket/src/Socket.cpp b/CrossSocket/src/SocketConcrete.cpp similarity index 59% rename from CrossSocket/src/Socket.cpp rename to CrossSocket/src/SocketConcrete.cpp index 56256ca6..f07e0783 100644 --- a/CrossSocket/src/Socket.cpp +++ b/CrossSocket/src/SocketConcrete.cpp @@ -5,8 +5,8 @@ * report any bug to andrecasa91@gmail.com. **/ -#include -#include "SocketHandler.h" +#include +#include "Handler.h" #include #include @@ -17,41 +17,32 @@ #endif namespace sck { - Socket::Socket() - : channel(std::make_unique()) - , opened(false) { - } - - Socket::Socket(std::unique_ptr channel) - : channel(std::move(channel)) - , opened(false) { - } + SocketConcrete::SocketConcrete(std::shared_ptr channel) + : channel(channel) { + } - Socket::~Socket() { - this->close(); - } + SocketConcrete::~SocketConcrete() { + if(this->isOpen()) { + this->close(); + } + } - void Socket::open(const std::chrono::milliseconds& timeout) { - if (this->opened) { - return; + void SocketConcrete::open(const std::chrono::milliseconds& timeout) { + if (this->isOpen()) { + return; } - if (!this->channel->isActive()) { - this->channel.reset(); - this->channel = std::make_unique(); - } - + std::atomic_bool stopWait(false); auto openSteps = [this, &stopWait]() { try { - this->initHandle(); - this->openConnection(); + this->channel->open(this->getProtocol(), this->getFamily()); + this->openSpecific(); } catch (...) { this->close(); stopWait = true; return; } - this->opened = true; stopWait = true; }; @@ -65,28 +56,30 @@ namespace sck { std::unique_lock notificationLck(notificationMtx); notification.wait_for(notificationLck, timeout, [&stopWait](){ return static_cast(stopWait); }); - if(!this->opened) { + if(!this->isOpen()) { this->close(); - this->opened = false; } } } - void Socket::close() { - if (!this->opened) { - return; - } - try { - this->closeConnection(); - } - catch (...) { - } - this->opened = false; - } + void SocketConcrete::close() { + if (!this->isOpen()) { + return; + } + try { + this->closeSpecific(); + } + catch (...) { + } + } + + void SocketConcrete::closeSpecific() { + this->channel->close(); + } - void Socket::bindToPort(const std::uint16_t& port) { + void SocketConcrete::bindToPort(const std::uint16_t& port) { int reusePortOptVal = 1; - ::setsockopt(this->channel->handle, SOL_SOCKET, REBIND_OPTION, reinterpret_castchannel->getSocketId(), SOL_SOCKET, REBIND_OPTION, reinterpret_castchannel->handle, reinterpret_cast(&addr), sizeof(SocketAddressIn_t)) == SCK_SOCKET_ERROR) { + if (::bind(this->channel->getSocketId(), reinterpret_cast(&addr), sizeof(SocketAddressIn_t)) == SCK_SOCKET_ERROR) { throwWithCode("can't bind localhost on port: " + std::to_string(port)); } } @@ -118,14 +111,9 @@ namespace sck { addr.sin6_flowinfo = 0; addr.sin6_addr = IN6ADDR_ANY_INIT; // apparently, there is no such a cross-system define for ipv4 addresses addr.sin6_port = htons(port); - if (::bind(this->channel->handle, reinterpret_cast(&addr), sizeof(SocketAddressIn6_t)) == SCK_SOCKET_ERROR) { + if (::bind(this->channel->getSocketId(), reinterpret_cast(&addr), sizeof(SocketAddressIn6_t)) == SCK_SOCKET_ERROR) { throwWithCode("can't bind localhost on port: " + std::to_string(port)); } - } - } - - void Socket::closeConnection() { - this->channel->close(); - } -} + } +} \ No newline at end of file diff --git a/CrossSocket/src/SocketDecorator.cpp b/CrossSocket/src/SocketDecorator.cpp new file mode 100644 index 00000000..2315a288 --- /dev/null +++ b/CrossSocket/src/SocketDecorator.cpp @@ -0,0 +1,18 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +namespace sck { + SocketDecorator::SocketDecorator(std::unique_ptr wrapped) { + if(nullptr == wrapped) { + throw Error("passed null wrapped when building socket decorator"); + } + this->wrapped = std::move(wrapped); + } +} \ No newline at end of file diff --git a/CrossSocket/src/SocketHandler.cpp b/CrossSocket/src/SocketHandler.cpp deleted file mode 100644 index bee04fd1..00000000 --- a/CrossSocket/src/SocketHandler.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#include "SocketHandler.h" -#include -#include - -#ifdef _WIN32 -#include -constexpr int shutDownMode = 2; //inactivate both outgoing and incoming messages -#define WSA_VERSION_PREFIX 2 //no reason for using something different -#define WSA_VERSION_SUFFIX 0 //no reason for using something different -#else -#include -#endif - -namespace sck { -#ifdef _WIN32 - std::shared_ptr SocketHandler::WSAManagerSingleton::managerInstance; - - std::shared_ptr SocketHandler::WSAManagerSingleton::getManager() { - if (nullptr == managerInstance) { - managerInstance = std::shared_ptr(new SocketHandler::WSAManagerSingleton()); //not possible to call make_shared because c'tor is private - } - return managerInstance; - } - - SocketHandler::WSAManagerSingleton::WSAManagerSingleton() { - WSADATA wsa; - WSAStartup(MAKEWORD(WSA_VERSION_PREFIX, WSA_VERSION_SUFFIX), &wsa); - } - - SocketHandler::WSAManagerSingleton::~WSAManagerSingleton() { - WSACleanup(); - } -#endif - - /** - * @brief tries to convert an address into a SocketAddressIn_t. - * Returns false in case the conversion was not possible - * There is no need to expose this function to the outside - */ - bool tryConversion(SocketAddressIn_t& recipient, const sck::Address& address) { -#if !defined(_WIN32) - in_addr ia; - if (1 == ::inet_pton(AF_INET, address.getHost().c_str(), &ia)) { - recipient.sin_addr.s_addr = ia.s_addr; - return true; - } -#endif - - addrinfo* res, hints = addrinfo{}; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - - int gai_err = ::getaddrinfo(address.getHost().c_str(), NULL, &hints, &res); - -#if !defined(_WIN32) - if (gai_err == EAI_SYSTEM) { - return false; - } -#endif - if (gai_err != 0) { - return false; - } - - auto ipv4 = reinterpret_cast(res->ai_addr); - ::freeaddrinfo(res); - recipient.sin_addr.s_addr = ipv4->sin_addr.s_addr; - return true; - }; - - std::unique_ptr convertIpv4(const sck::Address& address) { - if (sck::Family::IP_V4 == address.getFamily()) { - std::unique_ptr resolved = std::make_unique(); - // set everything to 0 first - ::memset(&(*resolved), 0, sizeof(SocketAddressIn_t)); - resolved->sin_family = AF_INET; - if (!tryConversion(*resolved, address)) { - return nullptr; - } - resolved->sin_port = htons(address.getPort()); - return resolved; - } - return nullptr; - } - - /** - * @brief tries to convert an address into a SocketAddressIn6_t. - * Returns false in case the conversion was not possible - * There is no need to expose this function to the outside - */ - bool tryConversion(SocketAddressIn6_t& recipient, const sck::Address& address) { -#if !defined(_WIN32) - in6_addr ia; - if (1 == ::inet_pton(AF_INET6, address.getHost().c_str(), &ia)) { - recipient.sin6_addr = ia; - return true; - } -#endif - - addrinfo* res, hints = addrinfo{}; - hints.ai_family = AF_INET6; - hints.ai_socktype = SOCK_STREAM; - - int gai_err = ::getaddrinfo(address.getHost().c_str(), NULL, &hints, &res); - -#if !defined(_WIN32) - if (gai_err == EAI_SYSTEM) { - return false; - } -#endif - if (gai_err != 0) { - return false; - } - - auto ipv6 = reinterpret_cast(res->ai_addr); - ::freeaddrinfo(res); - recipient.sin6_addr = ipv6->sin6_addr; - return true; - }; - - std::unique_ptr convertIpv6(const sck::Address& address) { - if (sck::Family::IP_V6 == address.getFamily()) { - std::unique_ptr resolved = std::make_unique(); - // set everything to 0 first - ::memset(&(*resolved), 0, sizeof(SocketAddressIn6_t)); - resolved->sin6_family = AF_INET6; - resolved->sin6_flowinfo = 0; - if (!tryConversion(*resolved, address)) { - return nullptr; - } - resolved->sin6_port = htons(address.getPort()); - return resolved; - } - return nullptr; - } - - AddressPtr convert(const SocketAddress_t& address) { - // refer to https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu - std::string ip; - std::uint16_t port; - if (AF_INET == address.sa_family) { - // ipv4 address - // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to an ip that has no sense - ip = std::string(::inet_ntoa(reinterpret_cast(&address)->sin_addr)); - port = ntohs(reinterpret_cast(&address)->sin_port); - } - else { - // ipv6 address - char temp[INET6_ADDRSTRLEN]; //this is the longest one - // refer to https://www.gnu.org/software/libc/manual/html_node/Host-Address-Functions.html - ::memset(temp, 0, INET6_ADDRSTRLEN); - ::inet_ntop(address.sa_family, &address, temp, INET6_ADDRSTRLEN); - ip = std::string(temp, INET6_ADDRSTRLEN); - port = ntohs(reinterpret_cast(&address)->sin6_port); - } - return sck::Address::create(ip, port); - } - - int castFamily(const sck::Family& family) { - switch (family) { - case sck::Family::IP_V4: - return AF_INET; - case sck::Family::IP_V6: - return AF_INET6; - default: - return -1; - } - } - - SocketHandler::~SocketHandler() { - this->close(); - } - - SocketHandler::SocketHandler() - : active(true) - , handle(SCK_INVALID_SOCKET) { -#ifdef _WIN32 - this->manager = WSAManagerSingleton::getManager(); -#endif - } - - SocketHandler::SocketHandler(SocketHandler&& o) - : active(o.active) - , handle(o.handle) { - o.active = false; - o.handle = SCK_INVALID_SOCKET; -#ifdef _WIN32 - this->manager = o.manager; -#endif - } - - int getLastError() { -#ifdef _WIN32 - return WSAGetLastError(); -#else - return static_cast(errno); -#endif - } - - void SocketHandler::close() { - if (!this->active) { - return; - } -#ifdef _WIN32 - shutdown(this->handle, shutDownMode); - closesocket(this->handle); -#else - ::shutdown(this->handle, SHUT_RDWR); - ::close(this->handle); -#endif - this->active = false; - this->handle = SCK_INVALID_SOCKET; - } - - bool SocketHandler::isActive() const { - return this->active; - } - - void throwWithCode(const std::string& what){ - std::stringstream s; - s << what << " , error code: " << getLastError(); - throw Error(s.str()); - } -} \ No newline at end of file diff --git a/CrossSocket/src/SocketServer.cpp b/CrossSocket/src/SocketServer.cpp deleted file mode 100644 index 1e84ebd1..00000000 --- a/CrossSocket/src/SocketServer.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "SocketHandler.h" - -namespace sck { - SocketServer::SocketServer(const std::uint16_t& port, const sck::Family& family) - : Socket() - , port(port) - , protocol(family) { - } - - std::uint16_t SocketServer::getPort() const { - return this->port; - } - - sck::Family SocketServer::getFamily() { - return this->protocol; - } -} \ No newline at end of file diff --git a/CrossSocket/src/async/AsyncClient.cpp b/CrossSocket/src/async/AsyncClient.cpp deleted file mode 100644 index 5725699c..00000000 --- a/CrossSocket/src/async/AsyncClient.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -namespace sck::async { - AsyncClient::AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity) - : AsyncDecorator(std::move(client)) { - this->buffer.resize(bufferCapacity); - }; - - class AsyncClient::ReceiveService : public Service { - public: - ReceiveService(AsyncClient& client) - : Service([&client](){ - std::size_t recvBytes = client.wrapped->receive(client.buffer.data(), client.buffer.capacity()); - if(recvBytes != client.buffer.capacity()) { - client.buffer.resize(recvBytes); - } - std::lock_guard lk(client.listenerMtx); - if(nullptr != client.listener) { - client.listener->handle(client.buffer.data(), recvBytes); - } - }) { - } - }; - - std::unique_ptr AsyncClient::make_service() { - return std::make_unique(*this); - } -} \ No newline at end of file diff --git a/CrossSocket/src/async/AsyncTcpServer.cpp b/CrossSocket/src/async/AsyncTcpServer.cpp deleted file mode 100644 index 3e0bc6a4..00000000 --- a/CrossSocket/src/async/AsyncTcpServer.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -namespace sck::async { - class AsyncTcpServer::AcceptanceService : public Service { - public: - AcceptanceService(AsyncTcpServer& server) - : Service([&server](){ - auto client = server.wrapped->acceptClient(); - std::lock_guard lk(server.listenerMtx); - if(nullptr != server.listener) { - server.listener->handle(std::move(client)); - } - }) { - } - }; - - std::unique_ptr AsyncTcpServer::make_service() { - return std::make_unique(*this); - } -} diff --git a/CrossSocket/src/async/Service.cpp b/CrossSocket/src/async/Service.cpp deleted file mode 100644 index dfa32017..00000000 --- a/CrossSocket/src/async/Service.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -namespace sck::async { - void Service::Barrier::wait() { - std::unique_lock lk(this->queueMtx); - ++this->queue; - if(2 == this->queue) { - this->stopWait = true; - this->notification.notify_all(); - } - else { - this->notification.wait(lk, [this](){ return static_cast(this->stopWait); }); - } - } - - Service::Service(const std::function& iterativeAction) - : listener(nullptr) - , loop([this, &iterativeAction](){ - std::function iter(iterativeAction); - this->barrier.wait(); - while (this->loopLife) { - try { - iter(); - } - catch(...) { - std::lock_guard lk(this->listenerMtx); - if(nullptr != this->listener) { - try { - std::rethrow_exception(std::current_exception()); - } - catch(const Error& e) { - this->listener->handle(e); - } - catch(const std::exception& e) { - this->listener->handle(e); - } - } - } - } - }) { - this->loopLife = true; - this->barrier.wait(); - } - - Service::~Service() { - this->loopLife = false; - this->loop.join(); - } - - void Service::resetErrorListener(ErrorListener* listener) { - std::lock_guard lk(this->listenerMtx); - this->listener = listener; - } -} \ No newline at end of file diff --git a/CrossSocket/src/tcp/TcpClient.cpp b/CrossSocket/src/tcp/TcpClient.cpp index aa8c1e59..827719bb 100644 --- a/CrossSocket/src/tcp/TcpClient.cpp +++ b/CrossSocket/src/tcp/TcpClient.cpp @@ -6,7 +6,7 @@ **/ #include -#include "../SocketHandler.h" +#include "../Handler.h" namespace sck::tcp { @@ -14,14 +14,7 @@ namespace sck::tcp { : SocketClient(remoteAddress) { } - TcpClient::TcpClient(const sck::Address& remoteAddress, std::unique_ptr channel) - : SocketClient(remoteAddress, std::move(channel)) { - } - - void TcpClient::initHandle() { - this->channel->handle = ::socket(castFamily(this->getFamily()), SOCK_STREAM, 0); - if (this->channel->handle == SCK_INVALID_SOCKET) { - throwWithCode("Stream socket could not be created"); - } + TcpClient::TcpClient(const sck::Address& remoteAddress, std::shared_ptr channel) + : SocketClient(remoteAddress, channel) { } } \ No newline at end of file diff --git a/CrossSocket/src/tcp/TcpServer.cpp b/CrossSocket/src/tcp/TcpServer.cpp index 232550da..9842a37e 100644 --- a/CrossSocket/src/tcp/TcpServer.cpp +++ b/CrossSocket/src/tcp/TcpServer.cpp @@ -7,7 +7,7 @@ #include #include -#include "../SocketHandler.h" +#include "../Handler.h" namespace sck::tcp { @@ -15,26 +15,26 @@ namespace sck::tcp { class ClientHandler : public TcpClient { public: - explicit ClientHandler(const sck::Address& remoteAddress, std::unique_ptr channel) - : TcpClient(remoteAddress, std::move(channel)) { - this->opened = true; + explicit ClientHandler(const sck::Address& remoteAddress, std::shared_ptr channel) + : TcpClient(remoteAddress, channel) { }; ~ClientHandler() override = default; private: - void openConnection() final { + void openSpecific() final { throw std::runtime_error("ClientHandler from TcpServer are not re-openable!"); }; }; TcpServer::TcpServer(const std::uint16_t& port, const Family& family) - : SocketServer(port, family) { + : SocketConcrete(std::make_shared()) + , port(port) + , protocol(family) { } std::unique_ptr TcpServer::acceptClient() { SocketAddress_t acceptedClientAddress; - std::unique_ptr acceptedClientHandler = std::make_unique(); #ifdef _WIN32 int acceptedAddressLength #else @@ -42,31 +42,25 @@ namespace sck::tcp { #endif = sizeof(SocketAddress_t); // accept: wait for a client to call connect and hit this server and get a pointer to this client. - acceptedClientHandler->handle = ::accept(this->channel->handle, &acceptedClientAddress, &acceptedAddressLength); - if (acceptedClientHandler->handle == SCK_INVALID_SOCKET) { + Socket_t temp = ::accept(this->channel->getSocketId(), &acceptedClientAddress, &acceptedAddressLength); + if (temp == SCK_INVALID_SOCKET) { throwWithCode("Error: accepting new client"); } + std::shared_ptr acceptedClientHandler = std::make_shared(temp); + AddressPtr remoteAddress = convert(acceptedClientAddress); if (nullptr != remoteAddress) { throw std::runtime_error("accepted client remote address is not resolvable"); } - return std::make_unique(*remoteAddress, std::move(acceptedClientHandler)); + return std::make_unique(*remoteAddress, acceptedClientHandler); } - void TcpServer::initHandle() { - this->channel->handle = ::socket(castFamily(this->SocketServer::getFamily()), SOCK_STREAM, 0); - if (this->channel->handle == SCK_INVALID_SOCKET) { - throwWithCode("Stream socket could not be created"); - } - } - - void TcpServer::openConnection() { - this->bindToPort(this->getPort()); + void TcpServer::openSpecific() { + this->bindToPort(this->port); // listen to the port to allow all the following clients acceptance - if (::listen(this->channel->handle, LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { - throwWithCode("Error: listening on port: " + std::to_string(this->getPort())); + if (::listen(this->channel->getSocketId(), LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { + throwWithCode("Error: listening on port: " + std::to_string(this->port)); } } - } \ No newline at end of file diff --git a/CrossSocket/src/udp/UdpClient.cpp b/CrossSocket/src/udp/UdpClient.cpp index 7aec047d..8105b12e 100644 --- a/CrossSocket/src/udp/UdpClient.cpp +++ b/CrossSocket/src/udp/UdpClient.cpp @@ -6,7 +6,7 @@ **/ #include -#include "../SocketHandler.h" +#include "../Handler.h" namespace sck::udp { @@ -15,16 +15,8 @@ namespace sck::udp { , port(localPort) { } - void UdpClient::initHandle() { - this->channel->handle = ::socket(castFamily(this->getFamily()), SOCK_DGRAM, 0); - if (this->channel->handle == SCK_INVALID_SOCKET) { - throwWithCode("DataGram socket could not be created"); - } - } - - void UdpClient::openConnection() { + void UdpClient::openSpecific() { this->bindToPort(this->port); - this->SocketClient::openConnection(); - + this->SocketClient::openSpecific(); } } \ No newline at end of file diff --git a/CrossSocket/src/udp/UdpServer.cpp b/CrossSocket/src/udp/UdpServer.cpp index f0a7161b..45c49534 100644 --- a/CrossSocket/src/udp/UdpServer.cpp +++ b/CrossSocket/src/udp/UdpServer.cpp @@ -6,7 +6,7 @@ **/ #include -#include "../SocketHandler.h" +#include "../Handler.h" #include namespace sck::udp { @@ -22,7 +22,7 @@ namespace sck::udp { : UdpClient(getInitialAddress(protocol), localPort) { } - void UdpServer::openConnection() { + void UdpServer::openSpecific() { this->bindToPort(this->port); // receive a message from the client, that from now on will beccome the recognized one. @@ -34,7 +34,7 @@ namespace sck::udp { unsigned int #endif remoteAddrLen = sizeof(SocketAddress_t); - if (::recvfrom(this->channel->handle, &bf, MAX_UDP_RECV_MESSAGE, 0, &remoteAddr, &remoteAddrLen) == SCK_SOCKET_ERROR) { + if (::recvfrom(this->channel->getSocketId(), &bf, MAX_UDP_RECV_MESSAGE, 0, &remoteAddr, &remoteAddrLen) == SCK_SOCKET_ERROR) { throwWithCode("recvfrom failed while identifying the target"); } AddressPtr remoteConverted = convert(remoteAddr); @@ -42,6 +42,6 @@ namespace sck::udp { throw Error(remoteAddr.sa_data, " is an invalid data for udp serer remote address"); } this->remoteAddress = *remoteConverted; - this->SocketClient::openConnection(); + this->SocketClient::openSpecific(); } } \ No newline at end of file diff --git a/TODO b/TODO index 5b7ba670..bb7a3730 100644 --- a/TODO +++ b/TODO @@ -3,3 +3,9 @@ make attribute const when possible inline getter when possible togliere cmake script inutili rimettere typed connections +virtual destroyer +delete copy c'tor in interfacce in cima e gerarchia +remove default d'tor +provare in Windows +remove Socket Server +add explicit a c?tor single input From 2d226508a64dfaec1a16efe396824990e03ac66c Mon Sep 17 00:00:00 2001 From: andrea Date: Sat, 30 Jan 2021 23:18:47 +0100 Subject: [PATCH 010/228] async reintroduced --- CMakeLists.txt | 4 ++ CrossSocket/include/MessangerConcrete.h | 8 ++- CrossSocket/include/async/AsyncClient.h | 45 ++++++++++++++ CrossSocket/include/async/AsyncDecorator.h | 59 ++++++++++++++++++ CrossSocket/include/async/AsyncTcpServer.h | 26 ++++++++ CrossSocket/include/async/ErrorListener.h | 22 +++++++ CrossSocket/include/async/MessageListener.h | 20 ++++++ CrossSocket/include/async/Service.h | 54 ++++++++++++++++ CrossSocket/include/async/TcpServerListener.h | 20 ++++++ CrossSocket/src/async/AsyncClient.cpp | 62 +++++++++++++++++++ CrossSocket/src/async/AsyncTcpServer.cpp | 34 ++++++++++ CrossSocket/src/async/Service.cpp | 61 ++++++++++++++++++ 12 files changed, 412 insertions(+), 3 deletions(-) create mode 100644 CrossSocket/include/async/AsyncClient.h create mode 100644 CrossSocket/include/async/AsyncDecorator.h create mode 100644 CrossSocket/include/async/AsyncTcpServer.h create mode 100644 CrossSocket/include/async/ErrorListener.h create mode 100644 CrossSocket/include/async/MessageListener.h create mode 100644 CrossSocket/include/async/Service.h create mode 100644 CrossSocket/include/async/TcpServerListener.h create mode 100644 CrossSocket/src/async/AsyncClient.cpp create mode 100644 CrossSocket/src/async/AsyncTcpServer.cpp create mode 100644 CrossSocket/src/async/Service.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f5fc020b..138865c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) project(Cross-Socket-Prj VERSION ${VERSION}) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + option(LIB_OPT "Compile shared libraries (ON) or static (OFF)" OFF) option(BUILD_SAMPLES "Build the samples showing how to use EFG" ON) diff --git a/CrossSocket/include/MessangerConcrete.h b/CrossSocket/include/MessangerConcrete.h index e0718ce9..55cbe93d 100644 --- a/CrossSocket/include/MessangerConcrete.h +++ b/CrossSocket/include/MessangerConcrete.h @@ -18,11 +18,13 @@ namespace sck { */ class Handler; - class MessangerConcrete - : public Messanger< + typedef Messanger< std::pair, std::pair - > { + > MessangerConcrete_t; + + class MessangerConcrete + : public MessangerConcrete_t { private: bool send(const std::pair& message) final; diff --git a/CrossSocket/include/async/AsyncClient.h b/CrossSocket/include/async/AsyncClient.h new file mode 100644 index 00000000..a48c4874 --- /dev/null +++ b/CrossSocket/include/async/AsyncClient.h @@ -0,0 +1,45 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ASYNCCLIENT_H +#define _CROSS_SOCKET_ASYNCCLIENT_H + +#include +#include +#include +#include +#include + +namespace sck::async { + class AsyncClient + : public AsyncDecorator + , public Messanger< + std::pair, + std::pair + > { + public: + AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); + + private: + inline bool send(const std::pair& message) final { + return dynamic_cast(this->wrapped.get())->send(message); + }; + + std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; + + std::mutex bufferMtx; + std::vector buffer; + class ReceiveService; + + std::mutex recvMutex; + std::condition_variable recvNotification; + + std::unique_ptr make_service() final; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/AsyncDecorator.h b/CrossSocket/include/async/AsyncDecorator.h new file mode 100644 index 00000000..edff9be1 --- /dev/null +++ b/CrossSocket/include/async/AsyncDecorator.h @@ -0,0 +1,59 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ASYNCDECORATOR_H +#define _CROSS_SOCKET_ASYNCDECORATOR_H + +#include +#include +#include + +namespace sck::async { + template + class AsyncDecorator + : public SocketDecorator { + public: + ~AsyncDecorator() { this->close(); }; + + inline void resetListener(Listener* listener) { + std::lock_guard lk(this->listenerMtx); + this->listener = listener; + }; + + void resetErrorListener(ErrorListener* listener) { + this->service->resetErrorListener(listener); + }; + + protected: + AsyncDecorator(std::unique_ptr client) + : SocketDecorator(std::move(client)) { + }; + + virtual std::unique_ptr make_service() = 0; + std::unique_ptr service; + + Listener* listener = nullptr; + std::mutex listenerMtx; + + private: + inline void open(const std::chrono::milliseconds& timeout) final { + if(!this->wrapped->isOpen()){ + this->wrapped->open(timeout); + this->service = this->make_service(); + } + }; + + inline void close() final { + if(this->wrapped->isOpen()){ + this->wrapped->close(); + this->service.reset(); + } + }; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/AsyncTcpServer.h b/CrossSocket/include/async/AsyncTcpServer.h new file mode 100644 index 00000000..6372fff7 --- /dev/null +++ b/CrossSocket/include/async/AsyncTcpServer.h @@ -0,0 +1,26 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ASYNCTCPSERVER_H +#define _CROSS_SOCKET_ASYNCTCPSERVER_H + +#include +#include + +namespace sck::async { + class AsyncTcpServer : public AsyncDecorator { + public: + AsyncTcpServer(std::unique_ptr server); + + private: + class AcceptanceService; + + std::unique_ptr make_service() final; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/ErrorListener.h b/CrossSocket/include/async/ErrorListener.h new file mode 100644 index 00000000..1484fa03 --- /dev/null +++ b/CrossSocket/include/async/ErrorListener.h @@ -0,0 +1,22 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ERRORLISTENER_H_ +#define _CROSS_SOCKET_ERRORLISTENER_H_ + +#include + +namespace sck::async { + class ErrorListener { + public: + virtual void handle(const Error& error) = 0; + + virtual void handle(const std::exception& error) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/MessageListener.h b/CrossSocket/include/async/MessageListener.h new file mode 100644 index 00000000..14a710dc --- /dev/null +++ b/CrossSocket/include/async/MessageListener.h @@ -0,0 +1,20 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_MESSAGELISTENER_H_ +#define _CROSS_SOCKET_MESSAGELISTENER_H_ + +#include + +namespace sck::async { + class MessageListener { + public: + virtual void handle(const std::pair& message) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/Service.h b/CrossSocket/include/async/Service.h new file mode 100644 index 00000000..a5660c4f --- /dev/null +++ b/CrossSocket/include/async/Service.h @@ -0,0 +1,54 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_SERVICE_H_ +#define _CROSS_SOCKET_SERVICE_H_ + +#include +#include +#include +#include +#include +#include + +namespace sck::async { + class Service { + public: + Service(const Service&) = delete; + Service& operator=(const Service&) = delete; + + // start + Service(const std::function& iterativeAction); + // stop + ~Service(); + + void resetErrorListener(ErrorListener* listener); + + private: + class Barrier { + public: + Barrier() { this->stopWait = false; }; + + void wait(); + + private: + std::uint8_t queue = 0; + std::mutex queueMtx; + std::condition_variable notification; + std::atomic_bool stopWait; + }; + Barrier barrier = {}; + + ErrorListener* listener; + std::mutex listenerMtx; + + std::atomic_bool loopLife; + std::thread loop; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/TcpServerListener.h b/CrossSocket/include/async/TcpServerListener.h new file mode 100644 index 00000000..ddce5e9b --- /dev/null +++ b/CrossSocket/include/async/TcpServerListener.h @@ -0,0 +1,20 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_TCPSERVERLISTENER_H_ +#define _CROSS_SOCKET_TCPSERVERLISTENER_H_ + +#include + +namespace sck::async { + class TcpServerListener { + public: + virtual void handle(std::unique_ptr clientHandler) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/src/async/AsyncClient.cpp b/CrossSocket/src/async/AsyncClient.cpp new file mode 100644 index 00000000..75be8747 --- /dev/null +++ b/CrossSocket/src/async/AsyncClient.cpp @@ -0,0 +1,62 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +namespace sck::async { + AsyncClient::AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity) + : AsyncDecorator(std::move(client)) { + this->buffer.resize(bufferCapacity); + if(this->wrapped->isOpen()){ + this->service = this->make_service(); + } + }; + + class AsyncClient::ReceiveService : public Service { + public: + ReceiveService(AsyncClient& client) + : Service([&client](){ + { + std::lock_guard bufferLock(client.bufferMtx); + auto pr = std::make_pair(client.buffer.data(), client.buffer.capacity()); + auto recvBytes = dynamic_cast(client.wrapped.get())->receive(pr, std::chrono::milliseconds(0)); + if(recvBytes != client.buffer.capacity()) { + client.buffer.resize(recvBytes); + } + std::lock_guard lk(client.listenerMtx); + if(nullptr != client.listener) { + client.listener->handle({client.buffer.data(), recvBytes}); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + }) { + } + }; + + std::size_t AsyncClient::receive(std::pair& message, const std::chrono::milliseconds& timeout) { + auto copyBuffer = [this, &message]() -> std::size_t { + std::lock_guard bufferLock(this->bufferMtx); + ::memcpy(message.first, this->buffer.data(), this->buffer.size()); + return this->buffer.size(); + }; + + std::unique_lock notifLock(this->recvMutex); + if(0 == timeout.count()) { + this->recvNotification.wait(notifLock); + return copyBuffer(); + } + if(this->recvNotification.wait_for(notifLock, timeout) != std::cv_status::timeout) { + return copyBuffer(); + } + return 0; + } + + std::unique_ptr AsyncClient::make_service() { + return std::make_unique(*this); + } +} \ No newline at end of file diff --git a/CrossSocket/src/async/AsyncTcpServer.cpp b/CrossSocket/src/async/AsyncTcpServer.cpp new file mode 100644 index 00000000..d1d7f565 --- /dev/null +++ b/CrossSocket/src/async/AsyncTcpServer.cpp @@ -0,0 +1,34 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +namespace sck::async { + AsyncTcpServer::AsyncTcpServer(std::unique_ptr server) + : AsyncDecorator(std::move(server)) { + if(this->wrapped->isOpen()){ + this->service = this->make_service(); + } + }; + + class AsyncTcpServer::AcceptanceService : public Service { + public: + AcceptanceService(AsyncTcpServer& server) + : Service([&server](){ + auto client = dynamic_cast(server.wrapped.get())->acceptClient(); + std::lock_guard lk(server.listenerMtx); + if(nullptr != server.listener) { + server.listener->handle(std::move(client)); + } + }) { + } + }; + + std::unique_ptr AsyncTcpServer::make_service() { + return std::make_unique(*this); + } +} diff --git a/CrossSocket/src/async/Service.cpp b/CrossSocket/src/async/Service.cpp new file mode 100644 index 00000000..dfa32017 --- /dev/null +++ b/CrossSocket/src/async/Service.cpp @@ -0,0 +1,61 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +namespace sck::async { + void Service::Barrier::wait() { + std::unique_lock lk(this->queueMtx); + ++this->queue; + if(2 == this->queue) { + this->stopWait = true; + this->notification.notify_all(); + } + else { + this->notification.wait(lk, [this](){ return static_cast(this->stopWait); }); + } + } + + Service::Service(const std::function& iterativeAction) + : listener(nullptr) + , loop([this, &iterativeAction](){ + std::function iter(iterativeAction); + this->barrier.wait(); + while (this->loopLife) { + try { + iter(); + } + catch(...) { + std::lock_guard lk(this->listenerMtx); + if(nullptr != this->listener) { + try { + std::rethrow_exception(std::current_exception()); + } + catch(const Error& e) { + this->listener->handle(e); + } + catch(const std::exception& e) { + this->listener->handle(e); + } + } + } + } + }) { + this->loopLife = true; + this->barrier.wait(); + } + + Service::~Service() { + this->loopLife = false; + this->loop.join(); + } + + void Service::resetErrorListener(ErrorListener* listener) { + std::lock_guard lk(this->listenerMtx); + this->listener = listener; + } +} \ No newline at end of file From 8b4a7312ecf85604aca33c704694b227e2a8fa8e Mon Sep 17 00:00:00 2001 From: andrea Date: Sun, 31 Jan 2021 01:30:03 +0100 Subject: [PATCH 011/228] implementing crypted socket --- CMakeLists.txt | 1 + ...essangerDecorator.h => MessangerCrypted.h} | 27 ++++--- CrossSocket/include/SocketCrypted.h | 40 +++++++++++ .../include/async/AsyncSocketCrypted.h | 70 +++++++++++++++++++ CrossSocket/include/async/MessageListener.h | 6 ++ CrossSocket/src/SocketConcrete.cpp | 2 +- Samples/CMakeLists.txt | 2 +- Utils/CMakeLists.txt | 31 ++++++++ Utils/Main.cpp | 0 Utils/include/ProcessLauncher.h | 65 +++++++++++++++++ Utils/include/StringMessanger.h | 21 ++++++ Utils/src/ProcessLauncher.cpp | 49 +++++++++++++ Utils/src/StringMessanger.cpp | 24 +++++++ 13 files changed, 322 insertions(+), 16 deletions(-) rename CrossSocket/include/{MessangerDecorator.h => MessangerCrypted.h} (58%) create mode 100644 CrossSocket/include/SocketCrypted.h create mode 100644 CrossSocket/include/async/AsyncSocketCrypted.h create mode 100644 Utils/CMakeLists.txt create mode 100644 Utils/Main.cpp create mode 100644 Utils/include/ProcessLauncher.h create mode 100644 Utils/include/StringMessanger.h create mode 100644 Utils/src/ProcessLauncher.cpp create mode 100644 Utils/src/StringMessanger.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 138865c8..b2ca46d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ include(SetInstallRPath) add_subdirectory(CrossSocket) if(BUILD_SAMPLES) + add_subdirectory(Utils) add_subdirectory(Samples) endif() diff --git a/CrossSocket/include/MessangerDecorator.h b/CrossSocket/include/MessangerCrypted.h similarity index 58% rename from CrossSocket/include/MessangerDecorator.h rename to CrossSocket/include/MessangerCrypted.h index 971a46bf..6f210d35 100644 --- a/CrossSocket/include/MessangerDecorator.h +++ b/CrossSocket/include/MessangerCrypted.h @@ -5,50 +5,49 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_MESSANGERDECORATOR_H_ -#define _CROSS_SOCKET_MESSANGERDECORATOR_H_ +#ifndef _CROSS_SOCKET_MESSANGERCRYPTED_H_ +#define _CROSS_SOCKET_MESSANGERCRYPTED_H_ #include #include namespace sck { - /** - * @brief The interface providing functionalities for exchanging data - */ template - class MessangerDecorator { + class MessangerCrypted + : public Messanger { private: bool send(const Send_t& message) final { this->encode(message); - this->messanger->send({this->sendBuffer.data(), this->sendBuffer.size()}); + return this->wrapped->send({this->sendBuffer.data(), this->sendBuffer.size()}); }; std::size_t receive(Recv_t& message, const std::chrono::milliseconds& timeout) final { - auto recvBytes = this->messanger->receive({this->recvBuffer.data(), this->recvBuffer.capacity()}, timeout); + auto pr = std::make_pair(this->recvBuffer.data(), this->recvBuffer.capacity()); + auto recvBytes = this->wrapped->receive(pr, timeout); if(recvBytes != this->recvBuffer.capacity()) { this->recvBuffer.resize(recvBytes); } this->decode(message); return recvBytes; }; - + protected: - MessangerDecorator(MessangerConcrete& mess, const std::size_t recvCapacity) { - this->messanger = &mess; + MessangerDecorator(MessangerConcrete_t& wrapped, const std::size_t recvCapacity) { + this->wrapped = &wrapped; this->recvBuffer.resize(recvCapacity); }; - MessangerConcrete* messanger; + MessangerConcrete_t* wrapped; /** * @brief write the message into sendBuffer, as a series of raw bytes */ - virtual encode(const Send_t& message) = 0; + virtual void encode(const Send_t& message) = 0; std::vector sendBuffer; /** * @brief convert the content of recvBuffer as a received message */ - virtual decode(Recv_t& message) = 0; + virtual void decode(Recv_t& message) = 0; std::vector recvBuffer; }; } diff --git a/CrossSocket/include/SocketCrypted.h b/CrossSocket/include/SocketCrypted.h new file mode 100644 index 00000000..bba0cd04 --- /dev/null +++ b/CrossSocket/include/SocketCrypted.h @@ -0,0 +1,40 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_SOCKETCRYPTED_H_ +#define _CROSS_SOCKET_SOCKETCRYPTED_H_ + +#include +#include +#include + +namespace sck { + template + class SocketCrypted + : public SocketDecorator + , public Messanger { + static_assert(std::is_base_of, MessangerCrypt_t>::value, "invalid MessangerCrypt_t"); + public: + SocketCrypted(std::unique_ptr socket) + : SocketDecorator(std::move(socket)) + , coder(*this->wrapped) { + }; + + private: + MessangerCrypt_t coder; + + inline bool send(const Send_t& message) final { + return this->coder.send(message); + }; + + std::size_t receive(Recv_t& message, const std::chrono::milliseconds& timeout) final { + return this->coder.receive(message, timeout); + }; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/AsyncSocketCrypted.h b/CrossSocket/include/async/AsyncSocketCrypted.h new file mode 100644 index 00000000..cd3e7eee --- /dev/null +++ b/CrossSocket/include/async/AsyncSocketCrypted.h @@ -0,0 +1,70 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ASYNCSOCKETCRYPTED_H_ +#define _CROSS_SOCKET_ASYNCSOCKETCRYPTED_H_ + +#include +#include + +namespace sck { + template + class SocketCrypted + : public SocketDecorator + , public Messanger { + static_assert(std::is_base_of, MessangerCrypt_t>::value, "invalid MessangerCrypt_t"); + public: + SocketCrypted(std::unique_ptr socket) + : SocketDecorator(std::move(socket)) + , coder(*this->wrapped) { + }; + + inline void resetListener(CryptedMessageListener* listener) { + std::lock_guard lk(this->listenerMtx); + this->listener = listener; + }; + + private: + class CoderListener + : public MessangerCrypt_t + , public MessageListener { + public: + CoderListener(SocketCrypted& user) + : MessangerCrypt_t(*user.wrapped) + , user(user) { + dynamic_cast(this->user.wrapped.get())->resetListener(this); + }; + + private: + void handle(const std::pair& message) final { + std::lock_guard lk(this->listenerMtx); + if(nullptr != user.listener) { + Recv_t decoded; + this->decode(decoded, ... usare message buffer ); + this->user.listener->handle(decoded); + } + }; + + SocketCrypted& user; + }; + + MessangerCrypt_t coder; + + inline bool send(const Send_t& message) final { + return this->coder.send(message); + }; + + std::size_t receive(Recv_t& message, const std::chrono::milliseconds& timeout) final { + return this->coder.receive(message, timeout); + }; + + std::mutex listenerMtx; + CryptedMessageListener* listener = nullptr; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/async/MessageListener.h b/CrossSocket/include/async/MessageListener.h index 14a710dc..23968a3f 100644 --- a/CrossSocket/include/async/MessageListener.h +++ b/CrossSocket/include/async/MessageListener.h @@ -11,6 +11,12 @@ #include namespace sck::async { + template + class CryptedMessageListener { + public: + virtual void handle(const Recv_t& message) = 0; + }; + class MessageListener { public: virtual void handle(const std::pair& message) = 0; diff --git a/CrossSocket/src/SocketConcrete.cpp b/CrossSocket/src/SocketConcrete.cpp index f07e0783..9b3047e9 100644 --- a/CrossSocket/src/SocketConcrete.cpp +++ b/CrossSocket/src/SocketConcrete.cpp @@ -97,7 +97,7 @@ namespace sck { #ifdef _WIN32 addr.sin_addr.s_addr = ADDR_ANY; #else - addr.sin_addr.s_addr = ::htonl(INADDR_ANY); + addr.sin_addr.s_addr = htonl(INADDR_ANY); #endif if (::bind(this->channel->getSocketId(), reinterpret_cast(&addr), sizeof(SocketAddressIn_t)) == SCK_SOCKET_ERROR) { throwWithCode("can't bind localhost on port: " + std::to_string(port)); diff --git a/Samples/CMakeLists.txt b/Samples/CMakeLists.txt index 7b3bf821..08b3b355 100644 --- a/Samples/CMakeLists.txt +++ b/Samples/CMakeLists.txt @@ -1,4 +1,4 @@ -add_subdirectory(Utils) +#add_subdirectory(Utils) add_subdirectory(Tcp) diff --git a/Utils/CMakeLists.txt b/Utils/CMakeLists.txt new file mode 100644 index 00000000..a8f6ef9f --- /dev/null +++ b/Utils/CMakeLists.txt @@ -0,0 +1,31 @@ +set(PROJECT_SHORTNAME "Utils") +project(${PROJECT_SHORTNAME} VERSION ${VERSION} LANGUAGES CXX) +string(REPLACE - _ COMPONENT_NAME ${PROJECT_NAME}) + +CollectSourceFiles(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) + +GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) + +if(LIB_OPT) + add_library(${PROJECT_NAME} SHARED ${SOURCES}) +else() + add_library(${PROJECT_NAME} STATIC ${SOURCES}) +endif() +add_library(SCK::${PROJECT_SHORTNAME} ALIAS ${PROJECT_NAME}) + +target_compile_features(${PROJECT_NAME} + PUBLIC cxx_auto_type + PRIVATE cxx_variadic_templates +) + +target_include_directories(${PROJECT_NAME} + PUBLIC + $ + $ +) + +target_link_libraries(${PROJECT_NAME} +PUBLIC +SCK::Cross-Socket +) + diff --git a/Utils/Main.cpp b/Utils/Main.cpp new file mode 100644 index 00000000..e69de29b diff --git a/Utils/include/ProcessLauncher.h b/Utils/include/ProcessLauncher.h new file mode 100644 index 00000000..8dd9cf7e --- /dev/null +++ b/Utils/include/ProcessLauncher.h @@ -0,0 +1,65 @@ +/** +* Author: Andrea Casalino +* Created: 16.05.2019 +* +* report any bug to andrecasa91@gmail.com. +**/ + +#ifndef _PROCESS_LAUNCHER_H_ +#define _PROCESS_LAUNCHER_H_ + +#include +#include +#include +#include + +/** + * @brief The object is used to print a script, that is able + * to launch in a precise sequence certain processes, opening different prompts + */ +class Launcher { +public: + /** + * @param[in] the name of script to print + */ + Launcher(const std::string& nameFile); + + /** + * @brief Add 1 process to launch, with possibly some additional command arguments. + * @param[in] the name of the process to launch + * @param[in] the list of arguments to pass when launching the process + */ + template + void addProcess(const std::string& procName, Args ... args) { + std::list parsed; + this->parseArgs(parsed, args ...); + + this->commands.emplace_back(std::make_pair(procName, parsed)); + }; + + /** + * @brief Print the script and runs it + */ + void operator()() const; + +private: + template + void parseArgs(std::list& parsed , const std::string& arg, Args ... args) { + parsed.push_back(arg); + parseArgs(parsed, args ...); + }; + + template + void parseArgs(std::list& parsed, const std::string& arg) { + parsed.push_back(arg); + }; + + void parseArgs(std::list& parsed) { + return; + }; + + std::string nameFile; + std::list>> commands; +}; + +#endif \ No newline at end of file diff --git a/Utils/include/StringMessanger.h b/Utils/include/StringMessanger.h new file mode 100644 index 00000000..145113e1 --- /dev/null +++ b/Utils/include/StringMessanger.h @@ -0,0 +1,21 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +namespace sck { + class StringMessanger + : public MessangerDecorator { + public: + StringMessanger(std::unique_ptr wrapped, const std::size_t recvCapacity); + + private: + void encode(const std::string& message) final; + void decode(std::string& message) final; + }; +} \ No newline at end of file diff --git a/Utils/src/ProcessLauncher.cpp b/Utils/src/ProcessLauncher.cpp new file mode 100644 index 00000000..62bca2eb --- /dev/null +++ b/Utils/src/ProcessLauncher.cpp @@ -0,0 +1,49 @@ +#include "../include/ProcessLauncher.h" +#include + +Launcher::Launcher(const std::string& nameFile) : nameFile(nameFile) { +#ifdef _WIN32 +#elif __linux__ +#elif + throw std::runtime_error("Unrecognized operative system"); +#endif +}; + +void Launcher::operator()() const { + std::string name = this->nameFile; +#ifdef _WIN32 + name += ".bat"; +#elif __linux__ + name += ".sh"; +#endif + + std::ofstream f(name); + auto it = this->commands.begin(); + auto itEnd = this->commands.end(); + --itEnd; + for (it; it != itEnd; ++it) { +#ifdef _WIN32 + f << std::endl << "start \"\" \"" << it->first << "\""; + for(auto a : it->second) f << " \"" << a << "\""; +#elif __linux__ + f << std::endl << "gnome-terminal -x sh -c \"./" << it->first; + for(auto a : it->second) f << " " << a; + f << "; bash\""; +#endif + } +#ifdef _WIN32 + f << std::endl << "\"" << this->commands.back().first << "\""; + for(auto a : this->commands.back().second) f << " \"" << a << "\""; +#elif __linux__ + f << std::endl << "./" << this->commands.back().first; + for(auto a : this->commands.back().second) f << " " << a; +#endif + + f.close(); + +#ifdef _WIN32 + system(name.c_str()); +#elif __linux__ + system(std::string("sh ./" + name).c_str()); +#endif +}; \ No newline at end of file diff --git a/Utils/src/StringMessanger.cpp b/Utils/src/StringMessanger.cpp new file mode 100644 index 00000000..29540142 --- /dev/null +++ b/Utils/src/StringMessanger.cpp @@ -0,0 +1,24 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +namespace sck { + StringMessanger::StringMessanger(std::unique_ptr wrapped, const std::size_t recvCapacity) + : MessangerDecorator(std::move(wrapped), recvCapacity) { + } + + void StringMessanger::encode(const std::string& message) { + this->sendBuffer.resize(message.size()); + memcpy(this->sendBuffer.data(), message.data(), message.size()); + }; + + void StringMessanger::decode(std::string& message) { + message.reserve(this->recvBuffer.size()); + memcpy(message.data(), this->recvBuffer.data(), this->recvBuffer.size()); + }; +} \ No newline at end of file From 0645d8d41fcbaf7697f285d962bbfa65eca42442 Mon Sep 17 00:00:00 2001 From: andrea Date: Sun, 31 Jan 2021 01:45:58 +0100 Subject: [PATCH 012/228] crypto socket removed --- CrossSocket/include/Messanger.h | 6 +- CrossSocket/include/MessangerConcrete.h | 8 +-- CrossSocket/include/MessangerCrypted.h | 55 --------------- CrossSocket/include/SocketCrypted.h | 40 ----------- CrossSocket/include/async/AsyncClient.h | 7 +- .../include/async/AsyncSocketCrypted.h | 70 ------------------- CrossSocket/include/async/MessageListener.h | 6 -- CrossSocket/src/async/AsyncClient.cpp | 2 +- 8 files changed, 7 insertions(+), 187 deletions(-) delete mode 100644 CrossSocket/include/MessangerCrypted.h delete mode 100644 CrossSocket/include/SocketCrypted.h delete mode 100644 CrossSocket/include/async/AsyncSocketCrypted.h diff --git a/CrossSocket/include/Messanger.h b/CrossSocket/include/Messanger.h index e981c7f6..97766d16 100644 --- a/CrossSocket/include/Messanger.h +++ b/CrossSocket/include/Messanger.h @@ -9,12 +9,12 @@ #define _CROSS_SOCKET_MESSANGER_H_ #include +#include namespace sck { /** * @brief The interface providing functionalities for exchanging data */ - template class Messanger { public: Messanger() = default; @@ -27,14 +27,14 @@ namespace sck { * @return true if the message was completely sent * @param[in] the message to send */ - virtual bool send(const Send_t& message) = 0; + virtual bool send(const std::pair& message) = 0; /** * @return the number of received bytes * @param[in] the recepient * @param[in] the timeout to consider */ - virtual std::size_t receive(Recv_t& message, const std::chrono::milliseconds& timeout) = 0; + virtual std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) = 0; }; } diff --git a/CrossSocket/include/MessangerConcrete.h b/CrossSocket/include/MessangerConcrete.h index 55cbe93d..1477fd7d 100644 --- a/CrossSocket/include/MessangerConcrete.h +++ b/CrossSocket/include/MessangerConcrete.h @@ -9,7 +9,6 @@ #define _CROSS_SOCKET_MESSANGERCONCRETE_H_ #include -#include #include namespace sck { @@ -18,13 +17,8 @@ namespace sck { */ class Handler; - typedef Messanger< - std::pair, - std::pair - > MessangerConcrete_t; - class MessangerConcrete - : public MessangerConcrete_t { + : public Messanger { private: bool send(const std::pair& message) final; diff --git a/CrossSocket/include/MessangerCrypted.h b/CrossSocket/include/MessangerCrypted.h deleted file mode 100644 index 6f210d35..00000000 --- a/CrossSocket/include/MessangerCrypted.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_MESSANGERCRYPTED_H_ -#define _CROSS_SOCKET_MESSANGERCRYPTED_H_ - -#include -#include - -namespace sck { - template - class MessangerCrypted - : public Messanger { - private: - bool send(const Send_t& message) final { - this->encode(message); - return this->wrapped->send({this->sendBuffer.data(), this->sendBuffer.size()}); - }; - - std::size_t receive(Recv_t& message, const std::chrono::milliseconds& timeout) final { - auto pr = std::make_pair(this->recvBuffer.data(), this->recvBuffer.capacity()); - auto recvBytes = this->wrapped->receive(pr, timeout); - if(recvBytes != this->recvBuffer.capacity()) { - this->recvBuffer.resize(recvBytes); - } - this->decode(message); - return recvBytes; - }; - - protected: - MessangerDecorator(MessangerConcrete_t& wrapped, const std::size_t recvCapacity) { - this->wrapped = &wrapped; - this->recvBuffer.resize(recvCapacity); - }; - MessangerConcrete_t* wrapped; - - /** - * @brief write the message into sendBuffer, as a series of raw bytes - */ - virtual void encode(const Send_t& message) = 0; - std::vector sendBuffer; - - /** - * @brief convert the content of recvBuffer as a received message - */ - virtual void decode(Recv_t& message) = 0; - std::vector recvBuffer; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/SocketCrypted.h b/CrossSocket/include/SocketCrypted.h deleted file mode 100644 index bba0cd04..00000000 --- a/CrossSocket/include/SocketCrypted.h +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SOCKETCRYPTED_H_ -#define _CROSS_SOCKET_SOCKETCRYPTED_H_ - -#include -#include -#include - -namespace sck { - template - class SocketCrypted - : public SocketDecorator - , public Messanger { - static_assert(std::is_base_of, MessangerCrypt_t>::value, "invalid MessangerCrypt_t"); - public: - SocketCrypted(std::unique_ptr socket) - : SocketDecorator(std::move(socket)) - , coder(*this->wrapped) { - }; - - private: - MessangerCrypt_t coder; - - inline bool send(const Send_t& message) final { - return this->coder.send(message); - }; - - std::size_t receive(Recv_t& message, const std::chrono::milliseconds& timeout) final { - return this->coder.receive(message, timeout); - }; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/async/AsyncClient.h b/CrossSocket/include/async/AsyncClient.h index a48c4874..edc9906a 100644 --- a/CrossSocket/include/async/AsyncClient.h +++ b/CrossSocket/include/async/AsyncClient.h @@ -17,16 +17,13 @@ namespace sck::async { class AsyncClient : public AsyncDecorator - , public Messanger< - std::pair, - std::pair - > { + , public Messanger { public: AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); private: inline bool send(const std::pair& message) final { - return dynamic_cast(this->wrapped.get())->send(message); + return dynamic_cast(this->wrapped.get())->send(message); }; std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; diff --git a/CrossSocket/include/async/AsyncSocketCrypted.h b/CrossSocket/include/async/AsyncSocketCrypted.h deleted file mode 100644 index cd3e7eee..00000000 --- a/CrossSocket/include/async/AsyncSocketCrypted.h +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ASYNCSOCKETCRYPTED_H_ -#define _CROSS_SOCKET_ASYNCSOCKETCRYPTED_H_ - -#include -#include - -namespace sck { - template - class SocketCrypted - : public SocketDecorator - , public Messanger { - static_assert(std::is_base_of, MessangerCrypt_t>::value, "invalid MessangerCrypt_t"); - public: - SocketCrypted(std::unique_ptr socket) - : SocketDecorator(std::move(socket)) - , coder(*this->wrapped) { - }; - - inline void resetListener(CryptedMessageListener* listener) { - std::lock_guard lk(this->listenerMtx); - this->listener = listener; - }; - - private: - class CoderListener - : public MessangerCrypt_t - , public MessageListener { - public: - CoderListener(SocketCrypted& user) - : MessangerCrypt_t(*user.wrapped) - , user(user) { - dynamic_cast(this->user.wrapped.get())->resetListener(this); - }; - - private: - void handle(const std::pair& message) final { - std::lock_guard lk(this->listenerMtx); - if(nullptr != user.listener) { - Recv_t decoded; - this->decode(decoded, ... usare message buffer ); - this->user.listener->handle(decoded); - } - }; - - SocketCrypted& user; - }; - - MessangerCrypt_t coder; - - inline bool send(const Send_t& message) final { - return this->coder.send(message); - }; - - std::size_t receive(Recv_t& message, const std::chrono::milliseconds& timeout) final { - return this->coder.receive(message, timeout); - }; - - std::mutex listenerMtx; - CryptedMessageListener* listener = nullptr; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/async/MessageListener.h b/CrossSocket/include/async/MessageListener.h index 23968a3f..14a710dc 100644 --- a/CrossSocket/include/async/MessageListener.h +++ b/CrossSocket/include/async/MessageListener.h @@ -11,12 +11,6 @@ #include namespace sck::async { - template - class CryptedMessageListener { - public: - virtual void handle(const Recv_t& message) = 0; - }; - class MessageListener { public: virtual void handle(const std::pair& message) = 0; diff --git a/CrossSocket/src/async/AsyncClient.cpp b/CrossSocket/src/async/AsyncClient.cpp index 75be8747..0d8f174e 100644 --- a/CrossSocket/src/async/AsyncClient.cpp +++ b/CrossSocket/src/async/AsyncClient.cpp @@ -24,7 +24,7 @@ namespace sck::async { { std::lock_guard bufferLock(client.bufferMtx); auto pr = std::make_pair(client.buffer.data(), client.buffer.capacity()); - auto recvBytes = dynamic_cast(client.wrapped.get())->receive(pr, std::chrono::milliseconds(0)); + auto recvBytes = dynamic_cast(client.wrapped.get())->receive(pr, std::chrono::milliseconds(0)); if(recvBytes != client.buffer.capacity()) { client.buffer.resize(recvBytes); } From 1be64fbebecbb6ca66803b51b81cf589bdf6fedb Mon Sep 17 00:00:00 2001 From: andrea Date: Sun, 31 Jan 2021 22:05:10 +0100 Subject: [PATCH 013/228] implementing samples --- CrossSocket/include/MessangerConcrete.h | 5 ++- CrossSocket/include/SocketConcrete.h | 1 - Utils/include/AsyncStringMessanger.h | 26 +++++++++++++ Utils/include/StringMessanger.h | 51 +++++++++++++++++++------ Utils/src/AsyncStringMessanger.cpp | 19 +++++++++ Utils/src/StringMessanger.cpp | 38 +++++++++++------- 6 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 Utils/include/AsyncStringMessanger.h create mode 100644 Utils/src/AsyncStringMessanger.cpp diff --git a/CrossSocket/include/MessangerConcrete.h b/CrossSocket/include/MessangerConcrete.h index 1477fd7d..576954dd 100644 --- a/CrossSocket/include/MessangerConcrete.h +++ b/CrossSocket/include/MessangerConcrete.h @@ -19,11 +19,12 @@ namespace sck { class MessangerConcrete : public Messanger { - private: + public: bool send(const std::pair& message) final; std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; - + + private: std::chrono::milliseconds actualTimeOut = std::chrono::milliseconds(0); std::shared_ptr messageChannel; diff --git a/CrossSocket/include/SocketConcrete.h b/CrossSocket/include/SocketConcrete.h index 1518f445..3809200a 100644 --- a/CrossSocket/include/SocketConcrete.h +++ b/CrossSocket/include/SocketConcrete.h @@ -26,7 +26,6 @@ namespace sck { public: ~SocketConcrete(); - private: void open(const std::chrono::milliseconds& timeout) final; void close() final; diff --git a/Utils/include/AsyncStringMessanger.h b/Utils/include/AsyncStringMessanger.h new file mode 100644 index 00000000..de5921cc --- /dev/null +++ b/Utils/include/AsyncStringMessanger.h @@ -0,0 +1,26 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef ASYNC_STRING_MESSANGER_H +#define ASYNC_STRING_MESSANGER_H + +#include +#include + +class AsyncStringMessanger + : public sck::async::MessageListener { +public: + AsyncStringMessanger(std::unique_ptr socket); + +private: + void handle(const std::pair& message) final; + + sck::async::AsyncClient socket; + +}; + +#endif \ No newline at end of file diff --git a/Utils/include/StringMessanger.h b/Utils/include/StringMessanger.h index 145113e1..1732295b 100644 --- a/Utils/include/StringMessanger.h +++ b/Utils/include/StringMessanger.h @@ -5,17 +5,46 @@ * report any bug to andrecasa91@gmail.com. **/ -#include -#include +#ifndef STRING_MESSANGER_H +#define STRING_MESSANGER_H -namespace sck { - class StringMessanger - : public MessangerDecorator { - public: - StringMessanger(std::unique_ptr wrapped, const std::size_t recvCapacity); +#include +#include +#include - private: - void encode(const std::string& message) final; - void decode(std::string& message) final; +class PersonRegister { +public: + static const std::string& getSurname(const std::string& name) { + auto it = persons.find(name); + if(it == persons.end()) return unknown; + return it->second; }; -} \ No newline at end of file + + static const std::map persons; +private: + static const std::string unknown; +}; +const std::string PersonRegister::unknown = "unknown"; +const std::map PersonRegister::persons = { + {"Benjamin", "Franklin"}, + {"Leonardo", "Da Vinci"}, + {"Napoleone", "Bonaparte"}, + {"Nikola", "Tesla"}, + {"Luciano", "Pavarotti"}, +}; + + + +class StringMessanger { +public: + StringMessanger(std::unique_ptr socket); + + std::string sendReceive(const std::string& name); + void receiveSend(); + +private: + std::unique_ptr socket; + std::vector receiveBuffer; +}; + +#endif \ No newline at end of file diff --git a/Utils/src/AsyncStringMessanger.cpp b/Utils/src/AsyncStringMessanger.cpp new file mode 100644 index 00000000..0f39edc0 --- /dev/null +++ b/Utils/src/AsyncStringMessanger.cpp @@ -0,0 +1,19 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +AsyncStringMessanger::AsyncStringMessanger(std::unique_ptr socket) + : socket(std::move(socket), 500) { + this->socket.resetListener(this); + this->socket.open(std::chrono::milliseconds(0)); +} + +void AsyncStringMessanger::handle(const std::pair& message) { + const std::string& surname = PersonRegister::getSurname(std::string(message.first, message.second)); + this->socket.send({surname.data(), surname.size()}); +} diff --git a/Utils/src/StringMessanger.cpp b/Utils/src/StringMessanger.cpp index 29540142..53af9e1c 100644 --- a/Utils/src/StringMessanger.cpp +++ b/Utils/src/StringMessanger.cpp @@ -7,18 +7,30 @@ #include -namespace sck { - StringMessanger::StringMessanger(std::unique_ptr wrapped, const std::size_t recvCapacity) - : MessangerDecorator(std::move(wrapped), recvCapacity) { - } +const std::string& PersonRegister::getSurname(const std::string& name) { + auto it = persons.find(name); + if(it == persons.end()) return unknown; + return it->second; +} - void StringMessanger::encode(const std::string& message) { - this->sendBuffer.resize(message.size()); - memcpy(this->sendBuffer.data(), message.data(), message.size()); - }; - void StringMessanger::decode(std::string& message) { - message.reserve(this->recvBuffer.size()); - memcpy(message.data(), this->recvBuffer.data(), this->recvBuffer.size()); - }; -} \ No newline at end of file + +StringMessanger::StringMessanger(std::unique_ptr socket) + : socket(std::move(socket)) { + this->socket->open(std::chrono::milliseconds(0)); + this->receiveBuffer.resize(500); +} + +std::string StringMessanger::sendReceive(const std::string& name) { + this->socket->send({name.data(), name.size()}); + std::pair p = std::make_pair(this->receiveBuffer.data(), this->receiveBuffer.capacity()); + auto recvBytes = this->socket->receive(p , std::chrono::milliseconds(0)); + return std::string(this->receiveBuffer.data(), recvBytes); +} + +void StringMessanger::receiveSend() { + std::pair p = std::make_pair(this->receiveBuffer.data(), this->receiveBuffer.capacity()); + auto recvBytes = this->socket->receive(p , std::chrono::milliseconds(0)); + const std::string& surname = PersonRegister::getSurname(std::string(p.first, recvBytes)); + this->socket->send({surname.data(), surname.size()}); +} From 5ae3e6ee9acce3945289a250dd513e471a914a9d Mon Sep 17 00:00:00 2001 From: andrea Date: Sun, 31 Jan 2021 22:18:06 +0100 Subject: [PATCH 014/228] making virtual override public --- CrossSocket/include/SocketDecorator.h | 2 +- CrossSocket/include/async/AsyncClient.h | 2 +- CrossSocket/include/async/AsyncDecorator.h | 27 +++++++++++----------- Utils/include/StringMessanger.h | 6 +---- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/CrossSocket/include/SocketDecorator.h b/CrossSocket/include/SocketDecorator.h index 2f56e9de..d582e18e 100644 --- a/CrossSocket/include/SocketDecorator.h +++ b/CrossSocket/include/SocketDecorator.h @@ -16,7 +16,7 @@ namespace sck { * @brief The interface every socket must derive from. */ class SocketDecorator : public Socket { - private: + public: inline void open(const std::chrono::milliseconds& timeout) override { this->wrapped->open(timeout); }; inline void close() override { this->wrapped->close(); }; diff --git a/CrossSocket/include/async/AsyncClient.h b/CrossSocket/include/async/AsyncClient.h index edc9906a..94751eb9 100644 --- a/CrossSocket/include/async/AsyncClient.h +++ b/CrossSocket/include/async/AsyncClient.h @@ -21,12 +21,12 @@ namespace sck::async { public: AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); - private: inline bool send(const std::pair& message) final { return dynamic_cast(this->wrapped.get())->send(message); }; std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; + private: std::mutex bufferMtx; std::vector buffer; diff --git a/CrossSocket/include/async/AsyncDecorator.h b/CrossSocket/include/async/AsyncDecorator.h index edff9be1..1ffed209 100644 --- a/CrossSocket/include/async/AsyncDecorator.h +++ b/CrossSocket/include/async/AsyncDecorator.h @@ -28,22 +28,12 @@ namespace sck::async { this->service->resetErrorListener(listener); }; - protected: - AsyncDecorator(std::unique_ptr client) - : SocketDecorator(std::move(client)) { - }; - - virtual std::unique_ptr make_service() = 0; - std::unique_ptr service; - - Listener* listener = nullptr; - std::mutex listenerMtx; - - private: inline void open(const std::chrono::milliseconds& timeout) final { if(!this->wrapped->isOpen()){ this->wrapped->open(timeout); - this->service = this->make_service(); + if(this->wrapped->isOpen()) { + this->service = this->make_service(); + } } }; @@ -53,6 +43,17 @@ namespace sck::async { this->service.reset(); } }; + + protected: + AsyncDecorator(std::unique_ptr client) + : SocketDecorator(std::move(client)) { + }; + + virtual std::unique_ptr make_service() = 0; + std::unique_ptr service; + + Listener* listener = nullptr; + std::mutex listenerMtx; }; } diff --git a/Utils/include/StringMessanger.h b/Utils/include/StringMessanger.h index 1732295b..e4b0e92c 100644 --- a/Utils/include/StringMessanger.h +++ b/Utils/include/StringMessanger.h @@ -14,11 +14,7 @@ class PersonRegister { public: - static const std::string& getSurname(const std::string& name) { - auto it = persons.find(name); - if(it == persons.end()) return unknown; - return it->second; - }; + static const std::string& getSurname(const std::string& name); static const std::map persons; private: From 736cfbe96d8cafe7b0ebff6faeb98a7e05ba4836 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 7 Feb 2021 15:22:17 +0100 Subject: [PATCH 015/228] code refactored --- CrossSocket/include/{Address.h => Ip.h} | 31 +++-- CrossSocket/include/Messanger.h | 2 +- CrossSocket/include/Socket.h | 2 +- CrossSocket/include/async/AsyncClient.h | 11 +- CrossSocket/include/async/AsyncDecorator.h | 23 ++-- CrossSocket/include/async/AsyncTcpServer.h | 4 +- CrossSocket/include/async/Service.h | 29 ++--- .../async/{ => listener}/ErrorListener.h | 2 +- .../async/{ => listener}/MessageListener.h | 4 +- .../async/{ => listener}/TcpServerListener.h | 4 +- .../include/{SocketClient.h => core/Client.h} | 20 ++-- .../include/{ => core}/MessangerConcrete.h | 10 +- .../include/{ => core}/SocketConcrete.h | 2 +- .../include/{ => core}/SocketDecorator.h | 0 CrossSocket/include/tcp/TcpClient.h | 8 +- CrossSocket/include/tcp/TcpServer.h | 4 +- CrossSocket/include/udp/UdpClient.h | 8 +- CrossSocket/include/udp/UdpServer.h | 3 +- CrossSocket/src/Address.cpp | 52 --------- CrossSocket/src/Ip.cpp | 54 +++++++++ CrossSocket/src/async/AsyncClient.cpp | 6 +- CrossSocket/src/async/AsyncTcpServer.cpp | 4 +- CrossSocket/src/async/Service.cpp | 21 +--- .../src/{SocketClient.cpp => core/Client.cpp} | 14 +-- .../src/{Handler.cpp => core/Core.cpp} | 110 +++++++++--------- CrossSocket/src/{Handler.h => core/Core.h} | 82 +++++++------ .../src/{ => core}/MessangerConcrete.cpp | 14 +-- CrossSocket/src/{ => core}/SocketConcrete.cpp | 48 +++++--- .../src/{ => core}/SocketDecorator.cpp | 2 +- CrossSocket/src/tcp/TcpClient.cpp | 10 +- CrossSocket/src/tcp/TcpServer.cpp | 21 ++-- CrossSocket/src/udp/UdpClient.cpp | 8 +- CrossSocket/src/udp/UdpServer.cpp | 23 ++-- TODO | 2 + 34 files changed, 318 insertions(+), 320 deletions(-) rename CrossSocket/include/{Address.h => Ip.h} (51%) rename CrossSocket/include/async/{ => listener}/ErrorListener.h (92%) rename CrossSocket/include/async/{ => listener}/MessageListener.h (86%) rename CrossSocket/include/async/{ => listener}/TcpServerListener.h (73%) rename CrossSocket/include/{SocketClient.h => core/Client.h} (65%) rename CrossSocket/include/{ => core}/MessangerConcrete.h (82%) rename CrossSocket/include/{ => core}/SocketConcrete.h (98%) rename CrossSocket/include/{ => core}/SocketDecorator.h (100%) delete mode 100644 CrossSocket/src/Address.cpp create mode 100644 CrossSocket/src/Ip.cpp rename CrossSocket/src/{SocketClient.cpp => core/Client.cpp} (68%) rename CrossSocket/src/{Handler.cpp => core/Core.cpp} (65%) rename CrossSocket/src/{Handler.h => core/Core.h} (50%) rename CrossSocket/src/{ => core}/MessangerConcrete.cpp (72%) rename CrossSocket/src/{ => core}/SocketConcrete.cpp (66%) rename CrossSocket/src/{ => core}/SocketDecorator.cpp (92%) diff --git a/CrossSocket/include/Address.h b/CrossSocket/include/Ip.h similarity index 51% rename from CrossSocket/include/Address.h rename to CrossSocket/include/Ip.h index 50f3aaa6..c6629308 100644 --- a/CrossSocket/include/Address.h +++ b/CrossSocket/include/Ip.h @@ -5,40 +5,39 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_ADDRESS_H_ -#define _CROSS_SOCKET_ADDRESS_H_ +#ifndef _CROSS_SOCKET_IP_H_ +#define _CROSS_SOCKET_IP_H_ #include -#include #include namespace sck { // refer to https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzab6/address.htm enum Family {IP_V4, IP_V6}; - class Address; - typedef std::unique_ptr
AddressPtr; + class Ip; + typedef std::unique_ptr IpPtr; /** * @brief representation of a network address */ - class Address { + class Ip { public: - Address(const Address&) = default; - Address& operator=(const Address&) = default; - Address(Address&&) = delete; - Address& operator=(Address&&) = delete; + Ip(const Ip&) = default; + Ip& operator=(const Ip&) = default; + Ip(Ip&&) = delete; + Ip& operator=(Ip&&) = delete; /** * @brief Internally the protocol Family is deduced according to the host content. - * @return nullptr if the host is invalid, otherwise a smart pointer storing a usable address + * @return nullptr if the host is invalid, otherwise a smart pointer storing a usable ip */ - static AddressPtr create(const std::string& host, const std::uint16_t& port); + static IpPtr create(const std::string& host, const std::uint16_t& port); /** - * @return an ipv4 or ipv6 address with localhost as host and the passed port + * @return an ipv4 or ipv6 with localhost as host and the passed port */ - static AddressPtr createLocalHost(const std::uint16_t& port = 0, const Family& protocolType = Family::IP_V4); + static IpPtr createLocalHost(const std::uint16_t& port = 0, const Family& protocolType = Family::IP_V4); inline const std::string& getHost() const { return this->host; }; @@ -47,14 +46,14 @@ namespace sck { inline Family getFamily() const { return this->family; }; private: - Address(const std::string& host, const std::uint16_t& port, const Family& family); + Ip(const std::string& host, const std::uint16_t& port, const Family& family); std::string host; std::uint16_t port; Family family; }; - bool operator==(const Address& lhs, const Address& rhs); + bool operator==(const Ip& lhs, const Ip& rhs); } diff --git a/CrossSocket/include/Messanger.h b/CrossSocket/include/Messanger.h index 97766d16..b189afbb 100644 --- a/CrossSocket/include/Messanger.h +++ b/CrossSocket/include/Messanger.h @@ -21,7 +21,7 @@ namespace sck { Messanger(const Messanger&) = delete; Messanger& operator=(const Messanger&) = delete; - virtual ~Messanger(); + virtual ~Messanger() = default; /** * @return true if the message was completely sent diff --git a/CrossSocket/include/Socket.h b/CrossSocket/include/Socket.h index 57eea745..5021cf46 100644 --- a/CrossSocket/include/Socket.h +++ b/CrossSocket/include/Socket.h @@ -20,7 +20,7 @@ namespace sck { Socket(const Socket&) = delete; Socket& operator=(const Socket&) = delete; - virtual ~Socket(); + virtual ~Socket() = default; virtual void open(const std::chrono::milliseconds& timeout) = 0; diff --git a/CrossSocket/include/async/AsyncClient.h b/CrossSocket/include/async/AsyncClient.h index 94751eb9..5c7f7a8b 100644 --- a/CrossSocket/include/async/AsyncClient.h +++ b/CrossSocket/include/async/AsyncClient.h @@ -9,25 +9,24 @@ #define _CROSS_SOCKET_ASYNCCLIENT_H #include -#include -#include +#include +#include #include -#include namespace sck::async { class AsyncClient - : public AsyncDecorator + : public AsyncDecorator , public Messanger { public: - AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); + AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); inline bool send(const std::pair& message) final { return dynamic_cast(this->wrapped.get())->send(message); }; std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; - private: + private: std::mutex bufferMtx; std::vector buffer; class ReceiveService; diff --git a/CrossSocket/include/async/AsyncDecorator.h b/CrossSocket/include/async/AsyncDecorator.h index 1ffed209..27f66037 100644 --- a/CrossSocket/include/async/AsyncDecorator.h +++ b/CrossSocket/include/async/AsyncDecorator.h @@ -8,8 +8,7 @@ #ifndef _CROSS_SOCKET_ASYNCDECORATOR_H #define _CROSS_SOCKET_ASYNCDECORATOR_H -#include -#include +#include #include namespace sck::async { @@ -24,24 +23,22 @@ namespace sck::async { this->listener = listener; }; - void resetErrorListener(ErrorListener* listener) { + inline void resetErrorListener(listener::ErrorListener* listener) { this->service->resetErrorListener(listener); }; inline void open(const std::chrono::milliseconds& timeout) final { - if(!this->wrapped->isOpen()){ - this->wrapped->open(timeout); - if(this->wrapped->isOpen()) { - this->service = this->make_service(); - } + if (nullptr != this->service) return; + this->wrapped->open(timeout); + if (this->wrapped->isOpen()) { + this->service = this->make_service(); } }; inline void close() final { - if(this->wrapped->isOpen()){ - this->wrapped->close(); - this->service.reset(); - } + this->wrapped->close(); + if (nullptr != this->service) return; + this->service.reset(); }; protected: @@ -52,8 +49,8 @@ namespace sck::async { virtual std::unique_ptr make_service() = 0; std::unique_ptr service; - Listener* listener = nullptr; std::mutex listenerMtx; + Listener* listener = nullptr; }; } diff --git a/CrossSocket/include/async/AsyncTcpServer.h b/CrossSocket/include/async/AsyncTcpServer.h index 6372fff7..3488b92b 100644 --- a/CrossSocket/include/async/AsyncTcpServer.h +++ b/CrossSocket/include/async/AsyncTcpServer.h @@ -9,10 +9,10 @@ #define _CROSS_SOCKET_ASYNCTCPSERVER_H #include -#include +#include namespace sck::async { - class AsyncTcpServer : public AsyncDecorator { + class AsyncTcpServer : public AsyncDecorator { public: AsyncTcpServer(std::unique_ptr server); diff --git a/CrossSocket/include/async/Service.h b/CrossSocket/include/async/Service.h index a5660c4f..136655d1 100644 --- a/CrossSocket/include/async/Service.h +++ b/CrossSocket/include/async/Service.h @@ -10,10 +10,9 @@ #include #include -#include #include -#include -#include +#include +#include namespace sck::async { class Service { @@ -21,32 +20,18 @@ namespace sck::async { Service(const Service&) = delete; Service& operator=(const Service&) = delete; - // start + // service is started when building Service(const std::function& iterativeAction); - // stop + // service is stop when destroying ~Service(); - void resetErrorListener(ErrorListener* listener); + void resetErrorListener(listener::ErrorListener* listener); private: - class Barrier { - public: - Barrier() { this->stopWait = false; }; - - void wait(); - - private: - std::uint8_t queue = 0; - std::mutex queueMtx; - std::condition_variable notification; - std::atomic_bool stopWait; - }; - Barrier barrier = {}; - - ErrorListener* listener; std::mutex listenerMtx; + listener::ErrorListener* listener; - std::atomic_bool loopLife; + std::atomic_bool loopLife = false; std::thread loop; }; } diff --git a/CrossSocket/include/async/ErrorListener.h b/CrossSocket/include/async/listener/ErrorListener.h similarity index 92% rename from CrossSocket/include/async/ErrorListener.h rename to CrossSocket/include/async/listener/ErrorListener.h index 1484fa03..64116a44 100644 --- a/CrossSocket/include/async/ErrorListener.h +++ b/CrossSocket/include/async/listener/ErrorListener.h @@ -10,7 +10,7 @@ #include -namespace sck::async { +namespace sck::async::listener { class ErrorListener { public: virtual void handle(const Error& error) = 0; diff --git a/CrossSocket/include/async/MessageListener.h b/CrossSocket/include/async/listener/MessageListener.h similarity index 86% rename from CrossSocket/include/async/MessageListener.h rename to CrossSocket/include/async/listener/MessageListener.h index 14a710dc..1c7c5112 100644 --- a/CrossSocket/include/async/MessageListener.h +++ b/CrossSocket/include/async/listener/MessageListener.h @@ -8,9 +8,9 @@ #ifndef _CROSS_SOCKET_MESSAGELISTENER_H_ #define _CROSS_SOCKET_MESSAGELISTENER_H_ -#include +#include -namespace sck::async { +namespace sck::async::listener { class MessageListener { public: virtual void handle(const std::pair& message) = 0; diff --git a/CrossSocket/include/async/TcpServerListener.h b/CrossSocket/include/async/listener/TcpServerListener.h similarity index 73% rename from CrossSocket/include/async/TcpServerListener.h rename to CrossSocket/include/async/listener/TcpServerListener.h index ddce5e9b..c7b8cc6f 100644 --- a/CrossSocket/include/async/TcpServerListener.h +++ b/CrossSocket/include/async/listener/TcpServerListener.h @@ -10,10 +10,10 @@ #include -namespace sck::async { +namespace sck::async::listener { class TcpServerListener { public: - virtual void handle(std::unique_ptr clientHandler) = 0; + virtual void handle(std::unique_ptr clientHandler) = 0; }; } diff --git a/CrossSocket/include/SocketClient.h b/CrossSocket/include/core/Client.h similarity index 65% rename from CrossSocket/include/SocketClient.h rename to CrossSocket/include/core/Client.h index 11e6d029..f0b32c72 100644 --- a/CrossSocket/include/SocketClient.h +++ b/CrossSocket/include/core/Client.h @@ -5,35 +5,35 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_SOCKETCLIENT_H_ -#define _CROSS_SOCKET_SOCKETCLIENT_H_ +#ifndef _CROSS_SOCKET_CLIENT_H_ +#define _CROSS_SOCKET_CLIENT_H_ -#include -#include +#include +#include namespace sck { /** - * @brief this is the abstract base class for every implementation of a concrete socket client (tcp or udp) + * @brief this is the abstract base class for every implementation of a concrete client (tcp or udp) */ - class SocketClient + class Client : public SocketConcrete , public MessangerConcrete { public: /** * @brief returns the address of the remote entity connected with this socket */ - inline const sck::Address& getRemoteAddress() const { return this->remoteAddress; }; + inline const sck::Ip& getRemoteAddress() const { return this->remoteAddress; }; protected: /** * @param[in] the address of the server to hit */ - SocketClient(const sck::Address& remoteAddress); + Client(const sck::Ip& remoteAddress); /** * @param[in] the remote address already connected * @param[in] an already created handler to steal */ - SocketClient(const sck::Address& remoteAddress, std::shared_ptr channel); + Client(const sck::Ip& remoteAddress, std::shared_ptr channel); /** * @brief connect is internally called @@ -43,7 +43,7 @@ namespace sck { /** * @brief address of the server connected to this socket */ - sck::Address remoteAddress; + sck::Ip remoteAddress; private: inline sck::Family getFamily() const final { return this->remoteAddress.getFamily(); }; diff --git a/CrossSocket/include/MessangerConcrete.h b/CrossSocket/include/core/MessangerConcrete.h similarity index 82% rename from CrossSocket/include/MessangerConcrete.h rename to CrossSocket/include/core/MessangerConcrete.h index 576954dd..123bdfed 100644 --- a/CrossSocket/include/MessangerConcrete.h +++ b/CrossSocket/include/core/MessangerConcrete.h @@ -24,13 +24,13 @@ namespace sck { std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; - private: - std::chrono::milliseconds actualTimeOut = std::chrono::milliseconds(0); - - std::shared_ptr messageChannel; - protected: MessangerConcrete(std::shared_ptr messageChannel); + + private: + std::chrono::milliseconds actualTimeOut = std::chrono::milliseconds(0); + + std::shared_ptr channelMsg; }; } diff --git a/CrossSocket/include/SocketConcrete.h b/CrossSocket/include/core/SocketConcrete.h similarity index 98% rename from CrossSocket/include/SocketConcrete.h rename to CrossSocket/include/core/SocketConcrete.h index 3809200a..ee54b524 100644 --- a/CrossSocket/include/SocketConcrete.h +++ b/CrossSocket/include/core/SocketConcrete.h @@ -9,7 +9,7 @@ #define _CROSS_SOCKET_SOCKETCONCRETE_H_ #include -#include +#include namespace sck { enum Protocol { UDP, TCP }; diff --git a/CrossSocket/include/SocketDecorator.h b/CrossSocket/include/core/SocketDecorator.h similarity index 100% rename from CrossSocket/include/SocketDecorator.h rename to CrossSocket/include/core/SocketDecorator.h diff --git a/CrossSocket/include/tcp/TcpClient.h b/CrossSocket/include/tcp/TcpClient.h index 940db0c4..0960f224 100644 --- a/CrossSocket/include/tcp/TcpClient.h +++ b/CrossSocket/include/tcp/TcpClient.h @@ -8,7 +8,7 @@ #ifndef _CROSS_SOCKET_TCPCLIENT_H_ #define _CROSS_SOCKET_TCPCLIENT_H_ -#include "SocketClient.h" +#include namespace sck::tcp { /** @@ -17,15 +17,15 @@ namespace sck::tcp { * it should be ready to listen and accept this client */ class TcpClient - : public SocketClient { + : public Client { public: /** * @param[in] the address of the server to reach */ - explicit TcpClient(const sck::Address& remoteAddress); + explicit TcpClient(const sck::Ip& remoteAddress); protected: - TcpClient(const sck::Address& remoteAddress, std::shared_ptr channel); + TcpClient(const sck::Ip& remoteAddress, std::shared_ptr channel); private: inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; diff --git a/CrossSocket/include/tcp/TcpServer.h b/CrossSocket/include/tcp/TcpServer.h index 52c887f6..a5d3652a 100644 --- a/CrossSocket/include/tcp/TcpServer.h +++ b/CrossSocket/include/tcp/TcpServer.h @@ -8,7 +8,7 @@ #ifndef _CROSS_SOCKET_TCPSERVER_H_ #define _CROSS_SOCKET_TCPSERVER_H_ -#include +#include namespace sck::tcp { /** @@ -29,7 +29,7 @@ namespace sck::tcp { * returns an interface to use for exchanging data to and from the accepted clients. * This is a blocking operation. */ - std::unique_ptr acceptClient(); + std::unique_ptr acceptClient(); private: void openSpecific() override; diff --git a/CrossSocket/include/udp/UdpClient.h b/CrossSocket/include/udp/UdpClient.h index 862cefe7..da951dca 100644 --- a/CrossSocket/include/udp/UdpClient.h +++ b/CrossSocket/include/udp/UdpClient.h @@ -8,7 +8,7 @@ #ifndef _CROSS_SOCKET_UDPCLIENT_H_ #define _CROSS_SOCKET_UDPCLIENT_H_ -#include +#include namespace sck::udp { @@ -20,13 +20,13 @@ namespace sck::udp { * A udp may or not reserve a port, in order to be reached by another udp client. */ class UdpClient - : public SocketClient { + : public Client { public: /** @param[in] Address of the remote host to hit - @param[in] port to reserve (passing 0 no port is reserved) + @param[in] port to reserve (passing 0 a random port is reserved) */ - UdpClient(const sck::Address& remoteAddress, const std::uint16_t& localPort = 0); + UdpClient(const sck::Ip& remoteAddress, const std::uint16_t& localPort = 0); protected: std::uint16_t port; diff --git a/CrossSocket/include/udp/UdpServer.h b/CrossSocket/include/udp/UdpServer.h index d603eb7a..0498ca5e 100644 --- a/CrossSocket/include/udp/UdpServer.h +++ b/CrossSocket/include/udp/UdpServer.h @@ -12,8 +12,7 @@ namespace sck::udp { /** - * @brief interface for a udp server. - * A UdpServer is an UdpClient, with the possibility to deduce the remoteAddress, + * @brief A UdpServer is an UdpClient, with the possibility to deduce the remoteAddress, * by setting as target the first UdpClient that hits this socket, sending * at least 1 byte of data. * IMPORTANT!!! The first message sent to the server will be lost. diff --git a/CrossSocket/src/Address.cpp b/CrossSocket/src/Address.cpp deleted file mode 100644 index 98b5bad3..00000000 --- a/CrossSocket/src/Address.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "Handler.h" - -namespace sck { - static const std::string LOCALHOST_IPv4 = "127.0.0.1"; - static const std::string LOCALHOST_IPv6 = "::1"; - - Address::Address(const std::string& host, const std::uint16_t& port, const Family& family) - : host(host) - , port(port) - , family(family) { - } - - AddressPtr Address::create(const std::string& host, const std::uint16_t& port) { - //try to resolve the address as an ipv4 - AddressPtr addr; - addr.reset(new Address(host, port, sck::Family::IP_V4)); - if (nullptr == convertIpv4(*addr)) { - return addr; - } - //try to resolve the address as an ipv6 - addr.reset(new Address(host, port, sck::Family::IP_V6)); - if (nullptr == convertIpv6(*addr)) { - return addr; - } - return nullptr; - } - - AddressPtr Address::createLocalHost(const std::uint16_t& port, const Family& protocolType) { - switch (protocolType) { - case Family::IP_V4: - return std::unique_ptr
(new Address(LOCALHOST_IPv4, port, sck::Family::IP_V4)); - case Family::IP_V6: - return std::unique_ptr
(new Address(LOCALHOST_IPv6, port, sck::Family::IP_V6)); - default: - return nullptr; - } - return nullptr; - } - - bool operator==(const Address& lhs, const Address& rhs) { - return (lhs.getHost() == rhs.getHost() && lhs.getPort() == rhs.getPort() && lhs.getFamily() == rhs.getFamily()); - }; - -} diff --git a/CrossSocket/src/Ip.cpp b/CrossSocket/src/Ip.cpp new file mode 100644 index 00000000..f3190faa --- /dev/null +++ b/CrossSocket/src/Ip.cpp @@ -0,0 +1,54 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include "core/Core.h" + +namespace sck { + static const std::string LOCALHOST_IPv4 = "127.0.0.1"; + static const std::string LOCALHOST_IPv6 = "::1"; + + Ip::Ip(const std::string& host, const std::uint16_t& port, const Family& family) + : host(host) + , port(port) + , family(family) { + } + + IpPtr Ip::create(const std::string& host, const std::uint16_t& port) { + //try to resolve the Ip as an ipv4 + IpPtr addr; + addr.reset(new Ip(host, port, sck::Family::IP_V4)); + if (nullptr == convertIpv4(*addr)) { + return addr; + } + //try to resolve the Ip as an ipv6 + addr.reset(new Ip(host, port, sck::Family::IP_V6)); + if (nullptr == convertIpv6(*addr)) { + return addr; + } + return nullptr; + } + + IpPtr Ip::createLocalHost(const std::uint16_t& port, const Family& protocolType) { + switch (protocolType) { + case Family::IP_V4: + return std::unique_ptr(new Ip(LOCALHOST_IPv4, port, sck::Family::IP_V4)); + case Family::IP_V6: + return std::unique_ptr(new Ip(LOCALHOST_IPv6, port, sck::Family::IP_V6)); + default: + return nullptr; + } + return nullptr; + } + + bool operator==(const Ip& lhs, const Ip& rhs) { + return ( (0 == lhs.getHost().compare(rhs.getHost())) && + (lhs.getPort() == rhs.getPort()) && + (lhs.getFamily() == rhs.getFamily()) ); + }; + +} diff --git a/CrossSocket/src/async/AsyncClient.cpp b/CrossSocket/src/async/AsyncClient.cpp index 0d8f174e..5dc2bc60 100644 --- a/CrossSocket/src/async/AsyncClient.cpp +++ b/CrossSocket/src/async/AsyncClient.cpp @@ -9,11 +9,11 @@ #include namespace sck::async { - AsyncClient::AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity) - : AsyncDecorator(std::move(client)) { + AsyncClient::AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity) + : AsyncDecorator(std::move(client)) { this->buffer.resize(bufferCapacity); if(this->wrapped->isOpen()){ - this->service = this->make_service(); + this->open(std::chrono::milliseconds(0)); } }; diff --git a/CrossSocket/src/async/AsyncTcpServer.cpp b/CrossSocket/src/async/AsyncTcpServer.cpp index d1d7f565..47c7fa66 100644 --- a/CrossSocket/src/async/AsyncTcpServer.cpp +++ b/CrossSocket/src/async/AsyncTcpServer.cpp @@ -9,9 +9,9 @@ namespace sck::async { AsyncTcpServer::AsyncTcpServer(std::unique_ptr server) - : AsyncDecorator(std::move(server)) { + : AsyncDecorator(std::move(server)) { if(this->wrapped->isOpen()){ - this->service = this->make_service(); + this->open(std::chrono::milliseconds(0)); } }; diff --git a/CrossSocket/src/async/Service.cpp b/CrossSocket/src/async/Service.cpp index dfa32017..e2ed522a 100644 --- a/CrossSocket/src/async/Service.cpp +++ b/CrossSocket/src/async/Service.cpp @@ -8,23 +8,11 @@ #include namespace sck::async { - void Service::Barrier::wait() { - std::unique_lock lk(this->queueMtx); - ++this->queue; - if(2 == this->queue) { - this->stopWait = true; - this->notification.notify_all(); - } - else { - this->notification.wait(lk, [this](){ return static_cast(this->stopWait); }); - } - } - Service::Service(const std::function& iterativeAction) : listener(nullptr) , loop([this, &iterativeAction](){ std::function iter(iterativeAction); - this->barrier.wait(); + this->loopLife = true; while (this->loopLife) { try { iter(); @@ -45,8 +33,9 @@ namespace sck::async { } } }) { - this->loopLife = true; - this->barrier.wait(); + while (!this->loopLife) { + std::this_thread::sleep_for(std::chrono::milliseconds(3)); + } } Service::~Service() { @@ -54,7 +43,7 @@ namespace sck::async { this->loop.join(); } - void Service::resetErrorListener(ErrorListener* listener) { + void Service::resetErrorListener(listener::ErrorListener* listener) { std::lock_guard lk(this->listenerMtx); this->listener = listener; } diff --git a/CrossSocket/src/SocketClient.cpp b/CrossSocket/src/core/Client.cpp similarity index 68% rename from CrossSocket/src/SocketClient.cpp rename to CrossSocket/src/core/Client.cpp index 2752c8b1..2cc7f572 100644 --- a/CrossSocket/src/SocketClient.cpp +++ b/CrossSocket/src/core/Client.cpp @@ -5,30 +5,30 @@ * report any bug to andrecasa91@gmail.com. **/ -#include -#include "Handler.h" +#include +#include "Core.h" namespace sck { - SocketClient::SocketClient(const sck::Address& remoteAddress) + Client::Client(const sck::Ip& remoteAddress) : SocketConcrete(std::make_shared()) , MessangerConcrete(this->SocketConcrete::channel) , remoteAddress(remoteAddress) { } - SocketClient::SocketClient(const sck::Address& remoteAddress, std::shared_ptr channel) + Client::Client(const sck::Ip& remoteAddress, std::shared_ptr channel) : SocketConcrete(channel) , MessangerConcrete(this->SocketConcrete::channel) , remoteAddress(remoteAddress) { } - void SocketClient::openSpecific() { + void Client::openSpecific() { if (sck::Family::IP_V4 == this->getFamily()) { //v4 family auto addr = convertIpv4(this->remoteAddress); if (!addr) { throw std::runtime_error(this->remoteAddress.getHost() + ":" + std::to_string(this->remoteAddress.getPort()) + " is an invalid server address"); } - if (::connect(this->channel->getSocketId(), reinterpret_cast(&(*addr)), sizeof(SocketAddressIn_t)) == SCK_SOCKET_ERROR) { + if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { throwWithCode("Connection can't be established"); } } @@ -38,7 +38,7 @@ namespace sck { if (!addr) { throw std::runtime_error(this->remoteAddress.getHost() + ":" + std::to_string(this->remoteAddress.getPort()) + " is an invalid server address"); } - if (::connect(this->channel->getSocketId(), reinterpret_cast(&(*addr)), sizeof(SocketAddressIn6_t)) == SCK_SOCKET_ERROR) { + if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { throwWithCode("Connection can't be established"); } } diff --git a/CrossSocket/src/Handler.cpp b/CrossSocket/src/core/Core.cpp similarity index 65% rename from CrossSocket/src/Handler.cpp rename to CrossSocket/src/core/Core.cpp index acfecce2..d894a888 100644 --- a/CrossSocket/src/Handler.cpp +++ b/CrossSocket/src/core/Core.cpp @@ -5,38 +5,40 @@ * report any bug to andrecasa91@gmail.com. **/ -#include "Handler.h" -#include -#include - #ifdef _WIN32 #include #else #include #endif +#include "Core.h" +#include namespace sck { #ifdef _WIN32 - std::shared_ptr SocketHandler::WSAManager::managerInstance; - - std::shared_ptr SocketHandler::WSAManager::getManager() { - if (nullptr == managerInstance) { - managerInstance = std::shared_ptr(new SocketHandler::WSAManager()); //not possible to call make_shared because c'tor is private - } - return managerInstance; + std::size_t Handler::SocketHandlerFactory::handlerCounter = 0; + std::mutex Handler::SocketHandlerFactory::handlerCounterMtx; + + void Handler::SocketHandlerFactory::beforeOpen() { + std::lock_guard hndLck(handlerCounterMtx); + ++handlerCounter; + if (1 == handlerCounter) { + // first socket opened + WSADATA wsa; + WSAStartup(MAKEWORD(2, 0), &wsa); + } } - SocketHandler::WSAManager::WSAManager() { - WSADATA wsa; - WSAStartup(MAKEWORD(2, 0), &wsa); - } - - SocketHandler::WSAManager::~WSAManager() { - WSACleanup(); + void Handler::SocketHandlerFactory::afterClose() { + std::lock_guard hndLck(handlerCounterMtx); + --handlerCounter; + if (0 == handlerCounter) { + // last socket closed + WSACleanup(); + } } #endif - int getLastError() { + int getLastErrorCode() { #ifdef _WIN32 return WSAGetLastError(); #else @@ -45,13 +47,11 @@ namespace sck { } void throwWithCode(const std::string& what){ - std::stringstream s; - s << what << " , error code: " << getLastError(); - throw Error(s.str()); + throw Error(what, " , error code: ", getLastErrorCode()); } - std::unique_ptr convertIpv4(const sck::Address& address) { - auto tryConversion = [](SocketAddressIn_t& recipient, const sck::Address& address) -> bool { + std::unique_ptr convertIpv4(const sck::Ip& address) { + auto tryConversion = [](SocketIp4& recipient, const sck::Ip& address) -> bool { #if !defined(_WIN32) in_addr ia; if (1 == ::inet_pton(AF_INET, address.getHost().c_str(), &ia)) { @@ -82,9 +82,9 @@ namespace sck { }; if (sck::Family::IP_V4 == address.getFamily()) { - std::unique_ptr resolved = std::make_unique(); + std::unique_ptr resolved = std::make_unique(); // set everything to 0 first - ::memset(&(*resolved), 0, sizeof(SocketAddressIn_t)); + ::memset(&(*resolved), 0, sizeof(SocketIp4)); resolved->sin_family = AF_INET; if (!tryConversion(*resolved, address)) { return nullptr; @@ -95,8 +95,8 @@ namespace sck { return nullptr; } - std::unique_ptr convertIpv6(const sck::Address& address) { - auto tryConversion = [](SocketAddressIn6_t& recipient, const sck::Address& address) -> bool { + std::unique_ptr convertIpv6(const sck::Ip& address) { + auto tryConversion = [](SocketIp6& recipient, const sck::Ip& address) -> bool { #if !defined(_WIN32) in6_addr ia; if (1 == ::inet_pton(AF_INET6, address.getHost().c_str(), &ia)) { @@ -127,9 +127,9 @@ namespace sck { }; if (sck::Family::IP_V6 == address.getFamily()) { - std::unique_ptr resolved = std::make_unique(); + std::unique_ptr resolved = std::make_unique(); // set everything to 0 first - ::memset(&(*resolved), 0, sizeof(SocketAddressIn6_t)); + ::memset(&(*resolved), 0, sizeof(SocketIp6)); resolved->sin6_family = AF_INET6; resolved->sin6_flowinfo = 0; if (!tryConversion(*resolved, address)) { @@ -141,15 +141,15 @@ namespace sck { return nullptr; } - AddressPtr convert(const SocketAddress_t& address) { + IpPtr convert(const SocketIp& address) { // refer to https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu std::string ip; std::uint16_t port; if (AF_INET == address.sa_family) { // ipv4 address // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to an ip that has no sense - ip = std::string(::inet_ntoa(reinterpret_cast(&address)->sin_addr)); - port = ntohs(reinterpret_cast(&address)->sin_port); + ip = std::string(::inet_ntoa(reinterpret_cast(&address)->sin_addr)); + port = ntohs(reinterpret_cast(&address)->sin_port); } else { // ipv6 address @@ -158,9 +158,9 @@ namespace sck { ::memset(temp, 0, INET6_ADDRSTRLEN); ::inet_ntop(address.sa_family, &address, temp, INET6_ADDRSTRLEN); ip = std::string(temp, INET6_ADDRSTRLEN); - port = ntohs(reinterpret_cast(&address)->sin6_port); + port = ntohs(reinterpret_cast(&address)->sin6_port); } - return sck::Address::create(ip, port); + return sck::Ip::create(ip, port); } Handler::~Handler() { @@ -171,21 +171,22 @@ namespace sck { Handler::Handler() : opened(false) - , socketId(SCK_INVALID_SOCKET) { -#ifdef _WIN32 - this->manager = WSAManager::getManager(); -#endif + , hndl(SCK_INVALID_SOCKET) { } - Handler::Handler(Socket_t extOpendSocket) - : Handler() { - this->opened = true; - this->socketId = extOpendSocket; + Handler::Handler(const SocketHandler& hndl) + : opened(true) + , hndl(hndl) { +#ifdef _WIN32 + SocketHandlerFactory::beforeOpen(); +#endif } void Handler::open(const Protocol& type, const Family& family) { if(this->opened) return; - +#ifdef _WIN32 + SocketHandlerFactory::beforeOpen(); +#endif auto toIntFamily = [](const Family& family) -> int { switch (family) { case sck::Family::IP_V4: @@ -200,15 +201,15 @@ namespace sck { switch (type) { case Protocol::TCP: - this->socketId = ::socket(toIntFamily(family), SOCK_STREAM, 0); - if (this->socketId == SCK_INVALID_SOCKET) { + this->hndl = ::socket(toIntFamily(family), SOCK_STREAM, 0); + if (this->hndl == SCK_INVALID_SOCKET) { this->close(); throwWithCode("Stream socket could not be created"); } break; case Protocol::UDP: - this->socketId = ::socket(toIntFamily(family), SOCK_DGRAM, 0); - if (this->socketId == SCK_INVALID_SOCKET) { + this->hndl = ::socket(toIntFamily(family), SOCK_DGRAM, 0); + if (this->hndl == SCK_INVALID_SOCKET) { this->close(); throwWithCode("DataGram socket could not be created"); } @@ -222,13 +223,16 @@ namespace sck { void Handler::close() { if (!this->opened) return; #ifdef _WIN32 - shutdown(this->socketId, 2); - closesocket(this->socketId); + shutdown(this->hndl, 2); + closesocket(this->hndl); #else - ::shutdown(this->socketId, SHUT_RDWR); - ::close(this->socketId); + ::shutdown(this->hndl, SHUT_RDWR); + ::close(this->hndl); #endif this->opened = false; - this->socketId = SCK_INVALID_SOCKET; + this->hndl = SCK_INVALID_SOCKET; +#ifdef _WIN32 + SocketHandlerFactory::afterClose(); +#endif } } \ No newline at end of file diff --git a/CrossSocket/src/Handler.h b/CrossSocket/src/core/Core.h similarity index 50% rename from CrossSocket/src/Handler.h rename to CrossSocket/src/core/Core.h index bfb5268e..85d40c81 100644 --- a/CrossSocket/src/Handler.h +++ b/CrossSocket/src/core/Core.h @@ -5,12 +5,9 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_HANDLER_H_ -#define _CROSS_SOCKET_HANDLER_H_ +#ifndef _CROSS_SOCKET_CORE_H_ +#define _CROSS_SOCKET_CORE_H_ -#include -#include -#include #ifdef _WIN32 #include #include @@ -27,68 +24,74 @@ #define SCK_INVALID_SOCKET -1 #define SCK_SOCKET_ERROR -1 #endif +#include +#include +#include +#include namespace sck { /** * socket handle */ #ifdef _WIN32 - using Socket_t = SOCKET; + using SocketHandler = SOCKET; #else - using Socket_t = int; + using SocketHandler = int; #endif /** * @brief representation of a generic socket address */ #ifdef _WIN32 - using SocketAddress_t = SOCKADDR; + using SocketIp = SOCKADDR; #else - using SocketAddress_t = sockaddr; + using SocketIp = sockaddr; #endif /** * @brief representation of an ipv4 socket address */ #ifdef _WIN32 - using SocketAddressIn_t = SOCKADDR_IN; + using SocketIp4 = SOCKADDR_IN; #else - using SocketAddressIn_t = sockaddr_in; + using SocketIp4 = sockaddr_in; #endif /** * @brief representation of an ipv6 socket address */ #ifdef _WIN32 - using SocketAddressIn6_t = SOCKADDR_IN6; + using SocketIp6 = SOCKADDR_IN6; #else - using SocketAddressIn6_t = sockaddr_in6; + using SocketIp6 = sockaddr_in6; #endif /** * @brief returns the last error code raised by the socket API */ - int getLastError(); + int getLastErrorCode(); void throwWithCode(const std::string& what); /** * @brief checks the address syntax and in case - * it's valid, creates the socket API representation + * it's valid as an ipv4, creates the socket API representation * of the address */ - std::unique_ptr convertIpv4(const sck::Address& address); + std::unique_ptr convertIpv4(const sck::Ip& address); /** * @brief checks the address syntax and in case - * it's valid, creates the socket API representation + * it's valid as an ipv6, creates the socket API representation * of the address */ - std::unique_ptr convertIpv6(const sck::Address& address); + std::unique_ptr convertIpv6(const sck::Ip& address); /** - * @brief Convert a SocketAddress_t into an Address + * @brief Convert a SocketAddress_t into an Address, internally + * deducing the family. */ - AddressPtr convert(const SocketAddress_t& address); + IpPtr convert(const SocketIp& address); /** - * contains the required things to work with the socket API - */ + * An object storing a socket API handler and containing the minimal + * functionalities for interacting with it. + */ class Handler { public: Handler(const Handler&) = delete; @@ -100,43 +103,46 @@ namespace sck { Handler(); /** - * @brief the passed socket should be already opened + * @brief the passed handler should be already created externally + * by the socket api */ - Handler(Socket_t extOpendSocket); + Handler(const SocketHandler& hndl); virtual ~Handler(); /** - * @brief internally creates a new socket handler + * @brief internally creates a new socket */ void open(const Protocol& type, const Family& family); /** - * @brief close and shutdown the current socket handler + * @brief close and shutdown the current socket */ void close(); + inline bool isOpen() const { return this->opened; }; - inline const Socket_t& getSocketId() const { return this->socketId; } + inline const SocketHandler& operator*() const { return this->hndl; }; private: std::atomic_bool opened; - Socket_t socketId; + SocketHandler hndl; #ifdef _WIN32 - /** - * @brief When the manager is created WSAStartup is called, - * when is destroyed WSACleanup is called - */ - class WSAManager { + class SocketHandlerFactory { public: - ~WSAManager(); - static std::shared_ptr getManager(); + /** + * @brief If we are about to open the first socket, WSAStartup() is invoked + */ + static void beforeOpen(); + /** + * @brief If we are closing the last socket, WSACleanup() is invoked + */ + static void afterClose(); private: - WSAManager(); - static std::shared_ptr managerInstance; + static std::mutex handlerCounterMtx; + static std::size_t handlerCounter; }; - std::shared_ptr manager; #endif }; diff --git a/CrossSocket/src/MessangerConcrete.cpp b/CrossSocket/src/core/MessangerConcrete.cpp similarity index 72% rename from CrossSocket/src/MessangerConcrete.cpp rename to CrossSocket/src/core/MessangerConcrete.cpp index c9a73c10..c191ea14 100644 --- a/CrossSocket/src/MessangerConcrete.cpp +++ b/CrossSocket/src/core/MessangerConcrete.cpp @@ -5,20 +5,20 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include -#include "Handler.h" +#include "Core.h" namespace sck { MessangerConcrete::MessangerConcrete(std::shared_ptr messageChannel) { if(nullptr == messageChannel) { throw Error("found null channel when building MessangerConcrete"); } - this->messageChannel = messageChannel; + this->channelMsg = messageChannel; } bool MessangerConcrete::send(const std::pair& message) { - int sentBytes = ::send(this->messageChannel->getSocketId(), message.first, static_cast(message.second), 0); + int sentBytes = ::send(**this->channelMsg, message.first, static_cast(message.second), 0); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; throwWithCode("send failed"); @@ -32,7 +32,7 @@ namespace sck { this->actualTimeOut = timeout; #ifdef _WIN32 auto tv = DWORD(this->actualTimeOut.count()); - if (setsockopt(this->messageChannel->getSocketId(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { + if (setsockopt(**this->channelMsg, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { #else struct timeval tv = { 0,0 }; if (this->actualTimeOut.count() >= 1000) { @@ -41,13 +41,13 @@ namespace sck { else { tv.tv_usec = std::chrono::duration_cast(this->actualTimeOut).count(); } - if (::setsockopt(this->messageChannel->getSocketId(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(struct timeval)) < 0) { + if (::setsockopt(**this->channelMsg, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(struct timeval)) < 0) { #endif throwWithCode("can't set timeout"); } } - int recvBytes = ::recv(this->messageChannel->getSocketId(), message.first, static_cast(message.second), 0); + int recvBytes = ::recv(**this->channelMsg, message.first, static_cast(message.second), 0); if (recvBytes == SCK_SOCKET_ERROR) { recvBytes = 0; throwWithCode("receive failed"); diff --git a/CrossSocket/src/SocketConcrete.cpp b/CrossSocket/src/core/SocketConcrete.cpp similarity index 66% rename from CrossSocket/src/SocketConcrete.cpp rename to CrossSocket/src/core/SocketConcrete.cpp index 9b3047e9..f36e50d9 100644 --- a/CrossSocket/src/SocketConcrete.cpp +++ b/CrossSocket/src/core/SocketConcrete.cpp @@ -5,8 +5,9 @@ * report any bug to andrecasa91@gmail.com. **/ -#include -#include "Handler.h" +#include +#include "Core.h" +#include #include #include @@ -19,6 +20,9 @@ namespace sck { SocketConcrete::SocketConcrete(std::shared_ptr channel) : channel(channel) { + if (nullptr == this->channel) { + throw Error("found null channel when building SocketConcrete"); + } } SocketConcrete::~SocketConcrete() { @@ -62,6 +66,10 @@ namespace sck { } } + bool SocketConcrete::isOpen() const { + return this->channel->isOpen(); + } + void SocketConcrete::close() { if (!this->isOpen()) { return; @@ -79,7 +87,7 @@ namespace sck { void SocketConcrete::bindToPort(const std::uint16_t& port) { int reusePortOptVal = 1; - ::setsockopt(this->channel->getSocketId(), SOL_SOCKET, REBIND_OPTION, reinterpret_castchannel, SOL_SOCKET, REBIND_OPTION, reinterpret_cast(&reusePortOptVal), sizeof(int)); // bind the server to the port - if (sck::Family::IP_V4 == this->getFamily()) { - //v4 family - SocketAddressIn_t addr; - ::memset(&addr, 0, sizeof(SocketAddressIn_t)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); + auto fam = this->getFamily(); + if (sck::Family::IP_V4 == fam) { + SocketIp4 addr; + ::memset(&addr, 0, sizeof(SocketIp4)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); #ifdef _WIN32 - addr.sin_addr.s_addr = ADDR_ANY; + addr.sin_addr.s_addr = ADDR_ANY; #else - addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_addr.s_addr = htonl(INADDR_ANY); #endif - if (::bind(this->channel->getSocketId(), reinterpret_cast(&addr), sizeof(SocketAddressIn_t)) == SCK_SOCKET_ERROR) { - throwWithCode("can't bind localhost on port: " + std::to_string(port)); - } + if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { + throwWithCode("can't bind localhost on port: " + std::to_string(port)); + } } - else { - //v6 family - SocketAddressIn6_t addr; - ::memset(&addr, 0, sizeof(SocketAddressIn6_t)); + else if(sck::Family::IP_V6 == fam) { + SocketIp6 addr; + ::memset(&addr, 0, sizeof(SocketIp6)); addr.sin6_family = AF_INET6; addr.sin6_flowinfo = 0; addr.sin6_addr = IN6ADDR_ANY_INIT; // apparently, there is no such a cross-system define for ipv4 addresses addr.sin6_port = htons(port); - if (::bind(this->channel->getSocketId(), reinterpret_cast(&addr), sizeof(SocketAddressIn6_t)) == SCK_SOCKET_ERROR) { + if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { throwWithCode("can't bind localhost on port: " + std::to_string(port)); } } + else { + throw Error("found an unrecognized family type when binding the socket"); + } } } \ No newline at end of file diff --git a/CrossSocket/src/SocketDecorator.cpp b/CrossSocket/src/core/SocketDecorator.cpp similarity index 92% rename from CrossSocket/src/SocketDecorator.cpp rename to CrossSocket/src/core/SocketDecorator.cpp index 2315a288..a897114e 100644 --- a/CrossSocket/src/SocketDecorator.cpp +++ b/CrossSocket/src/core/SocketDecorator.cpp @@ -5,7 +5,7 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include namespace sck { diff --git a/CrossSocket/src/tcp/TcpClient.cpp b/CrossSocket/src/tcp/TcpClient.cpp index 827719bb..c48a7cd4 100644 --- a/CrossSocket/src/tcp/TcpClient.cpp +++ b/CrossSocket/src/tcp/TcpClient.cpp @@ -6,15 +6,15 @@ **/ #include -#include "../Handler.h" +#include "../core/Core.h" namespace sck::tcp { - TcpClient::TcpClient(const sck::Address& remoteAddress) - : SocketClient(remoteAddress) { + TcpClient::TcpClient(const sck::Ip& remoteAddress) + : Client(remoteAddress) { } - TcpClient::TcpClient(const sck::Address& remoteAddress, std::shared_ptr channel) - : SocketClient(remoteAddress, channel) { + TcpClient::TcpClient(const sck::Ip& remoteAddress, std::shared_ptr channel) + : Client(remoteAddress, channel) { } } \ No newline at end of file diff --git a/CrossSocket/src/tcp/TcpServer.cpp b/CrossSocket/src/tcp/TcpServer.cpp index 9842a37e..40db9a66 100644 --- a/CrossSocket/src/tcp/TcpServer.cpp +++ b/CrossSocket/src/tcp/TcpServer.cpp @@ -7,15 +7,15 @@ #include #include -#include "../Handler.h" +#include "../core/Core.h" +#include namespace sck::tcp { - constexpr std::size_t LISTEN_BACKLOG = 50; class ClientHandler : public TcpClient { public: - explicit ClientHandler(const sck::Address& remoteAddress, std::shared_ptr channel) + explicit ClientHandler(const sck::Ip& remoteAddress, std::shared_ptr channel) : TcpClient(remoteAddress, channel) { }; @@ -33,22 +33,25 @@ namespace sck::tcp { , protocol(family) { } - std::unique_ptr TcpServer::acceptClient() { - SocketAddress_t acceptedClientAddress; + std::unique_ptr TcpServer::acceptClient() { + if (!this->isOpen()) { + throw Error("a tcp server should be opened before accepting a new client"); + } + SocketIp acceptedClientAddress; #ifdef _WIN32 int acceptedAddressLength #else unsigned int acceptedAddressLength #endif - = sizeof(SocketAddress_t); + = sizeof(SocketIp); // accept: wait for a client to call connect and hit this server and get a pointer to this client. - Socket_t temp = ::accept(this->channel->getSocketId(), &acceptedClientAddress, &acceptedAddressLength); + SocketHandler temp = ::accept(**this->channel, &acceptedClientAddress, &acceptedAddressLength); if (temp == SCK_INVALID_SOCKET) { throwWithCode("Error: accepting new client"); } std::shared_ptr acceptedClientHandler = std::make_shared(temp); - AddressPtr remoteAddress = convert(acceptedClientAddress); + IpPtr remoteAddress = convert(acceptedClientAddress); if (nullptr != remoteAddress) { throw std::runtime_error("accepted client remote address is not resolvable"); } @@ -59,7 +62,7 @@ namespace sck::tcp { void TcpServer::openSpecific() { this->bindToPort(this->port); // listen to the port to allow all the following clients acceptance - if (::listen(this->channel->getSocketId(), LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { + if (::listen(**this->channel, LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { throwWithCode("Error: listening on port: " + std::to_string(this->port)); } } diff --git a/CrossSocket/src/udp/UdpClient.cpp b/CrossSocket/src/udp/UdpClient.cpp index 8105b12e..52cbc3ec 100644 --- a/CrossSocket/src/udp/UdpClient.cpp +++ b/CrossSocket/src/udp/UdpClient.cpp @@ -6,17 +6,17 @@ **/ #include -#include "../Handler.h" +#include "../core/Core.h" namespace sck::udp { - UdpClient::UdpClient(const sck::Address& remoteAddress, const std::uint16_t& localPort) - : SocketClient(remoteAddress) + UdpClient::UdpClient(const sck::Ip& remoteAddress, const std::uint16_t& localPort) + : Client(remoteAddress) , port(localPort) { } void UdpClient::openSpecific() { this->bindToPort(this->port); - this->SocketClient::openSpecific(); + this->Client::openSpecific(); } } \ No newline at end of file diff --git a/CrossSocket/src/udp/UdpServer.cpp b/CrossSocket/src/udp/UdpServer.cpp index 45c49534..4dc2cf53 100644 --- a/CrossSocket/src/udp/UdpServer.cpp +++ b/CrossSocket/src/udp/UdpServer.cpp @@ -6,16 +6,19 @@ **/ #include -#include "../Handler.h" +#include "../core/Core.h" #include namespace sck::udp { - sck::Address getInitialAddress(const sck::Family& protocol) { - if (sck::Family::IP_V6 == protocol) { - return *sck::Address::createLocalHost(0, sck::Family::IP_V6); + sck::Ip getInitialAddress(const sck::Family& family) { + if (sck::Family::IP_V4 == family) { + return *sck::Ip::createLocalHost(0, sck::Family::IP_V4); } - return *sck::Address::createLocalHost(0, sck::Family::IP_V4); + if (sck::Family::IP_V6 == family) { + return *sck::Ip::createLocalHost(0, sck::Family::IP_V6); + } + throw Error("unrecognized family"); } UdpServer::UdpServer(const std::uint16_t& localPort, const sck::Family& protocol) @@ -27,21 +30,21 @@ namespace sck::udp { // receive a message from the client, that from now on will beccome the recognized one. char bf[MAX_UDP_RECV_MESSAGE]; - SocketAddress_t remoteAddr; + SocketIp remoteAddr; #ifdef _WIN32 int #else unsigned int #endif - remoteAddrLen = sizeof(SocketAddress_t); - if (::recvfrom(this->channel->getSocketId(), &bf, MAX_UDP_RECV_MESSAGE, 0, &remoteAddr, &remoteAddrLen) == SCK_SOCKET_ERROR) { + remoteAddrLen = sizeof(SocketIp); + if (::recvfrom(**this->channel, &bf[0], static_cast(MAX_UDP_RECV_MESSAGE), 0, &remoteAddr, &remoteAddrLen) == SCK_SOCKET_ERROR) { throwWithCode("recvfrom failed while identifying the target"); } - AddressPtr remoteConverted = convert(remoteAddr); + IpPtr remoteConverted = convert(remoteAddr); if(nullptr == remoteConverted) { throw Error(remoteAddr.sa_data, " is an invalid data for udp serer remote address"); } this->remoteAddress = *remoteConverted; - this->SocketClient::openSpecific(); + this->Client::openSpecific(); } } \ No newline at end of file diff --git a/TODO b/TODO index bb7a3730..010abc76 100644 --- a/TODO +++ b/TODO @@ -9,3 +9,5 @@ remove default d'tor provare in Windows remove Socket Server add explicit a c?tor single input +indicare quando throw in doc class +rename protocol in family everywhere From 48aabbc4c4759786133833d472f72bb6dbe1bb3f Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 7 Feb 2021 17:02:59 +0100 Subject: [PATCH 016/228] fixing samples --- CMakeLists.txt | 10 -- Samples/CMakeLists.txt | 4 +- Samples/Names.h | 45 +++++++++ Samples/{Utils/include => }/ProcessLauncher.h | 46 +++++++++- Samples/Tcp/01-server-client/CMakeLists.txt | 10 ++ .../Client.cpp | 0 .../Launcher.cpp | 20 ++-- .../Server.cpp | 0 .../CMakeLists.txt | 58 ++++++------ .../Client.cpp | 0 .../Launcher.cpp | 22 ++--- .../README.svg | 0 .../Server.cpp | 0 .../CMakeLists.txt | 62 ++++++------- .../Client.cpp | 0 .../Launcher.cpp | 22 ++--- .../README.svg | 0 .../Server.cpp | 0 .../CMakeLists.txt | 78 ++++++++-------- .../Client.cpp | 0 .../Launcher.cpp | 22 ++--- .../README.svg | 0 .../Repeater.cpp | 0 .../Server.cpp | 0 Samples/Tcp/CMakeLists.txt | 8 +- .../Sample_01_server_client/CMakeLists.txt | 29 ------ Samples/Utils/CMakeLists.txt | 22 ----- Samples/Utils/include/Service.h | 47 ---------- Samples/Utils/src/ProcessLauncher.cpp | 40 -------- Samples/Utils/src/Service.cpp | 91 ------------------- Utils/CMakeLists.txt | 31 ------- Utils/Main.cpp | 0 Utils/include/AsyncStringMessanger.h | 26 ------ Utils/include/ProcessLauncher.h | 65 ------------- Utils/include/StringMessanger.h | 46 ---------- Utils/src/AsyncStringMessanger.cpp | 19 ---- Utils/src/ProcessLauncher.cpp | 49 ---------- Utils/src/StringMessanger.cpp | 36 -------- cmake/CheckPlatform.cmake | 10 -- cmake/GetCompiler.cmake | 13 --- cmake/Helper.cmake | 38 -------- cmake/Log.cmake | 8 -- cmake/SetInstallRPath.cmake | 29 ------ cmake/TargetProperties.cmake | 33 ------- cmake/WriteBuildProperties.cmake | 17 ---- cmake/fmiplatform.cmake | 40 -------- 46 files changed, 245 insertions(+), 851 deletions(-) create mode 100644 Samples/Names.h rename Samples/{Utils/include => }/ProcessLauncher.h (58%) create mode 100644 Samples/Tcp/01-server-client/CMakeLists.txt rename Samples/Tcp/{Sample_01_server_client => 01-server-client}/Client.cpp (100%) rename Samples/Tcp/{Sample_01_server_client => 01-server-client}/Launcher.cpp (94%) rename Samples/Tcp/{Sample_01_server_client => 01-server-client}/Server.cpp (100%) rename Samples/Tcp/{Sample_02_singlethread_services => 02-server-clients}/CMakeLists.txt (93%) rename Samples/Tcp/{Sample_02_singlethread_services => 02-server-clients}/Client.cpp (100%) rename Samples/Tcp/{Sample_02_singlethread_services => 02-server-clients}/Launcher.cpp (95%) rename Samples/Tcp/{Sample_02_singlethread_services => 02-server-clients}/README.svg (100%) rename Samples/Tcp/{Sample_02_singlethread_services => 02-server-clients}/Server.cpp (100%) rename Samples/Tcp/{Sample_03_multithread_services => 03-servers-clients}/CMakeLists.txt (94%) rename Samples/Tcp/{Sample_03_multithread_services => 03-servers-clients}/Client.cpp (100%) rename Samples/Tcp/{Sample_03_multithread_services => 03-servers-clients}/Launcher.cpp (95%) rename Samples/Tcp/{Sample_03_multithread_services => 03-servers-clients}/README.svg (100%) rename Samples/Tcp/{Sample_03_multithread_services => 03-servers-clients}/Server.cpp (100%) rename Samples/Tcp/{Sample_04_repeater => 04-repeater}/CMakeLists.txt (93%) rename Samples/Tcp/{Sample_04_repeater => 04-repeater}/Client.cpp (100%) rename Samples/Tcp/{Sample_04_repeater => 04-repeater}/Launcher.cpp (95%) rename Samples/Tcp/{Sample_04_repeater => 04-repeater}/README.svg (100%) rename Samples/Tcp/{Sample_04_repeater => 04-repeater}/Repeater.cpp (100%) rename Samples/Tcp/{Sample_04_repeater => 04-repeater}/Server.cpp (100%) delete mode 100644 Samples/Tcp/Sample_01_server_client/CMakeLists.txt delete mode 100644 Samples/Utils/CMakeLists.txt delete mode 100644 Samples/Utils/include/Service.h delete mode 100644 Samples/Utils/src/ProcessLauncher.cpp delete mode 100644 Samples/Utils/src/Service.cpp delete mode 100644 Utils/CMakeLists.txt delete mode 100644 Utils/Main.cpp delete mode 100644 Utils/include/AsyncStringMessanger.h delete mode 100644 Utils/include/ProcessLauncher.h delete mode 100644 Utils/include/StringMessanger.h delete mode 100644 Utils/src/AsyncStringMessanger.cpp delete mode 100644 Utils/src/ProcessLauncher.cpp delete mode 100644 Utils/src/StringMessanger.cpp delete mode 100644 cmake/CheckPlatform.cmake delete mode 100644 cmake/GetCompiler.cmake delete mode 100644 cmake/Helper.cmake delete mode 100644 cmake/Log.cmake delete mode 100644 cmake/SetInstallRPath.cmake delete mode 100644 cmake/TargetProperties.cmake delete mode 100644 cmake/WriteBuildProperties.cmake delete mode 100644 cmake/fmiplatform.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index b2ca46d6..9623f6d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,5 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) -project(Cross-Socket-Prj VERSION ${VERSION}) - set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -9,11 +7,6 @@ set(CMAKE_CXX_EXTENSIONS OFF) option(LIB_OPT "Compile shared libraries (ON) or static (OFF)" OFF) option(BUILD_SAMPLES "Build the samples showing how to use EFG" ON) -# only windows specific stuff -if(WIN32) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) -endif() - # set macro-directory and find scripts set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") @@ -21,12 +14,9 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") set(WITH_SOURCE_TREE ON) include(GroupSources) include(AutoCollect) -include(Log) -include(SetInstallRPath) add_subdirectory(CrossSocket) if(BUILD_SAMPLES) - add_subdirectory(Utils) add_subdirectory(Samples) endif() diff --git a/Samples/CMakeLists.txt b/Samples/CMakeLists.txt index 08b3b355..47137a42 100644 --- a/Samples/CMakeLists.txt +++ b/Samples/CMakeLists.txt @@ -1,5 +1,3 @@ -#add_subdirectory(Utils) - add_subdirectory(Tcp) -add_subdirectory(Udp) +# add_subdirectory(Udp) diff --git a/Samples/Names.h b/Samples/Names.h new file mode 100644 index 00000000..7e78b18c --- /dev/null +++ b/Samples/Names.h @@ -0,0 +1,45 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef SAMPLE_NAMES_H +#define SAMPLE_NAMES_H + +#include +#include + +class Names { +public: + Names() { + this->cursor = namesSurnames.begin(); + } + + inline const std::string& getCursorName() const { return this->cursor->first; }; + inline const std::string& getCursorSurname() const { return this->cursor->second; }; + + Names& operator++() { + ++this->cursor; + if (this->cursor == namesSurnames.end()) { + this->cursor = namesSurnames.begin(); + } + return *this; + } + +private: + static const std::map namesSurnames; + + std::map::const_iterator cursor; +}; + +const std::map Names::namesSurnames = { + {"Luciano", "Pavarotti"}, + {"Gengis", "Khan"}, + {"Giulio", "Cesare"}, + {"Theodor", "Roosvelt"}, + {"Immanuel", "Kant"} +}; + +#endif diff --git a/Samples/Utils/include/ProcessLauncher.h b/Samples/ProcessLauncher.h similarity index 58% rename from Samples/Utils/include/ProcessLauncher.h rename to Samples/ProcessLauncher.h index a4ed63d7..d26bf07d 100644 --- a/Samples/Utils/include/ProcessLauncher.h +++ b/Samples/ProcessLauncher.h @@ -5,10 +5,9 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _PROCESS_LAUCNHER_ -#define _PROCESS_LAUCNHER_ +#ifndef SAMPLE_LAUNCHER_H +#define SAMPLE_LAUNCHER_H -#include #include #include #include @@ -63,4 +62,45 @@ class Launcher { std::list>> commands; }; +#include + +void Launcher::operator()() const { + std::string name = this->nameFile; +#ifdef _WIN32 + name += ".bat"; +#elif __linux__ + name += ".sh"; +#endif + + std::ofstream f(name); + auto it = this->commands.begin(); + auto itEnd = this->commands.end(); + --itEnd; + for (it; it != itEnd; ++it) { +#ifdef _WIN32 + f << std::endl << "start \"\" \"" << it->first << "\""; + for(auto a : it->second) f << " \"" << a << "\""; +#elif __linux__ + f << std::endl << "gnome-terminal -x sh -c \"./" << it->first; + for(auto a : it->second) f << " " << a; + f << "; bash\""; +#endif + } +#ifdef _WIN32 + f << std::endl << "\"" << this->commands.back().first << "\""; + for(auto a : this->commands.back().second) f << " \"" << a << "\""; +#elif __linux__ + f << std::endl << "./" << this->commands.back().first; + for(auto a : this->commands.back().second) f << " " << a; +#endif + + f.close(); + +#ifdef _WIN32 + system(name.c_str()); +#elif __linux__ + system(std::string("sh ./" + name).c_str()); +#endif +}; + #endif \ No newline at end of file diff --git a/Samples/Tcp/01-server-client/CMakeLists.txt b/Samples/Tcp/01-server-client/CMakeLists.txt new file mode 100644 index 00000000..bf0010da --- /dev/null +++ b/Samples/Tcp/01-server-client/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(Sample01_Client Client.cpp) +target_link_libraries( Sample01_Client Cross-Socket) + +add_executable(Sample01_Server Server.cpp) +target_link_libraries( Sample01_Server Cross-Socket) + +add_executable(Sample01_Launcher Launcher.cpp) +target_link_libraries(Sample01_Launcher Cross-Socket) + +add_dependencies(Sample01_Launcher Sample01_Server Sample01_Client) diff --git a/Samples/Tcp/Sample_01_server_client/Client.cpp b/Samples/Tcp/01-server-client/Client.cpp similarity index 100% rename from Samples/Tcp/Sample_01_server_client/Client.cpp rename to Samples/Tcp/01-server-client/Client.cpp diff --git a/Samples/Tcp/Sample_01_server_client/Launcher.cpp b/Samples/Tcp/01-server-client/Launcher.cpp similarity index 94% rename from Samples/Tcp/Sample_01_server_client/Launcher.cpp rename to Samples/Tcp/01-server-client/Launcher.cpp index 990424e8..82d8ba77 100644 --- a/Samples/Tcp/Sample_01_server_client/Launcher.cpp +++ b/Samples/Tcp/01-server-client/Launcher.cpp @@ -1,11 +1,11 @@ -#include - -int main() { - - Launcher lnc("launcher"); - lnc.addProcess("Sample01_Server"); - lnc.addProcess("Sample01_Client"); - lnc(); - - return 0; +#include + +int main() { + + Launcher lnc("launcher"); + lnc.addProcess("Sample01_Server"); + lnc.addProcess("Sample01_Client"); + lnc(); + + return 0; } \ No newline at end of file diff --git a/Samples/Tcp/Sample_01_server_client/Server.cpp b/Samples/Tcp/01-server-client/Server.cpp similarity index 100% rename from Samples/Tcp/Sample_01_server_client/Server.cpp rename to Samples/Tcp/01-server-client/Server.cpp diff --git a/Samples/Tcp/Sample_02_singlethread_services/CMakeLists.txt b/Samples/Tcp/02-server-clients/CMakeLists.txt similarity index 93% rename from Samples/Tcp/Sample_02_singlethread_services/CMakeLists.txt rename to Samples/Tcp/02-server-clients/CMakeLists.txt index 608ebeb8..067fc400 100644 --- a/Samples/Tcp/Sample_02_singlethread_services/CMakeLists.txt +++ b/Samples/Tcp/02-server-clients/CMakeLists.txt @@ -1,29 +1,29 @@ -##### sample 02 ##### - - add_executable( - Sample02_Client - Client.cpp - ) - target_link_libraries( Sample02_Client - PUBLIC - Utils - ) - - add_executable( - Sample02_Server - Server.cpp - ) - target_link_libraries( Sample02_Server - PUBLIC - Utils - ) - - add_executable( - Sample02__Launcher - Launcher.cpp - ) - target_link_libraries( Sample02__Launcher - PUBLIC - Utils - ) - add_dependencies(Sample02__Launcher Sample02_Client Sample02_Server) +##### sample 02 ##### + + add_executable( + Sample02_Client + Client.cpp + ) + target_link_libraries( Sample02_Client + PUBLIC + Utils + ) + + add_executable( + Sample02_Server + Server.cpp + ) + target_link_libraries( Sample02_Server + PUBLIC + Utils + ) + + add_executable( + Sample02__Launcher + Launcher.cpp + ) + target_link_libraries( Sample02__Launcher + PUBLIC + Utils + ) + add_dependencies(Sample02__Launcher Sample02_Client Sample02_Server) diff --git a/Samples/Tcp/Sample_02_singlethread_services/Client.cpp b/Samples/Tcp/02-server-clients/Client.cpp similarity index 100% rename from Samples/Tcp/Sample_02_singlethread_services/Client.cpp rename to Samples/Tcp/02-server-clients/Client.cpp diff --git a/Samples/Tcp/Sample_02_singlethread_services/Launcher.cpp b/Samples/Tcp/02-server-clients/Launcher.cpp similarity index 95% rename from Samples/Tcp/Sample_02_singlethread_services/Launcher.cpp rename to Samples/Tcp/02-server-clients/Launcher.cpp index 7a1a15bb..3866ddf8 100644 --- a/Samples/Tcp/Sample_02_singlethread_services/Launcher.cpp +++ b/Samples/Tcp/02-server-clients/Launcher.cpp @@ -1,12 +1,12 @@ -#include - -int main() { - - Launcher lnc("launcher"); - lnc.addProcess("Sample02_Server"); - lnc.addProcess("Sample02_Client"); - lnc.addProcess("Sample02_Client"); - lnc(); - - return 0; +#include + +int main() { + + Launcher lnc("launcher"); + lnc.addProcess("Sample02_Server"); + lnc.addProcess("Sample02_Client"); + lnc.addProcess("Sample02_Client"); + lnc(); + + return 0; } \ No newline at end of file diff --git a/Samples/Tcp/Sample_02_singlethread_services/README.svg b/Samples/Tcp/02-server-clients/README.svg similarity index 100% rename from Samples/Tcp/Sample_02_singlethread_services/README.svg rename to Samples/Tcp/02-server-clients/README.svg diff --git a/Samples/Tcp/Sample_02_singlethread_services/Server.cpp b/Samples/Tcp/02-server-clients/Server.cpp similarity index 100% rename from Samples/Tcp/Sample_02_singlethread_services/Server.cpp rename to Samples/Tcp/02-server-clients/Server.cpp diff --git a/Samples/Tcp/Sample_03_multithread_services/CMakeLists.txt b/Samples/Tcp/03-servers-clients/CMakeLists.txt similarity index 94% rename from Samples/Tcp/Sample_03_multithread_services/CMakeLists.txt rename to Samples/Tcp/03-servers-clients/CMakeLists.txt index 155c7fd6..df96882b 100644 --- a/Samples/Tcp/Sample_03_multithread_services/CMakeLists.txt +++ b/Samples/Tcp/03-servers-clients/CMakeLists.txt @@ -1,31 +1,31 @@ -##### sample 03 ##### - - add_executable( - Sample03_Client - Client.cpp - ) - target_link_libraries( Sample03_Client - PUBLIC - Utils - ) - - add_executable( - Sample03_Server - Server.cpp - ) - find_package( Threads ) - target_link_libraries( Sample03_Server - PUBLIC - Utils - ${CMAKE_THREAD_LIBS_INIT} - ) - - add_executable( - Sample03__Launcher - Launcher.cpp - ) - target_link_libraries( Sample03__Launcher - PUBLIC - Utils - ) - add_dependencies(Sample03__Launcher Sample03_Client Sample03_Server) +##### sample 03 ##### + + add_executable( + Sample03_Client + Client.cpp + ) + target_link_libraries( Sample03_Client + PUBLIC + Utils + ) + + add_executable( + Sample03_Server + Server.cpp + ) + find_package( Threads ) + target_link_libraries( Sample03_Server + PUBLIC + Utils + ${CMAKE_THREAD_LIBS_INIT} + ) + + add_executable( + Sample03__Launcher + Launcher.cpp + ) + target_link_libraries( Sample03__Launcher + PUBLIC + Utils + ) + add_dependencies(Sample03__Launcher Sample03_Client Sample03_Server) diff --git a/Samples/Tcp/Sample_03_multithread_services/Client.cpp b/Samples/Tcp/03-servers-clients/Client.cpp similarity index 100% rename from Samples/Tcp/Sample_03_multithread_services/Client.cpp rename to Samples/Tcp/03-servers-clients/Client.cpp diff --git a/Samples/Tcp/Sample_03_multithread_services/Launcher.cpp b/Samples/Tcp/03-servers-clients/Launcher.cpp similarity index 95% rename from Samples/Tcp/Sample_03_multithread_services/Launcher.cpp rename to Samples/Tcp/03-servers-clients/Launcher.cpp index ba8106ec..25561cda 100644 --- a/Samples/Tcp/Sample_03_multithread_services/Launcher.cpp +++ b/Samples/Tcp/03-servers-clients/Launcher.cpp @@ -1,12 +1,12 @@ -#include - -int main() { - - Launcher lnc("launcher"); - lnc.addProcess("Sample03_Server"); - lnc.addProcess("Sample03_Client", "500"); - lnc.addProcess("Sample03_Client", "50"); - lnc(); - - return 0; +#include + +int main() { + + Launcher lnc("launcher"); + lnc.addProcess("Sample03_Server"); + lnc.addProcess("Sample03_Client", "500"); + lnc.addProcess("Sample03_Client", "50"); + lnc(); + + return 0; } \ No newline at end of file diff --git a/Samples/Tcp/Sample_03_multithread_services/README.svg b/Samples/Tcp/03-servers-clients/README.svg similarity index 100% rename from Samples/Tcp/Sample_03_multithread_services/README.svg rename to Samples/Tcp/03-servers-clients/README.svg diff --git a/Samples/Tcp/Sample_03_multithread_services/Server.cpp b/Samples/Tcp/03-servers-clients/Server.cpp similarity index 100% rename from Samples/Tcp/Sample_03_multithread_services/Server.cpp rename to Samples/Tcp/03-servers-clients/Server.cpp diff --git a/Samples/Tcp/Sample_04_repeater/CMakeLists.txt b/Samples/Tcp/04-repeater/CMakeLists.txt similarity index 93% rename from Samples/Tcp/Sample_04_repeater/CMakeLists.txt rename to Samples/Tcp/04-repeater/CMakeLists.txt index 228b533a..deae45db 100644 --- a/Samples/Tcp/Sample_04_repeater/CMakeLists.txt +++ b/Samples/Tcp/04-repeater/CMakeLists.txt @@ -1,39 +1,39 @@ -##### sample 04 ##### - - add_executable( - Sample04_Client - Client.cpp - ) - target_link_libraries( Sample04_Client - PUBLIC - Utils - ) - - add_executable( - Sample04_Repeater - Repeater.cpp - ) - target_link_libraries( Sample04_Repeater - PUBLIC - Utils - ) - - add_executable( - Sample04_Server - Server.cpp - ) - find_package( Threads ) - target_link_libraries( Sample04_Server - PUBLIC - Utils - ) - - add_executable( - Sample04__Launcher - Launcher.cpp - ) - target_link_libraries( Sample04__Launcher - PUBLIC - Utils - ) - add_dependencies(Sample04__Launcher Sample04_Client Sample04_Repeater Sample04_Server) +##### sample 04 ##### + + add_executable( + Sample04_Client + Client.cpp + ) + target_link_libraries( Sample04_Client + PUBLIC + Utils + ) + + add_executable( + Sample04_Repeater + Repeater.cpp + ) + target_link_libraries( Sample04_Repeater + PUBLIC + Utils + ) + + add_executable( + Sample04_Server + Server.cpp + ) + find_package( Threads ) + target_link_libraries( Sample04_Server + PUBLIC + Utils + ) + + add_executable( + Sample04__Launcher + Launcher.cpp + ) + target_link_libraries( Sample04__Launcher + PUBLIC + Utils + ) + add_dependencies(Sample04__Launcher Sample04_Client Sample04_Repeater Sample04_Server) diff --git a/Samples/Tcp/Sample_04_repeater/Client.cpp b/Samples/Tcp/04-repeater/Client.cpp similarity index 100% rename from Samples/Tcp/Sample_04_repeater/Client.cpp rename to Samples/Tcp/04-repeater/Client.cpp diff --git a/Samples/Tcp/Sample_04_repeater/Launcher.cpp b/Samples/Tcp/04-repeater/Launcher.cpp similarity index 95% rename from Samples/Tcp/Sample_04_repeater/Launcher.cpp rename to Samples/Tcp/04-repeater/Launcher.cpp index fa28e0c5..a30e43bb 100644 --- a/Samples/Tcp/Sample_04_repeater/Launcher.cpp +++ b/Samples/Tcp/04-repeater/Launcher.cpp @@ -1,12 +1,12 @@ -#include - -int main() { - - Launcher lnc("launcher"); - lnc.addProcess("Sample04_Server"); - lnc.addProcess("Sample04_Repeater"); - lnc.addProcess("Sample04_Client"); - lnc(); - - return 0; +#include + +int main() { + + Launcher lnc("launcher"); + lnc.addProcess("Sample04_Server"); + lnc.addProcess("Sample04_Repeater"); + lnc.addProcess("Sample04_Client"); + lnc(); + + return 0; } \ No newline at end of file diff --git a/Samples/Tcp/Sample_04_repeater/README.svg b/Samples/Tcp/04-repeater/README.svg similarity index 100% rename from Samples/Tcp/Sample_04_repeater/README.svg rename to Samples/Tcp/04-repeater/README.svg diff --git a/Samples/Tcp/Sample_04_repeater/Repeater.cpp b/Samples/Tcp/04-repeater/Repeater.cpp similarity index 100% rename from Samples/Tcp/Sample_04_repeater/Repeater.cpp rename to Samples/Tcp/04-repeater/Repeater.cpp diff --git a/Samples/Tcp/Sample_04_repeater/Server.cpp b/Samples/Tcp/04-repeater/Server.cpp similarity index 100% rename from Samples/Tcp/Sample_04_repeater/Server.cpp rename to Samples/Tcp/04-repeater/Server.cpp diff --git a/Samples/Tcp/CMakeLists.txt b/Samples/Tcp/CMakeLists.txt index c38cb0d8..8ffdb01e 100644 --- a/Samples/Tcp/CMakeLists.txt +++ b/Samples/Tcp/CMakeLists.txt @@ -1,7 +1,7 @@ -add_subdirectory(Sample_01_server_client) +add_subdirectory(01-server-client) -add_subdirectory(Sample_02_singlethread_services) +# add_subdirectory(Sample_02_singlethread_services) -add_subdirectory(Sample_03_multithread_services) +# add_subdirectory(Sample_03_multithread_services) -add_subdirectory(Sample_04_repeater) +# add_subdirectory(Sample_04_repeater) diff --git a/Samples/Tcp/Sample_01_server_client/CMakeLists.txt b/Samples/Tcp/Sample_01_server_client/CMakeLists.txt deleted file mode 100644 index fe2cb952..00000000 --- a/Samples/Tcp/Sample_01_server_client/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -##### sample 01 ##### - - add_executable( - Sample01_Client - Client.cpp - ) - target_link_libraries( Sample01_Client - PUBLIC - Utils - ) - - add_executable( - Sample01_Server - Server.cpp - ) - target_link_libraries( Sample01_Server - PUBLIC - Utils - ) - - add_executable( - Sample01__Launcher - Launcher.cpp - ) - target_link_libraries( Sample01__Launcher - PUBLIC - Utils - ) - add_dependencies(Sample01__Launcher Sample01_Server Sample01_Client) diff --git a/Samples/Utils/CMakeLists.txt b/Samples/Utils/CMakeLists.txt deleted file mode 100644 index 22f36aa8..00000000 --- a/Samples/Utils/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -set(PROJECT_NAME "Utils") - -CollectSourceFiles(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) - -GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) - -if(LIB_OPT) - add_library(${PROJECT_NAME} SHARED ${SOURCES}) -else() - add_library(${PROJECT_NAME} STATIC ${SOURCES}) -endif() - -target_include_directories(${PROJECT_NAME} - PUBLIC - $ - $ -) - -target_link_libraries(${PROJECT_NAME} - PUBLIC - SCK::Cross-Socket -) diff --git a/Samples/Utils/include/Service.h b/Samples/Utils/include/Service.h deleted file mode 100644 index 52f1c818..00000000 --- a/Samples/Utils/include/Service.h +++ /dev/null @@ -1,47 +0,0 @@ -/** -* Author: Andrea Casalino -* Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. -**/ - -#ifndef _SERVICE_ -#define _SERVICE_ - -#include -#include - -void crossSleep(const int& ms); - - - -sck::Address parseAddress(int argc, char **argv, const std::uint16_t& port); - - - -class Names { -public: - static const std::string& getFirstName(); - static const std::string& getNextName(const std::string& attualName); - static const std::string& getSurname(const std::string& name); -private: - static const std::unordered_map names; -}; - -class Service { -public: - Service(std::unique_ptr connection) - : connection(std::move(connection)) { - }; - - bool serve(); - - void serveForever(); -private: - std::unique_ptr connection; -}; - - -void ClientLoop(sck::StringClient& client, const int& sleepTime); - -#endif diff --git a/Samples/Utils/src/ProcessLauncher.cpp b/Samples/Utils/src/ProcessLauncher.cpp deleted file mode 100644 index 88533af0..00000000 --- a/Samples/Utils/src/ProcessLauncher.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "../include/ProcessLauncher.h" - -void Launcher::operator()() const { - std::string name = this->nameFile; -#ifdef _WIN32 - name += ".bat"; -#elif __linux__ - name += ".sh"; -#endif - - std::ofstream f(name); - auto it = this->commands.begin(); - auto itEnd = this->commands.end(); - --itEnd; - for (it; it != itEnd; ++it) { -#ifdef _WIN32 - f << std::endl << "start \"\" \"" << it->first << "\""; - for(auto a : it->second) f << " \"" << a << "\""; -#elif __linux__ - f << std::endl << "gnome-terminal -x sh -c \"./" << it->first; - for(auto a : it->second) f << " " << a; - f << "; bash\""; -#endif - } -#ifdef _WIN32 - f << std::endl << "\"" << this->commands.back().first << "\""; - for(auto a : this->commands.back().second) f << " \"" << a << "\""; -#elif __linux__ - f << std::endl << "./" << this->commands.back().first; - for(auto a : this->commands.back().second) f << " " << a; -#endif - - f.close(); - -#ifdef _WIN32 - system(name.c_str()); -#elif __linux__ - system(std::string("sh ./" + name).c_str()); -#endif -}; \ No newline at end of file diff --git a/Samples/Utils/src/Service.cpp b/Samples/Utils/src/Service.cpp deleted file mode 100644 index e51051b3..00000000 --- a/Samples/Utils/src/Service.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include "../include/Service.h" -#include -#include - - -#ifdef _WIN32 -#include -#else -#include -#endif -void crossSleep(const int& ms){ -#ifdef _WIN32 - Sleep(ms); -#else - usleep(ms * 1000); -#endif -} - - -sck::Address parseAddress(int argc, char **argv, const std::uint16_t& port){ - if(argc >= 2) return sck::Address::FromIp( argv[1], port); - else return sck::Address::Localhost(port); -} - - - -const std::unordered_map Names::names = std::unordered_map -{ - {"Luciano", "Pavarotti"}, - {"Gengis", "Khan"}, - {"Giulio", "Cesare"}, - {"Theodor", "Roosvelt"}, - {"Immanuel", "Kant"} -}; - -const std::string unknownName = "Unknown"; - -const std::string& Names::getFirstName(){ - return names.begin()->first; -} - -const std::string& Names::getNextName(const std::string& attualName){ - auto attualIt = names.find(attualName); - if(attualIt == names.end()) return unknownName; - ++attualIt; - if(attualIt == names.end()) attualIt = names.begin(); - return attualIt->first; -} - -const std::string& Names::getSurname(const std::string& name){ - auto attualIt = names.find(name); - if(attualIt == names.end()) return unknownName; - return attualIt->second; -} - - -std::mutex coutMtx; -bool Service::serve(){ - auto got = this->connection->receive(500); - { - std::lock_guard lk(coutMtx); - std::cout << "got: " << *got.get() << std::endl; - } - if(got->size() == 0) return false; - this->connection->send(Names::getSurname(*got.get())); - return true; -}; - -void Service::serveForever(){ - while (true){ - if(!this->serve()) break; - } -} - - -void ClientLoop(sck::StringClient& client, const int& sleepTime){ - std::string name = Names::getFirstName(); -//keep sending request to remote server - while (true){ - std::cout << "sending: " << name; - client.send(name); - - std::string surname = *client.receive(500).get(); - if(surname.size() == 0) break; - std::cout << " got from the server: " << surname << std::endl; - - name = Names::getNextName(name); - - crossSleep(sleepTime); - } -} \ No newline at end of file diff --git a/Utils/CMakeLists.txt b/Utils/CMakeLists.txt deleted file mode 100644 index a8f6ef9f..00000000 --- a/Utils/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -set(PROJECT_SHORTNAME "Utils") -project(${PROJECT_SHORTNAME} VERSION ${VERSION} LANGUAGES CXX) -string(REPLACE - _ COMPONENT_NAME ${PROJECT_NAME}) - -CollectSourceFiles(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) - -GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) - -if(LIB_OPT) - add_library(${PROJECT_NAME} SHARED ${SOURCES}) -else() - add_library(${PROJECT_NAME} STATIC ${SOURCES}) -endif() -add_library(SCK::${PROJECT_SHORTNAME} ALIAS ${PROJECT_NAME}) - -target_compile_features(${PROJECT_NAME} - PUBLIC cxx_auto_type - PRIVATE cxx_variadic_templates -) - -target_include_directories(${PROJECT_NAME} - PUBLIC - $ - $ -) - -target_link_libraries(${PROJECT_NAME} -PUBLIC -SCK::Cross-Socket -) - diff --git a/Utils/Main.cpp b/Utils/Main.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/Utils/include/AsyncStringMessanger.h b/Utils/include/AsyncStringMessanger.h deleted file mode 100644 index de5921cc..00000000 --- a/Utils/include/AsyncStringMessanger.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef ASYNC_STRING_MESSANGER_H -#define ASYNC_STRING_MESSANGER_H - -#include -#include - -class AsyncStringMessanger - : public sck::async::MessageListener { -public: - AsyncStringMessanger(std::unique_ptr socket); - -private: - void handle(const std::pair& message) final; - - sck::async::AsyncClient socket; - -}; - -#endif \ No newline at end of file diff --git a/Utils/include/ProcessLauncher.h b/Utils/include/ProcessLauncher.h deleted file mode 100644 index 8dd9cf7e..00000000 --- a/Utils/include/ProcessLauncher.h +++ /dev/null @@ -1,65 +0,0 @@ -/** -* Author: Andrea Casalino -* Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. -**/ - -#ifndef _PROCESS_LAUNCHER_H_ -#define _PROCESS_LAUNCHER_H_ - -#include -#include -#include -#include - -/** - * @brief The object is used to print a script, that is able - * to launch in a precise sequence certain processes, opening different prompts - */ -class Launcher { -public: - /** - * @param[in] the name of script to print - */ - Launcher(const std::string& nameFile); - - /** - * @brief Add 1 process to launch, with possibly some additional command arguments. - * @param[in] the name of the process to launch - * @param[in] the list of arguments to pass when launching the process - */ - template - void addProcess(const std::string& procName, Args ... args) { - std::list parsed; - this->parseArgs(parsed, args ...); - - this->commands.emplace_back(std::make_pair(procName, parsed)); - }; - - /** - * @brief Print the script and runs it - */ - void operator()() const; - -private: - template - void parseArgs(std::list& parsed , const std::string& arg, Args ... args) { - parsed.push_back(arg); - parseArgs(parsed, args ...); - }; - - template - void parseArgs(std::list& parsed, const std::string& arg) { - parsed.push_back(arg); - }; - - void parseArgs(std::list& parsed) { - return; - }; - - std::string nameFile; - std::list>> commands; -}; - -#endif \ No newline at end of file diff --git a/Utils/include/StringMessanger.h b/Utils/include/StringMessanger.h deleted file mode 100644 index e4b0e92c..00000000 --- a/Utils/include/StringMessanger.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef STRING_MESSANGER_H -#define STRING_MESSANGER_H - -#include -#include -#include - -class PersonRegister { -public: - static const std::string& getSurname(const std::string& name); - - static const std::map persons; -private: - static const std::string unknown; -}; -const std::string PersonRegister::unknown = "unknown"; -const std::map PersonRegister::persons = { - {"Benjamin", "Franklin"}, - {"Leonardo", "Da Vinci"}, - {"Napoleone", "Bonaparte"}, - {"Nikola", "Tesla"}, - {"Luciano", "Pavarotti"}, -}; - - - -class StringMessanger { -public: - StringMessanger(std::unique_ptr socket); - - std::string sendReceive(const std::string& name); - void receiveSend(); - -private: - std::unique_ptr socket; - std::vector receiveBuffer; -}; - -#endif \ No newline at end of file diff --git a/Utils/src/AsyncStringMessanger.cpp b/Utils/src/AsyncStringMessanger.cpp deleted file mode 100644 index 0f39edc0..00000000 --- a/Utils/src/AsyncStringMessanger.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -AsyncStringMessanger::AsyncStringMessanger(std::unique_ptr socket) - : socket(std::move(socket), 500) { - this->socket.resetListener(this); - this->socket.open(std::chrono::milliseconds(0)); -} - -void AsyncStringMessanger::handle(const std::pair& message) { - const std::string& surname = PersonRegister::getSurname(std::string(message.first, message.second)); - this->socket.send({surname.data(), surname.size()}); -} diff --git a/Utils/src/ProcessLauncher.cpp b/Utils/src/ProcessLauncher.cpp deleted file mode 100644 index 62bca2eb..00000000 --- a/Utils/src/ProcessLauncher.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "../include/ProcessLauncher.h" -#include - -Launcher::Launcher(const std::string& nameFile) : nameFile(nameFile) { -#ifdef _WIN32 -#elif __linux__ -#elif - throw std::runtime_error("Unrecognized operative system"); -#endif -}; - -void Launcher::operator()() const { - std::string name = this->nameFile; -#ifdef _WIN32 - name += ".bat"; -#elif __linux__ - name += ".sh"; -#endif - - std::ofstream f(name); - auto it = this->commands.begin(); - auto itEnd = this->commands.end(); - --itEnd; - for (it; it != itEnd; ++it) { -#ifdef _WIN32 - f << std::endl << "start \"\" \"" << it->first << "\""; - for(auto a : it->second) f << " \"" << a << "\""; -#elif __linux__ - f << std::endl << "gnome-terminal -x sh -c \"./" << it->first; - for(auto a : it->second) f << " " << a; - f << "; bash\""; -#endif - } -#ifdef _WIN32 - f << std::endl << "\"" << this->commands.back().first << "\""; - for(auto a : this->commands.back().second) f << " \"" << a << "\""; -#elif __linux__ - f << std::endl << "./" << this->commands.back().first; - for(auto a : this->commands.back().second) f << " " << a; -#endif - - f.close(); - -#ifdef _WIN32 - system(name.c_str()); -#elif __linux__ - system(std::string("sh ./" + name).c_str()); -#endif -}; \ No newline at end of file diff --git a/Utils/src/StringMessanger.cpp b/Utils/src/StringMessanger.cpp deleted file mode 100644 index 53af9e1c..00000000 --- a/Utils/src/StringMessanger.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -const std::string& PersonRegister::getSurname(const std::string& name) { - auto it = persons.find(name); - if(it == persons.end()) return unknown; - return it->second; -} - - - -StringMessanger::StringMessanger(std::unique_ptr socket) - : socket(std::move(socket)) { - this->socket->open(std::chrono::milliseconds(0)); - this->receiveBuffer.resize(500); -} - -std::string StringMessanger::sendReceive(const std::string& name) { - this->socket->send({name.data(), name.size()}); - std::pair p = std::make_pair(this->receiveBuffer.data(), this->receiveBuffer.capacity()); - auto recvBytes = this->socket->receive(p , std::chrono::milliseconds(0)); - return std::string(this->receiveBuffer.data(), recvBytes); -} - -void StringMessanger::receiveSend() { - std::pair p = std::make_pair(this->receiveBuffer.data(), this->receiveBuffer.capacity()); - auto recvBytes = this->socket->receive(p , std::chrono::milliseconds(0)); - const std::string& surname = PersonRegister::getSurname(std::string(p.first, recvBytes)); - this->socket->send({surname.data(), surname.size()}); -} diff --git a/cmake/CheckPlatform.cmake b/cmake/CheckPlatform.cmake deleted file mode 100644 index e7680b5a..00000000 --- a/cmake/CheckPlatform.cmake +++ /dev/null @@ -1,10 +0,0 @@ -macro(check_platform platform) -# check what platform we're on (64-bit or 32-bit), and create a simpler test than CMAKE_SIZEOF_VOID_P -if(CMAKE_SIZEOF_VOID_P MATCHES 8) - set(${platform} 64) - #MESSAGE(STATUS "Detected 64-bit platform") -else() - set(${platform} 32) - #MESSAGE(STATUS "Detected 32-bit platform") -endif() -endmacro(check_platform) diff --git a/cmake/GetCompiler.cmake b/cmake/GetCompiler.cmake deleted file mode 100644 index 1f150195..00000000 --- a/cmake/GetCompiler.cmake +++ /dev/null @@ -1,13 +0,0 @@ -macro(get_compiler compiler) - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(${compiler} "clang") - elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set(${compiler} "gcc") - elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") - set(${compiler} "intel") - elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - set(${compiler} "msvc") - else() - set(${compiler} "unknown") - endif() -endmacro(get_compiler) diff --git a/cmake/Helper.cmake b/cmake/Helper.cmake deleted file mode 100644 index 3e177d49..00000000 --- a/cmake/Helper.cmake +++ /dev/null @@ -1,38 +0,0 @@ -# Get all propreties that cmake supports -execute_process(COMMAND cmake --help-property-list OUTPUT_VARIABLE CMAKE_PROPERTY_LIST) - -# Convert command output into a CMake list -STRING(REGEX REPLACE ";" "\\\\;" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}") -STRING(REGEX REPLACE "\n" ";" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}") - -function(print_properties) - message ("CMAKE_PROPERTY_LIST = ${CMAKE_PROPERTY_LIST}") -endfunction(print_properties) - -function(print_target_properties tgt) - if(NOT TARGET ${tgt}) - message("There is no target named '${tgt}'") - return() - endif() - - foreach (prop ${CMAKE_PROPERTY_LIST}) - string(REPLACE "" "${CMAKE_BUILD_TYPE}" prop ${prop}) - # Fix https://stackoverflow.com/questions/32197663/how-can-i-remove-the-the-location-property-may-not-be-read-from-target-error-i - if(prop STREQUAL "LOCATION" OR prop MATCHES "^LOCATION_" OR prop MATCHES "_LOCATION$") - continue() - endif() - # message ("Checking ${prop}") - get_property(propval TARGET ${tgt} PROPERTY ${prop} SET) - if (propval) - get_target_property(propval ${tgt} ${prop}) - message ("${tgt} ${prop} = ${propval}") - endif() - endforeach(prop) -endfunction(print_target_properties) - -function(print_all_variables) - get_cmake_property(_variableNames VARIABLES) - foreach (_variableName ${_variableNames}) - message(STATUS "${_variableName}=${${_variableName}}") - endforeach() -endfunction(print_all_variables) diff --git a/cmake/Log.cmake b/cmake/Log.cmake deleted file mode 100644 index a9ac9399..00000000 --- a/cmake/Log.cmake +++ /dev/null @@ -1,8 +0,0 @@ -function(debug msg line) - if(DEBUG) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${line} ] " ${msg}) - endif() -endfunction(debug) -function(log msg line) - message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${line} ] " ${msg}) -endfunction(log) diff --git a/cmake/SetInstallRPath.cmake b/cmake/SetInstallRPath.cmake deleted file mode 100644 index 0e2c404f..00000000 --- a/cmake/SetInstallRPath.cmake +++ /dev/null @@ -1,29 +0,0 @@ -function(setInstallRPath) - - cmake_parse_arguments( - PARSED_ARGS # prefix of output variables - "" # list of names of the boolean arguments (only defined ones will be true) - "TARGET" # list of names of mono-valued arguments - "INSTALL_RPATH" # list of names of multi-valued arguments (output variables are lists) - ${ARGN} # arguments of the function to parse, here we take the all original ones - ) - if(NOT PARSED_ARGS_TARGET) - message(FATAL_ERROR "setInstallRPath missing TARGET") - endif(NOT PARSED_ARGS_TARGET) - - if(NOT PARSED_ARGS_INSTALL_RPATH) - message(FATAL_ERROR "setInstallRPath missing INSTALL_RPATH") - endif(NOT PARSED_ARGS_INSTALL_RPATH) - - if(WIN32) - set(INSTALL_RPATH "") - elseif(APPLE) - list(TRANSFORM PARSED_ARGS_INSTALL_RPATH PREPEND "@loader_path") - list(JOIN PARSED_ARGS_INSTALL_RPATH ";" PARSED_ARGS_INSTALL_RPATH) - else() - list(TRANSFORM PARSED_ARGS_INSTALL_RPATH PREPEND "$ORIGIN") - list(JOIN PARSED_ARGS_INSTALL_RPATH ":" PARSED_ARGS_INSTALL_RPATH) - endif() - #message(STATUS "setting rpath: ${PARSED_ARGS_INSTALL_RPATH}") - set_target_properties(${PARSED_ARGS_TARGET} PROPERTIES INSTALL_RPATH "${PARSED_ARGS_INSTALL_RPATH}") -endfunction(setInstallRPath) diff --git a/cmake/TargetProperties.cmake b/cmake/TargetProperties.cmake deleted file mode 100644 index cb9497c8..00000000 --- a/cmake/TargetProperties.cmake +++ /dev/null @@ -1,33 +0,0 @@ -######################### -# Usage print_target_properties() - -######################### - - -# Get all propreties that cmake supports -execute_process(COMMAND cmake --help-property-list OUTPUT_VARIABLE CMAKE_PROPERTY_LIST) - -# Convert command output into a CMake list -STRING(REGEX REPLACE ";" "\\\\;" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}") -STRING(REGEX REPLACE "\n" ";" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}") - -function(print_target_properties tgt) - if(NOT TARGET ${tgt}) - message("There is no target named '${tgt}'") - return() - endif() - - foreach (prop ${CMAKE_PROPERTY_LIST}) - string(REPLACE "" "${CMAKE_BUILD_TYPE}" prop ${prop}) - # Fix https://stackoverflow.com/questions/32197663/how-can-i-remove-the-the-location-property-may-not-be-read-from-target-error-i - if(prop STREQUAL "LOCATION" OR prop MATCHES "^LOCATION_" OR prop MATCHES "_LOCATION$") - continue() - endif() - # message ("Checking ${prop}") - get_property(propval TARGET ${tgt} PROPERTY ${prop} SET) - if (propval) - get_target_property(propval ${tgt} ${prop}) - message ("${tgt} ${prop} = ${propval}") - endif() - endforeach(prop) -endfunction(print_target_properties) diff --git a/cmake/WriteBuildProperties.cmake b/cmake/WriteBuildProperties.cmake deleted file mode 100644 index e1c41210..00000000 --- a/cmake/WriteBuildProperties.cmake +++ /dev/null @@ -1,17 +0,0 @@ -include(GetCompiler) -# include this file after all other files, this uses cpack defined variables -set(BUILD_PROPERTIES_FILENAME "build.properties" CACHE INTERNAL "name of properties file") -get_compiler(COMPILER) -file(WRITE ${CMAKE_BINARY_DIR}/${BUILD_PROPERTIES_FILENAME} "// Automatically generated file by cmake\n") -if(CPACK_PACKAGE_VENDOR) -file(APPEND ${CMAKE_BINARY_DIR}/${BUILD_PROPERTIES_FILENAME} "COMPANY=${CPACK_PACKAGE_VENDOR}\n") -endif() -string(TIMESTAMP TIMESTAMP_FORMATED "%d.%m.%Y %H:%M:%S" ) -file(APPEND ${CMAKE_BINARY_DIR}/${BUILD_PROPERTIES_FILENAME} "DATE=${TIMESTAMP_FORMATED}\n") -file(APPEND ${CMAKE_BINARY_DIR}/${BUILD_PROPERTIES_FILENAME} "PROJECT_NAME=${PROJECT_NAME}\n") -file(APPEND ${CMAKE_BINARY_DIR}/${BUILD_PROPERTIES_FILENAME} "VERSION=${VERSION}\n") -if(CPACK_SYSTEM_NAME) -file(APPEND ${CMAKE_BINARY_DIR}/${BUILD_PROPERTIES_FILENAME} "SYSTEM_NAME=${CPACK_SYSTEM_NAME}\n") -endif() -file(APPEND ${CMAKE_BINARY_DIR}/${BUILD_PROPERTIES_FILENAME} "COMPILER=${COMPILER}\n") -message(STATUS "writing properties file of cmake build to ${BUILD_PROPERTIES_FILENAME}") diff --git a/cmake/fmiplatform.cmake b/cmake/fmiplatform.cmake deleted file mode 100644 index 20231631..00000000 --- a/cmake/fmiplatform.cmake +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (C) 2017 Modelon AB - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the BSD style license. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# FMILIB_License.txt file for more details. - -# You should have received a copy of the FMILIB_License.txt file -# along with this program. If not, contact Modelon AB . - -# fmi_platform(platform) sets platform to one of the by FMI defined platforms -# win32, win64, linux32, linux64, darwin32 or darwin64 depending on cmake -# generator -function(fmi_platform platform ) - if(CMAKE_HOST_WIN32) #Set to true when the host system is Windows and on cygwin. - if(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(${platform} win32 PARENT_SCOPE) - else(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(${platform} win64 PARENT_SCOPE) - endif(CMAKE_SIZEOF_VOID_P EQUAL 4) - elseif(CMAKE_HOST_APPLE) #Set to true when the host system is Apple OSX. - if(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(${platform} darwin32 PARENT_SCOPE) - else(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(${platform} darwin64 PARENT_SCOPE) - endif(CMAKE_SIZEOF_VOID_P EQUAL 4) - elseif(CMAKE_HOST_UNIX) #Set to true when the host system is UNIX or UNIX like (i.e. APPLE and CYGWIN). - if(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(${platform} linux32 PARENT_SCOPE) - else(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(${platform} linux64 PARENT_SCOPE) - endif(CMAKE_SIZEOF_VOID_P EQUAL 4) - else(CMAKE_HOST_WIN32) - message(SEND_ERROR "Target ${platform} is not supported by the tests") - set(${platform} not_supported) - endif() -endfunction() From e684fcd7615878468069d8d9bed1e3c8f98919d1 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 7 Feb 2021 18:29:22 +0100 Subject: [PATCH 017/228] tcp samples implemented --- Samples/CMakeLists.txt | 2 + Samples/ProcessLauncher.h | 10 ++-- Samples/Tcp/01-server-client/CMakeLists.txt | 11 ++--- Samples/Tcp/01-server-client/Client.cpp | 24 +++++---- Samples/Tcp/01-server-client/Launcher.cpp | 4 +- Samples/Tcp/01-server-client/Server.cpp | 24 ++++++--- Samples/Tcp/02-server-clients/CMakeLists.txt | 34 +++---------- Samples/Tcp/02-server-clients/Client.cpp | 28 ++++++----- Samples/Tcp/02-server-clients/Launcher.cpp | 4 +- Samples/Tcp/02-server-clients/Server.cpp | 49 ++++++++++--------- Samples/Tcp/03-servers-clients/CMakeLists.txt | 38 +++----------- Samples/Tcp/03-servers-clients/Client.cpp | 27 ++++++---- Samples/Tcp/03-servers-clients/Launcher.cpp | 2 +- Samples/Tcp/03-servers-clients/Server.cpp | 28 +++++++---- Samples/Tcp/04-repeater/CMakeLists.txt | 45 ++++------------- Samples/Tcp/04-repeater/Client.cpp | 28 ++++++----- Samples/Tcp/04-repeater/Launcher.cpp | 4 +- Samples/Tcp/04-repeater/Repeater.cpp | 46 ++++++++--------- Samples/Tcp/04-repeater/Server.cpp | 26 ++++++---- Samples/Tcp/CMakeLists.txt | 6 +-- Samples/Utils/CMakeLists.txt | 30 ++++++++++++ Samples/Utils/include/Asker.h | 28 +++++++++++ Samples/{ => Utils/include}/Names.h | 23 ++------- Samples/Utils/include/Responder.h | 27 ++++++++++ Samples/Utils/include/ResponderAsync.h | 0 Samples/Utils/src/Asker.cpp | 35 +++++++++++++ Samples/Utils/src/Names.cpp | 36 ++++++++++++++ Samples/Utils/src/Responder.cpp | 32 ++++++++++++ Samples/Utils/src/ResponderAsync.cpp | 0 TODO | 2 + 30 files changed, 407 insertions(+), 246 deletions(-) create mode 100644 Samples/Utils/CMakeLists.txt create mode 100644 Samples/Utils/include/Asker.h rename Samples/{ => Utils/include}/Names.h (56%) create mode 100644 Samples/Utils/include/Responder.h create mode 100644 Samples/Utils/include/ResponderAsync.h create mode 100644 Samples/Utils/src/Asker.cpp create mode 100644 Samples/Utils/src/Names.cpp create mode 100644 Samples/Utils/src/Responder.cpp create mode 100644 Samples/Utils/src/ResponderAsync.cpp diff --git a/Samples/CMakeLists.txt b/Samples/CMakeLists.txt index 47137a42..40d91555 100644 --- a/Samples/CMakeLists.txt +++ b/Samples/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory(Utils) + add_subdirectory(Tcp) # add_subdirectory(Udp) diff --git a/Samples/ProcessLauncher.h b/Samples/ProcessLauncher.h index d26bf07d..d8268b31 100644 --- a/Samples/ProcessLauncher.h +++ b/Samples/ProcessLauncher.h @@ -96,11 +96,11 @@ void Launcher::operator()() const { f.close(); -#ifdef _WIN32 - system(name.c_str()); -#elif __linux__ - system(std::string("sh ./" + name).c_str()); -#endif +//#ifdef _WIN32 +// system(name.c_str()); +//#elif __linux__ +// system(std::string("sh ./" + name).c_str()); +//#endif }; #endif \ No newline at end of file diff --git a/Samples/Tcp/01-server-client/CMakeLists.txt b/Samples/Tcp/01-server-client/CMakeLists.txt index bf0010da..f13aecdb 100644 --- a/Samples/Tcp/01-server-client/CMakeLists.txt +++ b/Samples/Tcp/01-server-client/CMakeLists.txt @@ -1,10 +1,9 @@ add_executable(Sample01_Client Client.cpp) -target_link_libraries( Sample01_Client Cross-Socket) +target_link_libraries( Sample01_Client Sample-Utils) add_executable(Sample01_Server Server.cpp) -target_link_libraries( Sample01_Server Cross-Socket) +target_link_libraries( Sample01_Server Sample-Utils) -add_executable(Sample01_Launcher Launcher.cpp) -target_link_libraries(Sample01_Launcher Cross-Socket) - -add_dependencies(Sample01_Launcher Sample01_Server Sample01_Client) +add_executable(Sample01Launcher Launcher.cpp) +target_link_libraries(Sample01Launcher) +add_dependencies(Sample01Launcher Sample01_Server Sample01_Client) diff --git a/Samples/Tcp/01-server-client/Client.cpp b/Samples/Tcp/01-server-client/Client.cpp index 5e167bcc..96d5429b 100644 --- a/Samples/Tcp/01-server-client/Client.cpp +++ b/Samples/Tcp/01-server-client/Client.cpp @@ -1,12 +1,12 @@ /** * Author: Andrea Casalino * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. + * + * report any bug to andrecasa91@gmail.com. **/ -#include -#include +#include +#include #include using namespace std; @@ -14,14 +14,18 @@ int main(int argc, char **argv){ cout << "----------------------- Client -----------------------" << endl; - sck::Address remoteAddress = parseAddress(argc, argv, 2000); - cout << "Asking connection to " << remoteAddress.getHost() << ":" << remoteAddress.getPort() << endl; + std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(20000)); + cout << "Asking connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - //build and initialize a connection to the server on port 2000 - sck::StringClient client( std::make_unique(remoteAddress) ); - client.open(); + // blocking open + client->open(std::chrono::milliseconds(0)); + if (!client->isOpen()) { + cout << "connection failed" << endl; + return EXIT_FAILURE; + } - ClientLoop(client , 500); + Asker ask(std::move(client)); + ask.askForever(std::chrono::milliseconds(500)); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/01-server-client/Launcher.cpp b/Samples/Tcp/01-server-client/Launcher.cpp index 82d8ba77..995b7662 100644 --- a/Samples/Tcp/01-server-client/Launcher.cpp +++ b/Samples/Tcp/01-server-client/Launcher.cpp @@ -1,4 +1,4 @@ -#include +#include "../../ProcessLauncher.h" int main() { @@ -7,5 +7,5 @@ int main() { lnc.addProcess("Sample01_Client"); lnc(); - return 0; + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/01-server-client/Server.cpp b/Samples/Tcp/01-server-client/Server.cpp index 83c65aa4..c6a6fc77 100644 --- a/Samples/Tcp/01-server-client/Server.cpp +++ b/Samples/Tcp/01-server-client/Server.cpp @@ -5,8 +5,8 @@ * report any bug to andrecasa91@gmail.com. **/ -#include -#include +#include +#include #include using namespace std; @@ -14,14 +14,22 @@ int main(){ cout << "----------------------- Server -----------------------" << endl; - // build and initialize a connection from a client on port 2000 - sck::TcpServer server(2000); - server.open(); + // build and initialize a connection from a client on port 20000 + sck::tcp::TcpServer server(20000); - //accept the client and create the service - Service srv(std::make_unique(server.acceptNewClient())); + // blocking open + server.open(std::chrono::milliseconds(0)); + if (!server.isOpen()) { + cout << "server open failed" << endl; + return EXIT_FAILURE; + } - srv.serveForever(); + //accept the client + auto clientHandler = server.acceptClient(); + cout << "client connected" << endl; + + Responder resp(std::move(clientHandler)); + resp.respondForever(); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/02-server-clients/CMakeLists.txt b/Samples/Tcp/02-server-clients/CMakeLists.txt index 067fc400..56c93ec1 100644 --- a/Samples/Tcp/02-server-clients/CMakeLists.txt +++ b/Samples/Tcp/02-server-clients/CMakeLists.txt @@ -1,29 +1,9 @@ -##### sample 02 ##### +add_executable(Sample02_Client Client.cpp) +target_link_libraries( Sample02_Client Sample-Utils) - add_executable( - Sample02_Client - Client.cpp - ) - target_link_libraries( Sample02_Client - PUBLIC - Utils - ) - - add_executable( - Sample02_Server - Server.cpp - ) - target_link_libraries( Sample02_Server - PUBLIC - Utils - ) +add_executable(Sample02_Server Server.cpp) +target_link_libraries( Sample02_Server Sample-Utils) - add_executable( - Sample02__Launcher - Launcher.cpp - ) - target_link_libraries( Sample02__Launcher - PUBLIC - Utils - ) - add_dependencies(Sample02__Launcher Sample02_Client Sample02_Server) +add_executable(Sample02Launcher Launcher.cpp) +target_link_libraries(Sample02Launcher) +add_dependencies(Sample02Launcher Sample02_Server Sample02_Client) diff --git a/Samples/Tcp/02-server-clients/Client.cpp b/Samples/Tcp/02-server-clients/Client.cpp index 0a1812fd..5f241e8a 100644 --- a/Samples/Tcp/02-server-clients/Client.cpp +++ b/Samples/Tcp/02-server-clients/Client.cpp @@ -1,27 +1,31 @@ /** * Author: Andrea Casalino * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. + * + * report any bug to andrecasa91@gmail.com. **/ -#include -#include +#include +#include #include using namespace std; -int main(int argc, char **argv){ +int main(int argc, char** argv) { cout << "----------------------- Client -----------------------" << endl; - sck::Address remoteAddress = parseAddress(argc, argv, 31000); - cout << "Asking connection to " << remoteAddress.getHost() << ":" << remoteAddress.getPort() << endl; + std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(31000)); + cout << "Asking connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - //build and initialize a connection to the server - sck::StringClient client( std::make_unique(remoteAddress) ); - client.open(); + // blocking open + client->open(std::chrono::milliseconds(0)); + if (!client->isOpen()) { + cout << "connection failed" << endl; + return EXIT_FAILURE; + } + + Asker ask(std::move(client)); + ask.askForever(std::chrono::milliseconds(500)); - ClientLoop(client , 100); - return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/02-server-clients/Launcher.cpp b/Samples/Tcp/02-server-clients/Launcher.cpp index 3866ddf8..433c736c 100644 --- a/Samples/Tcp/02-server-clients/Launcher.cpp +++ b/Samples/Tcp/02-server-clients/Launcher.cpp @@ -1,4 +1,4 @@ -#include +#include "../../ProcessLauncher.h" int main() { @@ -8,5 +8,5 @@ int main() { lnc.addProcess("Sample02_Client"); lnc(); - return 0; + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/02-server-clients/Server.cpp b/Samples/Tcp/02-server-clients/Server.cpp index 9f3cbb3c..4ffe8f26 100644 --- a/Samples/Tcp/02-server-clients/Server.cpp +++ b/Samples/Tcp/02-server-clients/Server.cpp @@ -1,39 +1,44 @@ /** * Author: Andrea Casalino * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. + * + * report any bug to andrecasa91@gmail.com. **/ -#include "Service.h" -#include +#include +#include #include -#include using namespace std; -int main(){ +int main() { cout << "----------------------- Server -----------------------" << endl; - //build two services: one for client A and one for client B - sck::TcpServer server(31000); - server.open(); - //init connection to client A - Service serviceA(std::make_unique(server.acceptNewClient())); + // build and initialize a connection from a client on port 20000 + sck::tcp::TcpServer server(31000); + + // blocking open + server.open(std::chrono::milliseconds(0)); + if (!server.isOpen()) { + cout << "server open failed" << endl; + return EXIT_FAILURE; + } + + //accept first client + auto clientAHandler = server.acceptClient(); cout << "client A connected" << endl; - //init connection to client B - Service serviceB(std::make_unique(server.acceptNewClient())); + + //accept second client + auto clientBHandler = server.acceptClient(); cout << "client B connected" << endl; - std::list active = {&serviceA, &serviceB}; - while (true){ - //serve all the active clients - auto it = active.begin(); - while (it!=active.end()){ - if(!(*it)->serve()) it = active.erase(it); - else ++it; - } + Responder respA(std::move(clientAHandler)); + Responder respB(std::move(clientBHandler)); + + while (true) { + respA.respond(); + respB.respond(); } - return 0; + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/03-servers-clients/CMakeLists.txt b/Samples/Tcp/03-servers-clients/CMakeLists.txt index df96882b..a4c0522b 100644 --- a/Samples/Tcp/03-servers-clients/CMakeLists.txt +++ b/Samples/Tcp/03-servers-clients/CMakeLists.txt @@ -1,31 +1,9 @@ -##### sample 03 ##### +add_executable(Sample03_Client Client.cpp) +target_link_libraries( Sample03_Client Sample-Utils) - add_executable( - Sample03_Client - Client.cpp - ) - target_link_libraries( Sample03_Client - PUBLIC - Utils - ) - - add_executable( - Sample03_Server - Server.cpp - ) - find_package( Threads ) - target_link_libraries( Sample03_Server - PUBLIC - Utils - ${CMAKE_THREAD_LIBS_INIT} - ) - - add_executable( - Sample03__Launcher - Launcher.cpp - ) - target_link_libraries( Sample03__Launcher - PUBLIC - Utils - ) - add_dependencies(Sample03__Launcher Sample03_Client Sample03_Server) +add_executable(Sample03_Server Server.cpp) +target_link_libraries( Sample03_Server Sample-Utils) + +add_executable(Sample03Launcher Launcher.cpp) +target_link_libraries(Sample03Launcher) +add_dependencies(Sample03Launcher Sample03_Server Sample03_Client) diff --git a/Samples/Tcp/03-servers-clients/Client.cpp b/Samples/Tcp/03-servers-clients/Client.cpp index ea9a8b9d..91b4c99b 100644 --- a/Samples/Tcp/03-servers-clients/Client.cpp +++ b/Samples/Tcp/03-servers-clients/Client.cpp @@ -5,8 +5,8 @@ * report any bug to andrecasa91@gmail.com. **/ -#include -#include +#include +#include #include using namespace std; @@ -14,17 +14,24 @@ int main(int argc, char **argv){ cout << "----------------------- Client -----------------------" << endl; - if(argc < 2) return EXIT_FAILURE; + if (argc < 2) { + cout << "sleep time not passed" << endl; + return EXIT_FAILURE; + } int sleepTime = atoi(std::string(argv[1]).c_str()); - sck::Address remoteAddress = sck::Address::Localhost(27300); - if(argc > 2) remoteAddress = sck::Address::FromIp(argv[2], 27300); - cout << "Asking connection to " << remoteAddress.getHost() << ":" << remoteAddress.getPort() << endl; - //build and initialize a connection to the server - sck::StringClient client( std::make_unique(remoteAddress) ); - client.open(); + std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(27300)); + cout << "Asking connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - ClientLoop(client , sleepTime); + // blocking open + client->open(std::chrono::milliseconds(0)); + if (!client->isOpen()) { + cout << "connection failed" << endl; + return EXIT_FAILURE; + } + + Asker ask(std::move(client)); + ask.askForever(std::chrono::milliseconds(sleepTime)); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/03-servers-clients/Launcher.cpp b/Samples/Tcp/03-servers-clients/Launcher.cpp index 25561cda..a8f97b05 100644 --- a/Samples/Tcp/03-servers-clients/Launcher.cpp +++ b/Samples/Tcp/03-servers-clients/Launcher.cpp @@ -1,4 +1,4 @@ -#include +#include "../../ProcessLauncher.h" int main() { diff --git a/Samples/Tcp/03-servers-clients/Server.cpp b/Samples/Tcp/03-servers-clients/Server.cpp index f3edd418..6822c20d 100644 --- a/Samples/Tcp/03-servers-clients/Server.cpp +++ b/Samples/Tcp/03-servers-clients/Server.cpp @@ -5,8 +5,8 @@ * report any bug to andrecasa91@gmail.com. **/ -#include "Service.h" -#include +#include +#include #include #include using namespace std; @@ -16,12 +16,22 @@ int main(){ cout << "----------------------- Server -----------------------" << endl; //build two services: one for client A and one for client B - sck::TcpServer server(27300); - server.open(); - - auto srv = [&](){ - Service service(std::make_unique(server.acceptNewClient())); - service.serveForever(); + sck::tcp::TcpServer server(27300); + + // blocking open + server.open(std::chrono::milliseconds(0)); + if (!server.isOpen()) { + cout << "server open failed" << endl; + return EXIT_FAILURE; + } + + auto srv = [&server](){ + //accept the client + auto clientHandler = server.acceptClient(); + cout << "client connected" << endl; + + Responder resp(std::move(clientHandler)); + resp.respondForever(); }; //spawn service to client A @@ -32,5 +42,5 @@ int main(){ tA.join(); tB.join(); - return 0; + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/04-repeater/CMakeLists.txt b/Samples/Tcp/04-repeater/CMakeLists.txt index deae45db..fe6f7405 100644 --- a/Samples/Tcp/04-repeater/CMakeLists.txt +++ b/Samples/Tcp/04-repeater/CMakeLists.txt @@ -1,39 +1,12 @@ -##### sample 04 ##### +add_executable(Sample04_Client Client.cpp) +target_link_libraries( Sample04_Client Sample-Utils) - add_executable( - Sample04_Client - Client.cpp - ) - target_link_libraries( Sample04_Client - PUBLIC - Utils - ) +add_executable(Sample04_Server Server.cpp) +target_link_libraries( Sample04_Server Sample-Utils) - add_executable( - Sample04_Repeater - Repeater.cpp - ) - target_link_libraries( Sample04_Repeater - PUBLIC - Utils - ) - - add_executable( - Sample04_Server - Server.cpp - ) - find_package( Threads ) - target_link_libraries( Sample04_Server - PUBLIC - Utils - ) +add_executable(Sample04_Repeater Repeater.cpp) +target_link_libraries( Sample04_Repeater Sample-Utils) - add_executable( - Sample04__Launcher - Launcher.cpp - ) - target_link_libraries( Sample04__Launcher - PUBLIC - Utils - ) - add_dependencies(Sample04__Launcher Sample04_Client Sample04_Repeater Sample04_Server) +add_executable(Sample04Launcher Launcher.cpp) +target_link_libraries(Sample04Launcher) +add_dependencies(Sample04Launcher Sample04_Server Sample04_Client Sample04_Repeater) \ No newline at end of file diff --git a/Samples/Tcp/04-repeater/Client.cpp b/Samples/Tcp/04-repeater/Client.cpp index 80e54b62..894841fd 100644 --- a/Samples/Tcp/04-repeater/Client.cpp +++ b/Samples/Tcp/04-repeater/Client.cpp @@ -1,27 +1,31 @@ /** * Author: Andrea Casalino * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. + * + * report any bug to andrecasa91@gmail.com. **/ -#include -#include +#include +#include #include using namespace std; -int main(int argc, char **argv){ +int main(int argc, char** argv) { cout << "----------------------- Client -----------------------" << endl; - sck::Address remoteAddress = parseAddress(argc, argv, 4000); - cout << "Asking connection to " << remoteAddress.getHost() << ":" << remoteAddress.getPort() << endl; + std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(4000)); + cout << "Asking connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - //build and initialize a connection to the server - sck::StringClient client( std::make_unique(remoteAddress) ); - client.open(); + // blocking open + client->open(std::chrono::milliseconds(0)); + if (!client->isOpen()) { + cout << "connection failed" << endl; + return EXIT_FAILURE; + } + + Asker ask(std::move(client)); + ask.askForever(std::chrono::milliseconds(150)); - ClientLoop(client , 150); - return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/04-repeater/Launcher.cpp b/Samples/Tcp/04-repeater/Launcher.cpp index a30e43bb..a1261e2b 100644 --- a/Samples/Tcp/04-repeater/Launcher.cpp +++ b/Samples/Tcp/04-repeater/Launcher.cpp @@ -1,4 +1,4 @@ -#include +#include "../../ProcessLauncher.h" int main() { @@ -8,5 +8,5 @@ int main() { lnc.addProcess("Sample04_Client"); lnc(); - return 0; + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/04-repeater/Repeater.cpp b/Samples/Tcp/04-repeater/Repeater.cpp index a841c099..371e8932 100644 --- a/Samples/Tcp/04-repeater/Repeater.cpp +++ b/Samples/Tcp/04-repeater/Repeater.cpp @@ -1,13 +1,12 @@ /** * Author: Andrea Casalino * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. + * + * report any bug to andrecasa91@gmail.com. **/ -#include -#include -#include +#include +#include #include using namespace std; @@ -16,30 +15,33 @@ int main(int argc, char **argv){ cout << "----------------------- Repeater -----------------------" << endl; // connecting to the server - sck::StringClient connection2Server( std::make_unique(sck::Address::Localhost(3000)) ); - connection2Server.open(); - cout << "connected to server" << endl; + std::unique_ptr connection2Server = std::make_unique(*sck::Ip::createLocalHost(3000)); + cout << "Asking connection to " << connection2Server->getRemoteAddress().getHost() << ":" << connection2Server->getRemoteAddress().getPort() << endl; // accepting client - std::unique_ptr connection2Client; + std::unique_ptr connection2Client; { - sck::TcpServer server(4000); - server.open(); - connection2Client = std::make_unique(server.acceptNewClient()); + sck::tcp::TcpServer server(3000); + server.open(std::chrono::milliseconds(0)); + if (!server.isOpen()) { + cout << "server open failed" << endl; + return EXIT_FAILURE; + } + connection2Client = server.acceptClient(); + cout << "client connected" << endl; } + char buffer[1000]; + std::pair temp = { &buffer[0], 1000 }; + std::size_t recvBytes; while (true) { - auto toForward = connection2Client->receive(500); - if(toForward->size() == 0) { - connection2Server.send(""); - break; - } - cout << "forwarding to server " << *toForward.get(); - connection2Server.send(*toForward.get()); + recvBytes = connection2Client->receive(temp, std::chrono::milliseconds(0)); + cout << "forwarding to server " << std::string(buffer, recvBytes); + connection2Server->send({&buffer[0], recvBytes}); - auto reply = connection2Server.receive(500); - cout << " reply to client " << *reply.get() << endl; - connection2Client->send(*reply.get()); + recvBytes = connection2Client->receive(temp, std::chrono::milliseconds(0)); + cout << " reply to client " << std::string(buffer, recvBytes) << endl; + connection2Client->send({ &buffer[0], recvBytes }); } return EXIT_SUCCESS; diff --git a/Samples/Tcp/04-repeater/Server.cpp b/Samples/Tcp/04-repeater/Server.cpp index de7630bc..a618ceb8 100644 --- a/Samples/Tcp/04-repeater/Server.cpp +++ b/Samples/Tcp/04-repeater/Server.cpp @@ -5,23 +5,31 @@ * report any bug to andrecasa91@gmail.com. **/ -#include -#include +#include +#include #include using namespace std; -int main(){ +int main() { cout << "----------------------- Server -----------------------" << endl; - // build and initialize a connection from a client on port 3000 - sck::TcpServer server(3000); - server.open(); + // build and initialize a connection from a client on port 20000 + sck::tcp::TcpServer server(3000); - //accept the client and create the service - Service srv(std::make_unique(server.acceptNewClient())); + // blocking open + server.open(std::chrono::milliseconds(0)); + if (!server.isOpen()) { + cout << "server open failed" << endl; + return EXIT_FAILURE; + } - srv.serveForever(); + //accept the client + auto clientHandler = server.acceptClient(); + cout << "client connected" << endl; + + Responder resp(std::move(clientHandler)); + resp.respondForever(); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/CMakeLists.txt b/Samples/Tcp/CMakeLists.txt index 8ffdb01e..9b7a04b9 100644 --- a/Samples/Tcp/CMakeLists.txt +++ b/Samples/Tcp/CMakeLists.txt @@ -1,7 +1,7 @@ add_subdirectory(01-server-client) -# add_subdirectory(Sample_02_singlethread_services) +add_subdirectory(02-server-clients) -# add_subdirectory(Sample_03_multithread_services) +add_subdirectory(03-servers-clients) -# add_subdirectory(Sample_04_repeater) +add_subdirectory(04-repeater) diff --git a/Samples/Utils/CMakeLists.txt b/Samples/Utils/CMakeLists.txt new file mode 100644 index 00000000..636a71fa --- /dev/null +++ b/Samples/Utils/CMakeLists.txt @@ -0,0 +1,30 @@ +set(PROJECT_SHORTNAME "Sample-Utils") +project(${PROJECT_SHORTNAME} VERSION ${VERSION} LANGUAGES CXX) +string(REPLACE - _ COMPONENT_NAME ${PROJECT_NAME}) + +CollectSourceFiles(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) + +GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) + +if(LIB_OPT) + add_library(${PROJECT_NAME} SHARED ${SOURCES}) +else() + add_library(${PROJECT_NAME} STATIC ${SOURCES}) +endif() + +target_compile_features(${PROJECT_NAME} + PUBLIC cxx_auto_type + PRIVATE cxx_variadic_templates +) + +target_include_directories(${PROJECT_NAME} + PUBLIC + $ + $ +) + +target_link_libraries(${PROJECT_NAME} + PUBLIC + Cross-Socket +) + diff --git a/Samples/Utils/include/Asker.h b/Samples/Utils/include/Asker.h new file mode 100644 index 00000000..995e8438 --- /dev/null +++ b/Samples/Utils/include/Asker.h @@ -0,0 +1,28 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef SAMPLE_ASKER_H +#define SAMPLE_ASKER_H + +#include +#include + +class Asker { +public: + Asker(std::unique_ptr socket); + + void ask(); + + void askForever(const std::chrono::milliseconds& sampleTime); + +private: + std::unique_ptr socket; + Names cursor; + char recvBuffer[1000]; +}; + +#endif \ No newline at end of file diff --git a/Samples/Names.h b/Samples/Utils/include/Names.h similarity index 56% rename from Samples/Names.h rename to Samples/Utils/include/Names.h index 7e78b18c..c9ddc8b3 100644 --- a/Samples/Names.h +++ b/Samples/Utils/include/Names.h @@ -13,33 +13,20 @@ class Names { public: - Names() { - this->cursor = namesSurnames.begin(); - } + Names(); inline const std::string& getCursorName() const { return this->cursor->first; }; inline const std::string& getCursorSurname() const { return this->cursor->second; }; - Names& operator++() { - ++this->cursor; - if (this->cursor == namesSurnames.end()) { - this->cursor = namesSurnames.begin(); - } - return *this; - } + static const std::string& getSurname(const std::string& name); + + Names& operator++(); private: static const std::map namesSurnames; + static const std::string unknown; std::map::const_iterator cursor; }; -const std::map Names::namesSurnames = { - {"Luciano", "Pavarotti"}, - {"Gengis", "Khan"}, - {"Giulio", "Cesare"}, - {"Theodor", "Roosvelt"}, - {"Immanuel", "Kant"} -}; - #endif diff --git a/Samples/Utils/include/Responder.h b/Samples/Utils/include/Responder.h new file mode 100644 index 00000000..e90f4c1e --- /dev/null +++ b/Samples/Utils/include/Responder.h @@ -0,0 +1,27 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef SAMPLE_RESPONDER_H +#define SAMPLE_RESPONDER_H + +#include +#include + +class Responder { +public: + Responder(std::unique_ptr socket); + + void respond(); + + void respondForever(); + +private: + std::unique_ptr socket; + char recvBuffer[1000]; +}; + +#endif \ No newline at end of file diff --git a/Samples/Utils/include/ResponderAsync.h b/Samples/Utils/include/ResponderAsync.h new file mode 100644 index 00000000..e69de29b diff --git a/Samples/Utils/src/Asker.cpp b/Samples/Utils/src/Asker.cpp new file mode 100644 index 00000000..8706e200 --- /dev/null +++ b/Samples/Utils/src/Asker.cpp @@ -0,0 +1,35 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +#include + +Asker::Asker(std::unique_ptr socket) + : socket(std::move(socket)) { +} + +void Asker::ask() { + std::cout << "sending: " << this->cursor.getCursorName(); + this->socket->send({this->cursor.getCursorName().data(), this->cursor.getCursorName().size()}); + std::pair temp = {&this->recvBuffer[0], 1000}; + std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); + std::string recStr(temp.first, recvBytes); + if (recStr.compare(this->cursor.getCursorSurname()) != 0) { + throw std::runtime_error("got wrong response"); + } + std::cout << " got: " << recStr << std::endl; + ++this->cursor; +} + +void Asker::askForever(const std::chrono::milliseconds& sampleTime) { + while (true) { + this->ask(); + std::this_thread::sleep_for(sampleTime); + } +} \ No newline at end of file diff --git a/Samples/Utils/src/Names.cpp b/Samples/Utils/src/Names.cpp new file mode 100644 index 00000000..5908c149 --- /dev/null +++ b/Samples/Utils/src/Names.cpp @@ -0,0 +1,36 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +Names::Names() { + this->cursor = namesSurnames.begin(); +} + +const std::map Names::namesSurnames = { + {"Luciano", "Pavarotti"}, + {"Gengis", "Khan"}, + {"Giulio", "Cesare"}, + {"Theodor", "Roosvelt"}, + {"Immanuel", "Kant"} +}; + +const std::string Names::unknown = "unknown"; + +Names& Names::operator++() { + ++this->cursor; + if (this->cursor == namesSurnames.end()) { + this->cursor = namesSurnames.begin(); + } + return *this; +} + +const std::string& Names::getSurname(const std::string& name) { + auto it = namesSurnames.find(name); + if (it == namesSurnames.end()) return unknown; + return it->second; +} diff --git a/Samples/Utils/src/Responder.cpp b/Samples/Utils/src/Responder.cpp new file mode 100644 index 00000000..4db3dee0 --- /dev/null +++ b/Samples/Utils/src/Responder.cpp @@ -0,0 +1,32 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +#include + +Responder::Responder(std::unique_ptr socket) + : socket(std::move(socket)) { +} + +void Responder::respond() { + std::pair temp = { &this->recvBuffer[0], 1000 }; + std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); + std::string recStr(temp.first, recvBytes); + std::cout << " got: " << recStr << std::endl; + + const std::string surname = Names::getSurname(recStr); + std::cout << "sending: " << surname; + this->socket->send({ surname.data(), surname.size() }); +} + +void Responder::respondForever() { + while (true) { + this->respond(); + } +} \ No newline at end of file diff --git a/Samples/Utils/src/ResponderAsync.cpp b/Samples/Utils/src/ResponderAsync.cpp new file mode 100644 index 00000000..e69de29b diff --git a/TODO b/TODO index 010abc76..7db66488 100644 --- a/TODO +++ b/TODO @@ -11,3 +11,5 @@ remove Socket Server add explicit a c?tor single input indicare quando throw in doc class rename protocol in family everywhere +spiegare bene come si lanciano samples +fix readme svg con giuste porte From 7981a3bd74582a2cf0ec852a201714273f1173ce Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 7 Feb 2021 18:38:40 +0100 Subject: [PATCH 018/228] async responder implemented --- Samples/Utils/include/ResponderAsync.h | 24 +++++++++++++++++++++++ Samples/Utils/src/Responder.cpp | 1 - Samples/Utils/src/ResponderAsync.cpp | 27 ++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/Samples/Utils/include/ResponderAsync.h b/Samples/Utils/include/ResponderAsync.h index e69de29b..0fcbb49b 100644 --- a/Samples/Utils/include/ResponderAsync.h +++ b/Samples/Utils/include/ResponderAsync.h @@ -0,0 +1,24 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef SAMPLE_RESPONDER_ASYNC_H +#define SAMPLE_RESPONDER_ASYNC_H + +#include +#include + +class ResponderAsync : public sck::async::listener::MessageListener { +public: + ResponderAsync(std::unique_ptr socket); + +private: + void handle(const std::pair& message) final; + + std::unique_ptr asyncSocket; +}; + +#endif \ No newline at end of file diff --git a/Samples/Utils/src/Responder.cpp b/Samples/Utils/src/Responder.cpp index 4db3dee0..ca2987e3 100644 --- a/Samples/Utils/src/Responder.cpp +++ b/Samples/Utils/src/Responder.cpp @@ -6,7 +6,6 @@ **/ #include -#include #include #include diff --git a/Samples/Utils/src/ResponderAsync.cpp b/Samples/Utils/src/ResponderAsync.cpp index e69de29b..44dd6fb9 100644 --- a/Samples/Utils/src/ResponderAsync.cpp +++ b/Samples/Utils/src/ResponderAsync.cpp @@ -0,0 +1,27 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include + +ResponderAsync::ResponderAsync(std::unique_ptr socket) { + this->asyncSocket = std::make_unique(std::move(socket), 1000); + this->asyncSocket->open(std::chrono::milliseconds(0)); +} + +void ResponderAsync::handle(const std::pair& message) { + std::string recStr(message.first, message.second); + std::cout << " got: " << recStr << std::endl; + + const std::string surname = Names::getSurname(recStr); + std::cout << "sending: " << surname; + this->asyncSocket->send({ surname.data(), surname.size() }); +} + + + From caaaa1007ecff39cc0e255175b0b956405c74f79 Mon Sep 17 00:00:00 2001 From: andrea Date: Sun, 7 Feb 2021 18:50:01 +0100 Subject: [PATCH 019/228] fixing Linux compile errors --- CrossSocket/include/async/AsyncClient.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CrossSocket/include/async/AsyncClient.h b/CrossSocket/include/async/AsyncClient.h index 5c7f7a8b..7dc1efa3 100644 --- a/CrossSocket/include/async/AsyncClient.h +++ b/CrossSocket/include/async/AsyncClient.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include namespace sck::async { From 16a0f9acb4c6abaea7d84b7464b7c73018a62066 Mon Sep 17 00:00:00 2001 From: andrea Date: Sun, 7 Feb 2021 18:55:32 +0100 Subject: [PATCH 020/228] link to pthreads to solve compile problems --- CrossSocket/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CrossSocket/CMakeLists.txt b/CrossSocket/CMakeLists.txt index 13373cab..7e36799e 100644 --- a/CrossSocket/CMakeLists.txt +++ b/CrossSocket/CMakeLists.txt @@ -30,4 +30,9 @@ if(WIN32) wsock32 ws2_32 ) endif() +find_package(Threads) +target_link_libraries(${PROJECT_NAME} + PUBLIC + ${CMAKE_THREAD_LIBS_INIT} +) From 30fff79106f15ce7462c040b97a26906e4779c77 Mon Sep 17 00:00:00 2001 From: andrea Date: Sun, 7 Feb 2021 19:30:28 +0100 Subject: [PATCH 021/228] udp samples implemented --- Samples/CMakeLists.txt | 2 +- Samples/Udp/05-server-client/CMakeLists.txt | 9 ++++ Samples/Udp/05-server-client/Client.cpp | 31 ++++++++++++ .../Launcher.cpp | 4 +- Samples/Udp/05-server-client/Server.cpp | 31 ++++++++++++ Samples/Udp/06-servers-clients/CMakeLists.txt | 9 ++++ Samples/Udp/06-servers-clients/Client.cpp | 40 +++++++++++++++ Samples/Udp/06-servers-clients/Launcher.cpp | 12 +++++ .../README.svg | 0 Samples/Udp/06-servers-clients/Server.cpp | 49 +++++++++++++++++++ Samples/Udp/CMakeLists.txt | 4 +- .../Sample_05_server_client/CMakeLists.txt | 29 ----------- .../Udp/Sample_05_server_client/Client.cpp | 27 ---------- .../Udp/Sample_05_server_client/Server.cpp | 28 ----------- .../CMakeLists.txt | 31 ------------ .../Sample_06_multithread_services/Client.cpp | 33 ------------- .../Launcher.cpp | 12 ----- .../Sample_06_multithread_services/Server.cpp | 37 -------------- TODO | 1 + 19 files changed, 187 insertions(+), 202 deletions(-) create mode 100644 Samples/Udp/05-server-client/CMakeLists.txt create mode 100644 Samples/Udp/05-server-client/Client.cpp rename Samples/Udp/{Sample_05_server_client => 05-server-client}/Launcher.cpp (65%) create mode 100644 Samples/Udp/05-server-client/Server.cpp create mode 100644 Samples/Udp/06-servers-clients/CMakeLists.txt create mode 100644 Samples/Udp/06-servers-clients/Client.cpp create mode 100644 Samples/Udp/06-servers-clients/Launcher.cpp rename Samples/Udp/{Sample_06_multithread_services => 06-servers-clients}/README.svg (100%) create mode 100644 Samples/Udp/06-servers-clients/Server.cpp delete mode 100644 Samples/Udp/Sample_05_server_client/CMakeLists.txt delete mode 100644 Samples/Udp/Sample_05_server_client/Client.cpp delete mode 100644 Samples/Udp/Sample_05_server_client/Server.cpp delete mode 100644 Samples/Udp/Sample_06_multithread_services/CMakeLists.txt delete mode 100644 Samples/Udp/Sample_06_multithread_services/Client.cpp delete mode 100644 Samples/Udp/Sample_06_multithread_services/Launcher.cpp delete mode 100644 Samples/Udp/Sample_06_multithread_services/Server.cpp diff --git a/Samples/CMakeLists.txt b/Samples/CMakeLists.txt index 40d91555..7b3bf821 100644 --- a/Samples/CMakeLists.txt +++ b/Samples/CMakeLists.txt @@ -2,4 +2,4 @@ add_subdirectory(Utils) add_subdirectory(Tcp) -# add_subdirectory(Udp) +add_subdirectory(Udp) diff --git a/Samples/Udp/05-server-client/CMakeLists.txt b/Samples/Udp/05-server-client/CMakeLists.txt new file mode 100644 index 00000000..464830c4 --- /dev/null +++ b/Samples/Udp/05-server-client/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(Sample05_Client Client.cpp) +target_link_libraries( Sample05_Client Sample-Utils) + +add_executable(Sample05_Server Server.cpp) +target_link_libraries( Sample05_Server Sample-Utils) + +add_executable(Sample05Launcher Launcher.cpp) +target_link_libraries(Sample05Launcher) +add_dependencies(Sample05Launcher Sample05_Server Sample05_Client) diff --git a/Samples/Udp/05-server-client/Client.cpp b/Samples/Udp/05-server-client/Client.cpp new file mode 100644 index 00000000..6597ec5c --- /dev/null +++ b/Samples/Udp/05-server-client/Client.cpp @@ -0,0 +1,31 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +using namespace std; + +int main(int argc, char **argv){ + + cout << "----------------------- Client -----------------------" << endl; + + std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(20000)); + cout << "Sending handshake message to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; + { + std::string handshakeMessage = "hello"; + client->send({handshakeMessage.data(), handshakeMessage.size()}); + } + + // blocking open + client->open(std::chrono::milliseconds(0)); + + Asker ask(std::move(client)); + ask.askForever(std::chrono::milliseconds(500)); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/Sample_05_server_client/Launcher.cpp b/Samples/Udp/05-server-client/Launcher.cpp similarity index 65% rename from Samples/Udp/Sample_05_server_client/Launcher.cpp rename to Samples/Udp/05-server-client/Launcher.cpp index 149bb3f6..334e34f7 100644 --- a/Samples/Udp/Sample_05_server_client/Launcher.cpp +++ b/Samples/Udp/05-server-client/Launcher.cpp @@ -1,4 +1,4 @@ -#include +#include "../../ProcessLauncher.h" int main() { @@ -7,5 +7,5 @@ int main() { lnc.addProcess("Sample05_Client"); lnc(); - return 0; + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Udp/05-server-client/Server.cpp b/Samples/Udp/05-server-client/Server.cpp new file mode 100644 index 00000000..2f51f049 --- /dev/null +++ b/Samples/Udp/05-server-client/Server.cpp @@ -0,0 +1,31 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 +* +* report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +using namespace std; + +int main(){ + + cout << "----------------------- Server -----------------------" << endl; + + // build and initialize a connection from a client on port 20000 + std::unique_ptr server = std::make_unique(2000); + + // blocking open: wait for handshake message from client + server->open(std::chrono::milliseconds(0)); + if (!server->isOpen()) { + cout << "server open failed" << endl; + return EXIT_FAILURE; + } + + Responder resp(std::move(server)); + resp.respondForever(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/06-servers-clients/CMakeLists.txt b/Samples/Udp/06-servers-clients/CMakeLists.txt new file mode 100644 index 00000000..6f9f1b60 --- /dev/null +++ b/Samples/Udp/06-servers-clients/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(Sample06_Client Client.cpp) +target_link_libraries( Sample06_Client Sample-Utils) + +add_executable(Sample06_Server Server.cpp) +target_link_libraries( Sample06_Server Sample-Utils) + +add_executable(Sample06Launcher Launcher.cpp) +target_link_libraries(Sample06Launcher) +add_dependencies(Sample06Launcher Sample06_Server Sample06_Client) diff --git a/Samples/Udp/06-servers-clients/Client.cpp b/Samples/Udp/06-servers-clients/Client.cpp new file mode 100644 index 00000000..875d2be1 --- /dev/null +++ b/Samples/Udp/06-servers-clients/Client.cpp @@ -0,0 +1,40 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +using namespace std; + +int main(int argc, char** argv) { + + cout << "----------------------- Client -----------------------" << endl; + + if (argc < 2) { + cout << "sleep time not passed" << endl; + return EXIT_FAILURE; + } + int sleepTime = atoi(std::string(argv[1]).c_str()); + + if (argc < 3) { + cout << "port to bind not passed" << endl; + return EXIT_FAILURE; + } + std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(31000), std::atoi(argv[2])); + + // blocking open + client->open(std::chrono::milliseconds(0)); + if (!client->isOpen()) { + cout << "connection failed" << endl; + return EXIT_FAILURE; + } + + Asker ask(std::move(client)); + ask.askForever(std::chrono::milliseconds(sleepTime)); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/06-servers-clients/Launcher.cpp b/Samples/Udp/06-servers-clients/Launcher.cpp new file mode 100644 index 00000000..328ac109 --- /dev/null +++ b/Samples/Udp/06-servers-clients/Launcher.cpp @@ -0,0 +1,12 @@ +#include "../../ProcessLauncher.h" + +int main() { + + Launcher lnc("launcher"); + lnc.addProcess("Sample06_Server", "3500", "4500"); + lnc.addProcess("Sample06_Client", "500", "3500"); + lnc.addProcess("Sample06_Client", "50", "4500"); + lnc(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/Sample_06_multithread_services/README.svg b/Samples/Udp/06-servers-clients/README.svg similarity index 100% rename from Samples/Udp/Sample_06_multithread_services/README.svg rename to Samples/Udp/06-servers-clients/README.svg diff --git a/Samples/Udp/06-servers-clients/Server.cpp b/Samples/Udp/06-servers-clients/Server.cpp new file mode 100644 index 00000000..6f0221b2 --- /dev/null +++ b/Samples/Udp/06-servers-clients/Server.cpp @@ -0,0 +1,49 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 +* +* report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +#include +using namespace std; + +int main(int argc, char** argv){ + + cout << "----------------------- Server -----------------------" << endl; + + //build two services: one for client A and one for client B + std::uint16_t portA, portB; + if (argc < 2) { + cout << "port A not passed" << endl; + return EXIT_FAILURE; + } + portA = std::atoi(argv[1]); + if (argc < 3) { + cout << "port B not passed" << endl; + return EXIT_FAILURE; + } + portB = std::atoi(argv[2]); + + auto srv = [](const std::uint16_t& port){ + //accept the client + std::unique_ptr socket = std::make_unique(*sck::Ip::createLocalHost(port), 31000); + socket->open(std::chrono::milliseconds(0)); + + Responder resp(std::move(socket)); + resp.respondForever(); + }; + + //spawn service to client A + std::thread tA([&srv, &portA](){ srv(portA); }); + //spawn service to client B + std::thread tB([&srv, &portB](){ srv(portB); }); + + tA.join(); + tB.join(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/CMakeLists.txt b/Samples/Udp/CMakeLists.txt index 9d947bc9..6309facc 100644 --- a/Samples/Udp/CMakeLists.txt +++ b/Samples/Udp/CMakeLists.txt @@ -1,4 +1,4 @@ -add_subdirectory(Sample_05_server_client) +add_subdirectory(05-server-client) -add_subdirectory(Sample_06_multithread_services) +add_subdirectory(06-servers-clients) diff --git a/Samples/Udp/Sample_05_server_client/CMakeLists.txt b/Samples/Udp/Sample_05_server_client/CMakeLists.txt deleted file mode 100644 index f860c767..00000000 --- a/Samples/Udp/Sample_05_server_client/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -##### sample 05 ##### - - add_executable( - Sample05_Client - Client.cpp - ) - target_link_libraries( Sample05_Client - PUBLIC - Utils - ) - - add_executable( - Sample05_Server - Server.cpp - ) - target_link_libraries( Sample05_Server - PUBLIC - Utils - ) - - add_executable( - Sample05__Launcher - Launcher.cpp - ) - target_link_libraries( Sample05__Launcher - PUBLIC - Utils - ) - add_dependencies(Sample05__Launcher Sample05_Server Sample05_Client) diff --git a/Samples/Udp/Sample_05_server_client/Client.cpp b/Samples/Udp/Sample_05_server_client/Client.cpp deleted file mode 100644 index 9128a21e..00000000 --- a/Samples/Udp/Sample_05_server_client/Client.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char **argv){ - - cout << "----------------------- Client -----------------------" << endl; - - sck::Address remoteAddress = parseAddress(argc, argv, 2000); - cout << "Asking connection to " << remoteAddress.getHost() << ":" << remoteAddress.getPort() << endl; - - //build and initialize a connection to the server on port 2000 and reserve port 3000 - sck::StringClient client( std::make_unique(remoteAddress, 3000) ); - client.open(); - - ClientLoop(client , 500); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Udp/Sample_05_server_client/Server.cpp b/Samples/Udp/Sample_05_server_client/Server.cpp deleted file mode 100644 index c5ff743c..00000000 --- a/Samples/Udp/Sample_05_server_client/Server.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char **argv){ - - cout << "----------------------- Server -----------------------" << endl; - - sck::Address clientAddress = parseAddress(argc, argv, 3000); - cout << "Client expected at " << clientAddress.getHost() << ":" << clientAddress.getPort() << endl; - - //build and initialize a connection to the client on port 3000 and reserve port 2000 - std::unique_ptr server = std::make_unique(std::make_unique(clientAddress, 2000)); - server->open(); - Service srv(std::move(server)); - - srv.serveForever(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Udp/Sample_06_multithread_services/CMakeLists.txt b/Samples/Udp/Sample_06_multithread_services/CMakeLists.txt deleted file mode 100644 index 282e5850..00000000 --- a/Samples/Udp/Sample_06_multithread_services/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -##### sample 06 ##### - - add_executable( - Sample06_Client - Client.cpp - ) - target_link_libraries( Sample06_Client - PUBLIC - Utils - ) - - add_executable( - Sample06_Server - Server.cpp - ) - find_package( Threads ) - target_link_libraries( Sample06_Server - PUBLIC - Utils - ${CMAKE_THREAD_LIBS_INIT} - ) - - add_executable( - Sample06__Launcher - Launcher.cpp - ) - target_link_libraries( Sample06__Launcher - PUBLIC - Utils - ) - add_dependencies(Sample06__Launcher Sample06_Client Sample06_Server) diff --git a/Samples/Udp/Sample_06_multithread_services/Client.cpp b/Samples/Udp/Sample_06_multithread_services/Client.cpp deleted file mode 100644 index 959d9710..00000000 --- a/Samples/Udp/Sample_06_multithread_services/Client.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char **argv){ - - cout << "----------------------- Client -----------------------" << endl; - - if(argc < 3) return EXIT_FAILURE; - int sleepTime = atoi(std::string(argv[1]).c_str()); - int port = atoi(std::string(argv[2]).c_str()); - sck::Address remoteAddress = sck::Address::Localhost(port); - if(argc > 3) remoteAddress = sck::Address::FromIp(argv[3], port); - cout << "Asking connection to " << remoteAddress.getHost() << ":" << remoteAddress.getPort() << endl; - - //build and initialize a connection to the server - sck::StringClient client( std::make_unique(remoteAddress) ); - client.open(); - //send 1 byte to make the server recognize the client - client.send("A"); - - ClientLoop(client , sleepTime); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Udp/Sample_06_multithread_services/Launcher.cpp b/Samples/Udp/Sample_06_multithread_services/Launcher.cpp deleted file mode 100644 index b5fee812..00000000 --- a/Samples/Udp/Sample_06_multithread_services/Launcher.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include - -int main() { - - Launcher lnc("launcher"); - lnc.addProcess("Sample06_Server"); - lnc.addProcess("Sample06_Client", "500" "3500"); - lnc.addProcess("Sample06_Client", "50" "4500"); - lnc(); - - return 0; -} \ No newline at end of file diff --git a/Samples/Udp/Sample_06_multithread_services/Server.cpp b/Samples/Udp/Sample_06_multithread_services/Server.cpp deleted file mode 100644 index 30a0dc64..00000000 --- a/Samples/Udp/Sample_06_multithread_services/Server.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include "Service.h" -#include -#include -#include -using namespace std; - -int main(){ - - cout << "----------------------- Server -----------------------" << endl; - - //build two services: one for client A and one for client B - - auto srv = [&](const std::uint16_t& localPort){ - std::unique_ptr server = std::make_unique(std::make_unique(localPort)); - server->open(); - return std::make_unique(std::move(server)); - }; - - //spawn service to client A - auto srvA = srv(3500); - std::thread tA([&srvA](){ srvA->serveForever(); }); - //spawn service to client B - auto srvB = srv(4500); - std::thread tB([&srvB](){ srvB->serveForever(); }); - - tA.join(); - tB.join(); - - return 0; -} \ No newline at end of file diff --git a/TODO b/TODO index 7db66488..c62ec08c 100644 --- a/TODO +++ b/TODO @@ -13,3 +13,4 @@ indicare quando throw in doc class rename protocol in family everywhere spiegare bene come si lanciano samples fix readme svg con giuste porte +aggiungere in samples qlcs che consuma async responder From b2f9849fc6b0d273ff6fbd86da4e193721ff0cf4 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 8 Feb 2021 20:35:35 +0100 Subject: [PATCH 022/228] debugging samples --- CrossSocket/src/Ip.cpp | 4 ++-- CrossSocket/src/tcp/TcpServer.cpp | 2 +- Samples/Utils/src/Responder.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CrossSocket/src/Ip.cpp b/CrossSocket/src/Ip.cpp index f3190faa..c94e9845 100644 --- a/CrossSocket/src/Ip.cpp +++ b/CrossSocket/src/Ip.cpp @@ -22,12 +22,12 @@ namespace sck { //try to resolve the Ip as an ipv4 IpPtr addr; addr.reset(new Ip(host, port, sck::Family::IP_V4)); - if (nullptr == convertIpv4(*addr)) { + if (nullptr != convertIpv4(*addr)) { return addr; } //try to resolve the Ip as an ipv6 addr.reset(new Ip(host, port, sck::Family::IP_V6)); - if (nullptr == convertIpv6(*addr)) { + if (nullptr != convertIpv6(*addr)) { return addr; } return nullptr; diff --git a/CrossSocket/src/tcp/TcpServer.cpp b/CrossSocket/src/tcp/TcpServer.cpp index 40db9a66..f95d39da 100644 --- a/CrossSocket/src/tcp/TcpServer.cpp +++ b/CrossSocket/src/tcp/TcpServer.cpp @@ -52,7 +52,7 @@ namespace sck::tcp { std::shared_ptr acceptedClientHandler = std::make_shared(temp); IpPtr remoteAddress = convert(acceptedClientAddress); - if (nullptr != remoteAddress) { + if (nullptr == remoteAddress) { throw std::runtime_error("accepted client remote address is not resolvable"); } diff --git a/Samples/Utils/src/Responder.cpp b/Samples/Utils/src/Responder.cpp index ca2987e3..4ee2c5bb 100644 --- a/Samples/Utils/src/Responder.cpp +++ b/Samples/Utils/src/Responder.cpp @@ -17,10 +17,10 @@ void Responder::respond() { std::pair temp = { &this->recvBuffer[0], 1000 }; std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); std::string recStr(temp.first, recvBytes); - std::cout << " got: " << recStr << std::endl; + std::cout << "got: " << recStr; const std::string surname = Names::getSurname(recStr); - std::cout << "sending: " << surname; + std::cout << " sending: " << surname << std::endl; this->socket->send({ surname.data(), surname.size() }); } From 1507cdb0064c5ee6441d19e8817ba2ef802ae285 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 8 Feb 2021 20:52:42 +0100 Subject: [PATCH 023/228] tcp samples debugged --- Samples/Tcp/04-repeater/Repeater.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Samples/Tcp/04-repeater/Repeater.cpp b/Samples/Tcp/04-repeater/Repeater.cpp index 371e8932..c5ee9a07 100644 --- a/Samples/Tcp/04-repeater/Repeater.cpp +++ b/Samples/Tcp/04-repeater/Repeater.cpp @@ -18,10 +18,17 @@ int main(int argc, char **argv){ std::unique_ptr connection2Server = std::make_unique(*sck::Ip::createLocalHost(3000)); cout << "Asking connection to " << connection2Server->getRemoteAddress().getHost() << ":" << connection2Server->getRemoteAddress().getPort() << endl; + // blocking open + connection2Server->open(std::chrono::milliseconds(0)); + if (!connection2Server->isOpen()) { + cout << "connection failed" << endl; + return EXIT_FAILURE; + } + // accepting client std::unique_ptr connection2Client; { - sck::tcp::TcpServer server(3000); + sck::tcp::TcpServer server(4000); server.open(std::chrono::milliseconds(0)); if (!server.isOpen()) { cout << "server open failed" << endl; @@ -39,7 +46,7 @@ int main(int argc, char **argv){ cout << "forwarding to server " << std::string(buffer, recvBytes); connection2Server->send({&buffer[0], recvBytes}); - recvBytes = connection2Client->receive(temp, std::chrono::milliseconds(0)); + recvBytes = connection2Server->receive(temp, std::chrono::milliseconds(0)); cout << " reply to client " << std::string(buffer, recvBytes) << endl; connection2Client->send({ &buffer[0], recvBytes }); } From 445d5da0a8c027a336d2dd655d7199f441d772b5 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 9 Feb 2021 21:46:41 +0100 Subject: [PATCH 024/228] tcp samples refactored --- Samples/CMakeLists.txt | 2 +- Samples/Tcp/04-repeater/Launcher.cpp | 12 ---- Samples/Tcp/CMakeLists.txt | 20 ++++-- Samples/Tcp/Client.cpp | 56 +++++++++++++++++ Samples/Tcp/Launcher-01-server-client.cpp | 11 ++++ Samples/Tcp/Launcher-02-server-clients.cpp | 12 ++++ Samples/Tcp/Launcher-03-repeater.cpp | 12 ++++ Samples/Tcp/{04-repeater => }/Repeater.cpp | 3 +- Samples/Tcp/Server.cpp | 73 ++++++++++++++++++++++ 9 files changed, 182 insertions(+), 19 deletions(-) delete mode 100644 Samples/Tcp/04-repeater/Launcher.cpp create mode 100644 Samples/Tcp/Client.cpp create mode 100644 Samples/Tcp/Launcher-01-server-client.cpp create mode 100644 Samples/Tcp/Launcher-02-server-clients.cpp create mode 100644 Samples/Tcp/Launcher-03-repeater.cpp rename Samples/Tcp/{04-repeater => }/Repeater.cpp (98%) create mode 100644 Samples/Tcp/Server.cpp diff --git a/Samples/CMakeLists.txt b/Samples/CMakeLists.txt index 7b3bf821..40d91555 100644 --- a/Samples/CMakeLists.txt +++ b/Samples/CMakeLists.txt @@ -2,4 +2,4 @@ add_subdirectory(Utils) add_subdirectory(Tcp) -add_subdirectory(Udp) +# add_subdirectory(Udp) diff --git a/Samples/Tcp/04-repeater/Launcher.cpp b/Samples/Tcp/04-repeater/Launcher.cpp deleted file mode 100644 index a1261e2b..00000000 --- a/Samples/Tcp/04-repeater/Launcher.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "../../ProcessLauncher.h" - -int main() { - - Launcher lnc("launcher"); - lnc.addProcess("Sample04_Server"); - lnc.addProcess("Sample04_Repeater"); - lnc.addProcess("Sample04_Client"); - lnc(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Tcp/CMakeLists.txt b/Samples/Tcp/CMakeLists.txt index 9b7a04b9..8e457c23 100644 --- a/Samples/Tcp/CMakeLists.txt +++ b/Samples/Tcp/CMakeLists.txt @@ -1,7 +1,19 @@ -add_subdirectory(01-server-client) +add_executable(TcpClient Client.cpp) +target_link_libraries(TcpClient Sample-Utils) -add_subdirectory(02-server-clients) +add_executable(TcpServer Server.cpp) +target_link_libraries(TcpServer Sample-Utils) -add_subdirectory(03-servers-clients) +add_executable(TcpRepeater Repeater.cpp) +target_link_libraries(TcpRepeater Sample-Utils) -add_subdirectory(04-repeater) +## launchers ## + +add_executable(Tcp01Launcher Launcher-01-server-client.cpp) +add_dependencies(Tcp01Launcher TcpClient TcpServer) + +add_executable(Tcp02Launcher Launcher-02-server-clients.cpp) +add_dependencies(Tcp02Launcher TcpClient TcpServer) + +add_executable(Tcp03Launcher Launcher-03-repeater.cpp) +add_dependencies(Tcp03Launcher TcpClient TcpServer TcpRepeater) diff --git a/Samples/Tcp/Client.cpp b/Samples/Tcp/Client.cpp new file mode 100644 index 00000000..61c615c3 --- /dev/null +++ b/Samples/Tcp/Client.cpp @@ -0,0 +1,56 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +using namespace std; + +int main(int argc, char **argv){ + cout << "----------------------- Client -----------------------" << endl; + + if (argc == 1) { + cout << "correct syntax is: 'server port', 'rate', 'server host'" << endl; + return EXIT_FAILURE; + } + + std::uint16_t serverPort = std::atoi(argv[1]); + + std::chrono::milliseconds rate(200); + if (argc > 2) { + rate = std::chrono::milliseconds(std::atoi(argv[2])); + } + cout << "rate assumed " << rate.count() << " [ms]" << endl; + + sck::IpPtr serverAddress; + if (argc > 3) { + serverAddress = sck::Ip::create(std::string(argv[3]), serverPort); + } + else { + serverAddress = sck::Ip::createLocalHost(serverPort); + } + if (nullptr == serverAddress) { + cout << "invalid server address" << endl; + return EXIT_FAILURE; + } + + std::unique_ptr client = std::make_unique(*serverAddress); + cout << "Asking connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; + + // blocking open + client->open(std::chrono::milliseconds(0)); + if (!client->isOpen()) { + cout << "connection failed" << endl; + return EXIT_FAILURE; + } + cout << "connection opened" << endl; + + Asker ask(std::move(client)); + ask.askForever(rate); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Tcp/Launcher-01-server-client.cpp b/Samples/Tcp/Launcher-01-server-client.cpp new file mode 100644 index 00000000..71d6b368 --- /dev/null +++ b/Samples/Tcp/Launcher-01-server-client.cpp @@ -0,0 +1,11 @@ +#include "../ProcessLauncher.h" + +int main() { + + Launcher lnc("Launcher01"); + lnc.addProcess("TcpServer","20000"); + lnc.addProcess("TcpClient","20000"); + lnc(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Tcp/Launcher-02-server-clients.cpp b/Samples/Tcp/Launcher-02-server-clients.cpp new file mode 100644 index 00000000..97c36f4e --- /dev/null +++ b/Samples/Tcp/Launcher-02-server-clients.cpp @@ -0,0 +1,12 @@ +#include "../ProcessLauncher.h" + +int main() { + + Launcher lnc("Launcher02"); + lnc.addProcess("TcpServer","25000", "2"); + lnc.addProcess("TcpClient","25000", "100"); + lnc.addProcess("TcpClient","25000", "500"); + lnc(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Tcp/Launcher-03-repeater.cpp b/Samples/Tcp/Launcher-03-repeater.cpp new file mode 100644 index 00000000..17373000 --- /dev/null +++ b/Samples/Tcp/Launcher-03-repeater.cpp @@ -0,0 +1,12 @@ +#include "../ProcessLauncher.h" + +int main() { + + Launcher lnc("Launcher03"); + lnc.addProcess("TcpServer","3000"); + lnc.addProcess("TcpRepeater","3000", "4000"); + lnc.addProcess("TcpClient","4000"); + lnc(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Tcp/04-repeater/Repeater.cpp b/Samples/Tcp/Repeater.cpp similarity index 98% rename from Samples/Tcp/04-repeater/Repeater.cpp rename to Samples/Tcp/Repeater.cpp index c5ee9a07..e982816f 100644 --- a/Samples/Tcp/04-repeater/Repeater.cpp +++ b/Samples/Tcp/Repeater.cpp @@ -10,8 +10,7 @@ #include using namespace std; -int main(int argc, char **argv){ - +int main(){ cout << "----------------------- Repeater -----------------------" << endl; // connecting to the server diff --git a/Samples/Tcp/Server.cpp b/Samples/Tcp/Server.cpp new file mode 100644 index 00000000..a9d06a9c --- /dev/null +++ b/Samples/Tcp/Server.cpp @@ -0,0 +1,73 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 +* +* report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +#include +#include +using namespace std; + +class AcceptedClient { +public: + AcceptedClient(std::unique_ptr acceptedClient) : acceptedClient(std::move(acceptedClient)) {}; + + void run() { + Responder resp(std::move(this->acceptedClient)); + resp.respondForever(); + }; + +private: + std::unique_ptr acceptedClient; +}; + +int main(int argc, char** argv){ + cout << "----------------------- Server -----------------------" << endl; + + if (argc == 1) { + cout << "correct syntax is: 'port to bind', 'number of clients to accept'" << endl; + return EXIT_FAILURE; + } + + std::uint16_t port = std::atoi(argv[1]); + std::size_t clientNumbers = 1; + if (argc > 2) { + clientNumbers = std::atoi(argv[2]); + } + if (0 == clientNumbers) { + cout << "invalid number of clients" << endl; + return EXIT_FAILURE; + } + cout << "clients excepted " << clientNumbers << endl; + + //build the server object + sck::tcp::TcpServer server(port); + cout << "Binding port " << port << endl; + + // blocking open + server.open(std::chrono::milliseconds(0)); + if (!server.isOpen()) { + cout << "server open failed" << endl; + return EXIT_FAILURE; + } + cout << "connection opened" << endl; + + std::list clients; + std::list clientThreads; + for (std::size_t c = 0; c < clientNumbers; ++c) { + //accept the client + clients.emplace_back(server.acceptClient()); + cout << "new client connected" << endl; + clientThreads.emplace_back(&AcceptedClient::run, &clients.back()); + } + + for (auto it = clientThreads.begin(); it != clientThreads.end(); ++it) { + it->join(); + } + + return EXIT_SUCCESS; +} \ No newline at end of file From 550821b822c0a71aee0f249cba2a54b605d555d0 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 9 Feb 2021 22:42:38 +0100 Subject: [PATCH 025/228] udp samples restructured --- Samples/CMakeLists.txt | 2 +- Samples/Tcp/01-server-client/CMakeLists.txt | 9 - Samples/Tcp/01-server-client/Client.cpp | 31 -- Samples/Tcp/01-server-client/Launcher.cpp | 11 - Samples/Tcp/01-server-client/Server.cpp | 35 -- Samples/Tcp/02-server-clients/CMakeLists.txt | 9 - Samples/Tcp/02-server-clients/Client.cpp | 31 -- Samples/Tcp/02-server-clients/Launcher.cpp | 12 - Samples/Tcp/02-server-clients/README.svg | 302 ------------- Samples/Tcp/02-server-clients/Server.cpp | 44 -- Samples/Tcp/03-servers-clients/CMakeLists.txt | 9 - Samples/Tcp/03-servers-clients/Client.cpp | 37 -- Samples/Tcp/03-servers-clients/Launcher.cpp | 12 - Samples/Tcp/03-servers-clients/README.svg | 382 ----------------- Samples/Tcp/03-servers-clients/Server.cpp | 46 -- Samples/Tcp/04-repeater/CMakeLists.txt | 12 - Samples/Tcp/04-repeater/Client.cpp | 31 -- Samples/Tcp/04-repeater/README.svg | 366 ---------------- Samples/Tcp/04-repeater/Server.cpp | 35 -- Samples/Tcp/CMakeLists.txt | 6 +- Samples/Tcp/Launcher-03-repeater.cpp | 2 +- Samples/Tcp/{Client.cpp => TcpClient.cpp} | 0 Samples/Tcp/{Repeater.cpp => TcpRepeater.cpp} | 0 Samples/Tcp/{Server.cpp => TcpServer.cpp} | 26 +- Samples/Udp/05-server-client/CMakeLists.txt | 9 - Samples/Udp/05-server-client/Client.cpp | 31 -- Samples/Udp/05-server-client/Launcher.cpp | 11 - Samples/Udp/05-server-client/Server.cpp | 31 -- Samples/Udp/06-servers-clients/CMakeLists.txt | 9 - Samples/Udp/06-servers-clients/Client.cpp | 40 -- Samples/Udp/06-servers-clients/Launcher.cpp | 12 - Samples/Udp/06-servers-clients/README.svg | 404 ------------------ Samples/Udp/06-servers-clients/Server.cpp | 49 --- Samples/Udp/CMakeLists.txt | 16 +- Samples/Udp/Launcher-01-client-client.cpp | 11 + Samples/Udp/Launcher-02-client-server.cpp | 11 + Samples/Udp/UdpClientAsker.cpp | 58 +++ Samples/Udp/UdpClientResponder.cpp | 52 +++ Samples/Udp/UdpServer.cpp | 34 ++ Samples/Utils/src/Responder.cpp | 3 - TODO | 1 + 41 files changed, 194 insertions(+), 2038 deletions(-) delete mode 100644 Samples/Tcp/01-server-client/CMakeLists.txt delete mode 100644 Samples/Tcp/01-server-client/Client.cpp delete mode 100644 Samples/Tcp/01-server-client/Launcher.cpp delete mode 100644 Samples/Tcp/01-server-client/Server.cpp delete mode 100644 Samples/Tcp/02-server-clients/CMakeLists.txt delete mode 100644 Samples/Tcp/02-server-clients/Client.cpp delete mode 100644 Samples/Tcp/02-server-clients/Launcher.cpp delete mode 100644 Samples/Tcp/02-server-clients/README.svg delete mode 100644 Samples/Tcp/02-server-clients/Server.cpp delete mode 100644 Samples/Tcp/03-servers-clients/CMakeLists.txt delete mode 100644 Samples/Tcp/03-servers-clients/Client.cpp delete mode 100644 Samples/Tcp/03-servers-clients/Launcher.cpp delete mode 100644 Samples/Tcp/03-servers-clients/README.svg delete mode 100644 Samples/Tcp/03-servers-clients/Server.cpp delete mode 100644 Samples/Tcp/04-repeater/CMakeLists.txt delete mode 100644 Samples/Tcp/04-repeater/Client.cpp delete mode 100644 Samples/Tcp/04-repeater/README.svg delete mode 100644 Samples/Tcp/04-repeater/Server.cpp rename Samples/Tcp/{Client.cpp => TcpClient.cpp} (100%) rename Samples/Tcp/{Repeater.cpp => TcpRepeater.cpp} (100%) rename Samples/Tcp/{Server.cpp => TcpServer.cpp} (69%) delete mode 100644 Samples/Udp/05-server-client/CMakeLists.txt delete mode 100644 Samples/Udp/05-server-client/Client.cpp delete mode 100644 Samples/Udp/05-server-client/Launcher.cpp delete mode 100644 Samples/Udp/05-server-client/Server.cpp delete mode 100644 Samples/Udp/06-servers-clients/CMakeLists.txt delete mode 100644 Samples/Udp/06-servers-clients/Client.cpp delete mode 100644 Samples/Udp/06-servers-clients/Launcher.cpp delete mode 100644 Samples/Udp/06-servers-clients/README.svg delete mode 100644 Samples/Udp/06-servers-clients/Server.cpp create mode 100644 Samples/Udp/Launcher-01-client-client.cpp create mode 100644 Samples/Udp/Launcher-02-client-server.cpp create mode 100644 Samples/Udp/UdpClientAsker.cpp create mode 100644 Samples/Udp/UdpClientResponder.cpp create mode 100644 Samples/Udp/UdpServer.cpp diff --git a/Samples/CMakeLists.txt b/Samples/CMakeLists.txt index 40d91555..7b3bf821 100644 --- a/Samples/CMakeLists.txt +++ b/Samples/CMakeLists.txt @@ -2,4 +2,4 @@ add_subdirectory(Utils) add_subdirectory(Tcp) -# add_subdirectory(Udp) +add_subdirectory(Udp) diff --git a/Samples/Tcp/01-server-client/CMakeLists.txt b/Samples/Tcp/01-server-client/CMakeLists.txt deleted file mode 100644 index f13aecdb..00000000 --- a/Samples/Tcp/01-server-client/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -add_executable(Sample01_Client Client.cpp) -target_link_libraries( Sample01_Client Sample-Utils) - -add_executable(Sample01_Server Server.cpp) -target_link_libraries( Sample01_Server Sample-Utils) - -add_executable(Sample01Launcher Launcher.cpp) -target_link_libraries(Sample01Launcher) -add_dependencies(Sample01Launcher Sample01_Server Sample01_Client) diff --git a/Samples/Tcp/01-server-client/Client.cpp b/Samples/Tcp/01-server-client/Client.cpp deleted file mode 100644 index 96d5429b..00000000 --- a/Samples/Tcp/01-server-client/Client.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char **argv){ - - cout << "----------------------- Client -----------------------" << endl; - - std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(20000)); - cout << "Asking connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - - // blocking open - client->open(std::chrono::milliseconds(0)); - if (!client->isOpen()) { - cout << "connection failed" << endl; - return EXIT_FAILURE; - } - - Asker ask(std::move(client)); - ask.askForever(std::chrono::milliseconds(500)); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Tcp/01-server-client/Launcher.cpp b/Samples/Tcp/01-server-client/Launcher.cpp deleted file mode 100644 index 995b7662..00000000 --- a/Samples/Tcp/01-server-client/Launcher.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "../../ProcessLauncher.h" - -int main() { - - Launcher lnc("launcher"); - lnc.addProcess("Sample01_Server"); - lnc.addProcess("Sample01_Client"); - lnc(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Tcp/01-server-client/Server.cpp b/Samples/Tcp/01-server-client/Server.cpp deleted file mode 100644 index c6a6fc77..00000000 --- a/Samples/Tcp/01-server-client/Server.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(){ - - cout << "----------------------- Server -----------------------" << endl; - - // build and initialize a connection from a client on port 20000 - sck::tcp::TcpServer server(20000); - - // blocking open - server.open(std::chrono::milliseconds(0)); - if (!server.isOpen()) { - cout << "server open failed" << endl; - return EXIT_FAILURE; - } - - //accept the client - auto clientHandler = server.acceptClient(); - cout << "client connected" << endl; - - Responder resp(std::move(clientHandler)); - resp.respondForever(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Tcp/02-server-clients/CMakeLists.txt b/Samples/Tcp/02-server-clients/CMakeLists.txt deleted file mode 100644 index 56c93ec1..00000000 --- a/Samples/Tcp/02-server-clients/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -add_executable(Sample02_Client Client.cpp) -target_link_libraries( Sample02_Client Sample-Utils) - -add_executable(Sample02_Server Server.cpp) -target_link_libraries( Sample02_Server Sample-Utils) - -add_executable(Sample02Launcher Launcher.cpp) -target_link_libraries(Sample02Launcher) -add_dependencies(Sample02Launcher Sample02_Server Sample02_Client) diff --git a/Samples/Tcp/02-server-clients/Client.cpp b/Samples/Tcp/02-server-clients/Client.cpp deleted file mode 100644 index 5f241e8a..00000000 --- a/Samples/Tcp/02-server-clients/Client.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char** argv) { - - cout << "----------------------- Client -----------------------" << endl; - - std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(31000)); - cout << "Asking connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - - // blocking open - client->open(std::chrono::milliseconds(0)); - if (!client->isOpen()) { - cout << "connection failed" << endl; - return EXIT_FAILURE; - } - - Asker ask(std::move(client)); - ask.askForever(std::chrono::milliseconds(500)); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Tcp/02-server-clients/Launcher.cpp b/Samples/Tcp/02-server-clients/Launcher.cpp deleted file mode 100644 index 433c736c..00000000 --- a/Samples/Tcp/02-server-clients/Launcher.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "../../ProcessLauncher.h" - -int main() { - - Launcher lnc("launcher"); - lnc.addProcess("Sample02_Server"); - lnc.addProcess("Sample02_Client"); - lnc.addProcess("Sample02_Client"); - lnc(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Tcp/02-server-clients/README.svg b/Samples/Tcp/02-server-clients/README.svg deleted file mode 100644 index 0e9b5458..00000000 --- a/Samples/Tcp/02-server-clients/README.svg +++ /dev/null @@ -1,302 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - Client A - - - - Client B - - - - Server - - - - - - - - diff --git a/Samples/Tcp/02-server-clients/Server.cpp b/Samples/Tcp/02-server-clients/Server.cpp deleted file mode 100644 index 4ffe8f26..00000000 --- a/Samples/Tcp/02-server-clients/Server.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main() { - - cout << "----------------------- Server -----------------------" << endl; - - // build and initialize a connection from a client on port 20000 - sck::tcp::TcpServer server(31000); - - // blocking open - server.open(std::chrono::milliseconds(0)); - if (!server.isOpen()) { - cout << "server open failed" << endl; - return EXIT_FAILURE; - } - - //accept first client - auto clientAHandler = server.acceptClient(); - cout << "client A connected" << endl; - - //accept second client - auto clientBHandler = server.acceptClient(); - cout << "client B connected" << endl; - - Responder respA(std::move(clientAHandler)); - Responder respB(std::move(clientBHandler)); - - while (true) { - respA.respond(); - respB.respond(); - } - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Tcp/03-servers-clients/CMakeLists.txt b/Samples/Tcp/03-servers-clients/CMakeLists.txt deleted file mode 100644 index a4c0522b..00000000 --- a/Samples/Tcp/03-servers-clients/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -add_executable(Sample03_Client Client.cpp) -target_link_libraries( Sample03_Client Sample-Utils) - -add_executable(Sample03_Server Server.cpp) -target_link_libraries( Sample03_Server Sample-Utils) - -add_executable(Sample03Launcher Launcher.cpp) -target_link_libraries(Sample03Launcher) -add_dependencies(Sample03Launcher Sample03_Server Sample03_Client) diff --git a/Samples/Tcp/03-servers-clients/Client.cpp b/Samples/Tcp/03-servers-clients/Client.cpp deleted file mode 100644 index 91b4c99b..00000000 --- a/Samples/Tcp/03-servers-clients/Client.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char **argv){ - - cout << "----------------------- Client -----------------------" << endl; - - if (argc < 2) { - cout << "sleep time not passed" << endl; - return EXIT_FAILURE; - } - int sleepTime = atoi(std::string(argv[1]).c_str()); - - std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(27300)); - cout << "Asking connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - - // blocking open - client->open(std::chrono::milliseconds(0)); - if (!client->isOpen()) { - cout << "connection failed" << endl; - return EXIT_FAILURE; - } - - Asker ask(std::move(client)); - ask.askForever(std::chrono::milliseconds(sleepTime)); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Tcp/03-servers-clients/Launcher.cpp b/Samples/Tcp/03-servers-clients/Launcher.cpp deleted file mode 100644 index a8f97b05..00000000 --- a/Samples/Tcp/03-servers-clients/Launcher.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "../../ProcessLauncher.h" - -int main() { - - Launcher lnc("launcher"); - lnc.addProcess("Sample03_Server"); - lnc.addProcess("Sample03_Client", "500"); - lnc.addProcess("Sample03_Client", "50"); - lnc(); - - return 0; -} \ No newline at end of file diff --git a/Samples/Tcp/03-servers-clients/README.svg b/Samples/Tcp/03-servers-clients/README.svg deleted file mode 100644 index 4fa72f46..00000000 --- a/Samples/Tcp/03-servers-clients/README.svg +++ /dev/null @@ -1,382 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - Client A - - - - Client B - - - - Server - - - - - - - - - thread 1 - thread 2 - 500ms - 50ms - - diff --git a/Samples/Tcp/03-servers-clients/Server.cpp b/Samples/Tcp/03-servers-clients/Server.cpp deleted file mode 100644 index 6822c20d..00000000 --- a/Samples/Tcp/03-servers-clients/Server.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -#include -using namespace std; - -int main(){ - - cout << "----------------------- Server -----------------------" << endl; - - //build two services: one for client A and one for client B - sck::tcp::TcpServer server(27300); - - // blocking open - server.open(std::chrono::milliseconds(0)); - if (!server.isOpen()) { - cout << "server open failed" << endl; - return EXIT_FAILURE; - } - - auto srv = [&server](){ - //accept the client - auto clientHandler = server.acceptClient(); - cout << "client connected" << endl; - - Responder resp(std::move(clientHandler)); - resp.respondForever(); - }; - - //spawn service to client A - std::thread tA(srv); - //spawn service to client B - std::thread tB(srv); - - tA.join(); - tB.join(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Tcp/04-repeater/CMakeLists.txt b/Samples/Tcp/04-repeater/CMakeLists.txt deleted file mode 100644 index fe6f7405..00000000 --- a/Samples/Tcp/04-repeater/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -add_executable(Sample04_Client Client.cpp) -target_link_libraries( Sample04_Client Sample-Utils) - -add_executable(Sample04_Server Server.cpp) -target_link_libraries( Sample04_Server Sample-Utils) - -add_executable(Sample04_Repeater Repeater.cpp) -target_link_libraries( Sample04_Repeater Sample-Utils) - -add_executable(Sample04Launcher Launcher.cpp) -target_link_libraries(Sample04Launcher) -add_dependencies(Sample04Launcher Sample04_Server Sample04_Client Sample04_Repeater) \ No newline at end of file diff --git a/Samples/Tcp/04-repeater/Client.cpp b/Samples/Tcp/04-repeater/Client.cpp deleted file mode 100644 index 894841fd..00000000 --- a/Samples/Tcp/04-repeater/Client.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char** argv) { - - cout << "----------------------- Client -----------------------" << endl; - - std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(4000)); - cout << "Asking connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - - // blocking open - client->open(std::chrono::milliseconds(0)); - if (!client->isOpen()) { - cout << "connection failed" << endl; - return EXIT_FAILURE; - } - - Asker ask(std::move(client)); - ask.askForever(std::chrono::milliseconds(150)); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Tcp/04-repeater/README.svg b/Samples/Tcp/04-repeater/README.svg deleted file mode 100644 index 9c04a6a2..00000000 --- a/Samples/Tcp/04-repeater/README.svg +++ /dev/null @@ -1,366 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - Client - - - - Repeater - - - - Server - - - - port 3000 - - - port 4000 - - 200 ms - - diff --git a/Samples/Tcp/04-repeater/Server.cpp b/Samples/Tcp/04-repeater/Server.cpp deleted file mode 100644 index a618ceb8..00000000 --- a/Samples/Tcp/04-repeater/Server.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main() { - - cout << "----------------------- Server -----------------------" << endl; - - // build and initialize a connection from a client on port 20000 - sck::tcp::TcpServer server(3000); - - // blocking open - server.open(std::chrono::milliseconds(0)); - if (!server.isOpen()) { - cout << "server open failed" << endl; - return EXIT_FAILURE; - } - - //accept the client - auto clientHandler = server.acceptClient(); - cout << "client connected" << endl; - - Responder resp(std::move(clientHandler)); - resp.respondForever(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Tcp/CMakeLists.txt b/Samples/Tcp/CMakeLists.txt index 8e457c23..e9cee7cf 100644 --- a/Samples/Tcp/CMakeLists.txt +++ b/Samples/Tcp/CMakeLists.txt @@ -1,10 +1,10 @@ -add_executable(TcpClient Client.cpp) +add_executable(TcpClient TcpClient.cpp) target_link_libraries(TcpClient Sample-Utils) -add_executable(TcpServer Server.cpp) +add_executable(TcpServer TcpServer.cpp) target_link_libraries(TcpServer Sample-Utils) -add_executable(TcpRepeater Repeater.cpp) +add_executable(TcpRepeater TcpRepeater.cpp) target_link_libraries(TcpRepeater Sample-Utils) ## launchers ## diff --git a/Samples/Tcp/Launcher-03-repeater.cpp b/Samples/Tcp/Launcher-03-repeater.cpp index 17373000..441af862 100644 --- a/Samples/Tcp/Launcher-03-repeater.cpp +++ b/Samples/Tcp/Launcher-03-repeater.cpp @@ -4,7 +4,7 @@ int main() { Launcher lnc("Launcher03"); lnc.addProcess("TcpServer","3000"); - lnc.addProcess("TcpRepeater","3000", "4000"); + lnc.addProcess("TcpRepeater"); lnc.addProcess("TcpClient","4000"); lnc(); diff --git a/Samples/Tcp/Client.cpp b/Samples/Tcp/TcpClient.cpp similarity index 100% rename from Samples/Tcp/Client.cpp rename to Samples/Tcp/TcpClient.cpp diff --git a/Samples/Tcp/Repeater.cpp b/Samples/Tcp/TcpRepeater.cpp similarity index 100% rename from Samples/Tcp/Repeater.cpp rename to Samples/Tcp/TcpRepeater.cpp diff --git a/Samples/Tcp/Server.cpp b/Samples/Tcp/TcpServer.cpp similarity index 69% rename from Samples/Tcp/Server.cpp rename to Samples/Tcp/TcpServer.cpp index a9d06a9c..9180aa84 100644 --- a/Samples/Tcp/Server.cpp +++ b/Samples/Tcp/TcpServer.cpp @@ -12,19 +12,6 @@ #include using namespace std; -class AcceptedClient { -public: - AcceptedClient(std::unique_ptr acceptedClient) : acceptedClient(std::move(acceptedClient)) {}; - - void run() { - Responder resp(std::move(this->acceptedClient)); - resp.respondForever(); - }; - -private: - std::unique_ptr acceptedClient; -}; - int main(int argc, char** argv){ cout << "----------------------- Server -----------------------" << endl; @@ -56,16 +43,19 @@ int main(int argc, char** argv){ } cout << "connection opened" << endl; - std::list clients; - std::list clientThreads; + std::list responders; + std::list respThreads; for (std::size_t c = 0; c < clientNumbers; ++c) { //accept the client - clients.emplace_back(server.acceptClient()); + responders.emplace_back(server.acceptClient()); cout << "new client connected" << endl; - clientThreads.emplace_back(&AcceptedClient::run, &clients.back()); + respThreads.emplace_back([&responders]() { + Responder* respRef = &responders.back(); + respRef->respondForever(); + }); } - for (auto it = clientThreads.begin(); it != clientThreads.end(); ++it) { + for (auto it = respThreads.begin(); it != respThreads.end(); ++it) { it->join(); } diff --git a/Samples/Udp/05-server-client/CMakeLists.txt b/Samples/Udp/05-server-client/CMakeLists.txt deleted file mode 100644 index 464830c4..00000000 --- a/Samples/Udp/05-server-client/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -add_executable(Sample05_Client Client.cpp) -target_link_libraries( Sample05_Client Sample-Utils) - -add_executable(Sample05_Server Server.cpp) -target_link_libraries( Sample05_Server Sample-Utils) - -add_executable(Sample05Launcher Launcher.cpp) -target_link_libraries(Sample05Launcher) -add_dependencies(Sample05Launcher Sample05_Server Sample05_Client) diff --git a/Samples/Udp/05-server-client/Client.cpp b/Samples/Udp/05-server-client/Client.cpp deleted file mode 100644 index 6597ec5c..00000000 --- a/Samples/Udp/05-server-client/Client.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char **argv){ - - cout << "----------------------- Client -----------------------" << endl; - - std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(20000)); - cout << "Sending handshake message to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - { - std::string handshakeMessage = "hello"; - client->send({handshakeMessage.data(), handshakeMessage.size()}); - } - - // blocking open - client->open(std::chrono::milliseconds(0)); - - Asker ask(std::move(client)); - ask.askForever(std::chrono::milliseconds(500)); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Udp/05-server-client/Launcher.cpp b/Samples/Udp/05-server-client/Launcher.cpp deleted file mode 100644 index 334e34f7..00000000 --- a/Samples/Udp/05-server-client/Launcher.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "../../ProcessLauncher.h" - -int main() { - - Launcher lnc("launcher"); - lnc.addProcess("Sample05_Server"); - lnc.addProcess("Sample05_Client"); - lnc(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Udp/05-server-client/Server.cpp b/Samples/Udp/05-server-client/Server.cpp deleted file mode 100644 index 2f51f049..00000000 --- a/Samples/Udp/05-server-client/Server.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(){ - - cout << "----------------------- Server -----------------------" << endl; - - // build and initialize a connection from a client on port 20000 - std::unique_ptr server = std::make_unique(2000); - - // blocking open: wait for handshake message from client - server->open(std::chrono::milliseconds(0)); - if (!server->isOpen()) { - cout << "server open failed" << endl; - return EXIT_FAILURE; - } - - Responder resp(std::move(server)); - resp.respondForever(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Udp/06-servers-clients/CMakeLists.txt b/Samples/Udp/06-servers-clients/CMakeLists.txt deleted file mode 100644 index 6f9f1b60..00000000 --- a/Samples/Udp/06-servers-clients/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -add_executable(Sample06_Client Client.cpp) -target_link_libraries( Sample06_Client Sample-Utils) - -add_executable(Sample06_Server Server.cpp) -target_link_libraries( Sample06_Server Sample-Utils) - -add_executable(Sample06Launcher Launcher.cpp) -target_link_libraries(Sample06Launcher) -add_dependencies(Sample06Launcher Sample06_Server Sample06_Client) diff --git a/Samples/Udp/06-servers-clients/Client.cpp b/Samples/Udp/06-servers-clients/Client.cpp deleted file mode 100644 index 875d2be1..00000000 --- a/Samples/Udp/06-servers-clients/Client.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char** argv) { - - cout << "----------------------- Client -----------------------" << endl; - - if (argc < 2) { - cout << "sleep time not passed" << endl; - return EXIT_FAILURE; - } - int sleepTime = atoi(std::string(argv[1]).c_str()); - - if (argc < 3) { - cout << "port to bind not passed" << endl; - return EXIT_FAILURE; - } - std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(31000), std::atoi(argv[2])); - - // blocking open - client->open(std::chrono::milliseconds(0)); - if (!client->isOpen()) { - cout << "connection failed" << endl; - return EXIT_FAILURE; - } - - Asker ask(std::move(client)); - ask.askForever(std::chrono::milliseconds(sleepTime)); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Udp/06-servers-clients/Launcher.cpp b/Samples/Udp/06-servers-clients/Launcher.cpp deleted file mode 100644 index 328ac109..00000000 --- a/Samples/Udp/06-servers-clients/Launcher.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "../../ProcessLauncher.h" - -int main() { - - Launcher lnc("launcher"); - lnc.addProcess("Sample06_Server", "3500", "4500"); - lnc.addProcess("Sample06_Client", "500", "3500"); - lnc.addProcess("Sample06_Client", "50", "4500"); - lnc(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Udp/06-servers-clients/README.svg b/Samples/Udp/06-servers-clients/README.svg deleted file mode 100644 index e3ccd4e1..00000000 --- a/Samples/Udp/06-servers-clients/README.svg +++ /dev/null @@ -1,404 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - Client A - - - - Client B - - - - Server - - - - - - - - - thread 1 - thread 2 - 500ms - 50ms - port 3500 - port 4500 - - diff --git a/Samples/Udp/06-servers-clients/Server.cpp b/Samples/Udp/06-servers-clients/Server.cpp deleted file mode 100644 index 6f0221b2..00000000 --- a/Samples/Udp/06-servers-clients/Server.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -#include -using namespace std; - -int main(int argc, char** argv){ - - cout << "----------------------- Server -----------------------" << endl; - - //build two services: one for client A and one for client B - std::uint16_t portA, portB; - if (argc < 2) { - cout << "port A not passed" << endl; - return EXIT_FAILURE; - } - portA = std::atoi(argv[1]); - if (argc < 3) { - cout << "port B not passed" << endl; - return EXIT_FAILURE; - } - portB = std::atoi(argv[2]); - - auto srv = [](const std::uint16_t& port){ - //accept the client - std::unique_ptr socket = std::make_unique(*sck::Ip::createLocalHost(port), 31000); - socket->open(std::chrono::milliseconds(0)); - - Responder resp(std::move(socket)); - resp.respondForever(); - }; - - //spawn service to client A - std::thread tA([&srv, &portA](){ srv(portA); }); - //spawn service to client B - std::thread tB([&srv, &portB](){ srv(portB); }); - - tA.join(); - tB.join(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Udp/CMakeLists.txt b/Samples/Udp/CMakeLists.txt index 6309facc..139e3804 100644 --- a/Samples/Udp/CMakeLists.txt +++ b/Samples/Udp/CMakeLists.txt @@ -1,4 +1,16 @@ -add_subdirectory(05-server-client) +add_executable(UdpClientAsker UdpClientAsker.cpp) +target_link_libraries(UdpClientAsker Sample-Utils) -add_subdirectory(06-servers-clients) +add_executable(UdpClientResponder UdpClientResponder.cpp) +target_link_libraries(UdpClientResponder Sample-Utils) +add_executable(UdpServer UdpServer.cpp) +target_link_libraries(UdpServer Sample-Utils) + +## launchers ## + +add_executable(Udp01Launcher Launcher-01-client-client.cpp) +add_dependencies(Udp01Launcher UdpClientAsker UdpClientResponder) + +add_executable(Udp02Launcher Launcher-02-client-server.cpp) +add_dependencies(Udp02Launcher UdpClientAsker UdpServer) diff --git a/Samples/Udp/Launcher-01-client-client.cpp b/Samples/Udp/Launcher-01-client-client.cpp new file mode 100644 index 00000000..a400485f --- /dev/null +++ b/Samples/Udp/Launcher-01-client-client.cpp @@ -0,0 +1,11 @@ +#include "../ProcessLauncher.h" + +int main() { + + Launcher lnc("Launcher01"); + lnc.addProcess("UdpClientAsker","15000","250","10000"); + lnc.addProcess("UdpClientResponder","10000", "15000"); + lnc(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/Launcher-02-client-server.cpp b/Samples/Udp/Launcher-02-client-server.cpp new file mode 100644 index 00000000..9ac8540f --- /dev/null +++ b/Samples/Udp/Launcher-02-client-server.cpp @@ -0,0 +1,11 @@ +#include "../ProcessLauncher.h" + +int main() { + + Launcher lnc("Launcher02"); + lnc.addProcess("UdpClientAsker", "35000", "250", "30000"); + lnc.addProcess("UpdServer", "35000"); + lnc(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/UdpClientAsker.cpp b/Samples/Udp/UdpClientAsker.cpp new file mode 100644 index 00000000..df7a3d94 --- /dev/null +++ b/Samples/Udp/UdpClientAsker.cpp @@ -0,0 +1,58 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +using namespace std; + +int main(int argc, char **argv){ + cout << "----------------------- Client Asker -----------------------" << endl; + + if (argc == 1) { + cout << "correct syntax is: 'server port', 'rate', 'port to reserve', 'server host'" << endl; + return EXIT_FAILURE; + } + + std::uint16_t serverPort = std::atoi(argv[1]); + + std::chrono::milliseconds rate(200); + if (argc > 2) { + rate = std::chrono::milliseconds(std::atoi(argv[2])); + } + cout << "rate assumed " << rate.count() << " [ms]" << endl; + + std::uint16_t port = 0; + if (argc > 3) { + port = std::atoi(argv[3]); + } + cout << "port reserved by this udp " << port << endl; + + sck::IpPtr serverAddress; + if (argc > 4) { + serverAddress = sck::Ip::create(std::string(argv[4]), serverPort); + } + else { + serverAddress = sck::Ip::createLocalHost(serverPort); + } + if (nullptr == serverAddress) { + cout << "invalid server address" << endl; + return EXIT_FAILURE; + } + + std::unique_ptr client = std::make_unique(*serverAddress, port); + cout << "Expecting connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; + + // blocking open + client->open(std::chrono::milliseconds(0)); + cout << "connection opened" << endl; + + Asker ask(std::move(client)); + ask.askForever(rate); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/UdpClientResponder.cpp b/Samples/Udp/UdpClientResponder.cpp new file mode 100644 index 00000000..216a4f9a --- /dev/null +++ b/Samples/Udp/UdpClientResponder.cpp @@ -0,0 +1,52 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +using namespace std; + +int main(int argc, char** argv) { + cout << "----------------------- Client Responder -----------------------" << endl; + + if (argc == 1) { + cout << "correct syntax is: 'server port', 'port to reserve', 'server host'" << endl; + return EXIT_FAILURE; + } + + std::uint16_t serverPort = std::atoi(argv[1]); + + std::uint16_t port = 0; + if (argc > 2) { + port = std::atoi(argv[2]); + } + cout << "port reserved by this udp " << port << endl; + + sck::IpPtr serverAddress; + if (argc > 3) { + serverAddress = sck::Ip::create(std::string(argv[3]), serverPort); + } + else { + serverAddress = sck::Ip::createLocalHost(serverPort); + } + if (nullptr == serverAddress) { + cout << "invalid server address" << endl; + return EXIT_FAILURE; + } + + std::unique_ptr client = std::make_unique(*serverAddress, port); + cout << "Expecting connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; + + // blocking open + client->open(std::chrono::milliseconds(0)); + cout << "connection opened" << endl; + + Responder responder(std::move(client)); + responder.respondForever(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/UdpServer.cpp b/Samples/Udp/UdpServer.cpp new file mode 100644 index 00000000..e721e8ca --- /dev/null +++ b/Samples/Udp/UdpServer.cpp @@ -0,0 +1,34 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +using namespace std; + +int main(int argc, char** argv) { + cout << "----------------------- Server -----------------------" << endl; + + if (argc == 1) { + cout << "correct syntax is: 'port to reserve'" << endl; + return EXIT_FAILURE; + } + + std::uint16_t serverPort = std::atoi(argv[1]); + + std::unique_ptr server = std::make_unique(serverPort); + cout << "waiting for the client" << endl; + + // blocking open + server->open(std::chrono::milliseconds(0)); + cout << "connection opened" << endl; + + Responder responder(std::move(server)); + responder.respondForever(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Utils/src/Responder.cpp b/Samples/Utils/src/Responder.cpp index 4ee2c5bb..cbb59b64 100644 --- a/Samples/Utils/src/Responder.cpp +++ b/Samples/Utils/src/Responder.cpp @@ -6,7 +6,6 @@ **/ #include -#include #include Responder::Responder(std::unique_ptr socket) @@ -17,10 +16,8 @@ void Responder::respond() { std::pair temp = { &this->recvBuffer[0], 1000 }; std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); std::string recStr(temp.first, recvBytes); - std::cout << "got: " << recStr; const std::string surname = Names::getSurname(recStr); - std::cout << " sending: " << surname << std::endl; this->socket->send({ surname.data(), surname.size() }); } diff --git a/TODO b/TODO index c62ec08c..1dde6670 100644 --- a/TODO +++ b/TODO @@ -14,3 +14,4 @@ rename protocol in family everywhere spiegare bene come si lanciano samples fix readme svg con giuste porte aggiungere in samples qlcs che consuma async responder +a volte esempio repeater non funziona From d25ce01f30bbd8eecf642437f746dc5b72e70a9a Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 10 Feb 2021 19:35:55 +0100 Subject: [PATCH 026/228] samples working but not robustly --- CrossSocket/src/core/Core.cpp | 3 ++- Samples/Udp/Launcher-01-client-client.cpp | 2 +- Samples/Udp/Launcher-02-client-server.cpp | 2 +- TODO | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CrossSocket/src/core/Core.cpp b/CrossSocket/src/core/Core.cpp index d894a888..459f5d70 100644 --- a/CrossSocket/src/core/Core.cpp +++ b/CrossSocket/src/core/Core.cpp @@ -212,7 +212,8 @@ namespace sck { if (this->hndl == SCK_INVALID_SOCKET) { this->close(); throwWithCode("DataGram socket could not be created"); - } + } + break; default: throw Error("unknown protocol type"); } diff --git a/Samples/Udp/Launcher-01-client-client.cpp b/Samples/Udp/Launcher-01-client-client.cpp index a400485f..11503e9a 100644 --- a/Samples/Udp/Launcher-01-client-client.cpp +++ b/Samples/Udp/Launcher-01-client-client.cpp @@ -3,8 +3,8 @@ int main() { Launcher lnc("Launcher01"); + lnc.addProcess("UdpClientResponder", "10000", "15000"); lnc.addProcess("UdpClientAsker","15000","250","10000"); - lnc.addProcess("UdpClientResponder","10000", "15000"); lnc(); return EXIT_SUCCESS; diff --git a/Samples/Udp/Launcher-02-client-server.cpp b/Samples/Udp/Launcher-02-client-server.cpp index 9ac8540f..cb238aab 100644 --- a/Samples/Udp/Launcher-02-client-server.cpp +++ b/Samples/Udp/Launcher-02-client-server.cpp @@ -3,8 +3,8 @@ int main() { Launcher lnc("Launcher02"); - lnc.addProcess("UdpClientAsker", "35000", "250", "30000"); lnc.addProcess("UpdServer", "35000"); + lnc.addProcess("UdpClientAsker", "35000", "250", "30000"); lnc(); return EXIT_SUCCESS; diff --git a/TODO b/TODO index 1dde6670..bb3dd9da 100644 --- a/TODO +++ b/TODO @@ -15,3 +15,4 @@ spiegare bene come si lanciano samples fix readme svg con giuste porte aggiungere in samples qlcs che consuma async responder a volte esempio repeater non funziona +dire in readme che samples potrebbero fallire per via di firewall From b855bbd2bb82fce8d62b337ddf7db13d042a21dd Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 10 Feb 2021 20:20:58 +0100 Subject: [PATCH 027/228] minor changes --- CrossSocket/include/async/AsyncDecorator.h | 2 +- CrossSocket/include/async/AsyncTcpServer.h | 2 +- CrossSocket/include/async/Service.h | 2 +- CrossSocket/include/core/Client.h | 2 +- CrossSocket/include/core/MessangerConcrete.h | 2 +- CrossSocket/include/core/SocketConcrete.h | 2 +- CrossSocket/include/core/SocketDecorator.h | 2 +- CrossSocket/include/tcp/TcpServer.h | 4 ++-- CrossSocket/include/udp/UdpClient.h | 2 +- CrossSocket/include/udp/UdpServer.h | 2 +- CrossSocket/src/core/Client.cpp | 5 +++-- CrossSocket/src/core/Core.h | 4 ++-- CrossSocket/src/core/SocketConcrete.cpp | 8 ++++---- CrossSocket/src/tcp/TcpServer.cpp | 6 +++--- TODO | 12 ------------ 15 files changed, 23 insertions(+), 34 deletions(-) diff --git a/CrossSocket/include/async/AsyncDecorator.h b/CrossSocket/include/async/AsyncDecorator.h index 27f66037..273c0c70 100644 --- a/CrossSocket/include/async/AsyncDecorator.h +++ b/CrossSocket/include/async/AsyncDecorator.h @@ -42,7 +42,7 @@ namespace sck::async { }; protected: - AsyncDecorator(std::unique_ptr client) + explicit AsyncDecorator(std::unique_ptr client) : SocketDecorator(std::move(client)) { }; diff --git a/CrossSocket/include/async/AsyncTcpServer.h b/CrossSocket/include/async/AsyncTcpServer.h index 3488b92b..e3b54371 100644 --- a/CrossSocket/include/async/AsyncTcpServer.h +++ b/CrossSocket/include/async/AsyncTcpServer.h @@ -14,7 +14,7 @@ namespace sck::async { class AsyncTcpServer : public AsyncDecorator { public: - AsyncTcpServer(std::unique_ptr server); + explicit AsyncTcpServer(std::unique_ptr server); private: class AcceptanceService; diff --git a/CrossSocket/include/async/Service.h b/CrossSocket/include/async/Service.h index 136655d1..5a237dcb 100644 --- a/CrossSocket/include/async/Service.h +++ b/CrossSocket/include/async/Service.h @@ -21,7 +21,7 @@ namespace sck::async { Service& operator=(const Service&) = delete; // service is started when building - Service(const std::function& iterativeAction); + explicit Service(const std::function& iterativeAction); // service is stop when destroying ~Service(); diff --git a/CrossSocket/include/core/Client.h b/CrossSocket/include/core/Client.h index f0b32c72..94d5211c 100644 --- a/CrossSocket/include/core/Client.h +++ b/CrossSocket/include/core/Client.h @@ -28,7 +28,7 @@ namespace sck { /** * @param[in] the address of the server to hit */ - Client(const sck::Ip& remoteAddress); + explicit Client(const sck::Ip& remoteAddress); /** * @param[in] the remote address already connected * @param[in] an already created handler to steal diff --git a/CrossSocket/include/core/MessangerConcrete.h b/CrossSocket/include/core/MessangerConcrete.h index 123bdfed..1d52c2cf 100644 --- a/CrossSocket/include/core/MessangerConcrete.h +++ b/CrossSocket/include/core/MessangerConcrete.h @@ -25,7 +25,7 @@ namespace sck { std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; protected: - MessangerConcrete(std::shared_ptr messageChannel); + explicit MessangerConcrete(std::shared_ptr messageChannel); private: std::chrono::milliseconds actualTimeOut = std::chrono::milliseconds(0); diff --git a/CrossSocket/include/core/SocketConcrete.h b/CrossSocket/include/core/SocketConcrete.h index ee54b524..26a58983 100644 --- a/CrossSocket/include/core/SocketConcrete.h +++ b/CrossSocket/include/core/SocketConcrete.h @@ -33,7 +33,7 @@ namespace sck { bool isOpen() const; protected: - SocketConcrete(std::shared_ptr channel); + explicit SocketConcrete(std::shared_ptr channel); virtual void openSpecific() = 0; diff --git a/CrossSocket/include/core/SocketDecorator.h b/CrossSocket/include/core/SocketDecorator.h index d582e18e..4b1e3234 100644 --- a/CrossSocket/include/core/SocketDecorator.h +++ b/CrossSocket/include/core/SocketDecorator.h @@ -24,7 +24,7 @@ namespace sck { inline bool isOpen() const override { return this->wrapped->isOpen(); }; protected: - SocketDecorator(std::unique_ptr wrapped); + explicit SocketDecorator(std::unique_ptr wrapped); std::unique_ptr wrapped; }; diff --git a/CrossSocket/include/tcp/TcpServer.h b/CrossSocket/include/tcp/TcpServer.h index a5d3652a..19a053d1 100644 --- a/CrossSocket/include/tcp/TcpServer.h +++ b/CrossSocket/include/tcp/TcpServer.h @@ -34,11 +34,11 @@ namespace sck::tcp { private: void openSpecific() override; - inline sck::Family getFamily() const final { return this->protocol; }; + inline sck::Family getFamily() const final { return this->family; }; inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; std::uint16_t port; - sck::Family protocol; + sck::Family family; }; } diff --git a/CrossSocket/include/udp/UdpClient.h b/CrossSocket/include/udp/UdpClient.h index da951dca..778cd42a 100644 --- a/CrossSocket/include/udp/UdpClient.h +++ b/CrossSocket/include/udp/UdpClient.h @@ -26,7 +26,7 @@ namespace sck::udp { @param[in] Address of the remote host to hit @param[in] port to reserve (passing 0 a random port is reserved) */ - UdpClient(const sck::Ip& remoteAddress, const std::uint16_t& localPort = 0); + explicit UdpClient(const sck::Ip& remoteAddress, const std::uint16_t& localPort = 0); protected: std::uint16_t port; diff --git a/CrossSocket/include/udp/UdpServer.h b/CrossSocket/include/udp/UdpServer.h index 0498ca5e..4132d5ac 100644 --- a/CrossSocket/include/udp/UdpServer.h +++ b/CrossSocket/include/udp/UdpServer.h @@ -24,7 +24,7 @@ namespace sck::udp { * @param[in] the port to reserve * @param[in] the expected protocol family of the client to accept */ - UdpServer(const std::uint16_t& localPort, const sck::Family& protocol = sck::Family::IP_V4); + explicit UdpServer(const std::uint16_t& localPort, const sck::Family& protocol = sck::Family::IP_V4); private: void openSpecific() final; diff --git a/CrossSocket/src/core/Client.cpp b/CrossSocket/src/core/Client.cpp index 2cc7f572..b78db472 100644 --- a/CrossSocket/src/core/Client.cpp +++ b/CrossSocket/src/core/Client.cpp @@ -7,6 +7,7 @@ #include #include "Core.h" +#include namespace sck { Client::Client(const sck::Ip& remoteAddress) @@ -26,7 +27,7 @@ namespace sck { //v4 family auto addr = convertIpv4(this->remoteAddress); if (!addr) { - throw std::runtime_error(this->remoteAddress.getHost() + ":" + std::to_string(this->remoteAddress.getPort()) + " is an invalid server address"); + throw Error(this->remoteAddress.getHost(), ":", std::to_string(this->remoteAddress.getPort()), " is an invalid server address"); } if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { throwWithCode("Connection can't be established"); @@ -36,7 +37,7 @@ namespace sck { //v6 family auto addr = convertIpv6(this->remoteAddress); if (!addr) { - throw std::runtime_error(this->remoteAddress.getHost() + ":" + std::to_string(this->remoteAddress.getPort()) + " is an invalid server address"); + throw Error(this->remoteAddress.getHost(), ":", std::to_string(this->remoteAddress.getPort()), " is an invalid server address"); } if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { throwWithCode("Connection can't be established"); diff --git a/CrossSocket/src/core/Core.h b/CrossSocket/src/core/Core.h index 85d40c81..e661566d 100644 --- a/CrossSocket/src/core/Core.h +++ b/CrossSocket/src/core/Core.h @@ -106,9 +106,9 @@ namespace sck { * @brief the passed handler should be already created externally * by the socket api */ - Handler(const SocketHandler& hndl); + explicit Handler(const SocketHandler& hndl); - virtual ~Handler(); + ~Handler(); /** * @brief internally creates a new socket diff --git a/CrossSocket/src/core/SocketConcrete.cpp b/CrossSocket/src/core/SocketConcrete.cpp index f36e50d9..d67f5a91 100644 --- a/CrossSocket/src/core/SocketConcrete.cpp +++ b/CrossSocket/src/core/SocketConcrete.cpp @@ -31,6 +31,10 @@ namespace sck { } } + bool SocketConcrete::isOpen() const { + return this->channel->isOpen(); + }; + void SocketConcrete::open(const std::chrono::milliseconds& timeout) { if (this->isOpen()) { return; @@ -66,10 +70,6 @@ namespace sck { } } - bool SocketConcrete::isOpen() const { - return this->channel->isOpen(); - } - void SocketConcrete::close() { if (!this->isOpen()) { return; diff --git a/CrossSocket/src/tcp/TcpServer.cpp b/CrossSocket/src/tcp/TcpServer.cpp index f95d39da..baeedf93 100644 --- a/CrossSocket/src/tcp/TcpServer.cpp +++ b/CrossSocket/src/tcp/TcpServer.cpp @@ -23,14 +23,14 @@ namespace sck::tcp { private: void openSpecific() final { - throw std::runtime_error("ClientHandler from TcpServer are not re-openable!"); + throw Error("connection returned from TcpServer::acceptClient are not re-openable"); }; }; TcpServer::TcpServer(const std::uint16_t& port, const Family& family) : SocketConcrete(std::make_shared()) , port(port) - , protocol(family) { + , family(family) { } std::unique_ptr TcpServer::acceptClient() { @@ -53,7 +53,7 @@ namespace sck::tcp { IpPtr remoteAddress = convert(acceptedClientAddress); if (nullptr == remoteAddress) { - throw std::runtime_error("accepted client remote address is not resolvable"); + throw Error("accepted client remote address is not resolvable"); } return std::make_unique(*remoteAddress, acceptedClientHandler); diff --git a/TODO b/TODO index bb3dd9da..71abb6fd 100644 --- a/TODO +++ b/TODO @@ -1,16 +1,4 @@ -throw sempre di Error e non runtime generica -make attribute const when possible -inline getter when possible -togliere cmake script inutili -rimettere typed connections -virtual destroyer -delete copy c'tor in interfacce in cima e gerarchia -remove default d'tor -provare in Windows -remove Socket Server -add explicit a c?tor single input indicare quando throw in doc class -rename protocol in family everywhere spiegare bene come si lanciano samples fix readme svg con giuste porte aggiungere in samples qlcs che consuma async responder From d7e003106297ddb365ca5f2a11199fe00ddec077 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 10 Feb 2021 21:06:27 +0100 Subject: [PATCH 028/228] readme of samples added --- Samples/Tcp/README.md | 28 ++++++++++++++++++++++++++++ Samples/Udp/README.md | 21 +++++++++++++++++++++ TODO | 3 --- 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 Samples/Tcp/README.md create mode 100644 Samples/Udp/README.md diff --git a/Samples/Tcp/README.md b/Samples/Tcp/README.md new file mode 100644 index 00000000..d8f52733 --- /dev/null +++ b/Samples/Tcp/README.md @@ -0,0 +1,28 @@ +This folder contains some samples using tcp connections. +In order to execute each sample more than 1 processes needs to be run. +You don't need to manually do that, since some appropriate launcher scripts +can be run to do all the work. To create the launchers you need to compile and run: +**Tcp01Launcher**, **Tcp02Launcher** or **Tcp03Launcher** +according to the sample you are interested in. +After running one of the above application a *.bat* or *.sh* script (according to +which is your system) is created: you can execute that script from the command line +to run the corresponding example + +**Samples description** + + *Sample01, run: + + * a Tcp server ready to accept 1 client + * a Tcp client that connects to the server and exchange messages + + *Sample02, run: + + * a Tcp server ready to accept 2 client + * a first Tcp client that connects to the server and exchange messages with an high frequency + * a second Tcp client that connects to the server and exchange messages with a low frequency + + *Sample03, run: + + * a Tcp server ready to accept 1 client + * a Tcp client that connects to an intermediate repeater + * a repeater creating 2 connections in order to receive the request of the client and forward them to the server, sending back the response diff --git a/Samples/Udp/README.md b/Samples/Udp/README.md new file mode 100644 index 00000000..62e0409a --- /dev/null +++ b/Samples/Udp/README.md @@ -0,0 +1,21 @@ +This folder contains some samples using udp connections. +In order to execute each sample more than 1 processes needs to be run. +You don't need to manually do that, since some appropriate launcher scripts +can be run to do all the work. To create the launchers you need to compile and run: +**Udp01Launcher** or **Udp02Launcher** +according to the sample you are interested in. +After running one of the above application a *.bat* or *.sh* script (according to +which is your system) is created: you can execute that script from the command line +to run the corresponding example + +**Samples description** + + *Sample01, run: + + * a first UdpClient reserve a specific port and it is ready to respond to another UdpClient + * a second UdpClient exchanging messages with the first + + *Sample02, run: + + * a UdpServer ready to receive an initial message from a client to detect it + * a UdpClient that connects to the server by sending one message and exchange messages \ No newline at end of file diff --git a/TODO b/TODO index 71abb6fd..da06fdfe 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,3 @@ indicare quando throw in doc class -spiegare bene come si lanciano samples -fix readme svg con giuste porte aggiungere in samples qlcs che consuma async responder -a volte esempio repeater non funziona dire in readme che samples potrebbero fallire per via di firewall From 80fa72a78f22598d43d47f5752a67a534cf48216 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 10 Feb 2021 21:14:02 +0100 Subject: [PATCH 029/228] handshake in udp server sample added --- Samples/Udp/Launcher-02-client-server.cpp | 2 +- Samples/Udp/UdpClientAsker.cpp | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Samples/Udp/Launcher-02-client-server.cpp b/Samples/Udp/Launcher-02-client-server.cpp index cb238aab..8e1d4c8e 100644 --- a/Samples/Udp/Launcher-02-client-server.cpp +++ b/Samples/Udp/Launcher-02-client-server.cpp @@ -4,7 +4,7 @@ int main() { Launcher lnc("Launcher02"); lnc.addProcess("UpdServer", "35000"); - lnc.addProcess("UdpClientAsker", "35000", "250", "30000"); + lnc.addProcess("UdpClientAsker", "35000", "250", "30000", "1"); lnc(); return EXIT_SUCCESS; diff --git a/Samples/Udp/UdpClientAsker.cpp b/Samples/Udp/UdpClientAsker.cpp index df7a3d94..df27694f 100644 --- a/Samples/Udp/UdpClientAsker.cpp +++ b/Samples/Udp/UdpClientAsker.cpp @@ -14,7 +14,7 @@ int main(int argc, char **argv){ cout << "----------------------- Client Asker -----------------------" << endl; if (argc == 1) { - cout << "correct syntax is: 'server port', 'rate', 'port to reserve', 'server host'" << endl; + cout << "correct syntax is: 'server port', 'rate', 'port to reserve', 'send initial handshake flag 0/1', 'server host'" << endl; return EXIT_FAILURE; } @@ -32,9 +32,14 @@ int main(int argc, char **argv){ } cout << "port reserved by this udp " << port << endl; - sck::IpPtr serverAddress; + bool handShake = false; if (argc > 4) { - serverAddress = sck::Ip::create(std::string(argv[4]), serverPort); + handShake = static_cast(std::atoi(argv[4])); + } + + sck::IpPtr serverAddress; + if (argc > 5) { + serverAddress = sck::Ip::create(std::string(argv[5]), serverPort); } else { serverAddress = sck::Ip::createLocalHost(serverPort); @@ -51,6 +56,12 @@ int main(int argc, char **argv){ client->open(std::chrono::milliseconds(0)); cout << "connection opened" << endl; + if (handShake) { + std::string hello = "hello"; + client->send({hello.data(), hello.size()}); + cout << "handshake sent" << endl; + } + Asker ask(std::move(client)); ask.askForever(rate); From aba58cc0502ac68f73cb72ea5ec7a182ee601a4d Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 10 Feb 2021 23:12:39 +0100 Subject: [PATCH 030/228] minor changes --- README.md | 9 +++------ Samples/Tcp/TcpServer.cpp | 1 + TODO | 1 - 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a2e88a6d..6be7a44b 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,12 @@ completely platform independent way. **Content** * The libarary is contained in the ./CrossSocket folder - * Samples showing the library usage are contained in the ./Samples folder - * Refer also to the README.svg files contained in the sample folders (use your favourite browser to open it) + * Samples showing the library usage are contained in the ./Samples folder. ATTENTION!!! Samples execution might be blocked by your firewall **Build** -Use [CMake](https://cmake.org) to configure and compile the library and the examples. +Use [CMake](https://cmake.org) to configure and compile the library and the samples. **Run the examples** -You can run the Launchers to automatically launch in the right sequence all the processes pertaining to an example. - -**IMPORTANT!!** If you are using **Visual Studio** do not launch the Launcher applications from inside VS, but go the folder where the exe are created and launch them from there +Check the *README.md* inside the sample folders diff --git a/Samples/Tcp/TcpServer.cpp b/Samples/Tcp/TcpServer.cpp index 9180aa84..4e7ecabf 100644 --- a/Samples/Tcp/TcpServer.cpp +++ b/Samples/Tcp/TcpServer.cpp @@ -6,6 +6,7 @@ **/ #include +#include #include #include #include diff --git a/TODO b/TODO index da06fdfe..296f4b8a 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,2 @@ indicare quando throw in doc class aggiungere in samples qlcs che consuma async responder -dire in readme che samples potrebbero fallire per via di firewall From ba596b42dc13c8cb3f1423fea2ea22de8aaca155 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 10 Feb 2021 23:44:44 +0100 Subject: [PATCH 031/228] sleep in process launcher added --- Samples/ProcessLauncher.h | 52 ++++++++++++++++------ Samples/Tcp/Launcher-01-server-client.cpp | 4 +- Samples/Tcp/Launcher-02-server-clients.cpp | 4 +- Samples/Tcp/Launcher-03-repeater.cpp | 4 +- Samples/Udp/Launcher-01-client-client.cpp | 2 +- Samples/Udp/Launcher-02-client-server.cpp | 4 +- 6 files changed, 47 insertions(+), 23 deletions(-) diff --git a/Samples/ProcessLauncher.h b/Samples/ProcessLauncher.h index d8268b31..4667665e 100644 --- a/Samples/ProcessLauncher.h +++ b/Samples/ProcessLauncher.h @@ -33,7 +33,20 @@ class Launcher { std::list parsed; this->parseArgs(parsed, args ...); - this->commands.emplace_back(std::make_pair(procName, parsed)); + this->commands.push_back({0, procName, parsed}); + }; + + /** + * @brief Add 1 process to launch, precedeed by a tunable sleep, with possibly some additional command arguments. + * @param[in] the name of the process to launch and the sleep in seconds + * @param[in] the list of arguments to pass when launching the process + */ + template + void addProcessSleep(const std::pair& procNameSleepTime, Args ... args) { + std::list parsed; + this->parseArgs(parsed, args ...); + + this->commands.push_back({ procNameSleepTime.second, procNameSleepTime.first, parsed }); }; /** @@ -58,8 +71,14 @@ class Launcher { return; }; + struct Info { + unsigned int msSleep; + std::string processName; + std::list processArguments; + }; + std::string nameFile; - std::list>> commands; + std::list commands; }; #include @@ -73,34 +92,39 @@ void Launcher::operator()() const { #endif std::ofstream f(name); + auto addSleep = [&f](const unsigned int& sleep) { + if (0 == sleep) return; +#ifdef _WIN32 + f << std::endl << "timeout /t " << sleep; +#else + f << std::endl << "sleep " << sleep; +#endif + }; + auto it = this->commands.begin(); auto itEnd = this->commands.end(); --itEnd; for (it; it != itEnd; ++it) { + addSleep(it->msSleep); #ifdef _WIN32 - f << std::endl << "start \"\" \"" << it->first << "\""; - for(auto a : it->second) f << " \"" << a << "\""; + f << std::endl << "start \"\" \"" << it->processName << "\""; + for(auto a : it->processArguments) f << " \"" << a << "\""; #elif __linux__ f << std::endl << "gnome-terminal -x sh -c \"./" << it->first; - for(auto a : it->second) f << " " << a; + for(auto a : it->processArguments) f << " " << a; f << "; bash\""; #endif } + addSleep(it->msSleep); #ifdef _WIN32 - f << std::endl << "\"" << this->commands.back().first << "\""; - for(auto a : this->commands.back().second) f << " \"" << a << "\""; + f << std::endl << "\"" << this->commands.back().processName << "\""; + for(auto a : this->commands.back().processArguments) f << " \"" << a << "\""; #elif __linux__ f << std::endl << "./" << this->commands.back().first; - for(auto a : this->commands.back().second) f << " " << a; + for(auto a : this->commands.back().processArguments) f << " " << a; #endif f.close(); - -//#ifdef _WIN32 -// system(name.c_str()); -//#elif __linux__ -// system(std::string("sh ./" + name).c_str()); -//#endif }; #endif \ No newline at end of file diff --git a/Samples/Tcp/Launcher-01-server-client.cpp b/Samples/Tcp/Launcher-01-server-client.cpp index 71d6b368..c0d712cc 100644 --- a/Samples/Tcp/Launcher-01-server-client.cpp +++ b/Samples/Tcp/Launcher-01-server-client.cpp @@ -3,8 +3,8 @@ int main() { Launcher lnc("Launcher01"); - lnc.addProcess("TcpServer","20000"); - lnc.addProcess("TcpClient","20000"); + lnc.addProcess("TcpServer", "20000"); + lnc.addProcessSleep(std::make_pair("TcpClient", 1), "20000"); lnc(); return EXIT_SUCCESS; diff --git a/Samples/Tcp/Launcher-02-server-clients.cpp b/Samples/Tcp/Launcher-02-server-clients.cpp index 97c36f4e..63d273c4 100644 --- a/Samples/Tcp/Launcher-02-server-clients.cpp +++ b/Samples/Tcp/Launcher-02-server-clients.cpp @@ -3,8 +3,8 @@ int main() { Launcher lnc("Launcher02"); - lnc.addProcess("TcpServer","25000", "2"); - lnc.addProcess("TcpClient","25000", "100"); + lnc.addProcess("TcpServer", "25000"); + lnc.addProcessSleep(std::make_pair("TcpClient", 1), "25000", "100"); lnc.addProcess("TcpClient","25000", "500"); lnc(); diff --git a/Samples/Tcp/Launcher-03-repeater.cpp b/Samples/Tcp/Launcher-03-repeater.cpp index 441af862..56a4ee42 100644 --- a/Samples/Tcp/Launcher-03-repeater.cpp +++ b/Samples/Tcp/Launcher-03-repeater.cpp @@ -4,8 +4,8 @@ int main() { Launcher lnc("Launcher03"); lnc.addProcess("TcpServer","3000"); - lnc.addProcess("TcpRepeater"); - lnc.addProcess("TcpClient","4000"); + lnc.addProcessSleep(std::make_pair("TcpRepeater", 1)); + lnc.addProcessSleep(std::make_pair("TcpClient", 1), "4000"); lnc(); return EXIT_SUCCESS; diff --git a/Samples/Udp/Launcher-01-client-client.cpp b/Samples/Udp/Launcher-01-client-client.cpp index 11503e9a..cd2d41a2 100644 --- a/Samples/Udp/Launcher-01-client-client.cpp +++ b/Samples/Udp/Launcher-01-client-client.cpp @@ -4,7 +4,7 @@ int main() { Launcher lnc("Launcher01"); lnc.addProcess("UdpClientResponder", "10000", "15000"); - lnc.addProcess("UdpClientAsker","15000","250","10000"); + lnc.addProcessSleep(std::make_pair("UdpClientAsker", 1), "15000", "250", "10000"); lnc(); return EXIT_SUCCESS; diff --git a/Samples/Udp/Launcher-02-client-server.cpp b/Samples/Udp/Launcher-02-client-server.cpp index 8e1d4c8e..675a5f34 100644 --- a/Samples/Udp/Launcher-02-client-server.cpp +++ b/Samples/Udp/Launcher-02-client-server.cpp @@ -3,8 +3,8 @@ int main() { Launcher lnc("Launcher02"); - lnc.addProcess("UpdServer", "35000"); - lnc.addProcess("UdpClientAsker", "35000", "250", "30000", "1"); + lnc.addProcess("UdpServer", "35000"); + lnc.addProcessSleep(std::make_pair("UdpClientAsker", 1), "35000", "250", "30000", "1"); lnc(); return EXIT_SUCCESS; From 246ae5f94c925571ecdf91efed0dbd5ff2cb7199 Mon Sep 17 00:00:00 2001 From: andrea Date: Sat, 13 Feb 2021 10:49:58 +0100 Subject: [PATCH 032/228] launcher linux compile error fixed --- Samples/ProcessLauncher.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Samples/ProcessLauncher.h b/Samples/ProcessLauncher.h index 4667665e..24283c5e 100644 --- a/Samples/ProcessLauncher.h +++ b/Samples/ProcessLauncher.h @@ -110,7 +110,7 @@ void Launcher::operator()() const { f << std::endl << "start \"\" \"" << it->processName << "\""; for(auto a : it->processArguments) f << " \"" << a << "\""; #elif __linux__ - f << std::endl << "gnome-terminal -x sh -c \"./" << it->first; + f << std::endl << "gnome-terminal -x sh -c \"./" << it->processName; for(auto a : it->processArguments) f << " " << a; f << "; bash\""; #endif @@ -120,7 +120,7 @@ void Launcher::operator()() const { f << std::endl << "\"" << this->commands.back().processName << "\""; for(auto a : this->commands.back().processArguments) f << " \"" << a << "\""; #elif __linux__ - f << std::endl << "./" << this->commands.back().first; + f << std::endl << "./" << this->commands.back().processName; for(auto a : this->commands.back().processArguments) f << " " << a; #endif From 8d0c9b6df5fa0112cc6af6e112c21ac5af065d68 Mon Sep 17 00:00:00 2001 From: andrea Date: Sat, 13 Feb 2021 11:56:28 +0100 Subject: [PATCH 033/228] async sample added --- CrossSocket/include/async/AsyncDecorator.h | 8 +++- Samples/Tcp/CMakeLists.txt | 6 +++ .../Tcp/Launcher-04-asyncserver-client.cpp | 11 +++++ Samples/Tcp/README.md | 6 +++ Samples/Tcp/TcpServer.cpp | 1 - Samples/Tcp/TcpServerAsync.cpp | 44 +++++++++++++++++++ Samples/Utils/include/ResponderAsync.h | 11 ++++- Samples/Utils/src/ResponderAsync.cpp | 16 +++++-- TODO | 2 - 9 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 Samples/Tcp/Launcher-04-asyncserver-client.cpp create mode 100644 Samples/Tcp/TcpServerAsync.cpp delete mode 100644 TODO diff --git a/CrossSocket/include/async/AsyncDecorator.h b/CrossSocket/include/async/AsyncDecorator.h index 273c0c70..d0aed9f4 100644 --- a/CrossSocket/include/async/AsyncDecorator.h +++ b/CrossSocket/include/async/AsyncDecorator.h @@ -29,15 +29,19 @@ namespace sck::async { inline void open(const std::chrono::milliseconds& timeout) final { if (nullptr != this->service) return; - this->wrapped->open(timeout); + if(!this->wrapped->isOpen()) { + this->wrapped->open(timeout); + } if (this->wrapped->isOpen()) { this->service = this->make_service(); } + else { + this->wrapped->close(); + } }; inline void close() final { this->wrapped->close(); - if (nullptr != this->service) return; this->service.reset(); }; diff --git a/Samples/Tcp/CMakeLists.txt b/Samples/Tcp/CMakeLists.txt index e9cee7cf..1a4986cc 100644 --- a/Samples/Tcp/CMakeLists.txt +++ b/Samples/Tcp/CMakeLists.txt @@ -4,6 +4,9 @@ target_link_libraries(TcpClient Sample-Utils) add_executable(TcpServer TcpServer.cpp) target_link_libraries(TcpServer Sample-Utils) +add_executable(TcpServerAsync TcpServerAsync.cpp) +target_link_libraries(TcpServerAsync Sample-Utils) + add_executable(TcpRepeater TcpRepeater.cpp) target_link_libraries(TcpRepeater Sample-Utils) @@ -17,3 +20,6 @@ add_dependencies(Tcp02Launcher TcpClient TcpServer) add_executable(Tcp03Launcher Launcher-03-repeater.cpp) add_dependencies(Tcp03Launcher TcpClient TcpServer TcpRepeater) + +add_executable(Tcp04Launcher Launcher-04-asyncserver-client.cpp) +add_dependencies(Tcp04Launcher TcpClient TcpServerAsync) diff --git a/Samples/Tcp/Launcher-04-asyncserver-client.cpp b/Samples/Tcp/Launcher-04-asyncserver-client.cpp new file mode 100644 index 00000000..cb22682c --- /dev/null +++ b/Samples/Tcp/Launcher-04-asyncserver-client.cpp @@ -0,0 +1,11 @@ +#include "../ProcessLauncher.h" + +int main() { + + Launcher lnc("Launcher04"); + lnc.addProcess("TcpServerAsync", "45000"); + lnc.addProcessSleep(std::make_pair("TcpClient", 1), "45000", "250"); + lnc(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Tcp/README.md b/Samples/Tcp/README.md index d8f52733..8c6ef8fa 100644 --- a/Samples/Tcp/README.md +++ b/Samples/Tcp/README.md @@ -26,3 +26,9 @@ to run the corresponding example * a Tcp server ready to accept 1 client * a Tcp client that connects to an intermediate repeater * a repeater creating 2 connections in order to receive the request of the client and forward them to the server, sending back the response + + *Sample04, run: + + * a Tcp server asynchronous accepting 1 single client + * a Tcp client that connects to the async server and exchange messages + diff --git a/Samples/Tcp/TcpServer.cpp b/Samples/Tcp/TcpServer.cpp index 4e7ecabf..9180aa84 100644 --- a/Samples/Tcp/TcpServer.cpp +++ b/Samples/Tcp/TcpServer.cpp @@ -6,7 +6,6 @@ **/ #include -#include #include #include #include diff --git a/Samples/Tcp/TcpServerAsync.cpp b/Samples/Tcp/TcpServerAsync.cpp new file mode 100644 index 00000000..1e5b7a5b --- /dev/null +++ b/Samples/Tcp/TcpServerAsync.cpp @@ -0,0 +1,44 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 +* +* report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +#include +using namespace std; + +int main(int argc, char** argv){ + cout << "----------------------- Server Async -----------------------" << endl; + + if (argc == 1) { + cout << "correct syntax is: 'port to bind'" << endl; + return EXIT_FAILURE; + } + + std::uint16_t port = std::atoi(argv[1]); + + //build the server object + sck::tcp::TcpServer server(port); + cout << "Binding port " << port << endl; + + // blocking open + server.open(std::chrono::milliseconds(0)); + if (!server.isOpen()) { + cout << "server open failed" << endl; + return EXIT_FAILURE; + } + cout << "connection opened" << endl; + + auto clientConenction = server.acceptClient(); + cout << "client accepted" << endl; + + ResponderAsync responder(std::move(clientConenction)); + while(responder.isRunning()) { + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Utils/include/ResponderAsync.h b/Samples/Utils/include/ResponderAsync.h index 0fcbb49b..66375c55 100644 --- a/Samples/Utils/include/ResponderAsync.h +++ b/Samples/Utils/include/ResponderAsync.h @@ -11,14 +11,23 @@ #include #include -class ResponderAsync : public sck::async::listener::MessageListener { +class ResponderAsync + : public sck::async::listener::MessageListener + , public sck::async::listener::ErrorListener { public: ResponderAsync(std::unique_ptr socket); + inline bool isRunning() const { return this->running; }; + private: void handle(const std::pair& message) final; + void handle(const sck::Error& error) final; + + void handle(const std::exception& error) final; + std::unique_ptr asyncSocket; + std::atomic_bool running; }; #endif \ No newline at end of file diff --git a/Samples/Utils/src/ResponderAsync.cpp b/Samples/Utils/src/ResponderAsync.cpp index 44dd6fb9..69424b5e 100644 --- a/Samples/Utils/src/ResponderAsync.cpp +++ b/Samples/Utils/src/ResponderAsync.cpp @@ -7,21 +7,29 @@ #include #include -#include ResponderAsync::ResponderAsync(std::unique_ptr socket) { + this->running = true; this->asyncSocket = std::make_unique(std::move(socket), 1000); this->asyncSocket->open(std::chrono::milliseconds(0)); + if(!this->asyncSocket->isOpen()) { + this->running = false; + } } void ResponderAsync::handle(const std::pair& message) { std::string recStr(message.first, message.second); - std::cout << " got: " << recStr << std::endl; const std::string surname = Names::getSurname(recStr); - std::cout << "sending: " << surname; this->asyncSocket->send({ surname.data(), surname.size() }); } +void ResponderAsync::handle(const sck::Error& error){ + this->asyncSocket->close(); + this->running = false; +}; - +void ResponderAsync::handle(const std::exception& error) { + this->asyncSocket->close(); + this->running = false; +}; diff --git a/TODO b/TODO deleted file mode 100644 index 296f4b8a..00000000 --- a/TODO +++ /dev/null @@ -1,2 +0,0 @@ -indicare quando throw in doc class -aggiungere in samples qlcs che consuma async responder From 919a3394b198803941a9eb0c0e83529cdb307aee Mon Sep 17 00:00:00 2001 From: andrea Date: Sat, 13 Feb 2021 12:52:03 +0100 Subject: [PATCH 034/228] debugging asybc sample --- CrossSocket/include/async/AsyncDecorator.h | 4 ++-- CrossSocket/include/async/Service.h | 2 +- CrossSocket/src/async/AsyncClient.cpp | 4 +--- CrossSocket/src/async/AsyncTcpServer.cpp | 4 +--- CrossSocket/src/async/Service.cpp | 4 ++-- Samples/Utils/src/ResponderAsync.cpp | 2 ++ 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/CrossSocket/include/async/AsyncDecorator.h b/CrossSocket/include/async/AsyncDecorator.h index d0aed9f4..3b67d031 100644 --- a/CrossSocket/include/async/AsyncDecorator.h +++ b/CrossSocket/include/async/AsyncDecorator.h @@ -18,9 +18,9 @@ namespace sck::async { public: ~AsyncDecorator() { this->close(); }; - inline void resetListener(Listener* listener) { + inline void resetListener(Listener* list) { std::lock_guard lk(this->listenerMtx); - this->listener = listener; + this->listener = list; }; inline void resetErrorListener(listener::ErrorListener* listener) { diff --git a/CrossSocket/include/async/Service.h b/CrossSocket/include/async/Service.h index 5a237dcb..cc981f9f 100644 --- a/CrossSocket/include/async/Service.h +++ b/CrossSocket/include/async/Service.h @@ -25,7 +25,7 @@ namespace sck::async { // service is stop when destroying ~Service(); - void resetErrorListener(listener::ErrorListener* listener); + void resetErrorListener(listener::ErrorListener* list); private: std::mutex listenerMtx; diff --git a/CrossSocket/src/async/AsyncClient.cpp b/CrossSocket/src/async/AsyncClient.cpp index 5dc2bc60..70b64876 100644 --- a/CrossSocket/src/async/AsyncClient.cpp +++ b/CrossSocket/src/async/AsyncClient.cpp @@ -11,10 +11,8 @@ namespace sck::async { AsyncClient::AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity) : AsyncDecorator(std::move(client)) { + this->wrapped->close(); this->buffer.resize(bufferCapacity); - if(this->wrapped->isOpen()){ - this->open(std::chrono::milliseconds(0)); - } }; class AsyncClient::ReceiveService : public Service { diff --git a/CrossSocket/src/async/AsyncTcpServer.cpp b/CrossSocket/src/async/AsyncTcpServer.cpp index 47c7fa66..1dcc53bc 100644 --- a/CrossSocket/src/async/AsyncTcpServer.cpp +++ b/CrossSocket/src/async/AsyncTcpServer.cpp @@ -10,9 +10,7 @@ namespace sck::async { AsyncTcpServer::AsyncTcpServer(std::unique_ptr server) : AsyncDecorator(std::move(server)) { - if(this->wrapped->isOpen()){ - this->open(std::chrono::milliseconds(0)); - } + this->wrapped->close(); }; class AsyncTcpServer::AcceptanceService : public Service { diff --git a/CrossSocket/src/async/Service.cpp b/CrossSocket/src/async/Service.cpp index e2ed522a..b0283ee3 100644 --- a/CrossSocket/src/async/Service.cpp +++ b/CrossSocket/src/async/Service.cpp @@ -43,8 +43,8 @@ namespace sck::async { this->loop.join(); } - void Service::resetErrorListener(listener::ErrorListener* listener) { + void Service::resetErrorListener(listener::ErrorListener* list) { std::lock_guard lk(this->listenerMtx); - this->listener = listener; + this->listener = list; } } \ No newline at end of file diff --git a/Samples/Utils/src/ResponderAsync.cpp b/Samples/Utils/src/ResponderAsync.cpp index 69424b5e..88aacb8f 100644 --- a/Samples/Utils/src/ResponderAsync.cpp +++ b/Samples/Utils/src/ResponderAsync.cpp @@ -11,6 +11,8 @@ ResponderAsync::ResponderAsync(std::unique_ptr socket) { this->running = true; this->asyncSocket = std::make_unique(std::move(socket), 1000); + //this->asyncSocket->resetErrorListener(this); + this->asyncSocket->resetListener(this); this->asyncSocket->open(std::chrono::milliseconds(0)); if(!this->asyncSocket->isOpen()) { this->running = false; From 6c6c865e0715895a62331bf474188837af3e473d Mon Sep 17 00:00:00 2001 From: andrea Date: Sat, 13 Feb 2021 13:08:58 +0100 Subject: [PATCH 035/228] class documentation improved --- CrossSocket/include/Ip.h | 2 +- CrossSocket/include/Socket.h | 3 +++ CrossSocket/include/async/AsyncClient.h | 5 +++++ CrossSocket/include/async/AsyncDecorator.h | 6 ++++++ CrossSocket/include/async/AsyncTcpServer.h | 5 +++++ CrossSocket/include/async/Service.h | 4 ++++ CrossSocket/include/core/Client.h | 5 +---- CrossSocket/include/core/MessangerConcrete.h | 3 --- CrossSocket/include/core/SocketConcrete.h | 16 ++++++++++------ CrossSocket/include/core/SocketDecorator.h | 3 --- CrossSocket/include/udp/UdpClient.h | 1 - CrossSocket/include/udp/UdpServer.h | 4 ++-- 12 files changed, 37 insertions(+), 20 deletions(-) diff --git a/CrossSocket/include/Ip.h b/CrossSocket/include/Ip.h index c6629308..6d09b5fa 100644 --- a/CrossSocket/include/Ip.h +++ b/CrossSocket/include/Ip.h @@ -19,7 +19,7 @@ namespace sck { typedef std::unique_ptr IpPtr; /** - * @brief representation of a network address + * @brief representation of a network ip address */ class Ip { public: diff --git a/CrossSocket/include/Socket.h b/CrossSocket/include/Socket.h index 5021cf46..cdf472f2 100644 --- a/CrossSocket/include/Socket.h +++ b/CrossSocket/include/Socket.h @@ -26,6 +26,9 @@ namespace sck { virtual void close() = 0; + /** + * @return true only if a previous successfull call to open was done + */ virtual bool isOpen() const = 0; }; } diff --git a/CrossSocket/include/async/AsyncClient.h b/CrossSocket/include/async/AsyncClient.h index 7dc1efa3..e80f6ff6 100644 --- a/CrossSocket/include/async/AsyncClient.h +++ b/CrossSocket/include/async/AsyncClient.h @@ -16,6 +16,11 @@ #include namespace sck::async { + /** + * @brief An asynchronous client can be any kind of socket that is MessangerConcrete, that keeps receive messages + * inside a private thread stored by this class. From the outside it is possible to send messages to the remote host + * or subscribe to the received messages by setting a MessageListener (calling AsyncDecorator::resetListener(...)) + */ class AsyncClient : public AsyncDecorator , public Messanger { diff --git a/CrossSocket/include/async/AsyncDecorator.h b/CrossSocket/include/async/AsyncDecorator.h index 3b67d031..6e9ae981 100644 --- a/CrossSocket/include/async/AsyncDecorator.h +++ b/CrossSocket/include/async/AsyncDecorator.h @@ -18,11 +18,17 @@ namespace sck::async { public: ~AsyncDecorator() { this->close(); }; + /** + * @brief Set a new Listener. + */ inline void resetListener(Listener* list) { std::lock_guard lk(this->listenerMtx); this->listener = list; }; + /** + * @brief Set a new error listener. + */ inline void resetErrorListener(listener::ErrorListener* listener) { this->service->resetErrorListener(listener); }; diff --git a/CrossSocket/include/async/AsyncTcpServer.h b/CrossSocket/include/async/AsyncTcpServer.h index e3b54371..f74627cf 100644 --- a/CrossSocket/include/async/AsyncTcpServer.h +++ b/CrossSocket/include/async/AsyncTcpServer.h @@ -12,6 +12,11 @@ #include namespace sck::async { + /** + * @brief An asynchronous tcp server keeps accpeting new clients + * inside a private thread stored by this class. From the outside it is possible to subscribe to the + * accepted clients by setting a TcpServerListener (calling AsyncDecorator::resetListener(...)) + */ class AsyncTcpServer : public AsyncDecorator { public: explicit AsyncTcpServer(std::unique_ptr server); diff --git a/CrossSocket/include/async/Service.h b/CrossSocket/include/async/Service.h index cc981f9f..bd2b6984 100644 --- a/CrossSocket/include/async/Service.h +++ b/CrossSocket/include/async/Service.h @@ -15,6 +15,10 @@ #include namespace sck::async { + /** + * @brief A service store a thread that keep executes a specific function, i.e. the iterativeAction, + * untill the object is destroyed from the outside + */ class Service { public: Service(const Service&) = delete; diff --git a/CrossSocket/include/core/Client.h b/CrossSocket/include/core/Client.h index 94d5211c..ecb49f89 100644 --- a/CrossSocket/include/core/Client.h +++ b/CrossSocket/include/core/Client.h @@ -12,9 +12,6 @@ #include namespace sck { - /** - * @brief this is the abstract base class for every implementation of a concrete client (tcp or udp) - */ class Client : public SocketConcrete , public MessangerConcrete { @@ -41,7 +38,7 @@ namespace sck { void openSpecific() override; /** - * @brief address of the server connected to this socket + * @brief address of the entity connected to this socket */ sck::Ip remoteAddress; diff --git a/CrossSocket/include/core/MessangerConcrete.h b/CrossSocket/include/core/MessangerConcrete.h index 1d52c2cf..a8d4e8d6 100644 --- a/CrossSocket/include/core/MessangerConcrete.h +++ b/CrossSocket/include/core/MessangerConcrete.h @@ -12,9 +12,6 @@ #include namespace sck { - /** - * @brief interface to the socket APIs - */ class Handler; class MessangerConcrete diff --git a/CrossSocket/include/core/SocketConcrete.h b/CrossSocket/include/core/SocketConcrete.h index 26a58983..472831e5 100644 --- a/CrossSocket/include/core/SocketConcrete.h +++ b/CrossSocket/include/core/SocketConcrete.h @@ -14,18 +14,16 @@ namespace sck { enum Protocol { UDP, TCP }; - /** - * @brief interface to the socket APIs - */ class Handler; - /** - * @brief The interface every socket must derive from. - */ class SocketConcrete : public Socket { public: ~SocketConcrete(); + /** + * @brief When something goes wrong inside the method, close is + * internally called, leaving the socket in a closed status. + */ void open(const std::chrono::milliseconds& timeout) final; void close() final; @@ -35,8 +33,14 @@ namespace sck { protected: explicit SocketConcrete(std::shared_ptr channel); + /** + * @brief The methods containing the specific steps to perform to fully open a concrete socket + */ virtual void openSpecific() = 0; + /** + * @brief The methods containing the specific steps to perform to fully close a concrete socket + */ virtual void closeSpecific(); /** diff --git a/CrossSocket/include/core/SocketDecorator.h b/CrossSocket/include/core/SocketDecorator.h index 4b1e3234..4a658a1c 100644 --- a/CrossSocket/include/core/SocketDecorator.h +++ b/CrossSocket/include/core/SocketDecorator.h @@ -12,9 +12,6 @@ #include namespace sck { - /** - * @brief The interface every socket must derive from. - */ class SocketDecorator : public Socket { public: inline void open(const std::chrono::milliseconds& timeout) override { this->wrapped->open(timeout); }; diff --git a/CrossSocket/include/udp/UdpClient.h b/CrossSocket/include/udp/UdpClient.h index 778cd42a..ecf1258b 100644 --- a/CrossSocket/include/udp/UdpClient.h +++ b/CrossSocket/include/udp/UdpClient.h @@ -17,7 +17,6 @@ namespace sck::udp { /** * @brief interface for a standard udp connection. - * A udp may or not reserve a port, in order to be reached by another udp client. */ class UdpClient : public Client { diff --git a/CrossSocket/include/udp/UdpServer.h b/CrossSocket/include/udp/UdpServer.h index 4132d5ac..fe3d2c9b 100644 --- a/CrossSocket/include/udp/UdpServer.h +++ b/CrossSocket/include/udp/UdpServer.h @@ -12,9 +12,9 @@ namespace sck::udp { /** - * @brief A UdpServer is an UdpClient, with the possibility to deduce the remoteAddress, + * @brief A UdpServer is an UdpClient, with the possibility to deduce the remoteAddress when calling open, * by setting as target the first UdpClient that hits this socket, sending - * at least 1 byte of data. + * at least a 1 byte (or more) message. * IMPORTANT!!! The first message sent to the server will be lost. */ class UdpServer From 3113a5979c943cd6f19017abfd1f8bf40b6e95db Mon Sep 17 00:00:00 2001 From: andrea Date: Sat, 13 Feb 2021 14:55:42 +0100 Subject: [PATCH 036/228] removing external receive from async shell --- CrossSocket/include/Messanger.h | 28 ++++++++++++++++--------- CrossSocket/include/async/AsyncClient.h | 8 +------ CrossSocket/src/async/AsyncClient.cpp | 19 ----------------- Samples/Utils/src/ResponderAsync.cpp | 2 +- 4 files changed, 20 insertions(+), 37 deletions(-) diff --git a/CrossSocket/include/Messanger.h b/CrossSocket/include/Messanger.h index b189afbb..5f4db17b 100644 --- a/CrossSocket/include/Messanger.h +++ b/CrossSocket/include/Messanger.h @@ -12,23 +12,17 @@ #include namespace sck { - /** - * @brief The interface providing functionalities for exchanging data - */ - class Messanger { + class Sender { public: - Messanger() = default; - Messanger(const Messanger&) = delete; - Messanger& operator=(const Messanger&) = delete; - - virtual ~Messanger() = default; - /** * @return true if the message was completely sent * @param[in] the message to send */ virtual bool send(const std::pair& message) = 0; + }; + class Receiver { + public: /** * @return the number of received bytes * @param[in] the recepient @@ -36,6 +30,20 @@ namespace sck { */ virtual std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) = 0; }; + + /** + * @brief The interface providing functionalities for exchanging data + */ + class Messanger + : public Sender + , public Receiver { + public: + Messanger() = default; + Messanger(const Messanger&) = delete; + Messanger& operator=(const Messanger&) = delete; + + virtual ~Messanger() = default; + }; } #endif \ No newline at end of file diff --git a/CrossSocket/include/async/AsyncClient.h b/CrossSocket/include/async/AsyncClient.h index e80f6ff6..0d1b6eec 100644 --- a/CrossSocket/include/async/AsyncClient.h +++ b/CrossSocket/include/async/AsyncClient.h @@ -23,7 +23,7 @@ namespace sck::async { */ class AsyncClient : public AsyncDecorator - , public Messanger { + , public Sender { public: AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); @@ -31,16 +31,10 @@ namespace sck::async { return dynamic_cast(this->wrapped.get())->send(message); }; - std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; - private: - std::mutex bufferMtx; std::vector buffer; class ReceiveService; - std::mutex recvMutex; - std::condition_variable recvNotification; - std::unique_ptr make_service() final; }; } diff --git a/CrossSocket/src/async/AsyncClient.cpp b/CrossSocket/src/async/AsyncClient.cpp index 70b64876..1eb7ad10 100644 --- a/CrossSocket/src/async/AsyncClient.cpp +++ b/CrossSocket/src/async/AsyncClient.cpp @@ -20,7 +20,6 @@ namespace sck::async { ReceiveService(AsyncClient& client) : Service([&client](){ { - std::lock_guard bufferLock(client.bufferMtx); auto pr = std::make_pair(client.buffer.data(), client.buffer.capacity()); auto recvBytes = dynamic_cast(client.wrapped.get())->receive(pr, std::chrono::milliseconds(0)); if(recvBytes != client.buffer.capacity()) { @@ -36,24 +35,6 @@ namespace sck::async { } }; - std::size_t AsyncClient::receive(std::pair& message, const std::chrono::milliseconds& timeout) { - auto copyBuffer = [this, &message]() -> std::size_t { - std::lock_guard bufferLock(this->bufferMtx); - ::memcpy(message.first, this->buffer.data(), this->buffer.size()); - return this->buffer.size(); - }; - - std::unique_lock notifLock(this->recvMutex); - if(0 == timeout.count()) { - this->recvNotification.wait(notifLock); - return copyBuffer(); - } - if(this->recvNotification.wait_for(notifLock, timeout) != std::cv_status::timeout) { - return copyBuffer(); - } - return 0; - } - std::unique_ptr AsyncClient::make_service() { return std::make_unique(*this); } diff --git a/Samples/Utils/src/ResponderAsync.cpp b/Samples/Utils/src/ResponderAsync.cpp index 88aacb8f..8be54a4b 100644 --- a/Samples/Utils/src/ResponderAsync.cpp +++ b/Samples/Utils/src/ResponderAsync.cpp @@ -11,7 +11,7 @@ ResponderAsync::ResponderAsync(std::unique_ptr socket) { this->running = true; this->asyncSocket = std::make_unique(std::move(socket), 1000); - //this->asyncSocket->resetErrorListener(this); + this->asyncSocket->resetErrorListener(this); this->asyncSocket->resetListener(this); this->asyncSocket->open(std::chrono::milliseconds(0)); if(!this->asyncSocket->isOpen()) { From 69cac7b1cffc55e923b402618adcb76ea0e7f349 Mon Sep 17 00:00:00 2001 From: andrea Date: Sat, 13 Feb 2021 17:58:29 +0100 Subject: [PATCH 037/228] samples debugged --- CrossSocket/include/async/AsyncClient.h | 4 +-- CrossSocket/include/async/AsyncDecorator.h | 11 ++++++-- CrossSocket/include/async/Service.h | 2 +- .../include/async/listener/ErrorListener.h | 1 + CrossSocket/src/async/AsyncClient.cpp | 28 +++++++++---------- CrossSocket/src/async/AsyncTcpServer.cpp | 8 +++--- CrossSocket/src/async/Service.cpp | 4 +-- Samples/Utils/src/ResponderAsync.cpp | 2 -- 8 files changed, 31 insertions(+), 29 deletions(-) diff --git a/CrossSocket/include/async/AsyncClient.h b/CrossSocket/include/async/AsyncClient.h index 0d1b6eec..b5d9a9a6 100644 --- a/CrossSocket/include/async/AsyncClient.h +++ b/CrossSocket/include/async/AsyncClient.h @@ -11,13 +11,11 @@ #include #include #include -#include -#include #include namespace sck::async { /** - * @brief An asynchronous client can be any kind of socket that is MessangerConcrete, that keeps receive messages + * @brief An asynchronous client can be any kind of socket that is a MessangerConcrete, that keeps receive messages * inside a private thread stored by this class. From the outside it is possible to send messages to the remote host * or subscribe to the received messages by setting a MessageListener (calling AsyncDecorator::resetListener(...)) */ diff --git a/CrossSocket/include/async/AsyncDecorator.h b/CrossSocket/include/async/AsyncDecorator.h index 6e9ae981..fb6996da 100644 --- a/CrossSocket/include/async/AsyncDecorator.h +++ b/CrossSocket/include/async/AsyncDecorator.h @@ -29,8 +29,12 @@ namespace sck::async { /** * @brief Set a new error listener. */ - inline void resetErrorListener(listener::ErrorListener* listener) { - this->service->resetErrorListener(listener); + inline void resetErrorListener(listener::ErrorListener* list) { + std::lock_guard lk(this->errorListenerMtx); + this->errorListener = list; + if(nullptr != this->service) { + this->service->resetErrorListener(list); + } }; inline void open(const std::chrono::milliseconds& timeout) final { @@ -61,6 +65,9 @@ namespace sck::async { std::mutex listenerMtx; Listener* listener = nullptr; + + std::mutex errorListenerMtx; + listener::ErrorListener* errorListener = nullptr; }; } diff --git a/CrossSocket/include/async/Service.h b/CrossSocket/include/async/Service.h index bd2b6984..bf92002e 100644 --- a/CrossSocket/include/async/Service.h +++ b/CrossSocket/include/async/Service.h @@ -25,7 +25,7 @@ namespace sck::async { Service& operator=(const Service&) = delete; // service is started when building - explicit Service(const std::function& iterativeAction); + Service(const std::function& iterativeAction, listener::ErrorListener* list); // service is stop when destroying ~Service(); diff --git a/CrossSocket/include/async/listener/ErrorListener.h b/CrossSocket/include/async/listener/ErrorListener.h index 64116a44..8f172832 100644 --- a/CrossSocket/include/async/listener/ErrorListener.h +++ b/CrossSocket/include/async/listener/ErrorListener.h @@ -9,6 +9,7 @@ #define _CROSS_SOCKET_ERRORLISTENER_H_ #include +#include namespace sck::async::listener { class ErrorListener { diff --git a/CrossSocket/src/async/AsyncClient.cpp b/CrossSocket/src/async/AsyncClient.cpp index 1eb7ad10..f36898c8 100644 --- a/CrossSocket/src/async/AsyncClient.cpp +++ b/CrossSocket/src/async/AsyncClient.cpp @@ -11,31 +11,29 @@ namespace sck::async { AsyncClient::AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity) : AsyncDecorator(std::move(client)) { - this->wrapped->close(); this->buffer.resize(bufferCapacity); }; class AsyncClient::ReceiveService : public Service { public: - ReceiveService(AsyncClient& client) + ReceiveService(AsyncClient& client, listener::ErrorListener* list) : Service([&client](){ - { - auto pr = std::make_pair(client.buffer.data(), client.buffer.capacity()); - auto recvBytes = dynamic_cast(client.wrapped.get())->receive(pr, std::chrono::milliseconds(0)); - if(recvBytes != client.buffer.capacity()) { - client.buffer.resize(recvBytes); - } - std::lock_guard lk(client.listenerMtx); - if(nullptr != client.listener) { - client.listener->handle({client.buffer.data(), recvBytes}); - } + client.buffer.resize(client.buffer.capacity()); + auto pr = std::make_pair(client.buffer.data(), client.buffer.capacity()); + auto recvBytes = dynamic_cast(client.wrapped.get())->receive(pr, std::chrono::milliseconds(0)); + if(recvBytes != client.buffer.capacity()) { + client.buffer.resize(recvBytes); } - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - }) { + std::lock_guard lk(client.listenerMtx); + if(nullptr != client.listener) { + client.listener->handle({client.buffer.data(), recvBytes}); + } + }, list) { } }; std::unique_ptr AsyncClient::make_service() { - return std::make_unique(*this); + std::lock_guard lk(this->errorListenerMtx); + return std::make_unique(*this, this->errorListener); } } \ No newline at end of file diff --git a/CrossSocket/src/async/AsyncTcpServer.cpp b/CrossSocket/src/async/AsyncTcpServer.cpp index 1dcc53bc..d9c39a63 100644 --- a/CrossSocket/src/async/AsyncTcpServer.cpp +++ b/CrossSocket/src/async/AsyncTcpServer.cpp @@ -10,23 +10,23 @@ namespace sck::async { AsyncTcpServer::AsyncTcpServer(std::unique_ptr server) : AsyncDecorator(std::move(server)) { - this->wrapped->close(); }; class AsyncTcpServer::AcceptanceService : public Service { public: - AcceptanceService(AsyncTcpServer& server) + AcceptanceService(AsyncTcpServer& server, listener::ErrorListener* list) : Service([&server](){ auto client = dynamic_cast(server.wrapped.get())->acceptClient(); std::lock_guard lk(server.listenerMtx); if(nullptr != server.listener) { server.listener->handle(std::move(client)); } - }) { + }, list) { } }; std::unique_ptr AsyncTcpServer::make_service() { - return std::make_unique(*this); + std::lock_guard lk(this->errorListenerMtx); + return std::make_unique(*this, this->errorListener); } } diff --git a/CrossSocket/src/async/Service.cpp b/CrossSocket/src/async/Service.cpp index b0283ee3..b6498ade 100644 --- a/CrossSocket/src/async/Service.cpp +++ b/CrossSocket/src/async/Service.cpp @@ -8,8 +8,8 @@ #include namespace sck::async { - Service::Service(const std::function& iterativeAction) - : listener(nullptr) + Service::Service(const std::function& iterativeAction, listener::ErrorListener* list) + : listener(list) , loop([this, &iterativeAction](){ std::function iter(iterativeAction); this->loopLife = true; diff --git a/Samples/Utils/src/ResponderAsync.cpp b/Samples/Utils/src/ResponderAsync.cpp index 8be54a4b..1fc1a2af 100644 --- a/Samples/Utils/src/ResponderAsync.cpp +++ b/Samples/Utils/src/ResponderAsync.cpp @@ -6,7 +6,6 @@ **/ #include -#include ResponderAsync::ResponderAsync(std::unique_ptr socket) { this->running = true; @@ -21,7 +20,6 @@ ResponderAsync::ResponderAsync(std::unique_ptr socket) { void ResponderAsync::handle(const std::pair& message) { std::string recStr(message.first, message.second); - const std::string surname = Names::getSurname(recStr); this->asyncSocket->send({ surname.data(), surname.size() }); } From eb206dcec72c234059b8f7225ff988cc35b0de32 Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Tue, 9 Mar 2021 18:22:13 +0100 Subject: [PATCH 038/228] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6be7a44b..4a394414 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,4 @@ Use [CMake](https://cmake.org) to configure and compile the library and the samp **Run the examples** -Check the *README.md* inside the sample folders +Check the *README.md* inside Samples/Tcp/ and Samples/Udp From 4681b82121961763ab04f6fcc3152e5d210796c5 Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Tue, 9 Mar 2021 18:22:23 +0100 Subject: [PATCH 039/228] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a394414..c598050e 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,4 @@ Use [CMake](https://cmake.org) to configure and compile the library and the samp **Run the examples** -Check the *README.md* inside Samples/Tcp/ and Samples/Udp +Check the *README.md* inside Samples/Tcp/ and Samples/Udp/ From 7240df383615ad10cdf7e609e7b9c6dda2e5412a Mon Sep 17 00:00:00 2001 From: AndreaC Date: Tue, 9 Mar 2021 18:42:00 +0100 Subject: [PATCH 040/228] mutex protecting send and receive introduced --- CrossSocket/include/Messanger.h | 7 +++++-- CrossSocket/include/core/MessangerConcrete.h | 6 ++++++ CrossSocket/src/core/MessangerConcrete.cpp | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CrossSocket/include/Messanger.h b/CrossSocket/include/Messanger.h index 5f4db17b..8dfbee3b 100644 --- a/CrossSocket/include/Messanger.h +++ b/CrossSocket/include/Messanger.h @@ -24,9 +24,12 @@ namespace sck { class Receiver { public: /** - * @return the number of received bytes - * @param[in] the recepient + * @param[in] the buffer that will receive the message: + * first element of the pair is the data pointer of the buffer + * second element of the pair is the buffer size + * A request to receive a message of maximal size equal to message.second will be forwarded to the socket api. * @param[in] the timeout to consider + * @return the number of received bytes actually received and copied into message (can be also less than the buffer size) */ virtual std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) = 0; }; diff --git a/CrossSocket/include/core/MessangerConcrete.h b/CrossSocket/include/core/MessangerConcrete.h index a8d4e8d6..5aed3435 100644 --- a/CrossSocket/include/core/MessangerConcrete.h +++ b/CrossSocket/include/core/MessangerConcrete.h @@ -10,6 +10,7 @@ #include #include +#include namespace sck { class Handler; @@ -28,6 +29,11 @@ namespace sck { std::chrono::milliseconds actualTimeOut = std::chrono::milliseconds(0); std::shared_ptr channelMsg; + + // enforces the calling of receive from a single thread + std::mutex receiveMtx; + // enforces the calling of send from a single thread + std::mutex sendMtx; }; } diff --git a/CrossSocket/src/core/MessangerConcrete.cpp b/CrossSocket/src/core/MessangerConcrete.cpp index c191ea14..703187e2 100644 --- a/CrossSocket/src/core/MessangerConcrete.cpp +++ b/CrossSocket/src/core/MessangerConcrete.cpp @@ -18,6 +18,7 @@ namespace sck { } bool MessangerConcrete::send(const std::pair& message) { + std::lock_guard sendLock(this->sendMtx); int sentBytes = ::send(**this->channelMsg, message.first, static_cast(message.second), 0); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; @@ -27,6 +28,7 @@ namespace sck { } std::size_t MessangerConcrete::receive(std::pair& message, const std::chrono::milliseconds& timeout) { + std::lock_guard recvLock(this->receiveMtx); if (timeout.count() != this->actualTimeOut.count()) { //set new timeout this->actualTimeOut = timeout; From 60d3dbdbad86cd57199d73d7662d6f1fa8d5ae37 Mon Sep 17 00:00:00 2001 From: AndreaC Date: Wed, 10 Mar 2021 21:18:57 +0100 Subject: [PATCH 041/228] ci pipeline created --- .github/workflows/action.yml | 67 ++++++++++++++++++++++++++++++++++++ CrossSocket/CMakeLists.txt | 5 +++ 2 files changed, 72 insertions(+) create mode 100644 .github/workflows/action.yml diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml new file mode 100644 index 00000000..e0610007 --- /dev/null +++ b/.github/workflows/action.yml @@ -0,0 +1,67 @@ +name: C/C++ CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + ubuntu-build-gcc: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.0.0 + - name: Build project + uses: nicledomaS/cmake_build_action@v1.3 + with: + cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=gcc;-DCMAKE_CXX_COMPILER=g++;-DBUILD_SAMPLES=OFF + submodule_update: ON + run_tests: ON + unit_test_build: -Dtest=ON + - name: Install artifacts + run: cmake --install ./build + - uses: actions/upload-artifact@v2 + with: + path: artifacts + name: Ubuntu_GCC.tar.gz + + ubuntu-build-clang: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.0.0 + - name: Build project + uses: nicledomaS/cmake_build_action@v1.3 + with: + cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=clang;-DCMAKE_CXX_COMPILER=clang++;-DBUILD_SAMPLES=OFF + submodule_update: ON + run_tests: ON + unit_test_build: -Dtest=ON + - name: Install artifacts + run: cmake --install ./build + - uses: actions/upload-artifact@v2 + with: + path: artifacts + name: Ubuntu_CLANG.tar.gz + + windows-build-vs: + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v2.0.0 + - name: Build project + uses: nicledomaS/cmake_build_action@v1.3 + with: + cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./build/artifacts/;-DBUILD_SAMPLES=OFF + submodule_update: ON + run_tests: ON + unit_test_build: -Dtest=ON + - name: Install artifacts + run: cmake --install ./build + - uses: actions/upload-artifact@v2 + with: + path: build/artifacts + name: Win32_VS.tar.gz diff --git a/CrossSocket/CMakeLists.txt b/CrossSocket/CMakeLists.txt index 7e36799e..ffc30ab9 100644 --- a/CrossSocket/CMakeLists.txt +++ b/CrossSocket/CMakeLists.txt @@ -36,3 +36,8 @@ target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT} ) +install(TARGETS ${PROJECT_NAME}) +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include + DESTINATION "include/" # target directory +) + From 126073e5b13dbdb2eaedb01964c66a2b9c41720a Mon Sep 17 00:00:00 2001 From: AndreaC Date: Wed, 10 Mar 2021 21:31:19 +0100 Subject: [PATCH 042/228] shared and static libraries compiled --- .github/workflows/action.yml | 65 +++++++++++++++++++++++++++++++++--- CrossSocket/CMakeLists.txt | 2 +- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index e0610007..149b014d 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -9,7 +9,7 @@ on: - master jobs: - ubuntu-build-gcc: + ubuntu-build-gcc-static: runs-on: ubuntu-latest steps: - name: Checkout @@ -28,7 +28,26 @@ jobs: path: artifacts name: Ubuntu_GCC.tar.gz - ubuntu-build-clang: + ubuntu-build-gcc-shared: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.0.0 + - name: Build project + uses: nicledomaS/cmake_build_action@v1.3 + with: + cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=gcc;-DCMAKE_CXX_COMPILER=g++;-DBUILD_SAMPLES=OFF;-DLIB_OPT=ON + submodule_update: ON + run_tests: ON + unit_test_build: -Dtest=ON + - name: Install artifacts + run: cmake --install ./build + - uses: actions/upload-artifact@v2 + with: + path: artifacts + name: Ubuntu_GCC.tar.gz + + ubuntu-build-clang-static: runs-on: ubuntu-latest steps: - name: Checkout @@ -46,8 +65,27 @@ jobs: with: path: artifacts name: Ubuntu_CLANG.tar.gz + + ubuntu-build-clang-shared: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.0.0 + - name: Build project + uses: nicledomaS/cmake_build_action@v1.3 + with: + cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=clang;-DCMAKE_CXX_COMPILER=clang++;-DBUILD_SAMPLES=OFF;-DLIB_OPT=ON + submodule_update: ON + run_tests: ON + unit_test_build: -Dtest=ON + - name: Install artifacts + run: cmake --install ./build + - uses: actions/upload-artifact@v2 + with: + path: artifacts + name: Ubuntu_CLANG.tar.gz - windows-build-vs: + windows-build-vs-static: runs-on: windows-latest steps: - name: Checkout @@ -64,4 +102,23 @@ jobs: - uses: actions/upload-artifact@v2 with: path: build/artifacts - name: Win32_VS.tar.gz + name: Win32_VisualStudio.tar.gz + + windows-build-vs-shared: + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v2.0.0 + - name: Build project + uses: nicledomaS/cmake_build_action@v1.3 + with: + cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./build/artifacts/;-DBUILD_SAMPLES=OFF;-DLIB_OPT=ON + submodule_update: ON + run_tests: ON + unit_test_build: -Dtest=ON + - name: Install artifacts + run: cmake --install ./build + - uses: actions/upload-artifact@v2 + with: + path: build/artifacts + name: Win32_VisualStudio.tar.gz diff --git a/CrossSocket/CMakeLists.txt b/CrossSocket/CMakeLists.txt index ffc30ab9..1067563b 100644 --- a/CrossSocket/CMakeLists.txt +++ b/CrossSocket/CMakeLists.txt @@ -38,6 +38,6 @@ target_link_libraries(${PROJECT_NAME} install(TARGETS ${PROJECT_NAME}) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include - DESTINATION "include/" # target directory + DESTINATION "./" # target directory ) From a4517aeb4f088b522622c8b496067326799c34e4 Mon Sep 17 00:00:00 2001 From: AndreaC Date: Wed, 10 Mar 2021 21:43:43 +0100 Subject: [PATCH 043/228] yaml file modified to separate names --- .github/workflows/action.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 149b014d..e848ec38 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/upload-artifact@v2 with: path: artifacts - name: Ubuntu_GCC.tar.gz + name: Ubuntu_GCC_static.tar.gz ubuntu-build-gcc-shared: runs-on: ubuntu-latest @@ -45,7 +45,7 @@ jobs: - uses: actions/upload-artifact@v2 with: path: artifacts - name: Ubuntu_GCC.tar.gz + name: Ubuntu_GCC_shared.tar.gz ubuntu-build-clang-static: runs-on: ubuntu-latest @@ -64,7 +64,7 @@ jobs: - uses: actions/upload-artifact@v2 with: path: artifacts - name: Ubuntu_CLANG.tar.gz + name: Ubuntu_CLANG_static.tar.gz ubuntu-build-clang-shared: runs-on: ubuntu-latest @@ -83,7 +83,7 @@ jobs: - uses: actions/upload-artifact@v2 with: path: artifacts - name: Ubuntu_CLANG.tar.gz + name: Ubuntu_CLANG_shared.tar.gz windows-build-vs-static: runs-on: windows-latest @@ -102,7 +102,7 @@ jobs: - uses: actions/upload-artifact@v2 with: path: build/artifacts - name: Win32_VisualStudio.tar.gz + name: Win32_VisualStudio_static.zip windows-build-vs-shared: runs-on: windows-latest @@ -121,4 +121,4 @@ jobs: - uses: actions/upload-artifact@v2 with: path: build/artifacts - name: Win32_VisualStudio.tar.gz + name: Win32_VisualStudio_shared.zip From a59f03c09bf3781f5a477acc59f84ae2cc01ed55 Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Wed, 10 Mar 2021 21:48:44 +0100 Subject: [PATCH 044/228] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c598050e..feca78cd 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,14 @@ completely platform independent way. * The libarary is contained in the ./CrossSocket folder * Samples showing the library usage are contained in the ./Samples folder. ATTENTION!!! Samples execution might be blocked by your firewall -**Build** +**Build from sources** Use [CMake](https://cmake.org) to configure and compile the library and the samples. **Run the examples** Check the *README.md* inside Samples/Tcp/ and Samples/Udp/ + +**Download compiled binaries** + +You can simply download and use the binaries of the latest master version [here](https://github.com/andreacasalino/Cross-Platform-Socket/actions/) From bf3c95bf539f35522d0d916132f39d5affa898e5 Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Wed, 31 Mar 2021 00:16:02 +0200 Subject: [PATCH 045/228] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index feca78cd..b3080d11 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,6 @@ Check the *README.md* inside Samples/Tcp/ and Samples/Udp/ **Download compiled binaries** -You can simply download and use the binaries of the latest master version [here](https://github.com/andreacasalino/Cross-Platform-Socket/actions/) +You can simply download and use the binaries of the latest master version [here](https://github.com/andreacasalino/Cross-Platform-Socket/actions/runs/640613596) + +If you have found this library usefull, remember to **start** it From 8be8a18ee7dc6fccc4e2221a665e4f64d97cec2a Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Wed, 31 Mar 2021 00:16:37 +0200 Subject: [PATCH 046/228] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3080d11..704f7228 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,4 @@ Check the *README.md* inside Samples/Tcp/ and Samples/Udp/ You can simply download and use the binaries of the latest master version [here](https://github.com/andreacasalino/Cross-Platform-Socket/actions/runs/640613596) -If you have found this library usefull, remember to **start** it +If you have found this library usefull, remember put a **star** on it From d7d1164d400222ada15df56049be93a16879b764 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 2 May 2021 00:59:15 +0200 Subject: [PATCH 047/228] async separated from sync --- CMakeLists.txt | 4 +- CrossSocket/AsynchSocket/CMakeLists.txt | 8 ++ .../include}/AsyncClient.h | 10 +- .../include}/AsyncDecorator.h | 21 +-- .../include}/AsyncTcpServer.h | 6 +- .../async => AsynchSocket/include}/Service.h | 8 +- .../include}/listener/ErrorListener.h | 2 +- .../include}/listener/MessageListener.h | 2 +- .../include}/listener/TcpServerListener.h | 2 +- .../src}/AsyncClient.cpp | 10 +- .../src}/AsyncTcpServer.cpp | 8 +- .../async => AsynchSocket/src}/Service.cpp | 6 +- CrossSocket/CMakeLists.txt | 45 +----- CrossSocket/SynchSocket/CMakeLists.txt | 15 ++ CrossSocket/{ => SynchSocket}/include/Error.h | 0 CrossSocket/{ => SynchSocket}/include/Ip.h | 8 +- CrossSocket/SynchSocket/include/core/Client.h | 52 +++++++ .../SynchSocket/include/core/OpenConcrete.h | 52 +++++++ .../SynchSocket/include/core/OpenDecorator.h | 30 ++++ .../SynchSocket/include/core/Receiver.h | 32 +++++ CrossSocket/SynchSocket/include/core/Sender.h | 30 ++++ .../include/core/components/ChannelAware.h | 27 ++++ .../include/core/components/FamilyAware.h | 28 ++++ .../include/core/components/OpenCapable.h | 36 +++++ .../include/core/components/ProtocolAware.h | 25 ++++ .../include/core/components/ReceiveCapable.h | 34 +++++ .../include/core/components/SendCapable.h | 30 ++++ .../{ => SynchSocket}/include/tcp/TcpClient.h | 2 +- .../{ => SynchSocket}/include/tcp/TcpServer.h | 3 +- .../include/udp/UdpConnection.h} | 12 +- .../{ => SynchSocket}/include/udp/UdpServer.h | 4 +- CrossSocket/SynchSocket/src/Channel.cpp | 109 +++++++++++++++ CrossSocket/SynchSocket/src/Channel.h | 75 ++++++++++ .../Core.cpp => SynchSocket/src/Commons.cpp} | 100 +------------- .../core/Core.h => SynchSocket/src/Commons.h} | 64 +-------- CrossSocket/{ => SynchSocket}/src/Ip.cpp | 2 +- CrossSocket/SynchSocket/src/core/Client.cpp | 45 ++++++ .../SynchSocket/src/core/OpenConcrete.cpp | 130 ++++++++++++++++++ .../src/core/OpenDecorator.cpp} | 8 +- CrossSocket/SynchSocket/src/core/Receiver.cpp | 45 ++++++ CrossSocket/SynchSocket/src/core/Sender.cpp | 21 +++ .../{ => SynchSocket}/src/tcp/TcpClient.cpp | 6 +- .../{ => SynchSocket}/src/tcp/TcpServer.cpp | 12 +- .../src/udp/UdpConnection.cpp} | 8 +- .../{ => SynchSocket}/src/udp/UdpServer.cpp | 6 +- CrossSocket/include/Messanger.h | 52 ------- CrossSocket/include/Socket.h | 36 ----- CrossSocket/include/core/Client.h | 50 ------- CrossSocket/include/core/MessangerConcrete.h | 40 ------ CrossSocket/include/core/SocketConcrete.h | 58 -------- CrossSocket/include/core/SocketDecorator.h | 30 ---- CrossSocket/src/core/Client.cpp | 47 ------- CrossSocket/src/core/MessangerConcrete.cpp | 63 --------- CrossSocket/src/core/SocketConcrete.cpp | 129 ----------------- cmake/MakeLibrary.cmake | 22 +++ 55 files changed, 932 insertions(+), 778 deletions(-) create mode 100644 CrossSocket/AsynchSocket/CMakeLists.txt rename CrossSocket/{include/async => AsynchSocket/include}/AsyncClient.h (80%) rename CrossSocket/{include/async => AsynchSocket/include}/AsyncDecorator.h (73%) rename CrossSocket/{include/async => AsynchSocket/include}/AsyncTcpServer.h (81%) rename CrossSocket/{include/async => AsynchSocket/include}/Service.h (77%) rename CrossSocket/{include/async => AsynchSocket/include}/listener/ErrorListener.h (92%) rename CrossSocket/{include/async => AsynchSocket/include}/listener/MessageListener.h (91%) rename CrossSocket/{include/async => AsynchSocket/include}/listener/TcpServerListener.h (91%) rename CrossSocket/{src/async => AsynchSocket/src}/AsyncClient.cpp (78%) rename CrossSocket/{src/async => AsynchSocket/src}/AsyncTcpServer.cpp (80%) rename CrossSocket/{src/async => AsynchSocket/src}/Service.cpp (91%) create mode 100644 CrossSocket/SynchSocket/CMakeLists.txt rename CrossSocket/{ => SynchSocket}/include/Error.h (100%) rename CrossSocket/{ => SynchSocket}/include/Ip.h (85%) create mode 100644 CrossSocket/SynchSocket/include/core/Client.h create mode 100644 CrossSocket/SynchSocket/include/core/OpenConcrete.h create mode 100644 CrossSocket/SynchSocket/include/core/OpenDecorator.h create mode 100644 CrossSocket/SynchSocket/include/core/Receiver.h create mode 100644 CrossSocket/SynchSocket/include/core/Sender.h create mode 100644 CrossSocket/SynchSocket/include/core/components/ChannelAware.h create mode 100644 CrossSocket/SynchSocket/include/core/components/FamilyAware.h create mode 100644 CrossSocket/SynchSocket/include/core/components/OpenCapable.h create mode 100644 CrossSocket/SynchSocket/include/core/components/ProtocolAware.h create mode 100644 CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h create mode 100644 CrossSocket/SynchSocket/include/core/components/SendCapable.h rename CrossSocket/{ => SynchSocket}/include/tcp/TcpClient.h (90%) rename CrossSocket/{ => SynchSocket}/include/tcp/TcpServer.h (95%) rename CrossSocket/{include/udp/UdpClient.h => SynchSocket/include/udp/UdpConnection.h} (64%) rename CrossSocket/{ => SynchSocket}/include/udp/UdpServer.h (93%) create mode 100644 CrossSocket/SynchSocket/src/Channel.cpp create mode 100644 CrossSocket/SynchSocket/src/Channel.h rename CrossSocket/{src/core/Core.cpp => SynchSocket/src/Commons.cpp} (63%) rename CrossSocket/{src/core/Core.h => SynchSocket/src/Commons.h} (55%) rename CrossSocket/{ => SynchSocket}/src/Ip.cpp (98%) create mode 100644 CrossSocket/SynchSocket/src/core/Client.cpp create mode 100644 CrossSocket/SynchSocket/src/core/OpenConcrete.cpp rename CrossSocket/{src/core/SocketDecorator.cpp => SynchSocket/src/core/OpenDecorator.cpp} (67%) create mode 100644 CrossSocket/SynchSocket/src/core/Receiver.cpp create mode 100644 CrossSocket/SynchSocket/src/core/Sender.cpp rename CrossSocket/{ => SynchSocket}/src/tcp/TcpClient.cpp (61%) rename CrossSocket/{ => SynchSocket}/src/tcp/TcpServer.cpp (84%) rename CrossSocket/{src/udp/UdpClient.cpp => SynchSocket/src/udp/UdpConnection.cpp} (59%) rename CrossSocket/{ => SynchSocket}/src/udp/UdpServer.cpp (92%) delete mode 100644 CrossSocket/include/Messanger.h delete mode 100644 CrossSocket/include/Socket.h delete mode 100644 CrossSocket/include/core/Client.h delete mode 100644 CrossSocket/include/core/MessangerConcrete.h delete mode 100644 CrossSocket/include/core/SocketConcrete.h delete mode 100644 CrossSocket/include/core/SocketDecorator.h delete mode 100644 CrossSocket/src/core/Client.cpp delete mode 100644 CrossSocket/src/core/MessangerConcrete.cpp delete mode 100644 CrossSocket/src/core/SocketConcrete.cpp create mode 100644 cmake/MakeLibrary.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 9623f6d6..68d558c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,9 +14,11 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") set(WITH_SOURCE_TREE ON) include(GroupSources) include(AutoCollect) +include(MakeLibrary) +project(CrossSocket) add_subdirectory(CrossSocket) + if(BUILD_SAMPLES) add_subdirectory(Samples) endif() - diff --git a/CrossSocket/AsynchSocket/CMakeLists.txt b/CrossSocket/AsynchSocket/CMakeLists.txt new file mode 100644 index 00000000..f5f34354 --- /dev/null +++ b/CrossSocket/AsynchSocket/CMakeLists.txt @@ -0,0 +1,8 @@ +set(PROJECT_SHORTNAME "Asynch-Cross-Socket") + +MakeLibrary(${PROJECT_SHORTNAME} include) + +target_link_libraries(${PROJECT_SHORTNAME} +PUBLIC + Synch-Cross-Socket +) diff --git a/CrossSocket/include/async/AsyncClient.h b/CrossSocket/AsynchSocket/include/AsyncClient.h similarity index 80% rename from CrossSocket/include/async/AsyncClient.h rename to CrossSocket/AsynchSocket/include/AsyncClient.h index b5d9a9a6..cfbdce53 100644 --- a/CrossSocket/include/async/AsyncClient.h +++ b/CrossSocket/AsynchSocket/include/AsyncClient.h @@ -8,9 +8,9 @@ #ifndef _CROSS_SOCKET_ASYNCCLIENT_H #define _CROSS_SOCKET_ASYNCCLIENT_H -#include +#include #include -#include +#include #include namespace sck::async { @@ -20,13 +20,13 @@ namespace sck::async { * or subscribe to the received messages by setting a MessageListener (calling AsyncDecorator::resetListener(...)) */ class AsyncClient - : public AsyncDecorator - , public Sender { + : public AsyncDecorator + , public SendCapable { public: AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); inline bool send(const std::pair& message) final { - return dynamic_cast(this->wrapped.get())->send(message); + return dynamic_cast(this->wrapped.get())->send(message); }; private: diff --git a/CrossSocket/include/async/AsyncDecorator.h b/CrossSocket/AsynchSocket/include/AsyncDecorator.h similarity index 73% rename from CrossSocket/include/async/AsyncDecorator.h rename to CrossSocket/AsynchSocket/include/AsyncDecorator.h index fb6996da..d8ad83ed 100644 --- a/CrossSocket/include/async/AsyncDecorator.h +++ b/CrossSocket/AsynchSocket/include/AsyncDecorator.h @@ -8,20 +8,21 @@ #ifndef _CROSS_SOCKET_ASYNCDECORATOR_H #define _CROSS_SOCKET_ASYNCDECORATOR_H -#include -#include +#include +#include +#include namespace sck::async { template class AsyncDecorator - : public SocketDecorator { + : public OpenDecorator { public: - ~AsyncDecorator() { this->close(); }; + virtual ~AsyncDecorator() override { this->close(); }; /** * @brief Set a new Listener. */ - inline void resetListener(Listener* list) { + inline void resetListener(std::shared_ptr list) { std::lock_guard lk(this->listenerMtx); this->listener = list; }; @@ -29,7 +30,7 @@ namespace sck::async { /** * @brief Set a new error listener. */ - inline void resetErrorListener(listener::ErrorListener* list) { + inline void resetErrorListener(std::shared_ptr list) { std::lock_guard lk(this->errorListenerMtx); this->errorListener = list; if(nullptr != this->service) { @@ -56,18 +57,18 @@ namespace sck::async { }; protected: - explicit AsyncDecorator(std::unique_ptr client) - : SocketDecorator(std::move(client)) { + explicit AsyncDecorator(std::unique_ptr client) + : OpenDecorator(std::move(client)) { }; virtual std::unique_ptr make_service() = 0; std::unique_ptr service; std::mutex listenerMtx; - Listener* listener = nullptr; + std::shared_ptr listener; std::mutex errorListenerMtx; - listener::ErrorListener* errorListener = nullptr; + std::shared_ptr errorListener; }; } diff --git a/CrossSocket/include/async/AsyncTcpServer.h b/CrossSocket/AsynchSocket/include/AsyncTcpServer.h similarity index 81% rename from CrossSocket/include/async/AsyncTcpServer.h rename to CrossSocket/AsynchSocket/include/AsyncTcpServer.h index f74627cf..48395136 100644 --- a/CrossSocket/include/async/AsyncTcpServer.h +++ b/CrossSocket/AsynchSocket/include/AsyncTcpServer.h @@ -8,8 +8,8 @@ #ifndef _CROSS_SOCKET_ASYNCTCPSERVER_H #define _CROSS_SOCKET_ASYNCTCPSERVER_H -#include -#include +#include +#include namespace sck::async { /** @@ -17,7 +17,7 @@ namespace sck::async { * inside a private thread stored by this class. From the outside it is possible to subscribe to the * accepted clients by setting a TcpServerListener (calling AsyncDecorator::resetListener(...)) */ - class AsyncTcpServer : public AsyncDecorator { + class AsyncTcpServer : public AsyncDecorator { public: explicit AsyncTcpServer(std::unique_ptr server); diff --git a/CrossSocket/include/async/Service.h b/CrossSocket/AsynchSocket/include/Service.h similarity index 77% rename from CrossSocket/include/async/Service.h rename to CrossSocket/AsynchSocket/include/Service.h index bf92002e..e1ed1a63 100644 --- a/CrossSocket/include/async/Service.h +++ b/CrossSocket/AsynchSocket/include/Service.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include namespace sck::async { /** @@ -25,15 +25,15 @@ namespace sck::async { Service& operator=(const Service&) = delete; // service is started when building - Service(const std::function& iterativeAction, listener::ErrorListener* list); + Service(const std::function& iterativeAction, ErrorListener* list); // service is stop when destroying ~Service(); - void resetErrorListener(listener::ErrorListener* list); + void resetErrorListener(std::shared_ptr list); private: std::mutex listenerMtx; - listener::ErrorListener* listener; + std::shared_ptr listener; std::atomic_bool loopLife = false; std::thread loop; diff --git a/CrossSocket/include/async/listener/ErrorListener.h b/CrossSocket/AsynchSocket/include/listener/ErrorListener.h similarity index 92% rename from CrossSocket/include/async/listener/ErrorListener.h rename to CrossSocket/AsynchSocket/include/listener/ErrorListener.h index 8f172832..940e3a70 100644 --- a/CrossSocket/include/async/listener/ErrorListener.h +++ b/CrossSocket/AsynchSocket/include/listener/ErrorListener.h @@ -11,7 +11,7 @@ #include #include -namespace sck::async::listener { +namespace sck::async { class ErrorListener { public: virtual void handle(const Error& error) = 0; diff --git a/CrossSocket/include/async/listener/MessageListener.h b/CrossSocket/AsynchSocket/include/listener/MessageListener.h similarity index 91% rename from CrossSocket/include/async/listener/MessageListener.h rename to CrossSocket/AsynchSocket/include/listener/MessageListener.h index 1c7c5112..8431636f 100644 --- a/CrossSocket/include/async/listener/MessageListener.h +++ b/CrossSocket/AsynchSocket/include/listener/MessageListener.h @@ -10,7 +10,7 @@ #include -namespace sck::async::listener { +namespace sck::async { class MessageListener { public: virtual void handle(const std::pair& message) = 0; diff --git a/CrossSocket/include/async/listener/TcpServerListener.h b/CrossSocket/AsynchSocket/include/listener/TcpServerListener.h similarity index 91% rename from CrossSocket/include/async/listener/TcpServerListener.h rename to CrossSocket/AsynchSocket/include/listener/TcpServerListener.h index c7b8cc6f..23cf6851 100644 --- a/CrossSocket/include/async/listener/TcpServerListener.h +++ b/CrossSocket/AsynchSocket/include/listener/TcpServerListener.h @@ -10,7 +10,7 @@ #include -namespace sck::async::listener { +namespace sck::async { class TcpServerListener { public: virtual void handle(std::unique_ptr clientHandler) = 0; diff --git a/CrossSocket/src/async/AsyncClient.cpp b/CrossSocket/AsynchSocket/src/AsyncClient.cpp similarity index 78% rename from CrossSocket/src/async/AsyncClient.cpp rename to CrossSocket/AsynchSocket/src/AsyncClient.cpp index f36898c8..b02895f6 100644 --- a/CrossSocket/src/async/AsyncClient.cpp +++ b/CrossSocket/AsynchSocket/src/AsyncClient.cpp @@ -5,22 +5,22 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include namespace sck::async { AsyncClient::AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity) - : AsyncDecorator(std::move(client)) { + : AsyncDecorator(std::move(client)) { this->buffer.resize(bufferCapacity); }; class AsyncClient::ReceiveService : public Service { public: - ReceiveService(AsyncClient& client, listener::ErrorListener* list) + ReceiveService(AsyncClient& client, ErrorListener* list) : Service([&client](){ client.buffer.resize(client.buffer.capacity()); auto pr = std::make_pair(client.buffer.data(), client.buffer.capacity()); - auto recvBytes = dynamic_cast(client.wrapped.get())->receive(pr, std::chrono::milliseconds(0)); + auto recvBytes = dynamic_cast(client.wrapped.get())->receive(pr, std::chrono::milliseconds(0)); if(recvBytes != client.buffer.capacity()) { client.buffer.resize(recvBytes); } @@ -34,6 +34,6 @@ namespace sck::async { std::unique_ptr AsyncClient::make_service() { std::lock_guard lk(this->errorListenerMtx); - return std::make_unique(*this, this->errorListener); + return std::make_unique(*this, this->errorListener.get()); } } \ No newline at end of file diff --git a/CrossSocket/src/async/AsyncTcpServer.cpp b/CrossSocket/AsynchSocket/src/AsyncTcpServer.cpp similarity index 80% rename from CrossSocket/src/async/AsyncTcpServer.cpp rename to CrossSocket/AsynchSocket/src/AsyncTcpServer.cpp index d9c39a63..a45c441c 100644 --- a/CrossSocket/src/async/AsyncTcpServer.cpp +++ b/CrossSocket/AsynchSocket/src/AsyncTcpServer.cpp @@ -5,16 +5,16 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include namespace sck::async { AsyncTcpServer::AsyncTcpServer(std::unique_ptr server) - : AsyncDecorator(std::move(server)) { + : AsyncDecorator(std::move(server)) { }; class AsyncTcpServer::AcceptanceService : public Service { public: - AcceptanceService(AsyncTcpServer& server, listener::ErrorListener* list) + AcceptanceService(AsyncTcpServer& server, ErrorListener* list) : Service([&server](){ auto client = dynamic_cast(server.wrapped.get())->acceptClient(); std::lock_guard lk(server.listenerMtx); @@ -27,6 +27,6 @@ namespace sck::async { std::unique_ptr AsyncTcpServer::make_service() { std::lock_guard lk(this->errorListenerMtx); - return std::make_unique(*this, this->errorListener); + return std::make_unique(*this, this->errorListener.get()); } } diff --git a/CrossSocket/src/async/Service.cpp b/CrossSocket/AsynchSocket/src/Service.cpp similarity index 91% rename from CrossSocket/src/async/Service.cpp rename to CrossSocket/AsynchSocket/src/Service.cpp index b6498ade..a813f7a7 100644 --- a/CrossSocket/src/async/Service.cpp +++ b/CrossSocket/AsynchSocket/src/Service.cpp @@ -5,10 +5,10 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include namespace sck::async { - Service::Service(const std::function& iterativeAction, listener::ErrorListener* list) + Service::Service(const std::function& iterativeAction, ErrorListener* list) : listener(list) , loop([this, &iterativeAction](){ std::function iter(iterativeAction); @@ -43,7 +43,7 @@ namespace sck::async { this->loop.join(); } - void Service::resetErrorListener(listener::ErrorListener* list) { + void Service::resetErrorListener(std::shared_ptr list) { std::lock_guard lk(this->listenerMtx); this->listener = list; } diff --git a/CrossSocket/CMakeLists.txt b/CrossSocket/CMakeLists.txt index 1067563b..26ad4789 100644 --- a/CrossSocket/CMakeLists.txt +++ b/CrossSocket/CMakeLists.txt @@ -1,43 +1,6 @@ -set(PROJECT_SHORTNAME "Cross-Socket") -project(${PROJECT_SHORTNAME} VERSION ${VERSION} LANGUAGES CXX) -string(REPLACE - _ COMPONENT_NAME ${PROJECT_NAME}) +add_subdirectory(SynchSocket) -CollectSourceFiles(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) - -GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) - -if(LIB_OPT) - add_library(${PROJECT_NAME} SHARED ${SOURCES}) -else() - add_library(${PROJECT_NAME} STATIC ${SOURCES}) -endif() -add_library(SCK::${PROJECT_SHORTNAME} ALIAS ${PROJECT_NAME}) - -target_compile_features(${PROJECT_NAME} - PUBLIC cxx_auto_type - PRIVATE cxx_variadic_templates -) - -target_include_directories(${PROJECT_NAME} - PUBLIC - $ - $ -) - -if(WIN32) - target_link_libraries(${PROJECT_NAME} - PRIVATE - wsock32 ws2_32 - ) +option(COMPILE_ASYNCH "Compile the asynchronous cross socket package" ON) +if(COMPILE_ASYNCH) +add_subdirectory(AsynchSocket) endif() -find_package(Threads) -target_link_libraries(${PROJECT_NAME} - PUBLIC - ${CMAKE_THREAD_LIBS_INIT} -) - -install(TARGETS ${PROJECT_NAME}) -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include - DESTINATION "./" # target directory -) - diff --git a/CrossSocket/SynchSocket/CMakeLists.txt b/CrossSocket/SynchSocket/CMakeLists.txt new file mode 100644 index 00000000..05ef2418 --- /dev/null +++ b/CrossSocket/SynchSocket/CMakeLists.txt @@ -0,0 +1,15 @@ +set(PROJECT_SHORTNAME "Synch-Cross-Socket") + +MakeLibrary(${PROJECT_SHORTNAME} include) + +if(WIN32) + target_link_libraries(${PROJECT_SHORTNAME} + PRIVATE + wsock32 ws2_32 + ) +endif() +find_package(Threads) +target_link_libraries(${PROJECT_SHORTNAME} + PUBLIC + ${CMAKE_THREAD_LIBS_INIT} +) diff --git a/CrossSocket/include/Error.h b/CrossSocket/SynchSocket/include/Error.h similarity index 100% rename from CrossSocket/include/Error.h rename to CrossSocket/SynchSocket/include/Error.h diff --git a/CrossSocket/include/Ip.h b/CrossSocket/SynchSocket/include/Ip.h similarity index 85% rename from CrossSocket/include/Ip.h rename to CrossSocket/SynchSocket/include/Ip.h index 6d09b5fa..68016b57 100644 --- a/CrossSocket/include/Ip.h +++ b/CrossSocket/SynchSocket/include/Ip.h @@ -12,7 +12,9 @@ #include namespace sck { - // refer to https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzab6/address.htm + /** + * @brief The address family. Refer to https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzab6/address.htm + */ enum Family {IP_V4, IP_V6}; class Ip; @@ -29,10 +31,10 @@ namespace sck { Ip& operator=(Ip&&) = delete; /** - * @brief Internally the protocol Family is deduced according to the host content. + * @brief Internally the protocol Family is deduced according to the hostIp content. * @return nullptr if the host is invalid, otherwise a smart pointer storing a usable ip */ - static IpPtr create(const std::string& host, const std::uint16_t& port); + static IpPtr create(const std::string& hostIp, const std::uint16_t& port); /** * @return an ipv4 or ipv6 with localhost as host and the passed port diff --git a/CrossSocket/SynchSocket/include/core/Client.h b/CrossSocket/SynchSocket/include/core/Client.h new file mode 100644 index 00000000..c1a74d06 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/Client.h @@ -0,0 +1,52 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_CLIENT_H_ +#define _CROSS_SOCKET_CLIENT_H_ + +#include +#include +#include + +namespace sck { + class Client + : public OpenConcrete + , public Receiver + , public Sender { + public: + /** + * @brief returns the address of the remote entity connected with this client + */ + inline const sck::Ip& getRemoteAddress() const { return this->remoteAddress; }; + + protected: + /** + * @param[in] the address of the server to hit + */ + explicit Client(const sck::Ip& remoteAddress); + /** + * @param[in] the remote address already connected + * @param[in] an already created handler to steal + */ + Client(const sck::Ip& remoteAddress, std::unique_ptr channel); + + /** + * @brief connect is internally called + */ + void openSpecific() override; + + /** + * @brief address of the entity connected to this socket + */ + sck::Ip remoteAddress; + + private: + inline sck::Family getFamily() const final { return this->remoteAddress.getFamily(); }; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/OpenConcrete.h b/CrossSocket/SynchSocket/include/core/OpenConcrete.h new file mode 100644 index 00000000..b6e5a96e --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/OpenConcrete.h @@ -0,0 +1,52 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_OPENCONCRETE_H_ +#define _CROSS_SOCKET_OPENCONCRETE_H_ + +#include +#include +#include +#include + +namespace sck { + class OpenConcrete + : virtual public ChannelAware + , virtual public OpenCapable + , public FamilyAware + , public ProtocolAware { + public: + virtual ~OpenConcrete() override; + + /** + * @brief When something goes wrong inside the method, close is + * internally called, leaving the object in a closed status. + */ + void open(const std::chrono::milliseconds& timeout) final; + + void close() final; + + bool isOpen() const; + + protected: + explicit OpenConcrete(std::unique_ptr channel); + + /** + * @brief The methods containing the specific steps to perform to fully open a concrete socket + */ + virtual void openSpecific() = 0; + + /** + * @brief The methods containing the specific steps to perform to fully close a concrete socket + */ + virtual void closeSpecific(); + + void bindToPort(const std::uint16_t& port); + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/OpenDecorator.h b/CrossSocket/SynchSocket/include/core/OpenDecorator.h new file mode 100644 index 00000000..bf011a63 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/OpenDecorator.h @@ -0,0 +1,30 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_OPENDECORATOR_H_ +#define _CROSS_SOCKET_OPENDECORATOR_H_ + +#include +#include + +namespace sck { + class OpenDecorator : public OpenCapable { + public: + inline void open(const std::chrono::milliseconds& timeout) override { this->wrapped->open(timeout); }; + + inline void close() override { this->wrapped->close(); }; + + inline bool isOpen() const override { return this->wrapped->isOpen(); }; + + protected: + explicit OpenDecorator(std::unique_ptr wrapped); + + std::unique_ptr wrapped; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/Receiver.h b/CrossSocket/SynchSocket/include/core/Receiver.h new file mode 100644 index 00000000..42eff660 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/Receiver.h @@ -0,0 +1,32 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_RECEIVER_H_ +#define _CROSS_SOCKET_RECEIVER_H_ + +#include +#include +#include + +namespace sck { + class Receiver + : virtual public ChannelAware + , virtual public ReceiveCapable { + public: + std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; + + private: + std::chrono::milliseconds actualTimeOut = std::chrono::milliseconds(0); + + /** + * @brief enforces to call receive from a single thread + */ + std::mutex receiveMtx; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/Sender.h b/CrossSocket/SynchSocket/include/core/Sender.h new file mode 100644 index 00000000..b5d37c17 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/Sender.h @@ -0,0 +1,30 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_SENDER_H_ +#define _CROSS_SOCKET_SENDER_H_ + +#include +#include +#include + +namespace sck { + class Sender + : virtual public ChannelAware + , virtual public SendCapable { + public: + bool send(const std::pair& message) final; + + private: + /** + * @brief enforces to call send from a single thread + */ + std::mutex sendMtx; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/ChannelAware.h b/CrossSocket/SynchSocket/include/core/components/ChannelAware.h new file mode 100644 index 00000000..094ad515 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/components/ChannelAware.h @@ -0,0 +1,27 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_CHANNELAWARE_H_ +#define _CROSS_SOCKET_CHANNELAWARE_H_ + +#include + +namespace sck { + class Channel; + + class ChannelAware { + public: + virtual ~ChannelAware() = default; + + protected: + ChannelAware() = default; + + std::unique_ptr channel; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/FamilyAware.h b/CrossSocket/SynchSocket/include/core/components/FamilyAware.h new file mode 100644 index 00000000..7243bc6f --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/components/FamilyAware.h @@ -0,0 +1,28 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_FAMILYAWARE_H_ +#define _CROSS_SOCKET_FAMILYAWARE_H_ + +#include + +namespace sck { + class FamilyAware { + public: + virtual ~FamilyAware() = default; + + protected: + FamilyAware() = default; + + /** + * @brief These values should be internally deduced from object to object + */ + virtual sck::Family getFamily() const = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/OpenCapable.h b/CrossSocket/SynchSocket/include/core/components/OpenCapable.h new file mode 100644 index 00000000..12e8bc2f --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/components/OpenCapable.h @@ -0,0 +1,36 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_OPENCAPABLE_H_ +#define _CROSS_SOCKET_OPENCAPABLE_H_ + +#include + +namespace sck { + class OpenCapable { + public: + virtual ~OpenCapable() = default; + + /** + * @brief Tries to open the object, until the passed timeout, after which the object spontaneously close itself. + * @param timeout to assume for the open operation. When passing 0, an infite timeout is assumed + */ + virtual void open(const std::chrono::milliseconds& timeout) = 0; + + virtual void close() = 0; + + /** + * @return true only if a previous successfull call to open was done + */ + virtual bool isOpen() const = 0; + + protected: + OpenCapable() = default; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/ProtocolAware.h b/CrossSocket/SynchSocket/include/core/components/ProtocolAware.h new file mode 100644 index 00000000..c8850b22 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/components/ProtocolAware.h @@ -0,0 +1,25 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_PROTOCOLAWARE_H_ +#define _CROSS_SOCKET_PROTOCOLAWARE_H_ + +namespace sck { + enum Protocol { UDP, TCP }; + + class ProtocolAware { + public: + virtual ~ProtocolAware() = default; + + protected: + ProtocolAware() = default; + + virtual sck::Protocol getProtocol() const = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h b/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h new file mode 100644 index 00000000..ffe9c54c --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h @@ -0,0 +1,34 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_RECEIVECAPABLE_H_ +#define _CROSS_SOCKET_RECEIVECAPABLE_H_ + +#include +#include + +namespace sck { + class ReceiveCapable { + public: + virtual ~ReceiveCapable() = default; + + /** + * @param[in] the buffer that will receive the message: + * first element of the pair is the data pointer of the buffer + * second element of the pair is the buffer size + * A request to receive a message of maximal size equal to message.second will be forwarded to the socket api. + * @param[in] the timeout to consider + * @return the number of received bytes actually received and copied into message (can be also less than the buffer size) + */ + virtual std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) = 0; + + protected: + ReceiveCapable() = default; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/SendCapable.h b/CrossSocket/SynchSocket/include/core/components/SendCapable.h new file mode 100644 index 00000000..da04da2b --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/components/SendCapable.h @@ -0,0 +1,30 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_SENDCAPABLE_H_ +#define _CROSS_SOCKET_SENDCAPABLE_H_ + +#include +#include + +namespace sck { + class SendCapable { + public: + virtual ~SendCapable() = default; + + /** + * @return true if the message was completely sent + * @param[in] the message to send + */ + virtual bool send(const std::pair& message) = 0; + + protected: + SendCapable() = default; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/include/tcp/TcpClient.h b/CrossSocket/SynchSocket/include/tcp/TcpClient.h similarity index 90% rename from CrossSocket/include/tcp/TcpClient.h rename to CrossSocket/SynchSocket/include/tcp/TcpClient.h index 0960f224..4e89e654 100644 --- a/CrossSocket/include/tcp/TcpClient.h +++ b/CrossSocket/SynchSocket/include/tcp/TcpClient.h @@ -25,7 +25,7 @@ namespace sck::tcp { explicit TcpClient(const sck::Ip& remoteAddress); protected: - TcpClient(const sck::Ip& remoteAddress, std::shared_ptr channel); + TcpClient(const sck::Ip& remoteAddress, std::unique_ptr channel); private: inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; diff --git a/CrossSocket/include/tcp/TcpServer.h b/CrossSocket/SynchSocket/include/tcp/TcpServer.h similarity index 95% rename from CrossSocket/include/tcp/TcpServer.h rename to CrossSocket/SynchSocket/include/tcp/TcpServer.h index 19a053d1..85708cf7 100644 --- a/CrossSocket/include/tcp/TcpServer.h +++ b/CrossSocket/SynchSocket/include/tcp/TcpServer.h @@ -8,6 +8,7 @@ #ifndef _CROSS_SOCKET_TCPSERVER_H_ #define _CROSS_SOCKET_TCPSERVER_H_ +#include #include namespace sck::tcp { @@ -16,7 +17,7 @@ namespace sck::tcp { * When calling open, the server binds and listen to the port, in order to be later ready to accept clients */ class TcpServer - : public SocketConcrete { + : public OpenConcrete { public: /** * @param[in] the port to reserve diff --git a/CrossSocket/include/udp/UdpClient.h b/CrossSocket/SynchSocket/include/udp/UdpConnection.h similarity index 64% rename from CrossSocket/include/udp/UdpClient.h rename to CrossSocket/SynchSocket/include/udp/UdpConnection.h index ecf1258b..8e731912 100644 --- a/CrossSocket/include/udp/UdpClient.h +++ b/CrossSocket/SynchSocket/include/udp/UdpConnection.h @@ -5,27 +5,29 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_UDPCLIENT_H_ -#define _CROSS_SOCKET_UDPCLIENT_H_ +#ifndef _CROSS_SOCKET_UDPCONNECTION_H_ +#define _CROSS_SOCKET_UDPCONNECTION_H_ #include namespace sck::udp { - // refer to https://en.wikipedia.org/wiki/User_Datagram_Protocol#:~:text=The%20field%20size%20sets%20a,−%2020%20byte%20IP%20header). + /** + * @brief refer to https://en.wikipedia.org/wiki/User_Datagram_Protocol#:~:text=The%20field%20size%20sets%20a,−%2020%20byte%20IP%20header). + */ constexpr std::size_t MAX_UDP_RECV_MESSAGE = 65507; /** * @brief interface for a standard udp connection. */ - class UdpClient + class UdpConnection : public Client { public: /** @param[in] Address of the remote host to hit @param[in] port to reserve (passing 0 a random port is reserved) */ - explicit UdpClient(const sck::Ip& remoteAddress, const std::uint16_t& localPort = 0); + explicit UdpConnection(const sck::Ip& remoteAddress, const std::uint16_t& localPort = 0); protected: std::uint16_t port; diff --git a/CrossSocket/include/udp/UdpServer.h b/CrossSocket/SynchSocket/include/udp/UdpServer.h similarity index 93% rename from CrossSocket/include/udp/UdpServer.h rename to CrossSocket/SynchSocket/include/udp/UdpServer.h index fe3d2c9b..3f88692a 100644 --- a/CrossSocket/include/udp/UdpServer.h +++ b/CrossSocket/SynchSocket/include/udp/UdpServer.h @@ -8,7 +8,7 @@ #ifndef _CROSS_SOCKET_UDPSERVER_H_ #define _CROSS_SOCKET_UDPSERVER_H_ -#include +#include namespace sck::udp { /** @@ -18,7 +18,7 @@ namespace sck::udp { * IMPORTANT!!! The first message sent to the server will be lost. */ class UdpServer - : public UdpClient { + : public UdpConnection { public: /** * @param[in] the port to reserve diff --git a/CrossSocket/SynchSocket/src/Channel.cpp b/CrossSocket/SynchSocket/src/Channel.cpp new file mode 100644 index 00000000..1516aa80 --- /dev/null +++ b/CrossSocket/SynchSocket/src/Channel.cpp @@ -0,0 +1,109 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include "Channel.h" +#include + +namespace sck { +#ifdef _WIN32 + std::size_t Channel::SocketHandlerFactory::handlerCounter = 0; + std::mutex Channel::SocketHandlerFactory::handlerCounterMtx; + + void Channel::SocketHandlerFactory::beforeOpen() { + std::lock_guard hndLck(handlerCounterMtx); + ++handlerCounter; + if (1 == handlerCounter) { + // first socket opened + WSADATA wsa; + WSAStartup(MAKEWORD(2, 0), &wsa); + } + } + + void Channel::SocketHandlerFactory::afterClose() { + std::lock_guard hndLck(handlerCounterMtx); + --handlerCounter; + if (0 == handlerCounter) { + // last socket closed + WSACleanup(); + } + } +#endif + + Channel::~Channel() { + if (this->opened) { + this->close(); + } + } + + Channel::Channel() + : opened(false) + , hndl(SCK_INVALID_SOCKET) { + } + + Channel::Channel(const SocketHandler& hndl) + : opened(true) + , hndl(hndl) { +#ifdef _WIN32 + SocketHandlerFactory::beforeOpen(); +#endif + } + + void Channel::open(const Protocol& type, const Family& family) { + if (this->opened) return; +#ifdef _WIN32 + SocketHandlerFactory::beforeOpen(); +#endif + auto toIntFamily = [](const Family& family) -> int { + switch (family) { + case sck::Family::IP_V4: + return AF_INET; + case sck::Family::IP_V6: + return AF_INET6; + default: + throw Error("unknown address family type"); + } + throw Error("unknown address family type"); + }; + + switch (type) { + case Protocol::TCP: + this->hndl = ::socket(toIntFamily(family), SOCK_STREAM, 0); + if (this->hndl == SCK_INVALID_SOCKET) { + this->close(); + throwWithCode("Stream socket could not be created"); + } + break; + case Protocol::UDP: + this->hndl = ::socket(toIntFamily(family), SOCK_DGRAM, 0); + if (this->hndl == SCK_INVALID_SOCKET) { + this->close(); + throwWithCode("DataGram socket could not be created"); + } + break; + default: + throw Error("unknown protocol type"); + } + + this->opened = true; + } + + void Channel::close() { + if (!this->opened) return; +#ifdef _WIN32 + shutdown(this->hndl, 2); + closesocket(this->hndl); +#else + ::shutdown(this->hndl, SHUT_RDWR); + ::close(this->hndl); +#endif + this->opened = false; + this->hndl = SCK_INVALID_SOCKET; +#ifdef _WIN32 + SocketHandlerFactory::afterClose(); +#endif + } +} \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/Channel.h b/CrossSocket/SynchSocket/src/Channel.h new file mode 100644 index 00000000..4a14d6b9 --- /dev/null +++ b/CrossSocket/SynchSocket/src/Channel.h @@ -0,0 +1,75 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_CHANNEL_H_ +#define _CROSS_SOCKET_CHANNEL_H_ + +#include "Commons.h" +#include + +namespace sck { + /** + * An object storing a socket API handler and containing the minimal + * functionalities for interacting with it. + */ + class Channel { + public: + Channel(const Channel&) = delete; + Channel& operator=(const Channel&) = delete; + + /** + * @brief a closed socket is created + */ + Channel(); + + /** + * @brief the passed handler should be already created externally + * by the socket api + */ + explicit Channel(const SocketHandler& hndl); + + ~Channel(); + + /** + * @brief internally creates a new socket + */ + void open(const Protocol& type, const Family& family); + /** + * @brief close and shutdown the current socket + */ + void close(); + + inline bool isOpen() const { return this->opened; }; + + inline const SocketHandler& operator*() const { return this->hndl; }; + + private: + std::atomic_bool opened; + SocketHandler hndl; + +#ifdef _WIN32 + class SocketHandlerFactory { + public: + /** + * @brief If we are about to open the first socket, WSAStartup() is invoked + */ + static void beforeOpen(); + /** + * @brief If we are closing the last socket, WSACleanup() is invoked + */ + static void afterClose(); + + private: + static std::mutex handlerCounterMtx; + static std::size_t handlerCounter; + }; +#endif + }; + +} + +#endif \ No newline at end of file diff --git a/CrossSocket/src/core/Core.cpp b/CrossSocket/SynchSocket/src/Commons.cpp similarity index 63% rename from CrossSocket/src/core/Core.cpp rename to CrossSocket/SynchSocket/src/Commons.cpp index 459f5d70..ed77af35 100644 --- a/CrossSocket/src/core/Core.cpp +++ b/CrossSocket/SynchSocket/src/Commons.cpp @@ -10,34 +10,10 @@ #else #include #endif -#include "Core.h" +#include "Commons.h" #include namespace sck { -#ifdef _WIN32 - std::size_t Handler::SocketHandlerFactory::handlerCounter = 0; - std::mutex Handler::SocketHandlerFactory::handlerCounterMtx; - - void Handler::SocketHandlerFactory::beforeOpen() { - std::lock_guard hndLck(handlerCounterMtx); - ++handlerCounter; - if (1 == handlerCounter) { - // first socket opened - WSADATA wsa; - WSAStartup(MAKEWORD(2, 0), &wsa); - } - } - - void Handler::SocketHandlerFactory::afterClose() { - std::lock_guard hndLck(handlerCounterMtx); - --handlerCounter; - if (0 == handlerCounter) { - // last socket closed - WSACleanup(); - } - } -#endif - int getLastErrorCode() { #ifdef _WIN32 return WSAGetLastError(); @@ -162,78 +138,4 @@ namespace sck { } return sck::Ip::create(ip, port); } - - Handler::~Handler() { - if(this->opened){ - this->close(); - } - } - - Handler::Handler() - : opened(false) - , hndl(SCK_INVALID_SOCKET) { - } - - Handler::Handler(const SocketHandler& hndl) - : opened(true) - , hndl(hndl) { -#ifdef _WIN32 - SocketHandlerFactory::beforeOpen(); -#endif - } - - void Handler::open(const Protocol& type, const Family& family) { - if(this->opened) return; -#ifdef _WIN32 - SocketHandlerFactory::beforeOpen(); -#endif - auto toIntFamily = [](const Family& family) -> int { - switch (family) { - case sck::Family::IP_V4: - return AF_INET; - case sck::Family::IP_V6: - return AF_INET6; - default: - throw Error("unknown address family type"); - } - throw Error("unknown address family type"); - }; - - switch (type) { - case Protocol::TCP: - this->hndl = ::socket(toIntFamily(family), SOCK_STREAM, 0); - if (this->hndl == SCK_INVALID_SOCKET) { - this->close(); - throwWithCode("Stream socket could not be created"); - } - break; - case Protocol::UDP: - this->hndl = ::socket(toIntFamily(family), SOCK_DGRAM, 0); - if (this->hndl == SCK_INVALID_SOCKET) { - this->close(); - throwWithCode("DataGram socket could not be created"); - } - break; - default: - throw Error("unknown protocol type"); - } - - this->opened = true; - } - - void Handler::close() { - if (!this->opened) return; -#ifdef _WIN32 - shutdown(this->hndl, 2); - closesocket(this->hndl); -#else - ::shutdown(this->hndl, SHUT_RDWR); - ::close(this->hndl); -#endif - this->opened = false; - this->hndl = SCK_INVALID_SOCKET; -#ifdef _WIN32 - SocketHandlerFactory::afterClose(); -#endif - } } \ No newline at end of file diff --git a/CrossSocket/src/core/Core.h b/CrossSocket/SynchSocket/src/Commons.h similarity index 55% rename from CrossSocket/src/core/Core.h rename to CrossSocket/SynchSocket/src/Commons.h index e661566d..078d1d06 100644 --- a/CrossSocket/src/core/Core.h +++ b/CrossSocket/SynchSocket/src/Commons.h @@ -5,8 +5,8 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_CORE_H_ -#define _CROSS_SOCKET_CORE_H_ +#ifndef _CROSS_SOCKET_COMMONS_H_ +#define _CROSS_SOCKET_COMMONS_H_ #ifdef _WIN32 #include @@ -25,7 +25,6 @@ #define SCK_SOCKET_ERROR -1 #endif #include -#include #include #include @@ -87,65 +86,6 @@ namespace sck { * deducing the family. */ IpPtr convert(const SocketIp& address); - - /** - * An object storing a socket API handler and containing the minimal - * functionalities for interacting with it. - */ - class Handler { - public: - Handler(const Handler&) = delete; - Handler& operator=(const Handler&) = delete; - - /** - * @brief a closed socket is created - */ - Handler(); - - /** - * @brief the passed handler should be already created externally - * by the socket api - */ - explicit Handler(const SocketHandler& hndl); - - ~Handler(); - - /** - * @brief internally creates a new socket - */ - void open(const Protocol& type, const Family& family); - /** - * @brief close and shutdown the current socket - */ - void close(); - - inline bool isOpen() const { return this->opened; }; - - inline const SocketHandler& operator*() const { return this->hndl; }; - - private: - std::atomic_bool opened; - SocketHandler hndl; - -#ifdef _WIN32 - class SocketHandlerFactory { - public: - /** - * @brief If we are about to open the first socket, WSAStartup() is invoked - */ - static void beforeOpen(); - /** - * @brief If we are closing the last socket, WSACleanup() is invoked - */ - static void afterClose(); - - private: - static std::mutex handlerCounterMtx; - static std::size_t handlerCounter; - }; -#endif - }; - } #endif \ No newline at end of file diff --git a/CrossSocket/src/Ip.cpp b/CrossSocket/SynchSocket/src/Ip.cpp similarity index 98% rename from CrossSocket/src/Ip.cpp rename to CrossSocket/SynchSocket/src/Ip.cpp index c94e9845..9b19977b 100644 --- a/CrossSocket/src/Ip.cpp +++ b/CrossSocket/SynchSocket/src/Ip.cpp @@ -6,7 +6,7 @@ **/ #include -#include "core/Core.h" +#include "Commons.h" namespace sck { static const std::string LOCALHOST_IPv4 = "127.0.0.1"; diff --git a/CrossSocket/SynchSocket/src/core/Client.cpp b/CrossSocket/SynchSocket/src/core/Client.cpp new file mode 100644 index 00000000..7451afd5 --- /dev/null +++ b/CrossSocket/SynchSocket/src/core/Client.cpp @@ -0,0 +1,45 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include "../Channel.h" +#include + +namespace sck { + Client::Client(const sck::Ip& remoteAddress) + : OpenConcrete(std::make_unique()) + , remoteAddress(remoteAddress) { + } + + Client::Client(const sck::Ip& remoteAddress, std::unique_ptr channel) + : OpenConcrete(std::move(channel)) + , remoteAddress(remoteAddress) { + } + + void Client::openSpecific() { + if (sck::Family::IP_V4 == this->getFamily()) { + //v4 family + auto addr = convertIpv4(this->remoteAddress); + if (!addr) { + throw Error(this->remoteAddress.getHost(), ":", std::to_string(this->remoteAddress.getPort()), " is an invalid server address"); + } + if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { + throwWithCode("Connection can't be established"); + } + } + else { + //v6 family + auto addr = convertIpv6(this->remoteAddress); + if (!addr) { + throw Error(this->remoteAddress.getHost(), ":", std::to_string(this->remoteAddress.getPort()), " is an invalid server address"); + } + if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { + throwWithCode("Connection can't be established"); + } + } + } +} \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/core/OpenConcrete.cpp b/CrossSocket/SynchSocket/src/core/OpenConcrete.cpp new file mode 100644 index 00000000..d9cc222d --- /dev/null +++ b/CrossSocket/SynchSocket/src/core/OpenConcrete.cpp @@ -0,0 +1,130 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include "../Channel.h" +#include +#include +#include +#include + +#ifdef _WIN32 +#define REBIND_OPTION SO_REUSEADDR +#else +#define REBIND_OPTION SO_REUSEPORT +#endif + +namespace sck { + OpenConcrete::OpenConcrete(std::unique_ptr channel) { + if (nullptr == channel) { + throw Error("found null channel when building OpenConcrete"); + } + this->channel = std::move(channel); + } + + OpenConcrete::~OpenConcrete() { + if (this->isOpen()) { + this->close(); + } + } + + bool OpenConcrete::isOpen() const { + return this->channel->isOpen(); + }; + + void OpenConcrete::open(const std::chrono::milliseconds& timeout) { + if (this->isOpen()) { + return; + } + + std::atomic_bool stopWait(false); + auto openSteps = [this, &stopWait]() { + try { + this->channel->open(this->getProtocol(), this->getFamily()); + this->openSpecific(); + } + catch (...) { + this->close(); + stopWait = true; + return; + } + stopWait = true; + }; + + if (0 == timeout.count()) { + openSteps(); + } + else { + std::condition_variable notification; + std::mutex notificationMtx; + std::thread opener(openSteps); + + std::unique_lock notificationLck(notificationMtx); + notification.wait_for(notificationLck, timeout, [&stopWait]() { return static_cast(stopWait); }); + if (!this->isOpen()) { + this->close(); + } + } + } + + void OpenConcrete::close() { + if (!this->isOpen()) { + return; + } + try { + this->closeSpecific(); + } + catch (...) { + } + } + + void OpenConcrete::closeSpecific() { + this->channel->close(); + } + + void OpenConcrete::bindToPort(const std::uint16_t& port) { + int reusePortOptVal = 1; + ::setsockopt(**this->channel, SOL_SOCKET, REBIND_OPTION, reinterpret_cast(&reusePortOptVal), sizeof(int)); + + // bind the server to the port + auto fam = this->getFamily(); + if (sck::Family::IP_V4 == fam) { + SocketIp4 addr; + ::memset(&addr, 0, sizeof(SocketIp4)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); +#ifdef _WIN32 + addr.sin_addr.s_addr = ADDR_ANY; +#else + addr.sin_addr.s_addr = htonl(INADDR_ANY); +#endif + if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { + throwWithCode("can't bind localhost on port: " + std::to_string(port)); + } + } + else if (sck::Family::IP_V6 == fam) { + SocketIp6 addr; + ::memset(&addr, 0, sizeof(SocketIp6)); + addr.sin6_family = AF_INET6; + addr.sin6_flowinfo = 0; + addr.sin6_addr = IN6ADDR_ANY_INIT; // apparently, there is no such a cross-system define for ipv4 addresses + addr.sin6_port = htons(port); + if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { + throwWithCode("can't bind localhost on port: " + std::to_string(port)); + } + } + else { + throw Error("found an unrecognized family type when binding the socket"); + } + } +} diff --git a/CrossSocket/src/core/SocketDecorator.cpp b/CrossSocket/SynchSocket/src/core/OpenDecorator.cpp similarity index 67% rename from CrossSocket/src/core/SocketDecorator.cpp rename to CrossSocket/SynchSocket/src/core/OpenDecorator.cpp index a897114e..f23f78c2 100644 --- a/CrossSocket/src/core/SocketDecorator.cpp +++ b/CrossSocket/SynchSocket/src/core/OpenDecorator.cpp @@ -5,14 +5,14 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include namespace sck { - SocketDecorator::SocketDecorator(std::unique_ptr wrapped) { - if(nullptr == wrapped) { + OpenDecorator::OpenDecorator(std::unique_ptr wrapped) { + if (nullptr == wrapped) { throw Error("passed null wrapped when building socket decorator"); } this->wrapped = std::move(wrapped); } -} \ No newline at end of file +} diff --git a/CrossSocket/SynchSocket/src/core/Receiver.cpp b/CrossSocket/SynchSocket/src/core/Receiver.cpp new file mode 100644 index 00000000..02b21455 --- /dev/null +++ b/CrossSocket/SynchSocket/src/core/Receiver.cpp @@ -0,0 +1,45 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include "../Channel.h" + +namespace sck { + std::size_t Receiver::receive(std::pair& message, const std::chrono::milliseconds& timeout) { + std::lock_guard recvLock(this->receiveMtx); + if (timeout.count() != this->actualTimeOut.count()) { + //set new timeout + this->actualTimeOut = timeout; +#ifdef _WIN32 + auto tv = DWORD(this->actualTimeOut.count()); + if (setsockopt(**this->channel, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { +#else + struct timeval tv = { 0,0 }; + if (this->actualTimeOut.count() >= 1000) { + tv.tv_sec = std::chrono::duration_cast(this->actualTimeOut).count(); + } + else { + tv.tv_usec = std::chrono::duration_cast(this->actualTimeOut).count(); + } + if (::setsockopt(**this->channelMsg, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(struct timeval)) < 0) { +#endif + throwWithCode("can't set timeout"); + } + } + + int recvBytes = ::recv(**this->channel, message.first, static_cast(message.second), 0); + if (recvBytes == SCK_SOCKET_ERROR) { + recvBytes = 0; + throwWithCode("receive failed"); + } + if (recvBytes > message.second) { + // if here, the message received is probably corrupted + recvBytes = 0; + } + return static_cast(recvBytes); + } +} diff --git a/CrossSocket/SynchSocket/src/core/Sender.cpp b/CrossSocket/SynchSocket/src/core/Sender.cpp new file mode 100644 index 00000000..22d24956 --- /dev/null +++ b/CrossSocket/SynchSocket/src/core/Sender.cpp @@ -0,0 +1,21 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include "../Channel.h" + +namespace sck { + bool Sender::send(const std::pair& message) { + std::lock_guard sendLock(this->sendMtx); + int sentBytes = ::send(**this->channel, message.first, static_cast(message.second), 0); + if (sentBytes == SCK_SOCKET_ERROR) { + sentBytes = 0; + throwWithCode("send failed"); + } + return (sentBytes == static_cast(message.second)); + } +} diff --git a/CrossSocket/src/tcp/TcpClient.cpp b/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp similarity index 61% rename from CrossSocket/src/tcp/TcpClient.cpp rename to CrossSocket/SynchSocket/src/tcp/TcpClient.cpp index c48a7cd4..240d1dff 100644 --- a/CrossSocket/src/tcp/TcpClient.cpp +++ b/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp @@ -6,7 +6,7 @@ **/ #include -#include "../core/Core.h" +#include "../Channel.h" namespace sck::tcp { @@ -14,7 +14,7 @@ namespace sck::tcp { : Client(remoteAddress) { } - TcpClient::TcpClient(const sck::Ip& remoteAddress, std::shared_ptr channel) - : Client(remoteAddress, channel) { + TcpClient::TcpClient(const sck::Ip& remoteAddress, std::unique_ptr channel) + : Client(remoteAddress, std::move(channel)) { } } \ No newline at end of file diff --git a/CrossSocket/src/tcp/TcpServer.cpp b/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp similarity index 84% rename from CrossSocket/src/tcp/TcpServer.cpp rename to CrossSocket/SynchSocket/src/tcp/TcpServer.cpp index baeedf93..5d0d5e41 100644 --- a/CrossSocket/src/tcp/TcpServer.cpp +++ b/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp @@ -7,7 +7,7 @@ #include #include -#include "../core/Core.h" +#include "../Channel.h" #include namespace sck::tcp { @@ -15,8 +15,8 @@ namespace sck::tcp { class ClientHandler : public TcpClient { public: - explicit ClientHandler(const sck::Ip& remoteAddress, std::shared_ptr channel) - : TcpClient(remoteAddress, channel) { + explicit ClientHandler(const sck::Ip& remoteAddress, std::unique_ptr channel) + : TcpClient(remoteAddress, std::move(channel)) { }; ~ClientHandler() override = default; @@ -28,7 +28,7 @@ namespace sck::tcp { }; TcpServer::TcpServer(const std::uint16_t& port, const Family& family) - : SocketConcrete(std::make_shared()) + : OpenConcrete(std::make_unique()) , port(port) , family(family) { } @@ -49,14 +49,14 @@ namespace sck::tcp { if (temp == SCK_INVALID_SOCKET) { throwWithCode("Error: accepting new client"); } - std::shared_ptr acceptedClientHandler = std::make_shared(temp); + std::unique_ptr acceptedClientHandler = std::make_unique(temp); IpPtr remoteAddress = convert(acceptedClientAddress); if (nullptr == remoteAddress) { throw Error("accepted client remote address is not resolvable"); } - return std::make_unique(*remoteAddress, acceptedClientHandler); + return std::make_unique(*remoteAddress, std::move(acceptedClientHandler)); } void TcpServer::openSpecific() { diff --git a/CrossSocket/src/udp/UdpClient.cpp b/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp similarity index 59% rename from CrossSocket/src/udp/UdpClient.cpp rename to CrossSocket/SynchSocket/src/udp/UdpConnection.cpp index 52cbc3ec..7d2937e7 100644 --- a/CrossSocket/src/udp/UdpClient.cpp +++ b/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp @@ -5,17 +5,17 @@ * report any bug to andrecasa91@gmail.com. **/ -#include -#include "../core/Core.h" +#include +#include "../Channel.h" namespace sck::udp { - UdpClient::UdpClient(const sck::Ip& remoteAddress, const std::uint16_t& localPort) + UdpConnection::UdpConnection(const sck::Ip& remoteAddress, const std::uint16_t& localPort) : Client(remoteAddress) , port(localPort) { } - void UdpClient::openSpecific() { + void UdpConnection::openSpecific() { this->bindToPort(this->port); this->Client::openSpecific(); } diff --git a/CrossSocket/src/udp/UdpServer.cpp b/CrossSocket/SynchSocket/src/udp/UdpServer.cpp similarity index 92% rename from CrossSocket/src/udp/UdpServer.cpp rename to CrossSocket/SynchSocket/src/udp/UdpServer.cpp index 4dc2cf53..485ecdf5 100644 --- a/CrossSocket/src/udp/UdpServer.cpp +++ b/CrossSocket/SynchSocket/src/udp/UdpServer.cpp @@ -6,7 +6,7 @@ **/ #include -#include "../core/Core.h" +#include "../Channel.h" #include namespace sck::udp { @@ -22,13 +22,13 @@ namespace sck::udp { } UdpServer::UdpServer(const std::uint16_t& localPort, const sck::Family& protocol) - : UdpClient(getInitialAddress(protocol), localPort) { + : UdpConnection(getInitialAddress(protocol), localPort) { } void UdpServer::openSpecific() { this->bindToPort(this->port); - // receive a message from the client, that from now on will beccome the recognized one. + // receive a message from the client, that from now on will become the recognized one. char bf[MAX_UDP_RECV_MESSAGE]; SocketIp remoteAddr; #ifdef _WIN32 diff --git a/CrossSocket/include/Messanger.h b/CrossSocket/include/Messanger.h deleted file mode 100644 index 8dfbee3b..00000000 --- a/CrossSocket/include/Messanger.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_MESSANGER_H_ -#define _CROSS_SOCKET_MESSANGER_H_ - -#include -#include - -namespace sck { - class Sender { - public: - /** - * @return true if the message was completely sent - * @param[in] the message to send - */ - virtual bool send(const std::pair& message) = 0; - }; - - class Receiver { - public: - /** - * @param[in] the buffer that will receive the message: - * first element of the pair is the data pointer of the buffer - * second element of the pair is the buffer size - * A request to receive a message of maximal size equal to message.second will be forwarded to the socket api. - * @param[in] the timeout to consider - * @return the number of received bytes actually received and copied into message (can be also less than the buffer size) - */ - virtual std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) = 0; - }; - - /** - * @brief The interface providing functionalities for exchanging data - */ - class Messanger - : public Sender - , public Receiver { - public: - Messanger() = default; - Messanger(const Messanger&) = delete; - Messanger& operator=(const Messanger&) = delete; - - virtual ~Messanger() = default; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/Socket.h b/CrossSocket/include/Socket.h deleted file mode 100644 index cdf472f2..00000000 --- a/CrossSocket/include/Socket.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SOCKET_H_ -#define _CROSS_SOCKET_SOCKET_H_ - -#include - -namespace sck { - /** - * @brief The interface every socket must derive from. - */ - class Socket { - public: - Socket() = default; - Socket(const Socket&) = delete; - Socket& operator=(const Socket&) = delete; - - virtual ~Socket() = default; - - virtual void open(const std::chrono::milliseconds& timeout) = 0; - - virtual void close() = 0; - - /** - * @return true only if a previous successfull call to open was done - */ - virtual bool isOpen() const = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/core/Client.h b/CrossSocket/include/core/Client.h deleted file mode 100644 index ecb49f89..00000000 --- a/CrossSocket/include/core/Client.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_CLIENT_H_ -#define _CROSS_SOCKET_CLIENT_H_ - -#include -#include - -namespace sck { - class Client - : public SocketConcrete - , public MessangerConcrete { - public: - /** - * @brief returns the address of the remote entity connected with this socket - */ - inline const sck::Ip& getRemoteAddress() const { return this->remoteAddress; }; - - protected: - /** - * @param[in] the address of the server to hit - */ - explicit Client(const sck::Ip& remoteAddress); - /** - * @param[in] the remote address already connected - * @param[in] an already created handler to steal - */ - Client(const sck::Ip& remoteAddress, std::shared_ptr channel); - - /** - * @brief connect is internally called - */ - void openSpecific() override; - - /** - * @brief address of the entity connected to this socket - */ - sck::Ip remoteAddress; - - private: - inline sck::Family getFamily() const final { return this->remoteAddress.getFamily(); }; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/core/MessangerConcrete.h b/CrossSocket/include/core/MessangerConcrete.h deleted file mode 100644 index 5aed3435..00000000 --- a/CrossSocket/include/core/MessangerConcrete.h +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_MESSANGERCONCRETE_H_ -#define _CROSS_SOCKET_MESSANGERCONCRETE_H_ - -#include -#include -#include - -namespace sck { - class Handler; - - class MessangerConcrete - : public Messanger { - public: - bool send(const std::pair& message) final; - - std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; - - protected: - explicit MessangerConcrete(std::shared_ptr messageChannel); - - private: - std::chrono::milliseconds actualTimeOut = std::chrono::milliseconds(0); - - std::shared_ptr channelMsg; - - // enforces the calling of receive from a single thread - std::mutex receiveMtx; - // enforces the calling of send from a single thread - std::mutex sendMtx; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/core/SocketConcrete.h b/CrossSocket/include/core/SocketConcrete.h deleted file mode 100644 index 472831e5..00000000 --- a/CrossSocket/include/core/SocketConcrete.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SOCKETCONCRETE_H_ -#define _CROSS_SOCKET_SOCKETCONCRETE_H_ - -#include -#include - -namespace sck { - enum Protocol { UDP, TCP }; - - class Handler; - - class SocketConcrete : public Socket { - public: - ~SocketConcrete(); - - /** - * @brief When something goes wrong inside the method, close is - * internally called, leaving the socket in a closed status. - */ - void open(const std::chrono::milliseconds& timeout) final; - - void close() final; - - bool isOpen() const; - - protected: - explicit SocketConcrete(std::shared_ptr channel); - - /** - * @brief The methods containing the specific steps to perform to fully open a concrete socket - */ - virtual void openSpecific() = 0; - - /** - * @brief The methods containing the specific steps to perform to fully close a concrete socket - */ - virtual void closeSpecific(); - - /** - * @brief These values should be internally deduced from object to object - */ - virtual sck::Family getFamily() const = 0; - virtual sck::Protocol getProtocol() const = 0; - - void bindToPort(const std::uint16_t& port); - - std::shared_ptr channel; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/include/core/SocketDecorator.h b/CrossSocket/include/core/SocketDecorator.h deleted file mode 100644 index 4a658a1c..00000000 --- a/CrossSocket/include/core/SocketDecorator.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SOCKETDECORATOR_H_ -#define _CROSS_SOCKET_SOCKETDECORATOR_H_ - -#include -#include - -namespace sck { - class SocketDecorator : public Socket { - public: - inline void open(const std::chrono::milliseconds& timeout) override { this->wrapped->open(timeout); }; - - inline void close() override { this->wrapped->close(); }; - - inline bool isOpen() const override { return this->wrapped->isOpen(); }; - - protected: - explicit SocketDecorator(std::unique_ptr wrapped); - - std::unique_ptr wrapped; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/src/core/Client.cpp b/CrossSocket/src/core/Client.cpp deleted file mode 100644 index b78db472..00000000 --- a/CrossSocket/src/core/Client.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "Core.h" -#include - -namespace sck { - Client::Client(const sck::Ip& remoteAddress) - : SocketConcrete(std::make_shared()) - , MessangerConcrete(this->SocketConcrete::channel) - , remoteAddress(remoteAddress) { - } - - Client::Client(const sck::Ip& remoteAddress, std::shared_ptr channel) - : SocketConcrete(channel) - , MessangerConcrete(this->SocketConcrete::channel) - , remoteAddress(remoteAddress) { - } - - void Client::openSpecific() { - if (sck::Family::IP_V4 == this->getFamily()) { - //v4 family - auto addr = convertIpv4(this->remoteAddress); - if (!addr) { - throw Error(this->remoteAddress.getHost(), ":", std::to_string(this->remoteAddress.getPort()), " is an invalid server address"); - } - if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { - throwWithCode("Connection can't be established"); - } - } - else { - //v6 family - auto addr = convertIpv6(this->remoteAddress); - if (!addr) { - throw Error(this->remoteAddress.getHost(), ":", std::to_string(this->remoteAddress.getPort()), " is an invalid server address"); - } - if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { - throwWithCode("Connection can't be established"); - } - } - } -} \ No newline at end of file diff --git a/CrossSocket/src/core/MessangerConcrete.cpp b/CrossSocket/src/core/MessangerConcrete.cpp deleted file mode 100644 index 703187e2..00000000 --- a/CrossSocket/src/core/MessangerConcrete.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include "Core.h" - -namespace sck { - MessangerConcrete::MessangerConcrete(std::shared_ptr messageChannel) { - if(nullptr == messageChannel) { - throw Error("found null channel when building MessangerConcrete"); - } - this->channelMsg = messageChannel; - } - - bool MessangerConcrete::send(const std::pair& message) { - std::lock_guard sendLock(this->sendMtx); - int sentBytes = ::send(**this->channelMsg, message.first, static_cast(message.second), 0); - if (sentBytes == SCK_SOCKET_ERROR) { - sentBytes = 0; - throwWithCode("send failed"); - } - return (sentBytes == static_cast(message.second)); - } - - std::size_t MessangerConcrete::receive(std::pair& message, const std::chrono::milliseconds& timeout) { - std::lock_guard recvLock(this->receiveMtx); - if (timeout.count() != this->actualTimeOut.count()) { - //set new timeout - this->actualTimeOut = timeout; -#ifdef _WIN32 - auto tv = DWORD(this->actualTimeOut.count()); - if (setsockopt(**this->channelMsg, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { -#else - struct timeval tv = { 0,0 }; - if (this->actualTimeOut.count() >= 1000) { - tv.tv_sec = std::chrono::duration_cast(this->actualTimeOut).count(); - } - else { - tv.tv_usec = std::chrono::duration_cast(this->actualTimeOut).count(); - } - if (::setsockopt(**this->channelMsg, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(struct timeval)) < 0) { -#endif - throwWithCode("can't set timeout"); - } - } - - int recvBytes = ::recv(**this->channelMsg, message.first, static_cast(message.second), 0); - if (recvBytes == SCK_SOCKET_ERROR) { - recvBytes = 0; - throwWithCode("receive failed"); - } - if (recvBytes > message.second) { - // if here, the message received is probably corrupted - recvBytes = 0; - } - return static_cast(recvBytes); - } -} \ No newline at end of file diff --git a/CrossSocket/src/core/SocketConcrete.cpp b/CrossSocket/src/core/SocketConcrete.cpp deleted file mode 100644 index d67f5a91..00000000 --- a/CrossSocket/src/core/SocketConcrete.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "Core.h" -#include -#include -#include - -#ifdef _WIN32 -#define REBIND_OPTION SO_REUSEADDR -#else -#define REBIND_OPTION SO_REUSEPORT -#endif - -namespace sck { - SocketConcrete::SocketConcrete(std::shared_ptr channel) - : channel(channel) { - if (nullptr == this->channel) { - throw Error("found null channel when building SocketConcrete"); - } - } - - SocketConcrete::~SocketConcrete() { - if(this->isOpen()) { - this->close(); - } - } - - bool SocketConcrete::isOpen() const { - return this->channel->isOpen(); - }; - - void SocketConcrete::open(const std::chrono::milliseconds& timeout) { - if (this->isOpen()) { - return; - } - - std::atomic_bool stopWait(false); - auto openSteps = [this, &stopWait]() { - try { - this->channel->open(this->getProtocol(), this->getFamily()); - this->openSpecific(); - } - catch (...) { - this->close(); - stopWait = true; - return; - } - stopWait = true; - }; - - if(0 == timeout.count()) { - openSteps(); - } - else { - std::condition_variable notification; - std::mutex notificationMtx; - std::thread opener(openSteps); - - std::unique_lock notificationLck(notificationMtx); - notification.wait_for(notificationLck, timeout, [&stopWait](){ return static_cast(stopWait); }); - if(!this->isOpen()) { - this->close(); - } - } - } - - void SocketConcrete::close() { - if (!this->isOpen()) { - return; - } - try { - this->closeSpecific(); - } - catch (...) { - } - } - - void SocketConcrete::closeSpecific() { - this->channel->close(); - } - - void SocketConcrete::bindToPort(const std::uint16_t& port) { - int reusePortOptVal = 1; - ::setsockopt(**this->channel, SOL_SOCKET, REBIND_OPTION, reinterpret_cast(&reusePortOptVal), sizeof(int)); - - // bind the server to the port - auto fam = this->getFamily(); - if (sck::Family::IP_V4 == fam) { - SocketIp4 addr; - ::memset(&addr, 0, sizeof(SocketIp4)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); -#ifdef _WIN32 - addr.sin_addr.s_addr = ADDR_ANY; -#else - addr.sin_addr.s_addr = htonl(INADDR_ANY); -#endif - if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { - throwWithCode("can't bind localhost on port: " + std::to_string(port)); - } - } - else if(sck::Family::IP_V6 == fam) { - SocketIp6 addr; - ::memset(&addr, 0, sizeof(SocketIp6)); - addr.sin6_family = AF_INET6; - addr.sin6_flowinfo = 0; - addr.sin6_addr = IN6ADDR_ANY_INIT; // apparently, there is no such a cross-system define for ipv4 addresses - addr.sin6_port = htons(port); - if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { - throwWithCode("can't bind localhost on port: " + std::to_string(port)); - } - } - else { - throw Error("found an unrecognized family type when binding the socket"); - } - } -} \ No newline at end of file diff --git a/cmake/MakeLibrary.cmake b/cmake/MakeLibrary.cmake new file mode 100644 index 00000000..edf7c2d6 --- /dev/null +++ b/cmake/MakeLibrary.cmake @@ -0,0 +1,22 @@ +function(MakeLibrary LIBRARY_NAME INCLUDE_DIR) +CollectSourceFiles(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) +GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) + +if(LIB_OPT) + if (WIN32) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + endif () + + add_library(${LIBRARY_NAME} SHARED ${SOURCES}) +else() + add_library(${LIBRARY_NAME} STATIC ${SOURCES}) +endif() + +target_include_directories(${LIBRARY_NAME} + PUBLIC + $ +) + +install(TARGETS ${LIBRARY_NAME}) +install (DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_DIR}/ DESTINATION include/${LIBRARY_NAME} FILES_MATCHING PATTERN "*.h*") +endfunction() \ No newline at end of file From 727b0557033637b32eea40fb4d93b76e82b2033d Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 2 May 2021 01:15:26 +0200 Subject: [PATCH 048/228] minor changes --- CrossSocket/AsynchSocket/include/{listener => }/ErrorListener.h | 0 CrossSocket/AsynchSocket/include/Service.h | 2 +- CrossSocket/AsynchSocket/include/{ => client}/AsyncClient.h | 2 +- .../AsynchSocket/include/{listener => client}/MessageListener.h | 0 .../AsynchSocket/include/{ => tcpServer}/AsyncTcpServer.h | 2 +- .../include/{listener => tcpServer}/TcpServerListener.h | 0 CrossSocket/AsynchSocket/src/{ => client}/AsyncClient.cpp | 2 +- CrossSocket/AsynchSocket/src/{ => tcpServer}/AsyncTcpServer.cpp | 2 +- 8 files changed, 5 insertions(+), 5 deletions(-) rename CrossSocket/AsynchSocket/include/{listener => }/ErrorListener.h (100%) rename CrossSocket/AsynchSocket/include/{ => client}/AsyncClient.h (96%) rename CrossSocket/AsynchSocket/include/{listener => client}/MessageListener.h (100%) rename CrossSocket/AsynchSocket/include/{ => tcpServer}/AsyncTcpServer.h (95%) rename CrossSocket/AsynchSocket/include/{listener => tcpServer}/TcpServerListener.h (100%) rename CrossSocket/AsynchSocket/src/{ => client}/AsyncClient.cpp (97%) rename CrossSocket/AsynchSocket/src/{ => tcpServer}/AsyncTcpServer.cpp (96%) diff --git a/CrossSocket/AsynchSocket/include/listener/ErrorListener.h b/CrossSocket/AsynchSocket/include/ErrorListener.h similarity index 100% rename from CrossSocket/AsynchSocket/include/listener/ErrorListener.h rename to CrossSocket/AsynchSocket/include/ErrorListener.h diff --git a/CrossSocket/AsynchSocket/include/Service.h b/CrossSocket/AsynchSocket/include/Service.h index e1ed1a63..1e97a3d3 100644 --- a/CrossSocket/AsynchSocket/include/Service.h +++ b/CrossSocket/AsynchSocket/include/Service.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include namespace sck::async { /** diff --git a/CrossSocket/AsynchSocket/include/AsyncClient.h b/CrossSocket/AsynchSocket/include/client/AsyncClient.h similarity index 96% rename from CrossSocket/AsynchSocket/include/AsyncClient.h rename to CrossSocket/AsynchSocket/include/client/AsyncClient.h index cfbdce53..438f1b0d 100644 --- a/CrossSocket/AsynchSocket/include/AsyncClient.h +++ b/CrossSocket/AsynchSocket/include/client/AsyncClient.h @@ -10,7 +10,7 @@ #include #include -#include +#include #include namespace sck::async { diff --git a/CrossSocket/AsynchSocket/include/listener/MessageListener.h b/CrossSocket/AsynchSocket/include/client/MessageListener.h similarity index 100% rename from CrossSocket/AsynchSocket/include/listener/MessageListener.h rename to CrossSocket/AsynchSocket/include/client/MessageListener.h diff --git a/CrossSocket/AsynchSocket/include/AsyncTcpServer.h b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h similarity index 95% rename from CrossSocket/AsynchSocket/include/AsyncTcpServer.h rename to CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h index 48395136..3f67b314 100644 --- a/CrossSocket/AsynchSocket/include/AsyncTcpServer.h +++ b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h @@ -9,7 +9,7 @@ #define _CROSS_SOCKET_ASYNCTCPSERVER_H #include -#include +#include namespace sck::async { /** diff --git a/CrossSocket/AsynchSocket/include/listener/TcpServerListener.h b/CrossSocket/AsynchSocket/include/tcpServer/TcpServerListener.h similarity index 100% rename from CrossSocket/AsynchSocket/include/listener/TcpServerListener.h rename to CrossSocket/AsynchSocket/include/tcpServer/TcpServerListener.h diff --git a/CrossSocket/AsynchSocket/src/AsyncClient.cpp b/CrossSocket/AsynchSocket/src/client/AsyncClient.cpp similarity index 97% rename from CrossSocket/AsynchSocket/src/AsyncClient.cpp rename to CrossSocket/AsynchSocket/src/client/AsyncClient.cpp index b02895f6..504f9647 100644 --- a/CrossSocket/AsynchSocket/src/AsyncClient.cpp +++ b/CrossSocket/AsynchSocket/src/client/AsyncClient.cpp @@ -5,7 +5,7 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include namespace sck::async { diff --git a/CrossSocket/AsynchSocket/src/AsyncTcpServer.cpp b/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp similarity index 96% rename from CrossSocket/AsynchSocket/src/AsyncTcpServer.cpp rename to CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp index a45c441c..445a8794 100644 --- a/CrossSocket/AsynchSocket/src/AsyncTcpServer.cpp +++ b/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp @@ -5,7 +5,7 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include namespace sck::async { AsyncTcpServer::AsyncTcpServer(std::unique_ptr server) From 6920099923b61a7ca8905fe7677f348d556f2673 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 2 May 2021 21:49:39 +0200 Subject: [PATCH 049/228] implementing new structure --- .gitmodules | 3 + CMakeLists.txt | 9 ++ .../SynchSocket/include/core/BindCapable.h | 23 ++++ CrossSocket/SynchSocket/include/core/Client.h | 52 ------- .../SynchSocket/include/core/Connection.h | 36 +++++ .../include/core/{Receiver.h => Messanger.h} | 17 ++- .../SynchSocket/include/core/OpenDecorator.h | 30 ---- CrossSocket/SynchSocket/include/core/Sender.h | 30 ---- .../include/core/{OpenConcrete.h => Socket.h} | 48 ++++--- .../include/core/components/Closable.h | 18 +++ .../include/core/components/FamilyAware.h | 5 - .../components/{OpenCapable.h => Openable.h} | 13 +- .../include/core/components/ProtocolAware.h | 5 - .../include/core/components/ReceiveCapable.h | 5 - .../core/components/RemoteAddressAware.h | 30 ++++ .../include/core/components/SendCapable.h | 5 - .../SynchSocket/include/tcp/TcpClient.h | 7 +- .../SynchSocket/include/tcp/TcpServer.h | 22 ++- .../SynchSocket/include/udp/UdpConnection.h | 8 +- .../SynchSocket/include/udp/UdpServer.h | 2 +- .../SynchSocket/src/core/BindCapable.cpp | 60 ++++++++ .../src/core/{Client.cpp => Connection.cpp} | 23 ++-- .../src/core/{Receiver.cpp => Messanger.cpp} | 14 +- .../SynchSocket/src/core/OpenConcrete.cpp | 130 ------------------ .../SynchSocket/src/core/OpenDecorator.cpp | 18 --- CrossSocket/SynchSocket/src/core/Sender.cpp | 21 --- CrossSocket/SynchSocket/src/core/Socket.cpp | 86 ++++++++++++ CrossSocket/SynchSocket/src/tcp/TcpClient.cpp | 10 +- CrossSocket/SynchSocket/src/tcp/TcpServer.cpp | 26 ++-- .../SynchSocket/src/udp/UdpConnection.cpp | 6 +- CrossSocket/SynchSocket/src/udp/UdpServer.cpp | 6 +- Samples/CMakeLists.txt | 2 - Samples/Tcp/CMakeLists.txt | 10 ++ ...client.cpp => Sample-01-server-client.cpp} | 0 ...ients.cpp => Sample-02-server-clients.cpp} | 0 ...03-repeater.cpp => Sample-03-repeater.cpp} | 0 ...t.cpp => Sample-04-asyncserver-client.cpp} | 0 Samples/Tcp/{ => applications}/TcpClient.cpp | 0 .../Tcp/{ => applications}/TcpRepeater.cpp | 0 Samples/Tcp/{ => applications}/TcpServer.cpp | 0 .../Tcp/{ => applications}/TcpServerAsync.cpp | 0 Samples/Udp/CMakeLists.txt | 2 + ...client.cpp => Sample-01-client-client.cpp} | 0 ...server.cpp => Sample-02-client-server.cpp} | 0 .../Udp/{ => applications}/UdpClientAsker.cpp | 0 .../{ => applications}/UdpClientResponder.cpp | 0 Samples/Udp/{ => applications}/UdpServer.cpp | 0 Samples/Utils/CMakeLists.txt | 30 ---- Tests/CMakeLists.txt | 20 +++ Tests/GoogleTest | 1 + Tests/Test01-tcp-synch.cpp | 0 Tests/Test02-tcp-asynch.cpp | 0 Utils/CMakeLists.txt | 16 +++ {Samples/Utils => Utils}/include/Asker.h | 0 {Samples/Utils => Utils}/include/Names.h | 0 {Samples/Utils => Utils}/include/Responder.h | 0 .../Utils => Utils}/include/ResponderAsync.h | 10 +- {Samples/Utils => Utils}/src/Asker.cpp | 0 {Samples/Utils => Utils}/src/Names.cpp | 0 {Samples/Utils => Utils}/src/Responder.cpp | 0 .../Utils => Utils}/src/ResponderAsync.cpp | 2 + 61 files changed, 429 insertions(+), 432 deletions(-) create mode 100644 .gitmodules create mode 100644 CrossSocket/SynchSocket/include/core/BindCapable.h delete mode 100644 CrossSocket/SynchSocket/include/core/Client.h create mode 100644 CrossSocket/SynchSocket/include/core/Connection.h rename CrossSocket/SynchSocket/include/core/{Receiver.h => Messanger.h} (60%) delete mode 100644 CrossSocket/SynchSocket/include/core/OpenDecorator.h delete mode 100644 CrossSocket/SynchSocket/include/core/Sender.h rename CrossSocket/SynchSocket/include/core/{OpenConcrete.h => Socket.h} (61%) create mode 100644 CrossSocket/SynchSocket/include/core/components/Closable.h rename CrossSocket/SynchSocket/include/core/components/{OpenCapable.h => Openable.h} (74%) create mode 100644 CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h create mode 100644 CrossSocket/SynchSocket/src/core/BindCapable.cpp rename CrossSocket/SynchSocket/src/core/{Client.cpp => Connection.cpp} (50%) rename CrossSocket/SynchSocket/src/core/{Receiver.cpp => Messanger.cpp} (73%) delete mode 100644 CrossSocket/SynchSocket/src/core/OpenConcrete.cpp delete mode 100644 CrossSocket/SynchSocket/src/core/OpenDecorator.cpp delete mode 100644 CrossSocket/SynchSocket/src/core/Sender.cpp create mode 100644 CrossSocket/SynchSocket/src/core/Socket.cpp rename Samples/Tcp/{Launcher-01-server-client.cpp => Sample-01-server-client.cpp} (100%) rename Samples/Tcp/{Launcher-02-server-clients.cpp => Sample-02-server-clients.cpp} (100%) rename Samples/Tcp/{Launcher-03-repeater.cpp => Sample-03-repeater.cpp} (100%) rename Samples/Tcp/{Launcher-04-asyncserver-client.cpp => Sample-04-asyncserver-client.cpp} (100%) rename Samples/Tcp/{ => applications}/TcpClient.cpp (100%) rename Samples/Tcp/{ => applications}/TcpRepeater.cpp (100%) rename Samples/Tcp/{ => applications}/TcpServer.cpp (100%) rename Samples/Tcp/{ => applications}/TcpServerAsync.cpp (100%) rename Samples/Udp/{Launcher-01-client-client.cpp => Sample-01-client-client.cpp} (100%) rename Samples/Udp/{Launcher-02-client-server.cpp => Sample-02-client-server.cpp} (100%) rename Samples/Udp/{ => applications}/UdpClientAsker.cpp (100%) rename Samples/Udp/{ => applications}/UdpClientResponder.cpp (100%) rename Samples/Udp/{ => applications}/UdpServer.cpp (100%) delete mode 100644 Samples/Utils/CMakeLists.txt create mode 100644 Tests/CMakeLists.txt create mode 160000 Tests/GoogleTest create mode 100644 Tests/Test01-tcp-synch.cpp create mode 100644 Tests/Test02-tcp-asynch.cpp create mode 100644 Utils/CMakeLists.txt rename {Samples/Utils => Utils}/include/Asker.h (100%) rename {Samples/Utils => Utils}/include/Names.h (100%) rename {Samples/Utils => Utils}/include/Responder.h (100%) rename {Samples/Utils => Utils}/include/ResponderAsync.h (78%) rename {Samples/Utils => Utils}/src/Asker.cpp (100%) rename {Samples/Utils => Utils}/src/Names.cpp (100%) rename {Samples/Utils => Utils}/src/Responder.cpp (100%) rename {Samples/Utils => Utils}/src/ResponderAsync.cpp (97%) diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..0a6a6211 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Tests/GoogleTest"] + path = Tests/GoogleTest + url = https://github.com/google/googletest diff --git a/CMakeLists.txt b/CMakeLists.txt index 68d558c3..07a9dc5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) option(LIB_OPT "Compile shared libraries (ON) or static (OFF)" OFF) option(BUILD_SAMPLES "Build the samples showing how to use EFG" ON) +option(BUILD_TESTS "" OFF) # set macro-directory and find scripts set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") @@ -19,6 +20,14 @@ include(MakeLibrary) project(CrossSocket) add_subdirectory(CrossSocket) +if(BUILD_SAMPLES OR BUILD_TESTS) + add_subdirectory(Utils) +endif() + if(BUILD_SAMPLES) add_subdirectory(Samples) endif() + +if(BUILD_TESTS) + add_subdirectory(Tests) +endif() diff --git a/CrossSocket/SynchSocket/include/core/BindCapable.h b/CrossSocket/SynchSocket/include/core/BindCapable.h new file mode 100644 index 00000000..06e0dcb8 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/BindCapable.h @@ -0,0 +1,23 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_BINDCAPABLE_H_ +#define _CROSS_SOCKET_BINDCAPABLE_H_ + +#include +#include + +namespace sck { + class BindCapable + : virtual public ChannelAware + , virtual public FamilyAware { + protected: + void bindToPort(const std::uint16_t& port); + }; +} + +#endif diff --git a/CrossSocket/SynchSocket/include/core/Client.h b/CrossSocket/SynchSocket/include/core/Client.h deleted file mode 100644 index c1a74d06..00000000 --- a/CrossSocket/SynchSocket/include/core/Client.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_CLIENT_H_ -#define _CROSS_SOCKET_CLIENT_H_ - -#include -#include -#include - -namespace sck { - class Client - : public OpenConcrete - , public Receiver - , public Sender { - public: - /** - * @brief returns the address of the remote entity connected with this client - */ - inline const sck::Ip& getRemoteAddress() const { return this->remoteAddress; }; - - protected: - /** - * @param[in] the address of the server to hit - */ - explicit Client(const sck::Ip& remoteAddress); - /** - * @param[in] the remote address already connected - * @param[in] an already created handler to steal - */ - Client(const sck::Ip& remoteAddress, std::unique_ptr channel); - - /** - * @brief connect is internally called - */ - void openSpecific() override; - - /** - * @brief address of the entity connected to this socket - */ - sck::Ip remoteAddress; - - private: - inline sck::Family getFamily() const final { return this->remoteAddress.getFamily(); }; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/Connection.h b/CrossSocket/SynchSocket/include/core/Connection.h new file mode 100644 index 00000000..de22ea48 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/Connection.h @@ -0,0 +1,36 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_CONNECTION_H_ +#define _CROSS_SOCKET_CONNECTION_H_ + +#include +#include +#include + +namespace sck { + class Connection + : public SocketOpenable + , public Messanger + , public RemoteAddressAware { + protected: + /** + * @param[in] the address of the server to hit + */ + explicit Connection(const sck::Ip& remoteAddress); + + /** + * @brief connect is internally called + */ + void openSteps() override; + + private: + inline sck::Family getFamily() const final { return this->remoteAddress->getFamily(); }; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/Receiver.h b/CrossSocket/SynchSocket/include/core/Messanger.h similarity index 60% rename from CrossSocket/SynchSocket/include/core/Receiver.h rename to CrossSocket/SynchSocket/include/core/Messanger.h index 42eff660..2eaaf12c 100644 --- a/CrossSocket/SynchSocket/include/core/Receiver.h +++ b/CrossSocket/SynchSocket/include/core/Messanger.h @@ -5,20 +5,24 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_RECEIVER_H_ -#define _CROSS_SOCKET_RECEIVER_H_ +#ifndef _CROSS_SOCKET_MESSANGER_H_ +#define _CROSS_SOCKET_MESSANGER_H_ #include #include +#include #include namespace sck { - class Receiver + class Messanger : virtual public ChannelAware - , virtual public ReceiveCapable { + , virtual public ReceiveCapable + , virtual public SendCapable { public: std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; + bool send(const std::pair& message) final; + private: std::chrono::milliseconds actualTimeOut = std::chrono::milliseconds(0); @@ -26,6 +30,11 @@ namespace sck { * @brief enforces to call receive from a single thread */ std::mutex receiveMtx; + + /** + * @brief enforces to call send from a single thread + */ + std::mutex sendMtx; }; } diff --git a/CrossSocket/SynchSocket/include/core/OpenDecorator.h b/CrossSocket/SynchSocket/include/core/OpenDecorator.h deleted file mode 100644 index bf011a63..00000000 --- a/CrossSocket/SynchSocket/include/core/OpenDecorator.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_OPENDECORATOR_H_ -#define _CROSS_SOCKET_OPENDECORATOR_H_ - -#include -#include - -namespace sck { - class OpenDecorator : public OpenCapable { - public: - inline void open(const std::chrono::milliseconds& timeout) override { this->wrapped->open(timeout); }; - - inline void close() override { this->wrapped->close(); }; - - inline bool isOpen() const override { return this->wrapped->isOpen(); }; - - protected: - explicit OpenDecorator(std::unique_ptr wrapped); - - std::unique_ptr wrapped; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/Sender.h b/CrossSocket/SynchSocket/include/core/Sender.h deleted file mode 100644 index b5d37c17..00000000 --- a/CrossSocket/SynchSocket/include/core/Sender.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SENDER_H_ -#define _CROSS_SOCKET_SENDER_H_ - -#include -#include -#include - -namespace sck { - class Sender - : virtual public ChannelAware - , virtual public SendCapable { - public: - bool send(const std::pair& message) final; - - private: - /** - * @brief enforces to call send from a single thread - */ - std::mutex sendMtx; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/OpenConcrete.h b/CrossSocket/SynchSocket/include/core/Socket.h similarity index 61% rename from CrossSocket/SynchSocket/include/core/OpenConcrete.h rename to CrossSocket/SynchSocket/include/core/Socket.h index b6e5a96e..51e4edf9 100644 --- a/CrossSocket/SynchSocket/include/core/OpenConcrete.h +++ b/CrossSocket/SynchSocket/include/core/Socket.h @@ -5,47 +5,57 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_OPENCONCRETE_H_ -#define _CROSS_SOCKET_OPENCONCRETE_H_ +#ifndef _CROSS_SOCKET_SOCKET_H_ +#define _CROSS_SOCKET_SOCKET_H_ #include -#include #include #include +#include +#include +#include namespace sck { - class OpenConcrete + class Socket : virtual public ChannelAware - , virtual public OpenCapable + , public Closable { + public: + virtual ~Socket() override; + + void close() final; + + protected: + explicit Socket(std::unique_ptr channel); + + /** + * @brief The methods containing the specific steps to perform to fully close a concrete socket + */ + virtual void closeSteps(); + }; + + + + class SocketOpenable + : public Socket + , public Openable , public FamilyAware , public ProtocolAware { public: - virtual ~OpenConcrete() override; - /** * @brief When something goes wrong inside the method, close is * internally called, leaving the object in a closed status. */ void open(const std::chrono::milliseconds& timeout) final; - void close() final; - - bool isOpen() const; + bool isOpen() const final; protected: - explicit OpenConcrete(std::unique_ptr channel); + explicit SocketOpenable(std::unique_ptr channel); /** * @brief The methods containing the specific steps to perform to fully open a concrete socket */ - virtual void openSpecific() = 0; - - /** - * @brief The methods containing the specific steps to perform to fully close a concrete socket - */ - virtual void closeSpecific(); - - void bindToPort(const std::uint16_t& port); + virtual void openSteps() = 0; }; } diff --git a/CrossSocket/SynchSocket/include/core/components/Closable.h b/CrossSocket/SynchSocket/include/core/components/Closable.h new file mode 100644 index 00000000..45662ee3 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/components/Closable.h @@ -0,0 +1,18 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_CLOSABLE_H_ +#define _CROSS_SOCKET_CLOSABLE_H_ + +namespace sck { + class Closable { + public: + virtual void close() = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/FamilyAware.h b/CrossSocket/SynchSocket/include/core/components/FamilyAware.h index 7243bc6f..c4452f2d 100644 --- a/CrossSocket/SynchSocket/include/core/components/FamilyAware.h +++ b/CrossSocket/SynchSocket/include/core/components/FamilyAware.h @@ -12,12 +12,7 @@ namespace sck { class FamilyAware { - public: - virtual ~FamilyAware() = default; - protected: - FamilyAware() = default; - /** * @brief These values should be internally deduced from object to object */ diff --git a/CrossSocket/SynchSocket/include/core/components/OpenCapable.h b/CrossSocket/SynchSocket/include/core/components/Openable.h similarity index 74% rename from CrossSocket/SynchSocket/include/core/components/OpenCapable.h rename to CrossSocket/SynchSocket/include/core/components/Openable.h index 12e8bc2f..df53f693 100644 --- a/CrossSocket/SynchSocket/include/core/components/OpenCapable.h +++ b/CrossSocket/SynchSocket/include/core/components/Openable.h @@ -5,31 +5,24 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_OPENCAPABLE_H_ -#define _CROSS_SOCKET_OPENCAPABLE_H_ +#ifndef _CROSS_SOCKET_OPENABLE_H_ +#define _CROSS_SOCKET_OPENABLE_H_ #include namespace sck { - class OpenCapable { + class Openable { public: - virtual ~OpenCapable() = default; - /** * @brief Tries to open the object, until the passed timeout, after which the object spontaneously close itself. * @param timeout to assume for the open operation. When passing 0, an infite timeout is assumed */ virtual void open(const std::chrono::milliseconds& timeout) = 0; - virtual void close() = 0; - /** * @return true only if a previous successfull call to open was done */ virtual bool isOpen() const = 0; - - protected: - OpenCapable() = default; }; } diff --git a/CrossSocket/SynchSocket/include/core/components/ProtocolAware.h b/CrossSocket/SynchSocket/include/core/components/ProtocolAware.h index c8850b22..008263f7 100644 --- a/CrossSocket/SynchSocket/include/core/components/ProtocolAware.h +++ b/CrossSocket/SynchSocket/include/core/components/ProtocolAware.h @@ -12,12 +12,7 @@ namespace sck { enum Protocol { UDP, TCP }; class ProtocolAware { - public: - virtual ~ProtocolAware() = default; - protected: - ProtocolAware() = default; - virtual sck::Protocol getProtocol() const = 0; }; } diff --git a/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h b/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h index ffe9c54c..a74afa8d 100644 --- a/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h +++ b/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h @@ -14,8 +14,6 @@ namespace sck { class ReceiveCapable { public: - virtual ~ReceiveCapable() = default; - /** * @param[in] the buffer that will receive the message: * first element of the pair is the data pointer of the buffer @@ -25,9 +23,6 @@ namespace sck { * @return the number of received bytes actually received and copied into message (can be also less than the buffer size) */ virtual std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) = 0; - - protected: - ReceiveCapable() = default; }; } diff --git a/CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h b/CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h new file mode 100644 index 00000000..4d1e178e --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h @@ -0,0 +1,30 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_REMOTEADDRESSAWARE_H_ +#define _CROSS_SOCKET_REMOTEADDRESSAWARE_H_ + +#include +#include + +namespace sck { + class RemoteAddressAware { + protected: + /** + * @brief returns the address of the remote entity connected with this socket + */ + inline const sck::Ip& getRemoteAddress() const { return *this->remoteAddress; }; + + protected: + /** + * @brief address of the entity connected to this socket + */ + std::unique_ptr remoteAddress; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/SendCapable.h b/CrossSocket/SynchSocket/include/core/components/SendCapable.h index da04da2b..dd79e4ce 100644 --- a/CrossSocket/SynchSocket/include/core/components/SendCapable.h +++ b/CrossSocket/SynchSocket/include/core/components/SendCapable.h @@ -14,16 +14,11 @@ namespace sck { class SendCapable { public: - virtual ~SendCapable() = default; - /** * @return true if the message was completely sent * @param[in] the message to send */ virtual bool send(const std::pair& message) = 0; - - protected: - SendCapable() = default; }; } diff --git a/CrossSocket/SynchSocket/include/tcp/TcpClient.h b/CrossSocket/SynchSocket/include/tcp/TcpClient.h index 4e89e654..01a90919 100644 --- a/CrossSocket/SynchSocket/include/tcp/TcpClient.h +++ b/CrossSocket/SynchSocket/include/tcp/TcpClient.h @@ -8,7 +8,7 @@ #ifndef _CROSS_SOCKET_TCPCLIENT_H_ #define _CROSS_SOCKET_TCPCLIENT_H_ -#include +#include namespace sck::tcp { /** @@ -17,16 +17,13 @@ namespace sck::tcp { * it should be ready to listen and accept this client */ class TcpClient - : public Client { + : public Connection { public: /** * @param[in] the address of the server to reach */ explicit TcpClient(const sck::Ip& remoteAddress); - protected: - TcpClient(const sck::Ip& remoteAddress, std::unique_ptr channel); - private: inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; }; diff --git a/CrossSocket/SynchSocket/include/tcp/TcpServer.h b/CrossSocket/SynchSocket/include/tcp/TcpServer.h index 85708cf7..72753768 100644 --- a/CrossSocket/SynchSocket/include/tcp/TcpServer.h +++ b/CrossSocket/SynchSocket/include/tcp/TcpServer.h @@ -8,16 +8,28 @@ #ifndef _CROSS_SOCKET_TCPSERVER_H_ #define _CROSS_SOCKET_TCPSERVER_H_ -#include -#include +#include +#include +#include +#include namespace sck::tcp { + class TcpClientHandler + : public Socket + , public Messanger + , public RemoteAddressAware { + friend class TcpServer; + protected: + TcpClientHandler(std::unique_ptr channel, const sck::Ip& remoteAddress); + }; + /** * @brief interface for a tcp server. * When calling open, the server binds and listen to the port, in order to be later ready to accept clients */ class TcpServer - : public OpenConcrete { + : public SocketOpenable + , public BindCapable { public: /** * @param[in] the port to reserve @@ -30,10 +42,10 @@ namespace sck::tcp { * returns an interface to use for exchanging data to and from the accepted clients. * This is a blocking operation. */ - std::unique_ptr acceptClient(); + std::unique_ptr acceptClient(); private: - void openSpecific() override; + void openSteps() override; inline sck::Family getFamily() const final { return this->family; }; inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; diff --git a/CrossSocket/SynchSocket/include/udp/UdpConnection.h b/CrossSocket/SynchSocket/include/udp/UdpConnection.h index 8e731912..ebd2f778 100644 --- a/CrossSocket/SynchSocket/include/udp/UdpConnection.h +++ b/CrossSocket/SynchSocket/include/udp/UdpConnection.h @@ -8,7 +8,8 @@ #ifndef _CROSS_SOCKET_UDPCONNECTION_H_ #define _CROSS_SOCKET_UDPCONNECTION_H_ -#include +#include +#include namespace sck::udp { @@ -21,7 +22,8 @@ namespace sck::udp { * @brief interface for a standard udp connection. */ class UdpConnection - : public Client { + : public Connection + , public BindCapable { public: /** @param[in] Address of the remote host to hit @@ -32,7 +34,7 @@ namespace sck::udp { protected: std::uint16_t port; - void openSpecific() override; + void openSteps() override; private: inline sck::Protocol getProtocol() const final { return Protocol::UDP; }; diff --git a/CrossSocket/SynchSocket/include/udp/UdpServer.h b/CrossSocket/SynchSocket/include/udp/UdpServer.h index 3f88692a..70add4ca 100644 --- a/CrossSocket/SynchSocket/include/udp/UdpServer.h +++ b/CrossSocket/SynchSocket/include/udp/UdpServer.h @@ -27,7 +27,7 @@ namespace sck::udp { explicit UdpServer(const std::uint16_t& localPort, const sck::Family& protocol = sck::Family::IP_V4); private: - void openSpecific() final; + void openSteps() final; }; } diff --git a/CrossSocket/SynchSocket/src/core/BindCapable.cpp b/CrossSocket/SynchSocket/src/core/BindCapable.cpp new file mode 100644 index 00000000..bb177025 --- /dev/null +++ b/CrossSocket/SynchSocket/src/core/BindCapable.cpp @@ -0,0 +1,60 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include "../Channel.h" +#include + +#ifdef _WIN32 +#define REBIND_OPTION SO_REUSEADDR +#else +#define REBIND_OPTION SO_REUSEPORT +#endif + +namespace sck { + void BindCapable::bindToPort(const std::uint16_t& port) { + int reusePortOptVal = 1; + ::setsockopt(**this->channel, SOL_SOCKET, REBIND_OPTION, reinterpret_cast(&reusePortOptVal), sizeof(int)); + + // bind the socket to the port + auto fam = this->getFamily(); + if (sck::Family::IP_V4 == fam) { + SocketIp4 addr; + ::memset(&addr, 0, sizeof(SocketIp4)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); +#ifdef _WIN32 + addr.sin_addr.s_addr = ADDR_ANY; +#else + addr.sin_addr.s_addr = htonl(INADDR_ANY); +#endif + if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { + throwWithCode("can't bind localhost on port: " + std::to_string(port)); + } + } + else if (sck::Family::IP_V6 == fam) { + SocketIp6 addr; + ::memset(&addr, 0, sizeof(SocketIp6)); + addr.sin6_family = AF_INET6; + addr.sin6_flowinfo = 0; + addr.sin6_addr = IN6ADDR_ANY_INIT; // apparently, there is no such a cross-system define for ipv4 addresses + addr.sin6_port = htons(port); + if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { + throwWithCode("can't bind localhost on port: " + std::to_string(port)); + } + } + else { + throw Error("found an unrecognized family type when binding the socket"); + } + }; +} diff --git a/CrossSocket/SynchSocket/src/core/Client.cpp b/CrossSocket/SynchSocket/src/core/Connection.cpp similarity index 50% rename from CrossSocket/SynchSocket/src/core/Client.cpp rename to CrossSocket/SynchSocket/src/core/Connection.cpp index 7451afd5..0ed293ce 100644 --- a/CrossSocket/SynchSocket/src/core/Client.cpp +++ b/CrossSocket/SynchSocket/src/core/Connection.cpp @@ -5,27 +5,22 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include "../Channel.h" #include namespace sck { - Client::Client(const sck::Ip& remoteAddress) - : OpenConcrete(std::make_unique()) - , remoteAddress(remoteAddress) { + Connection::Connection(const sck::Ip& remoteAddress) + : SocketOpenable(std::make_unique()) { + this->remoteAddress = std::make_unique(remoteAddress); } - Client::Client(const sck::Ip& remoteAddress, std::unique_ptr channel) - : OpenConcrete(std::move(channel)) - , remoteAddress(remoteAddress) { - } - - void Client::openSpecific() { + void Connection::openSteps() { if (sck::Family::IP_V4 == this->getFamily()) { //v4 family - auto addr = convertIpv4(this->remoteAddress); + auto addr = convertIpv4(*this->remoteAddress); if (!addr) { - throw Error(this->remoteAddress.getHost(), ":", std::to_string(this->remoteAddress.getPort()), " is an invalid server address"); + throw Error(this->remoteAddress->getHost(), ":", std::to_string(this->remoteAddress->getPort()), " is an invalid server address"); } if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { throwWithCode("Connection can't be established"); @@ -33,9 +28,9 @@ namespace sck { } else { //v6 family - auto addr = convertIpv6(this->remoteAddress); + auto addr = convertIpv6(*this->remoteAddress); if (!addr) { - throw Error(this->remoteAddress.getHost(), ":", std::to_string(this->remoteAddress.getPort()), " is an invalid server address"); + throw Error(this->remoteAddress->getHost(), ":", std::to_string(this->remoteAddress->getPort()), " is an invalid server address"); } if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { throwWithCode("Connection can't be established"); diff --git a/CrossSocket/SynchSocket/src/core/Receiver.cpp b/CrossSocket/SynchSocket/src/core/Messanger.cpp similarity index 73% rename from CrossSocket/SynchSocket/src/core/Receiver.cpp rename to CrossSocket/SynchSocket/src/core/Messanger.cpp index 02b21455..88125e25 100644 --- a/CrossSocket/SynchSocket/src/core/Receiver.cpp +++ b/CrossSocket/SynchSocket/src/core/Messanger.cpp @@ -5,11 +5,11 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include "../Channel.h" namespace sck { - std::size_t Receiver::receive(std::pair& message, const std::chrono::milliseconds& timeout) { + std::size_t Messanger::receive(std::pair& message, const std::chrono::milliseconds& timeout) { std::lock_guard recvLock(this->receiveMtx); if (timeout.count() != this->actualTimeOut.count()) { //set new timeout @@ -42,4 +42,14 @@ namespace sck { } return static_cast(recvBytes); } + + bool Messanger::send(const std::pair& message) { + std::lock_guard sendLock(this->sendMtx); + int sentBytes = ::send(**this->channel, message.first, static_cast(message.second), 0); + if (sentBytes == SCK_SOCKET_ERROR) { + sentBytes = 0; + throwWithCode("send failed"); + } + return (sentBytes == static_cast(message.second)); + } } diff --git a/CrossSocket/SynchSocket/src/core/OpenConcrete.cpp b/CrossSocket/SynchSocket/src/core/OpenConcrete.cpp deleted file mode 100644 index d9cc222d..00000000 --- a/CrossSocket/SynchSocket/src/core/OpenConcrete.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "../Channel.h" -#include -#include -#include -#include - -#ifdef _WIN32 -#define REBIND_OPTION SO_REUSEADDR -#else -#define REBIND_OPTION SO_REUSEPORT -#endif - -namespace sck { - OpenConcrete::OpenConcrete(std::unique_ptr channel) { - if (nullptr == channel) { - throw Error("found null channel when building OpenConcrete"); - } - this->channel = std::move(channel); - } - - OpenConcrete::~OpenConcrete() { - if (this->isOpen()) { - this->close(); - } - } - - bool OpenConcrete::isOpen() const { - return this->channel->isOpen(); - }; - - void OpenConcrete::open(const std::chrono::milliseconds& timeout) { - if (this->isOpen()) { - return; - } - - std::atomic_bool stopWait(false); - auto openSteps = [this, &stopWait]() { - try { - this->channel->open(this->getProtocol(), this->getFamily()); - this->openSpecific(); - } - catch (...) { - this->close(); - stopWait = true; - return; - } - stopWait = true; - }; - - if (0 == timeout.count()) { - openSteps(); - } - else { - std::condition_variable notification; - std::mutex notificationMtx; - std::thread opener(openSteps); - - std::unique_lock notificationLck(notificationMtx); - notification.wait_for(notificationLck, timeout, [&stopWait]() { return static_cast(stopWait); }); - if (!this->isOpen()) { - this->close(); - } - } - } - - void OpenConcrete::close() { - if (!this->isOpen()) { - return; - } - try { - this->closeSpecific(); - } - catch (...) { - } - } - - void OpenConcrete::closeSpecific() { - this->channel->close(); - } - - void OpenConcrete::bindToPort(const std::uint16_t& port) { - int reusePortOptVal = 1; - ::setsockopt(**this->channel, SOL_SOCKET, REBIND_OPTION, reinterpret_cast(&reusePortOptVal), sizeof(int)); - - // bind the server to the port - auto fam = this->getFamily(); - if (sck::Family::IP_V4 == fam) { - SocketIp4 addr; - ::memset(&addr, 0, sizeof(SocketIp4)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); -#ifdef _WIN32 - addr.sin_addr.s_addr = ADDR_ANY; -#else - addr.sin_addr.s_addr = htonl(INADDR_ANY); -#endif - if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { - throwWithCode("can't bind localhost on port: " + std::to_string(port)); - } - } - else if (sck::Family::IP_V6 == fam) { - SocketIp6 addr; - ::memset(&addr, 0, sizeof(SocketIp6)); - addr.sin6_family = AF_INET6; - addr.sin6_flowinfo = 0; - addr.sin6_addr = IN6ADDR_ANY_INIT; // apparently, there is no such a cross-system define for ipv4 addresses - addr.sin6_port = htons(port); - if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { - throwWithCode("can't bind localhost on port: " + std::to_string(port)); - } - } - else { - throw Error("found an unrecognized family type when binding the socket"); - } - } -} diff --git a/CrossSocket/SynchSocket/src/core/OpenDecorator.cpp b/CrossSocket/SynchSocket/src/core/OpenDecorator.cpp deleted file mode 100644 index f23f78c2..00000000 --- a/CrossSocket/SynchSocket/src/core/OpenDecorator.cpp +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include - -namespace sck { - OpenDecorator::OpenDecorator(std::unique_ptr wrapped) { - if (nullptr == wrapped) { - throw Error("passed null wrapped when building socket decorator"); - } - this->wrapped = std::move(wrapped); - } -} diff --git a/CrossSocket/SynchSocket/src/core/Sender.cpp b/CrossSocket/SynchSocket/src/core/Sender.cpp deleted file mode 100644 index 22d24956..00000000 --- a/CrossSocket/SynchSocket/src/core/Sender.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "../Channel.h" - -namespace sck { - bool Sender::send(const std::pair& message) { - std::lock_guard sendLock(this->sendMtx); - int sentBytes = ::send(**this->channel, message.first, static_cast(message.second), 0); - if (sentBytes == SCK_SOCKET_ERROR) { - sentBytes = 0; - throwWithCode("send failed"); - } - return (sentBytes == static_cast(message.second)); - } -} diff --git a/CrossSocket/SynchSocket/src/core/Socket.cpp b/CrossSocket/SynchSocket/src/core/Socket.cpp new file mode 100644 index 00000000..d32f343b --- /dev/null +++ b/CrossSocket/SynchSocket/src/core/Socket.cpp @@ -0,0 +1,86 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include "../Channel.h" +#include +#include +#include +#include + +namespace sck { + Socket::Socket(std::unique_ptr channel) { + if (nullptr == channel) { + throw Error("found null channel when building the socket object"); + } + this->channel = std::move(channel); + } + + Socket::~Socket() { + if (this->isOpen()) { + this->close(); + } + } + + void Socket::close() { + if (!this->isOpen()) { + return; + } + try { + this->closeSteps(); + } + catch (...) { + } + } + + void Socket::closeSteps() { + this->channel->close(); + } + + SocketOpenable::SocketOpenable(std::unique_ptr channel) + : Socket(std::move(channel)) { + } + + bool SocketOpenable::isOpen() const { + return this->channel->isOpen(); + }; + + void SocketOpenable::open(const std::chrono::milliseconds& timeout) { + if (this->isOpen()) { + return; + } + + std::atomic_bool stopWait(false); + auto openSteps = [this, &stopWait]() { + try { + this->channel->open(this->getProtocol(), this->getFamily()); + this->openSteps(); + } + catch (...) { + this->close(); + stopWait = true; + return; + } + stopWait = true; + }; + + if (0 == timeout.count()) { + openSteps(); + } + else { + std::condition_variable notification; + std::mutex notificationMtx; + std::thread opener(openSteps); + + std::unique_lock notificationLck(notificationMtx); + notification.wait_for(notificationLck, timeout, [&stopWait]() { return static_cast(stopWait); }); + if (!this->isOpen()) { + this->close(); + } + } + } +} diff --git a/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp b/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp index 240d1dff..f27bc7d3 100644 --- a/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp +++ b/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp @@ -6,15 +6,9 @@ **/ #include -#include "../Channel.h" namespace sck::tcp { - TcpClient::TcpClient(const sck::Ip& remoteAddress) - : Client(remoteAddress) { - } - - TcpClient::TcpClient(const sck::Ip& remoteAddress, std::unique_ptr channel) - : Client(remoteAddress, std::move(channel)) { + : Connection(remoteAddress) { } -} \ No newline at end of file +} diff --git a/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp b/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp index 5d0d5e41..28bbfa15 100644 --- a/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp +++ b/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp @@ -6,34 +6,24 @@ **/ #include -#include #include "../Channel.h" #include namespace sck::tcp { constexpr std::size_t LISTEN_BACKLOG = 50; - class ClientHandler : public TcpClient { - public: - explicit ClientHandler(const sck::Ip& remoteAddress, std::unique_ptr channel) - : TcpClient(remoteAddress, std::move(channel)) { - }; - - ~ClientHandler() override = default; - - private: - void openSpecific() final { - throw Error("connection returned from TcpServer::acceptClient are not re-openable"); - }; - }; + TcpClientHandler::TcpClientHandler(std::unique_ptr channel, const sck::Ip& remoteAddress) + : Socket(std::move(channel)) { + this->remoteAddress = std::make_unique(remoteAddress); + } TcpServer::TcpServer(const std::uint16_t& port, const Family& family) - : OpenConcrete(std::make_unique()) + : SocketOpenable(std::make_unique()) , port(port) , family(family) { } - std::unique_ptr TcpServer::acceptClient() { + std::unique_ptr TcpServer::acceptClient() { if (!this->isOpen()) { throw Error("a tcp server should be opened before accepting a new client"); } @@ -56,10 +46,10 @@ namespace sck::tcp { throw Error("accepted client remote address is not resolvable"); } - return std::make_unique(*remoteAddress, std::move(acceptedClientHandler)); + return std::make_unique(std::move(acceptedClientHandler), *remoteAddress); } - void TcpServer::openSpecific() { + void TcpServer::openSteps() { this->bindToPort(this->port); // listen to the port to allow all the following clients acceptance if (::listen(**this->channel, LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { diff --git a/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp b/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp index 7d2937e7..5ae308e9 100644 --- a/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp +++ b/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp @@ -11,12 +11,12 @@ namespace sck::udp { UdpConnection::UdpConnection(const sck::Ip& remoteAddress, const std::uint16_t& localPort) - : Client(remoteAddress) + : Connection(remoteAddress) , port(localPort) { } - void UdpConnection::openSpecific() { + void UdpConnection::openSteps() { this->bindToPort(this->port); - this->Client::openSpecific(); + this->Connection::openSteps(); } } \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/udp/UdpServer.cpp b/CrossSocket/SynchSocket/src/udp/UdpServer.cpp index 485ecdf5..7ae6284e 100644 --- a/CrossSocket/SynchSocket/src/udp/UdpServer.cpp +++ b/CrossSocket/SynchSocket/src/udp/UdpServer.cpp @@ -25,7 +25,7 @@ namespace sck::udp { : UdpConnection(getInitialAddress(protocol), localPort) { } - void UdpServer::openSpecific() { + void UdpServer::openSteps() { this->bindToPort(this->port); // receive a message from the client, that from now on will become the recognized one. @@ -44,7 +44,7 @@ namespace sck::udp { if(nullptr == remoteConverted) { throw Error(remoteAddr.sa_data, " is an invalid data for udp serer remote address"); } - this->remoteAddress = *remoteConverted; - this->Client::openSpecific(); + this->remoteAddress = std::move(remoteConverted); + this->Connection::openSteps(); } } \ No newline at end of file diff --git a/Samples/CMakeLists.txt b/Samples/CMakeLists.txt index 7b3bf821..ddea8e59 100644 --- a/Samples/CMakeLists.txt +++ b/Samples/CMakeLists.txt @@ -1,5 +1,3 @@ -add_subdirectory(Utils) - add_subdirectory(Tcp) add_subdirectory(Udp) diff --git a/Samples/Tcp/CMakeLists.txt b/Samples/Tcp/CMakeLists.txt index 1a4986cc..dedf7bdf 100644 --- a/Samples/Tcp/CMakeLists.txt +++ b/Samples/Tcp/CMakeLists.txt @@ -1,3 +1,13 @@ +project("Tcp-Samples") + +function(MakeSample NAME) + +endfunction() + +function(MakeInstallableSample NAME) + +endfunction() + add_executable(TcpClient TcpClient.cpp) target_link_libraries(TcpClient Sample-Utils) diff --git a/Samples/Tcp/Launcher-01-server-client.cpp b/Samples/Tcp/Sample-01-server-client.cpp similarity index 100% rename from Samples/Tcp/Launcher-01-server-client.cpp rename to Samples/Tcp/Sample-01-server-client.cpp diff --git a/Samples/Tcp/Launcher-02-server-clients.cpp b/Samples/Tcp/Sample-02-server-clients.cpp similarity index 100% rename from Samples/Tcp/Launcher-02-server-clients.cpp rename to Samples/Tcp/Sample-02-server-clients.cpp diff --git a/Samples/Tcp/Launcher-03-repeater.cpp b/Samples/Tcp/Sample-03-repeater.cpp similarity index 100% rename from Samples/Tcp/Launcher-03-repeater.cpp rename to Samples/Tcp/Sample-03-repeater.cpp diff --git a/Samples/Tcp/Launcher-04-asyncserver-client.cpp b/Samples/Tcp/Sample-04-asyncserver-client.cpp similarity index 100% rename from Samples/Tcp/Launcher-04-asyncserver-client.cpp rename to Samples/Tcp/Sample-04-asyncserver-client.cpp diff --git a/Samples/Tcp/TcpClient.cpp b/Samples/Tcp/applications/TcpClient.cpp similarity index 100% rename from Samples/Tcp/TcpClient.cpp rename to Samples/Tcp/applications/TcpClient.cpp diff --git a/Samples/Tcp/TcpRepeater.cpp b/Samples/Tcp/applications/TcpRepeater.cpp similarity index 100% rename from Samples/Tcp/TcpRepeater.cpp rename to Samples/Tcp/applications/TcpRepeater.cpp diff --git a/Samples/Tcp/TcpServer.cpp b/Samples/Tcp/applications/TcpServer.cpp similarity index 100% rename from Samples/Tcp/TcpServer.cpp rename to Samples/Tcp/applications/TcpServer.cpp diff --git a/Samples/Tcp/TcpServerAsync.cpp b/Samples/Tcp/applications/TcpServerAsync.cpp similarity index 100% rename from Samples/Tcp/TcpServerAsync.cpp rename to Samples/Tcp/applications/TcpServerAsync.cpp diff --git a/Samples/Udp/CMakeLists.txt b/Samples/Udp/CMakeLists.txt index 139e3804..d77a1f28 100644 --- a/Samples/Udp/CMakeLists.txt +++ b/Samples/Udp/CMakeLists.txt @@ -1,3 +1,5 @@ +project("Udp-Samples") + add_executable(UdpClientAsker UdpClientAsker.cpp) target_link_libraries(UdpClientAsker Sample-Utils) diff --git a/Samples/Udp/Launcher-01-client-client.cpp b/Samples/Udp/Sample-01-client-client.cpp similarity index 100% rename from Samples/Udp/Launcher-01-client-client.cpp rename to Samples/Udp/Sample-01-client-client.cpp diff --git a/Samples/Udp/Launcher-02-client-server.cpp b/Samples/Udp/Sample-02-client-server.cpp similarity index 100% rename from Samples/Udp/Launcher-02-client-server.cpp rename to Samples/Udp/Sample-02-client-server.cpp diff --git a/Samples/Udp/UdpClientAsker.cpp b/Samples/Udp/applications/UdpClientAsker.cpp similarity index 100% rename from Samples/Udp/UdpClientAsker.cpp rename to Samples/Udp/applications/UdpClientAsker.cpp diff --git a/Samples/Udp/UdpClientResponder.cpp b/Samples/Udp/applications/UdpClientResponder.cpp similarity index 100% rename from Samples/Udp/UdpClientResponder.cpp rename to Samples/Udp/applications/UdpClientResponder.cpp diff --git a/Samples/Udp/UdpServer.cpp b/Samples/Udp/applications/UdpServer.cpp similarity index 100% rename from Samples/Udp/UdpServer.cpp rename to Samples/Udp/applications/UdpServer.cpp diff --git a/Samples/Utils/CMakeLists.txt b/Samples/Utils/CMakeLists.txt deleted file mode 100644 index 636a71fa..00000000 --- a/Samples/Utils/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -set(PROJECT_SHORTNAME "Sample-Utils") -project(${PROJECT_SHORTNAME} VERSION ${VERSION} LANGUAGES CXX) -string(REPLACE - _ COMPONENT_NAME ${PROJECT_NAME}) - -CollectSourceFiles(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) - -GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) - -if(LIB_OPT) - add_library(${PROJECT_NAME} SHARED ${SOURCES}) -else() - add_library(${PROJECT_NAME} STATIC ${SOURCES}) -endif() - -target_compile_features(${PROJECT_NAME} - PUBLIC cxx_auto_type - PRIVATE cxx_variadic_templates -) - -target_include_directories(${PROJECT_NAME} - PUBLIC - $ - $ -) - -target_link_libraries(${PROJECT_NAME} - PUBLIC - Cross-Socket -) - diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt new file mode 100644 index 00000000..f6494420 --- /dev/null +++ b/Tests/CMakeLists.txt @@ -0,0 +1,20 @@ +if (WIN32) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +endif (WIN32) +add_subdirectory(GoogleTest/) + +function(MakeTest NAME) +add_executable(${NAME} ${NAME}.cpp) + +target_link_libraries(${NAME} +PUBLIC + Utils + gtest +) + +install(TARGETS ${NAME}) +endfunction() + +MakeTest(Test01-tcp-synch) + +# MakeTest(Test02-tcp-asynch) diff --git a/Tests/GoogleTest b/Tests/GoogleTest new file mode 160000 index 00000000..f5e592d8 --- /dev/null +++ b/Tests/GoogleTest @@ -0,0 +1 @@ +Subproject commit f5e592d8ee5ffb1d9af5be7f715ce3576b8bf9c4 diff --git a/Tests/Test01-tcp-synch.cpp b/Tests/Test01-tcp-synch.cpp new file mode 100644 index 00000000..e69de29b diff --git a/Tests/Test02-tcp-asynch.cpp b/Tests/Test02-tcp-asynch.cpp new file mode 100644 index 00000000..e69de29b diff --git a/Utils/CMakeLists.txt b/Utils/CMakeLists.txt new file mode 100644 index 00000000..52ccffd8 --- /dev/null +++ b/Utils/CMakeLists.txt @@ -0,0 +1,16 @@ +set(PROJECT_SHORTNAME "Utils") + +MakeLibrary(${PROJECT_SHORTNAME} include) + +if(COMPILE_ASYNCH) +target_link_libraries(${PROJECT_SHORTNAME} + PUBLIC + Asynch-Cross-Socket +) +target_compile_definitions(${PROJECT_SHORTNAME} PUBLIC ASYNCH_ENABLED) +else() +target_link_libraries(${PROJECT_SHORTNAME} + PUBLIC + Synch-Cross-Socket +) +endif() diff --git a/Samples/Utils/include/Asker.h b/Utils/include/Asker.h similarity index 100% rename from Samples/Utils/include/Asker.h rename to Utils/include/Asker.h diff --git a/Samples/Utils/include/Names.h b/Utils/include/Names.h similarity index 100% rename from Samples/Utils/include/Names.h rename to Utils/include/Names.h diff --git a/Samples/Utils/include/Responder.h b/Utils/include/Responder.h similarity index 100% rename from Samples/Utils/include/Responder.h rename to Utils/include/Responder.h diff --git a/Samples/Utils/include/ResponderAsync.h b/Utils/include/ResponderAsync.h similarity index 78% rename from Samples/Utils/include/ResponderAsync.h rename to Utils/include/ResponderAsync.h index 66375c55..bb00123f 100644 --- a/Samples/Utils/include/ResponderAsync.h +++ b/Utils/include/ResponderAsync.h @@ -8,12 +8,13 @@ #ifndef SAMPLE_RESPONDER_ASYNC_H #define SAMPLE_RESPONDER_ASYNC_H -#include +#ifdef ASYNCH_ENABLED +#include #include -class ResponderAsync - : public sck::async::listener::MessageListener - , public sck::async::listener::ErrorListener { +class ResponderAsync + : public sck::async::MessageListener + , public sck::async::ErrorListener { public: ResponderAsync(std::unique_ptr socket); @@ -29,5 +30,6 @@ class ResponderAsync std::unique_ptr asyncSocket; std::atomic_bool running; }; +#endif #endif \ No newline at end of file diff --git a/Samples/Utils/src/Asker.cpp b/Utils/src/Asker.cpp similarity index 100% rename from Samples/Utils/src/Asker.cpp rename to Utils/src/Asker.cpp diff --git a/Samples/Utils/src/Names.cpp b/Utils/src/Names.cpp similarity index 100% rename from Samples/Utils/src/Names.cpp rename to Utils/src/Names.cpp diff --git a/Samples/Utils/src/Responder.cpp b/Utils/src/Responder.cpp similarity index 100% rename from Samples/Utils/src/Responder.cpp rename to Utils/src/Responder.cpp diff --git a/Samples/Utils/src/ResponderAsync.cpp b/Utils/src/ResponderAsync.cpp similarity index 97% rename from Samples/Utils/src/ResponderAsync.cpp rename to Utils/src/ResponderAsync.cpp index 1fc1a2af..f8ee84ad 100644 --- a/Samples/Utils/src/ResponderAsync.cpp +++ b/Utils/src/ResponderAsync.cpp @@ -5,6 +5,7 @@ * report any bug to andrecasa91@gmail.com. **/ +#ifdef ASYNCH_ENABLED #include ResponderAsync::ResponderAsync(std::unique_ptr socket) { @@ -33,3 +34,4 @@ void ResponderAsync::handle(const std::exception& error) { this->asyncSocket->close(); this->running = false; }; +#endif From 9865593427ef03c52ec347d6f174cf3e8e7ac9f2 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 2 May 2021 22:20:03 +0200 Subject: [PATCH 050/228] minor changes --- CrossSocket/SynchSocket/include/core/Socket.h | 6 ++++-- .../include/core/components/Openable.h | 5 ----- .../include/core/components/StateAware.h | 21 +++++++++++++++++++ .../SynchSocket/include/tcp/TcpServer.h | 18 +++++++++------- CrossSocket/SynchSocket/src/core/Socket.cpp | 8 +++---- CrossSocket/SynchSocket/src/tcp/TcpClient.cpp | 1 + CrossSocket/SynchSocket/src/tcp/TcpServer.cpp | 2 +- 7 files changed, 41 insertions(+), 20 deletions(-) create mode 100644 CrossSocket/SynchSocket/include/core/components/StateAware.h diff --git a/CrossSocket/SynchSocket/include/core/Socket.h b/CrossSocket/SynchSocket/include/core/Socket.h index 51e4edf9..0ec0d6d2 100644 --- a/CrossSocket/SynchSocket/include/core/Socket.h +++ b/CrossSocket/SynchSocket/include/core/Socket.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -18,10 +19,13 @@ namespace sck { class Socket : virtual public ChannelAware + , public StateAware , public Closable { public: virtual ~Socket() override; + bool isOpen() const final; + void close() final; protected: @@ -47,8 +51,6 @@ namespace sck { */ void open(const std::chrono::milliseconds& timeout) final; - bool isOpen() const final; - protected: explicit SocketOpenable(std::unique_ptr channel); diff --git a/CrossSocket/SynchSocket/include/core/components/Openable.h b/CrossSocket/SynchSocket/include/core/components/Openable.h index df53f693..624ff7fd 100644 --- a/CrossSocket/SynchSocket/include/core/components/Openable.h +++ b/CrossSocket/SynchSocket/include/core/components/Openable.h @@ -18,11 +18,6 @@ namespace sck { * @param timeout to assume for the open operation. When passing 0, an infite timeout is assumed */ virtual void open(const std::chrono::milliseconds& timeout) = 0; - - /** - * @return true only if a previous successfull call to open was done - */ - virtual bool isOpen() const = 0; }; } diff --git a/CrossSocket/SynchSocket/include/core/components/StateAware.h b/CrossSocket/SynchSocket/include/core/components/StateAware.h new file mode 100644 index 00000000..d3347501 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/components/StateAware.h @@ -0,0 +1,21 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_STATEAWARE_H_ +#define _CROSS_SOCKET_STATEAWARE_H_ + +namespace sck { + class StateAware { + public: + /** + * @return true only if this object was previously successfully opened + */ + virtual bool isOpen() const = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/tcp/TcpServer.h b/CrossSocket/SynchSocket/include/tcp/TcpServer.h index 72753768..65328949 100644 --- a/CrossSocket/SynchSocket/include/tcp/TcpServer.h +++ b/CrossSocket/SynchSocket/include/tcp/TcpServer.h @@ -14,14 +14,7 @@ #include namespace sck::tcp { - class TcpClientHandler - : public Socket - , public Messanger - , public RemoteAddressAware { - friend class TcpServer; - protected: - TcpClientHandler(std::unique_ptr channel, const sck::Ip& remoteAddress); - }; + class TcpClientHandler; /** * @brief interface for a tcp server. @@ -53,6 +46,15 @@ namespace sck::tcp { std::uint16_t port; sck::Family family; }; + + class TcpClientHandler + : public Socket + , public Messanger + , public RemoteAddressAware { + friend class TcpServer; + protected: + TcpClientHandler(std::unique_ptr channel, const sck::Ip& remoteAddress); + }; } #endif diff --git a/CrossSocket/SynchSocket/src/core/Socket.cpp b/CrossSocket/SynchSocket/src/core/Socket.cpp index d32f343b..a843c36f 100644 --- a/CrossSocket/SynchSocket/src/core/Socket.cpp +++ b/CrossSocket/SynchSocket/src/core/Socket.cpp @@ -20,6 +20,10 @@ namespace sck { this->channel = std::move(channel); } + bool Socket::isOpen() const { + return this->channel->isOpen(); + }; + Socket::~Socket() { if (this->isOpen()) { this->close(); @@ -45,10 +49,6 @@ namespace sck { : Socket(std::move(channel)) { } - bool SocketOpenable::isOpen() const { - return this->channel->isOpen(); - }; - void SocketOpenable::open(const std::chrono::milliseconds& timeout) { if (this->isOpen()) { return; diff --git a/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp b/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp index f27bc7d3..93fac67b 100644 --- a/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp +++ b/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp @@ -6,6 +6,7 @@ **/ #include +#include "../Channel.h" namespace sck::tcp { TcpClient::TcpClient(const sck::Ip& remoteAddress) diff --git a/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp b/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp index 28bbfa15..2a3843cd 100644 --- a/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp +++ b/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp @@ -46,7 +46,7 @@ namespace sck::tcp { throw Error("accepted client remote address is not resolvable"); } - return std::make_unique(std::move(acceptedClientHandler), *remoteAddress); + return std::unique_ptr(new TcpClientHandler(std::move(acceptedClientHandler), *remoteAddress)); } void TcpServer::openSteps() { From 5e12e0f28c1d347d3b13ff73b79672d11769665e Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 3 May 2021 23:06:36 +0200 Subject: [PATCH 051/228] implementing tests --- .../{AsyncDecorator.h => AsyncSocket.h} | 39 +++++++-------- .../AsyncMessanger.h} | 14 +++--- .../MessangerListener.h} | 6 +-- .../include/tcpServer/AsyncTcpServer.h | 4 +- .../include/tcpServer/TcpServerListener.h | 2 +- CrossSocket/AsynchSocket/src/Service.cpp | 1 + .../AsyncMessanger.cpp} | 0 .../src/tcpServer/AsyncTcpServer.cpp | 2 +- .../include/core/SocketDecorator.h | 36 +++++++++++++ .../SynchSocket/src/core/SocketDecorator.cpp | 26 ++++++++++ Utils/CMakeLists.txt | 18 +++---- Utils/include/Asker.h | 26 ++++++---- Utils/include/Logger.h | 50 +++++++++++++++++++ Utils/include/Names.h | 26 +++++----- Utils/include/Responder.h | 24 +++++---- Utils/src/Asker.cpp | 42 ++++++++-------- Utils/src/Logger.cpp | 29 +++++++++++ Utils/src/Names.cpp | 44 ++++++++-------- Utils/src/Responder.cpp | 29 ++++++----- 19 files changed, 287 insertions(+), 131 deletions(-) rename CrossSocket/AsynchSocket/include/{AsyncDecorator.h => AsyncSocket.h} (67%) rename CrossSocket/AsynchSocket/include/{client/AsyncClient.h => messanger/AsyncMessanger.h} (73%) rename CrossSocket/AsynchSocket/include/{client/MessageListener.h => messanger/MessangerListener.h} (69%) rename CrossSocket/AsynchSocket/src/{client/AsyncClient.cpp => messanger/AsyncMessanger.cpp} (100%) create mode 100644 CrossSocket/SynchSocket/include/core/SocketDecorator.h create mode 100644 CrossSocket/SynchSocket/src/core/SocketDecorator.cpp create mode 100644 Utils/include/Logger.h create mode 100644 Utils/src/Logger.cpp diff --git a/CrossSocket/AsynchSocket/include/AsyncDecorator.h b/CrossSocket/AsynchSocket/include/AsyncSocket.h similarity index 67% rename from CrossSocket/AsynchSocket/include/AsyncDecorator.h rename to CrossSocket/AsynchSocket/include/AsyncSocket.h index d8ad83ed..899b275d 100644 --- a/CrossSocket/AsynchSocket/include/AsyncDecorator.h +++ b/CrossSocket/AsynchSocket/include/AsyncSocket.h @@ -5,31 +5,30 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_ASYNCDECORATOR_H -#define _CROSS_SOCKET_ASYNCDECORATOR_H +#ifndef _CROSS_SOCKET_ASYNCSOCKET_H +#define _CROSS_SOCKET_ASYNCSOCKET_H -#include -#include +#include #include namespace sck::async { template - class AsyncDecorator - : public OpenDecorator { + class AsyncSocket + : public SocketDecorator { public: - virtual ~AsyncDecorator() override { this->close(); }; + virtual ~AsyncSocket() override { this->close(); }; - /** - * @brief Set a new Listener. - */ + /** + * @brief Set a new Listener. + */ inline void resetListener(std::shared_ptr list) { std::lock_guard lk(this->listenerMtx); this->listener = list; }; - /** - * @brief Set a new error listener. - */ + /** + * @brief Set a new error listener. + */ inline void resetErrorListener(std::shared_ptr list) { std::lock_guard lk(this->errorListenerMtx); this->errorListener = list; @@ -40,15 +39,10 @@ namespace sck::async { inline void open(const std::chrono::milliseconds& timeout) final { if (nullptr != this->service) return; - if(!this->wrapped->isOpen()) { - this->wrapped->open(timeout); - } + this->SocketDecorator::open(timeout); if (this->wrapped->isOpen()) { this->service = this->make_service(); } - else { - this->wrapped->close(); - } }; inline void close() final { @@ -57,8 +51,11 @@ namespace sck::async { }; protected: - explicit AsyncDecorator(std::unique_ptr client) - : OpenDecorator(std::move(client)) { + AsyncDecorator(std::unique_ptr socket) + : SocketDecorator(std::move(socket)) { + if (this->wrapped->isOpen()) { + this->service = this->make_service(); + } }; virtual std::unique_ptr make_service() = 0; diff --git a/CrossSocket/AsynchSocket/include/client/AsyncClient.h b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h similarity index 73% rename from CrossSocket/AsynchSocket/include/client/AsyncClient.h rename to CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h index 438f1b0d..df962a61 100644 --- a/CrossSocket/AsynchSocket/include/client/AsyncClient.h +++ b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h @@ -5,12 +5,12 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_ASYNCCLIENT_H -#define _CROSS_SOCKET_ASYNCCLIENT_H +#ifndef _CROSS_SOCKET_ASYNCMESSANGER_H +#define _CROSS_SOCKET_ASYNCMESSANGER_H -#include -#include -#include +#include +#include +#include #include namespace sck::async { @@ -20,10 +20,10 @@ namespace sck::async { * or subscribe to the received messages by setting a MessageListener (calling AsyncDecorator::resetListener(...)) */ class AsyncClient - : public AsyncDecorator + : public AsyncSocket , public SendCapable { public: - AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); + AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); inline bool send(const std::pair& message) final { return dynamic_cast(this->wrapped.get())->send(message); diff --git a/CrossSocket/AsynchSocket/include/client/MessageListener.h b/CrossSocket/AsynchSocket/include/messanger/MessangerListener.h similarity index 69% rename from CrossSocket/AsynchSocket/include/client/MessageListener.h rename to CrossSocket/AsynchSocket/include/messanger/MessangerListener.h index 8431636f..5a955b79 100644 --- a/CrossSocket/AsynchSocket/include/client/MessageListener.h +++ b/CrossSocket/AsynchSocket/include/messanger/MessangerListener.h @@ -5,13 +5,13 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_MESSAGELISTENER_H_ -#define _CROSS_SOCKET_MESSAGELISTENER_H_ +#ifndef _CROSS_SOCKET_MESSANGERLISTENER_H_ +#define _CROSS_SOCKET_MESSANGERLISTENER_H_ #include namespace sck::async { - class MessageListener { + class MessangerListener { public: virtual void handle(const std::pair& message) = 0; }; diff --git a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h index 3f67b314..86812b08 100644 --- a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h +++ b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h @@ -8,7 +8,7 @@ #ifndef _CROSS_SOCKET_ASYNCTCPSERVER_H #define _CROSS_SOCKET_ASYNCTCPSERVER_H -#include +#include #include namespace sck::async { @@ -17,7 +17,7 @@ namespace sck::async { * inside a private thread stored by this class. From the outside it is possible to subscribe to the * accepted clients by setting a TcpServerListener (calling AsyncDecorator::resetListener(...)) */ - class AsyncTcpServer : public AsyncDecorator { + class AsyncTcpServer : public AsyncSocket { public: explicit AsyncTcpServer(std::unique_ptr server); diff --git a/CrossSocket/AsynchSocket/include/tcpServer/TcpServerListener.h b/CrossSocket/AsynchSocket/include/tcpServer/TcpServerListener.h index 23cf6851..475c734b 100644 --- a/CrossSocket/AsynchSocket/include/tcpServer/TcpServerListener.h +++ b/CrossSocket/AsynchSocket/include/tcpServer/TcpServerListener.h @@ -13,7 +13,7 @@ namespace sck::async { class TcpServerListener { public: - virtual void handle(std::unique_ptr clientHandler) = 0; + virtual void handle(std::unique_ptr clientHandler) = 0; }; } diff --git a/CrossSocket/AsynchSocket/src/Service.cpp b/CrossSocket/AsynchSocket/src/Service.cpp index a813f7a7..8d984abb 100644 --- a/CrossSocket/AsynchSocket/src/Service.cpp +++ b/CrossSocket/AsynchSocket/src/Service.cpp @@ -33,6 +33,7 @@ namespace sck::async { } } }) { + // wait till the thread is actually spawned while (!this->loopLife) { std::this_thread::sleep_for(std::chrono::milliseconds(3)); } diff --git a/CrossSocket/AsynchSocket/src/client/AsyncClient.cpp b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp similarity index 100% rename from CrossSocket/AsynchSocket/src/client/AsyncClient.cpp rename to CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp diff --git a/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp b/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp index 445a8794..eeb3f724 100644 --- a/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp +++ b/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp @@ -9,7 +9,7 @@ namespace sck::async { AsyncTcpServer::AsyncTcpServer(std::unique_ptr server) - : AsyncDecorator(std::move(server)) { + : AsyncSocket(std::move(server)) { }; class AsyncTcpServer::AcceptanceService : public Service { diff --git a/CrossSocket/SynchSocket/include/core/SocketDecorator.h b/CrossSocket/SynchSocket/include/core/SocketDecorator.h new file mode 100644 index 00000000..bced0961 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/SocketDecorator.h @@ -0,0 +1,36 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_SOCKETDECORATOR_H_ +#define _CROSS_SOCKET_SOCKETDECORATOR_H_ + +#include + +namespace sck { + class SocketDecorator + : public StateAware + , public Openable + , public Closable { + public: + inline bool isOpen() const final { return this->wrapped->isOpen(); }; + + inline void close() override { this->wrapped->close(); }; + + /** + * @brief Tries to open the wrapped socket + * @throw when the wrapped socket is not openable + */ + void open(const std::chrono::milliseconds& timeout) override; + + protected: + explicit SocketDecorator(std::unique_ptr channel); + + std::unique_ptr wrapped; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp b/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp new file mode 100644 index 00000000..ff7a6a6f --- /dev/null +++ b/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp @@ -0,0 +1,26 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +namespace sck { + SocketDecorator::SocketDecorator(std::unique_ptr channel) + : wrapped(std::move(channel)) { + if (nullptr == this->wrapped) { + throw Error("The decorator can't wrap a nullptr"); + } + } + + void SocketDecorator::open(const std::chrono::milliseconds& timeout) { + Openable* openable = dynamic_cast(this->wrapped.get()); + if (nullptr == openable) { + throw Error("The wrapped object is not openable"); + } + openable->open(timeout); + } +} diff --git a/Utils/CMakeLists.txt b/Utils/CMakeLists.txt index 52ccffd8..3b99c416 100644 --- a/Utils/CMakeLists.txt +++ b/Utils/CMakeLists.txt @@ -3,14 +3,14 @@ set(PROJECT_SHORTNAME "Utils") MakeLibrary(${PROJECT_SHORTNAME} include) if(COMPILE_ASYNCH) -target_link_libraries(${PROJECT_SHORTNAME} - PUBLIC - Asynch-Cross-Socket -) -target_compile_definitions(${PROJECT_SHORTNAME} PUBLIC ASYNCH_ENABLED) + target_link_libraries(${PROJECT_SHORTNAME} + PUBLIC + Asynch-Cross-Socket + ) + target_compile_definitions(${PROJECT_SHORTNAME} PUBLIC ASYNCH_ENABLED) else() -target_link_libraries(${PROJECT_SHORTNAME} - PUBLIC - Synch-Cross-Socket -) + target_link_libraries(${PROJECT_SHORTNAME} + PUBLIC + Synch-Cross-Socket + ) endif() diff --git a/Utils/include/Asker.h b/Utils/include/Asker.h index 995e8438..84c389e2 100644 --- a/Utils/include/Asker.h +++ b/Utils/include/Asker.h @@ -8,21 +8,25 @@ #ifndef SAMPLE_ASKER_H #define SAMPLE_ASKER_H -#include +#include #include +#include -class Asker { -public: - Asker(std::unique_ptr socket); +namespace sck::sample { + class Asker + : public Logger { + public: + Asker(std::unique_ptr socket); - void ask(); + void ask(); - void askForever(const std::chrono::milliseconds& sampleTime); + void askForever(const std::chrono::milliseconds& sampleTime); -private: - std::unique_ptr socket; - Names cursor; - char recvBuffer[1000]; -}; + private: + std::unique_ptr socket; + Names cursor; + char recvBuffer[1000]; + }; +} #endif \ No newline at end of file diff --git a/Utils/include/Logger.h b/Utils/include/Logger.h new file mode 100644 index 00000000..a759766c --- /dev/null +++ b/Utils/include/Logger.h @@ -0,0 +1,50 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef SAMPLE_LOGGER_H +#define SAMPLE_LOGGER_H + +#include +#include +#include +#include + +namespace sck::sample { + class Logger { + public: + Logger(const std::string& logName); + + void log(const std::string& message) const; + + template + void log(Slices ... slices) const { + std::stringstream stream; + pack(stream, slices...); + this->log(stream.str()); + }; + + Logger& operator=(const Logger& ) = delete; + + private: + Logger(const Logger& o); + + template + void pack(std::stringstream& stream, const std::string& slice, Slices ... remaining) const { + stream << slice; + pack(stream, remaining...); + }; + + template + void pack(std::stringstream& stream, const std::string& slice) const { + stream << slice; + }; + + const std::string logName; + }; +} + +#endif diff --git a/Utils/include/Names.h b/Utils/include/Names.h index c9ddc8b3..e7e6e4bf 100644 --- a/Utils/include/Names.h +++ b/Utils/include/Names.h @@ -11,22 +11,24 @@ #include #include -class Names { -public: - Names(); +namespace sck::sample { + class Names { + public: + Names(); - inline const std::string& getCursorName() const { return this->cursor->first; }; - inline const std::string& getCursorSurname() const { return this->cursor->second; }; + inline const std::string& getCursorName() const { return this->cursor->first; }; + inline const std::string& getCursorSurname() const { return this->cursor->second; }; - static const std::string& getSurname(const std::string& name); + static const std::string& getSurname(const std::string& name); - Names& operator++(); + Names& operator++(); -private: - static const std::map namesSurnames; - static const std::string unknown; + private: + static const std::map namesSurnames; + static const std::string unknown; - std::map::const_iterator cursor; -}; + std::map::const_iterator cursor; + }; +} #endif diff --git a/Utils/include/Responder.h b/Utils/include/Responder.h index e90f4c1e..e5fc6afe 100644 --- a/Utils/include/Responder.h +++ b/Utils/include/Responder.h @@ -8,20 +8,24 @@ #ifndef SAMPLE_RESPONDER_H #define SAMPLE_RESPONDER_H -#include +#include #include +#include -class Responder { -public: - Responder(std::unique_ptr socket); +namespace sck::sample { + class Responder + : public Logger { + public: + Responder(std::unique_ptr socket); - void respond(); + void respond(); - void respondForever(); + void respondForever(); -private: - std::unique_ptr socket; - char recvBuffer[1000]; -}; + private: + std::unique_ptr socket; + char recvBuffer[1000]; + }; +} #endif \ No newline at end of file diff --git a/Utils/src/Asker.cpp b/Utils/src/Asker.cpp index 8706e200..d895aea9 100644 --- a/Utils/src/Asker.cpp +++ b/Utils/src/Asker.cpp @@ -7,29 +7,31 @@ #include #include -#include #include -Asker::Asker(std::unique_ptr socket) - : socket(std::move(socket)) { -} +namespace sck::sample { + Asker::Asker(std::unique_ptr socket) + : Logger("Asker") + , socket(std::move(socket)) { + } -void Asker::ask() { - std::cout << "sending: " << this->cursor.getCursorName(); - this->socket->send({this->cursor.getCursorName().data(), this->cursor.getCursorName().size()}); - std::pair temp = {&this->recvBuffer[0], 1000}; - std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); - std::string recStr(temp.first, recvBytes); - if (recStr.compare(this->cursor.getCursorSurname()) != 0) { - throw std::runtime_error("got wrong response"); + void Asker::ask() { + this->log("sending: ", this->cursor.getCursorName()); + this->socket->send({ this->cursor.getCursorName().data(), this->cursor.getCursorName().size() }); + std::pair temp = { &this->recvBuffer[0], 1000 }; + std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); + std::string recStr(temp.first, recvBytes); + if (recStr.compare(this->cursor.getCursorSurname()) != 0) { + throw std::runtime_error("got wrong response"); + } + this->log("got: ", recStr); + ++this->cursor; } - std::cout << " got: " << recStr << std::endl; - ++this->cursor; -} -void Asker::askForever(const std::chrono::milliseconds& sampleTime) { - while (true) { - this->ask(); - std::this_thread::sleep_for(sampleTime); + void Asker::askForever(const std::chrono::milliseconds& sampleTime) { + while (true) { + this->ask(); + std::this_thread::sleep_for(sampleTime); + } } -} \ No newline at end of file +} diff --git a/Utils/src/Logger.cpp b/Utils/src/Logger.cpp new file mode 100644 index 00000000..324acfaa --- /dev/null +++ b/Utils/src/Logger.cpp @@ -0,0 +1,29 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include + +namespace sck::sample { + static std::set logNamesReserved; + + Logger::Logger(const std::string& logName) + : logName(logName) { + if (logNamesReserved.find(logName) != logNamesReserved.end()) { + throw Error("Names already reserved"); + } + logNamesReserved.emplace(logName); + } + + static std::mutex coutMtx; + + void Logger::log(const std::string& message) const { + std::lock_guard coutLk(coutMtx); + std::cout << this->logName << " : " << message << std::endl; + } +} diff --git a/Utils/src/Names.cpp b/Utils/src/Names.cpp index 5908c149..e2e891f4 100644 --- a/Utils/src/Names.cpp +++ b/Utils/src/Names.cpp @@ -7,30 +7,32 @@ #include -Names::Names() { - this->cursor = namesSurnames.begin(); -} +namespace sck::sample { + Names::Names() { + this->cursor = namesSurnames.begin(); + } -const std::map Names::namesSurnames = { - {"Luciano", "Pavarotti"}, - {"Gengis", "Khan"}, - {"Giulio", "Cesare"}, - {"Theodor", "Roosvelt"}, - {"Immanuel", "Kant"} -}; + const std::map Names::namesSurnames = { + {"Luciano", "Pavarotti"}, + {"Gengis", "Khan"}, + {"Giulio", "Cesare"}, + {"Theodor", "Roosvelt"}, + {"Immanuel", "Kant"} + }; -const std::string Names::unknown = "unknown"; + const std::string Names::unknown = "unknown"; -Names& Names::operator++() { - ++this->cursor; - if (this->cursor == namesSurnames.end()) { - this->cursor = namesSurnames.begin(); + Names& Names::operator++() { + ++this->cursor; + if (this->cursor == namesSurnames.end()) { + this->cursor = namesSurnames.begin(); + } + return *this; } - return *this; -} -const std::string& Names::getSurname(const std::string& name) { - auto it = namesSurnames.find(name); - if (it == namesSurnames.end()) return unknown; - return it->second; + const std::string& Names::getSurname(const std::string& name) { + auto it = namesSurnames.find(name); + if (it == namesSurnames.end()) return unknown; + return it->second; + } } diff --git a/Utils/src/Responder.cpp b/Utils/src/Responder.cpp index cbb59b64..69ea6941 100644 --- a/Utils/src/Responder.cpp +++ b/Utils/src/Responder.cpp @@ -8,21 +8,24 @@ #include #include -Responder::Responder(std::unique_ptr socket) - : socket(std::move(socket)) { -} +namespace sck::sample { + Responder::Responder(std::unique_ptr socket) + : Logger("Responder") + , socket(std::move(socket)) { + } -void Responder::respond() { - std::pair temp = { &this->recvBuffer[0], 1000 }; - std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); - std::string recStr(temp.first, recvBytes); + void Responder::respond() { + std::pair temp = { &this->recvBuffer[0], 1000 }; + std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); + std::string recStr(temp.first, recvBytes); - const std::string surname = Names::getSurname(recStr); - this->socket->send({ surname.data(), surname.size() }); -} + const std::string surname = Names::getSurname(recStr); + this->socket->send({ surname.data(), surname.size() }); + } -void Responder::respondForever() { - while (true) { - this->respond(); + void Responder::respondForever() { + while (true) { + this->respond(); + } } } \ No newline at end of file From 63ec85f42e4f6278af5be2d97bebbabdf3aaa6a8 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 3 May 2021 23:23:59 +0200 Subject: [PATCH 052/228] implementing tests --- Tests/Test00-ip.cpp | 0 Tests/Test01-tcp-synch.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 Tests/Test00-ip.cpp diff --git a/Tests/Test00-ip.cpp b/Tests/Test00-ip.cpp new file mode 100644 index 00000000..e69de29b diff --git a/Tests/Test01-tcp-synch.cpp b/Tests/Test01-tcp-synch.cpp index e69de29b..bad06300 100644 --- a/Tests/Test01-tcp-synch.cpp +++ b/Tests/Test01-tcp-synch.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include +using namespace sck; +using namespace sck::tcp; + +TEST(TcpSynch, establishConnection) { + const std::uint16_t port = 10000; + + TcpClient client(*sck::Ip::createLocalHost(port)); + std::thread t([&]() { + client.open(std::chrono::milliseconds(0)); + EXPECT_TRUE(client.isOpen()); + }); + + TcpServer server(port); + server.open(std::chrono::milliseconds(0)); + EXPECT_TRUE(server.isOpen()); +} + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From e0e2d8c1f3c31e37a3a9dd3f96b918a51e8a3d27 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 5 May 2021 00:15:40 +0200 Subject: [PATCH 053/228] implementing async --- .../AsynchSocket/include/AsyncSocket.h | 72 ------------------- CrossSocket/AsynchSocket/include/Service.h | 43 ----------- .../AsynchSocket/include/core/AsyncSocket.h | 43 +++++++++++ .../include/{ => core}/ErrorListener.h | 0 .../AsynchSocket/include/core/Talker.h | 44 ++++++++++++ .../include/messanger/AsyncMessanger.h | 23 +++--- .../include/tcpServer/AsyncTcpServer.h | 10 +-- CrossSocket/AsynchSocket/src/Service.cpp | 51 ------------- .../AsynchSocket/src/core/AsyncSocket.cpp | 66 +++++++++++++++++ .../src/messanger/AsyncMessanger.cpp | 48 ++++++------- .../src/tcpServer/AsyncTcpServer.cpp | 21 ++---- .../include/core/SocketDecorator.h | 2 +- .../SynchSocket/src/core/SocketDecorator.cpp | 2 +- 13 files changed, 199 insertions(+), 226 deletions(-) delete mode 100644 CrossSocket/AsynchSocket/include/AsyncSocket.h delete mode 100644 CrossSocket/AsynchSocket/include/Service.h create mode 100644 CrossSocket/AsynchSocket/include/core/AsyncSocket.h rename CrossSocket/AsynchSocket/include/{ => core}/ErrorListener.h (100%) create mode 100644 CrossSocket/AsynchSocket/include/core/Talker.h delete mode 100644 CrossSocket/AsynchSocket/src/Service.cpp create mode 100644 CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp diff --git a/CrossSocket/AsynchSocket/include/AsyncSocket.h b/CrossSocket/AsynchSocket/include/AsyncSocket.h deleted file mode 100644 index 899b275d..00000000 --- a/CrossSocket/AsynchSocket/include/AsyncSocket.h +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ASYNCSOCKET_H -#define _CROSS_SOCKET_ASYNCSOCKET_H - -#include -#include - -namespace sck::async { - template - class AsyncSocket - : public SocketDecorator { - public: - virtual ~AsyncSocket() override { this->close(); }; - - /** - * @brief Set a new Listener. - */ - inline void resetListener(std::shared_ptr list) { - std::lock_guard lk(this->listenerMtx); - this->listener = list; - }; - - /** - * @brief Set a new error listener. - */ - inline void resetErrorListener(std::shared_ptr list) { - std::lock_guard lk(this->errorListenerMtx); - this->errorListener = list; - if(nullptr != this->service) { - this->service->resetErrorListener(list); - } - }; - - inline void open(const std::chrono::milliseconds& timeout) final { - if (nullptr != this->service) return; - this->SocketDecorator::open(timeout); - if (this->wrapped->isOpen()) { - this->service = this->make_service(); - } - }; - - inline void close() final { - this->wrapped->close(); - this->service.reset(); - }; - - protected: - AsyncDecorator(std::unique_ptr socket) - : SocketDecorator(std::move(socket)) { - if (this->wrapped->isOpen()) { - this->service = this->make_service(); - } - }; - - virtual std::unique_ptr make_service() = 0; - std::unique_ptr service; - - std::mutex listenerMtx; - std::shared_ptr listener; - - std::mutex errorListenerMtx; - std::shared_ptr errorListener; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/include/Service.h b/CrossSocket/AsynchSocket/include/Service.h deleted file mode 100644 index 1e97a3d3..00000000 --- a/CrossSocket/AsynchSocket/include/Service.h +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SERVICE_H_ -#define _CROSS_SOCKET_SERVICE_H_ - -#include -#include -#include -#include -#include - -namespace sck::async { - /** - * @brief A service store a thread that keep executes a specific function, i.e. the iterativeAction, - * untill the object is destroyed from the outside - */ - class Service { - public: - Service(const Service&) = delete; - Service& operator=(const Service&) = delete; - - // service is started when building - Service(const std::function& iterativeAction, ErrorListener* list); - // service is stop when destroying - ~Service(); - - void resetErrorListener(std::shared_ptr list); - - private: - std::mutex listenerMtx; - std::shared_ptr listener; - - std::atomic_bool loopLife = false; - std::thread loop; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h new file mode 100644 index 00000000..6b0185bc --- /dev/null +++ b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h @@ -0,0 +1,43 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ASYNCSOCKET_H +#define _CROSS_SOCKET_ASYNCSOCKET_H + +#include +#include +#include +#include +#include + +namespace sck::async { + class Service; + + class AsyncSocket + : public SocketDecorator + , public Talker { + public: + virtual ~AsyncSocket() { this->close(); }; + + void open(const std::chrono::milliseconds& timeout) final; + + void close() final; + + protected: + AsyncSocket(std::unique_ptr socket); + + virtual void serviceIteration() = 0; + + private: + void spawn(); + + std::atomic_bool serviceLife = false; + std::unique_ptr service; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/include/ErrorListener.h b/CrossSocket/AsynchSocket/include/core/ErrorListener.h similarity index 100% rename from CrossSocket/AsynchSocket/include/ErrorListener.h rename to CrossSocket/AsynchSocket/include/core/ErrorListener.h diff --git a/CrossSocket/AsynchSocket/include/core/Talker.h b/CrossSocket/AsynchSocket/include/core/Talker.h new file mode 100644 index 00000000..2d383036 --- /dev/null +++ b/CrossSocket/AsynchSocket/include/core/Talker.h @@ -0,0 +1,44 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_TALKER_H_ +#define _CROSS_SOCKET_TALKER_H_ + +#include +#include + +namespace sck::async { + template + class Talker { + public: + void resetListener(std::shared_ptr listener) { + if (nullptr == listener) { + throw Error("The passed listener is empty"); + } + std::lock_guard lk(this->listenerMtx); + this->listener = listener; + }; + + protected: + Talker() = default; + + template + void notify(T content) { + std::lock_guard lk(this->listenerMtx); + if (nullptr == this->listener) { + return; + } + this->listener->handle(content); + }; + + private: + std::mutex listenerMtx; + Listener* listener = nullptr; + }; +} + +#endif diff --git a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h index df962a61..d9f5b2b7 100644 --- a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h +++ b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h @@ -8,7 +8,7 @@ #ifndef _CROSS_SOCKET_ASYNCMESSANGER_H #define _CROSS_SOCKET_ASYNCMESSANGER_H -#include +#include #include #include #include @@ -19,21 +19,22 @@ namespace sck::async { * inside a private thread stored by this class. From the outside it is possible to send messages to the remote host * or subscribe to the received messages by setting a MessageListener (calling AsyncDecorator::resetListener(...)) */ - class AsyncClient - : public AsyncSocket - , public SendCapable { + class AsyncMessanger + : public AsyncSocket + , public SendCapable + , public Talker { public: - AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity); + AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity); - inline bool send(const std::pair& message) final { - return dynamic_cast(this->wrapped.get())->send(message); - }; + bool send(const std::pair& message) final; private: - std::vector buffer; - class ReceiveService; + void serviceIteration() override; - std::unique_ptr make_service() final; + std::vector receiveBuffer; + + ReceiveCapable* recvPtr; + //SendCapable* sendPtr; }; } diff --git a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h index 86812b08..edc32a06 100644 --- a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h +++ b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h @@ -8,7 +8,7 @@ #ifndef _CROSS_SOCKET_ASYNCTCPSERVER_H #define _CROSS_SOCKET_ASYNCTCPSERVER_H -#include +#include #include namespace sck::async { @@ -17,14 +17,14 @@ namespace sck::async { * inside a private thread stored by this class. From the outside it is possible to subscribe to the * accepted clients by setting a TcpServerListener (calling AsyncDecorator::resetListener(...)) */ - class AsyncTcpServer : public AsyncSocket { + class AsyncTcpServer + : public AsyncSocket + , public Talker { public: explicit AsyncTcpServer(std::unique_ptr server); private: - class AcceptanceService; - - std::unique_ptr make_service() final; + void serviceIteration() override; }; } diff --git a/CrossSocket/AsynchSocket/src/Service.cpp b/CrossSocket/AsynchSocket/src/Service.cpp deleted file mode 100644 index 8d984abb..00000000 --- a/CrossSocket/AsynchSocket/src/Service.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -namespace sck::async { - Service::Service(const std::function& iterativeAction, ErrorListener* list) - : listener(list) - , loop([this, &iterativeAction](){ - std::function iter(iterativeAction); - this->loopLife = true; - while (this->loopLife) { - try { - iter(); - } - catch(...) { - std::lock_guard lk(this->listenerMtx); - if(nullptr != this->listener) { - try { - std::rethrow_exception(std::current_exception()); - } - catch(const Error& e) { - this->listener->handle(e); - } - catch(const std::exception& e) { - this->listener->handle(e); - } - } - } - } - }) { - // wait till the thread is actually spawned - while (!this->loopLife) { - std::this_thread::sleep_for(std::chrono::milliseconds(3)); - } - } - - Service::~Service() { - this->loopLife = false; - this->loop.join(); - } - - void Service::resetErrorListener(std::shared_ptr list) { - std::lock_guard lk(this->listenerMtx); - this->listener = list; - } -} \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp b/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp new file mode 100644 index 00000000..0b4fb7f1 --- /dev/null +++ b/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp @@ -0,0 +1,66 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +namespace sck::async { + AsyncSocket::AsyncSocket(std::unique_ptr socket) + : SocketDecorator(std::move(socket)) { + if (this->wrapped->isOpen()) { + this->spawn(); + } + }; + + void AsyncSocket::spawn() { + if (this->wrapped->isOpen()) { + this->serviceLife = true; + this->service = std::make_unique([this]() { + this->serviceLife = true; + while (this->serviceLife) { + try { + this->serviceIteration(); + } + catch (...) { + try { + std::rethrow_exception(std::current_exception()); + } + catch (const Error & e) { + this->Talker::notify(e); + } + catch (const std::exception & e) { + this->Talker::notify(e); + } + } + } + }); + // wait till the thread is actually spawned + while (!this->serviceLife) { + std::this_thread::sleep_for(std::chrono::milliseconds(3)); + } + } + } + + void AsyncSocket::open(const std::chrono::milliseconds& timeout) { + if (nullptr != this->service) { + throw Error("The socket was already opened"); + } + this->SocketDecorator::open(timeout); + this->spawn(); + }; + + void AsyncSocket::close() { + if (!this->serviceLife) { + return; + } + this->serviceLife = false; + this->wrapped->close(); + if (this->service->joinable()) { + this->service->join(); + } + this->service.reset(); + }; +} diff --git a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp index 504f9647..e2b62f6e 100644 --- a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp +++ b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp @@ -5,35 +5,33 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include +#include namespace sck::async { - AsyncClient::AsyncClient(std::unique_ptr client, const std::size_t& bufferCapacity) - : AsyncDecorator(std::move(client)) { - this->buffer.resize(bufferCapacity); + AsyncMessanger::AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) + : AsyncSocket(std::move(messanger)) { + //this->recvPtr = dynamic_cast (messanger.get()); + //if (nullptr == this->recvPtr) { + // throw Error("The passed socket is not receive capable"); + //} + //this->sendPtr = dynamic_cast (messanger.get()); + this->receiveBuffer.resize(bufferCapacity); }; - class AsyncClient::ReceiveService : public Service { - public: - ReceiveService(AsyncClient& client, ErrorListener* list) - : Service([&client](){ - client.buffer.resize(client.buffer.capacity()); - auto pr = std::make_pair(client.buffer.data(), client.buffer.capacity()); - auto recvBytes = dynamic_cast(client.wrapped.get())->receive(pr, std::chrono::milliseconds(0)); - if(recvBytes != client.buffer.capacity()) { - client.buffer.resize(recvBytes); - } - std::lock_guard lk(client.listenerMtx); - if(nullptr != client.listener) { - client.listener->handle({client.buffer.data(), recvBytes}); - } - }, list) { - } - }; - - std::unique_ptr AsyncClient::make_service() { - std::lock_guard lk(this->errorListenerMtx); - return std::make_unique(*this, this->errorListener.get()); + void AsyncMessanger::serviceIteration() { + this->receiveBuffer.resize(this->receiveBuffer.capacity()); + auto pr = std::make_pair(this->receiveBuffer.data(), this->receiveBuffer.capacity()); + /* auto recvBytes = this->recvPtr->receive(pr, std::chrono::milliseconds(0)); + if (recvBytes != this->receiveBuffer.capacity()) { + this->receiveBuffer.resize(recvBytes); + }*/ + this->Talker::notify(pr); } + + inline bool AsyncMessanger::send(const std::pair& message) { + //return this->sendPtr->send(message); + return true; + }; } \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp b/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp index eeb3f724..20bf4819 100644 --- a/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp +++ b/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp @@ -9,24 +9,11 @@ namespace sck::async { AsyncTcpServer::AsyncTcpServer(std::unique_ptr server) - : AsyncSocket(std::move(server)) { + : AsyncSocket(std::move(server)) { }; - class AsyncTcpServer::AcceptanceService : public Service { - public: - AcceptanceService(AsyncTcpServer& server, ErrorListener* list) - : Service([&server](){ - auto client = dynamic_cast(server.wrapped.get())->acceptClient(); - std::lock_guard lk(server.listenerMtx); - if(nullptr != server.listener) { - server.listener->handle(std::move(client)); - } - }, list) { - } - }; - - std::unique_ptr AsyncTcpServer::make_service() { - std::lock_guard lk(this->errorListenerMtx); - return std::make_unique(*this, this->errorListener.get()); + void AsyncTcpServer::serviceIteration() { + auto client = dynamic_cast(this->wrapped.get())->acceptClient(); + //this->Talker::notify(std::move(client)); } } diff --git a/CrossSocket/SynchSocket/include/core/SocketDecorator.h b/CrossSocket/SynchSocket/include/core/SocketDecorator.h index bced0961..972fdc5c 100644 --- a/CrossSocket/SynchSocket/include/core/SocketDecorator.h +++ b/CrossSocket/SynchSocket/include/core/SocketDecorator.h @@ -27,7 +27,7 @@ namespace sck { void open(const std::chrono::milliseconds& timeout) override; protected: - explicit SocketDecorator(std::unique_ptr channel); + SocketDecorator(std::unique_ptr channel); std::unique_ptr wrapped; }; diff --git a/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp b/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp index ff7a6a6f..17a1be53 100644 --- a/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp +++ b/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp @@ -9,7 +9,7 @@ #include namespace sck { - SocketDecorator::SocketDecorator(std::unique_ptr channel) + SocketDecorator::SocketDecorator(std::unique_ptr channel) : wrapped(std::move(channel)) { if (nullptr == this->wrapped) { throw Error("The decorator can't wrap a nullptr"); From 4c074c4abfc9df6d5b59430162abd233f2e2cbd3 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 6 May 2021 19:52:02 +0200 Subject: [PATCH 054/228] made compilable --- .../AsynchSocket/include/core/AsyncSocket.h | 7 ++- .../AsynchSocket/include/core/Talker.h | 2 +- .../include/messanger/AsyncMessanger.h | 14 +++--- .../include/tcpServer/AsyncTcpServer.h | 2 +- .../AsynchSocket/src/core/AsyncSocket.cpp | 46 +++++++++---------- .../src/messanger/AsyncMessanger.cpp | 18 +++----- .../src/tcpServer/AsyncTcpServer.cpp | 2 +- 7 files changed, 42 insertions(+), 49 deletions(-) diff --git a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h index 6b0185bc..e0e7892b 100644 --- a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h +++ b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h @@ -15,14 +15,13 @@ #include namespace sck::async { - class Service; - class AsyncSocket : public SocketDecorator , public Talker { public: virtual ~AsyncSocket() { this->close(); }; + // the wrapped socket is opened and the service is spawned void open(const std::chrono::milliseconds& timeout) final; void close() final; @@ -33,11 +32,11 @@ namespace sck::async { virtual void serviceIteration() = 0; private: - void spawn(); + void spawnService(); std::atomic_bool serviceLife = false; std::unique_ptr service; }; } -#endif \ No newline at end of file +#endif diff --git a/CrossSocket/AsynchSocket/include/core/Talker.h b/CrossSocket/AsynchSocket/include/core/Talker.h index 2d383036..1547590f 100644 --- a/CrossSocket/AsynchSocket/include/core/Talker.h +++ b/CrossSocket/AsynchSocket/include/core/Talker.h @@ -32,7 +32,7 @@ namespace sck::async { if (nullptr == this->listener) { return; } - this->listener->handle(content); + this->listener->handle(std::forward(content)); }; private: diff --git a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h index d9f5b2b7..4644bcaf 100644 --- a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h +++ b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace sck::async { @@ -21,21 +22,20 @@ namespace sck::async { */ class AsyncMessanger : public AsyncSocket - , public SendCapable - , public Talker { + , public Talker + , public SendCapable { public: AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity); - bool send(const std::pair& message) final; + inline bool send(const std::pair& message) final { return this->messPtr->send(message); }; private: - void serviceIteration() override; + void serviceIteration() final; std::vector receiveBuffer; - ReceiveCapable* recvPtr; - //SendCapable* sendPtr; + Messanger* messPtr; }; } -#endif \ No newline at end of file +#endif diff --git a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h index edc32a06..6a2e817f 100644 --- a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h +++ b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h @@ -24,7 +24,7 @@ namespace sck::async { explicit AsyncTcpServer(std::unique_ptr server); private: - void serviceIteration() override; + void serviceIteration() final; }; } diff --git a/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp b/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp index 0b4fb7f1..1221d478 100644 --- a/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp +++ b/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp @@ -11,36 +11,34 @@ namespace sck::async { AsyncSocket::AsyncSocket(std::unique_ptr socket) : SocketDecorator(std::move(socket)) { if (this->wrapped->isOpen()) { - this->spawn(); + this->spawnService(); } }; - void AsyncSocket::spawn() { - if (this->wrapped->isOpen()) { + void AsyncSocket::spawnService() { + this->serviceLife = false; + this->service = std::make_unique([this]() { this->serviceLife = true; - this->service = std::make_unique([this]() { - this->serviceLife = true; - while (this->serviceLife) { + while (this->serviceLife) { + try { + this->serviceIteration(); + } + catch (...) { try { - this->serviceIteration(); + std::rethrow_exception(std::current_exception()); } - catch (...) { - try { - std::rethrow_exception(std::current_exception()); - } - catch (const Error & e) { - this->Talker::notify(e); - } - catch (const std::exception & e) { - this->Talker::notify(e); - } + catch (const Error & e) { + this->Talker::notify(e); + } + catch (const std::exception & e) { + this->Talker::notify(e); } } - }); - // wait till the thread is actually spawned - while (!this->serviceLife) { - std::this_thread::sleep_for(std::chrono::milliseconds(3)); } + }); + // wait till the thread is actually spawned + while (!this->serviceLife) { + std::this_thread::sleep_for(std::chrono::milliseconds(3)); } } @@ -49,11 +47,13 @@ namespace sck::async { throw Error("The socket was already opened"); } this->SocketDecorator::open(timeout); - this->spawn(); + if (this->wrapped->isOpen()) { + this->spawnService(); + } }; void AsyncSocket::close() { - if (!this->serviceLife) { + if (nullptr == this->service) { return; } this->serviceLife = false; diff --git a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp index e2b62f6e..da5afd13 100644 --- a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp +++ b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp @@ -12,26 +12,20 @@ namespace sck::async { AsyncMessanger::AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) : AsyncSocket(std::move(messanger)) { - //this->recvPtr = dynamic_cast (messanger.get()); - //if (nullptr == this->recvPtr) { - // throw Error("The passed socket is not receive capable"); - //} - //this->sendPtr = dynamic_cast (messanger.get()); + this->messPtr = dynamic_cast (this->wrapped.get()); + if (nullptr == this->messPtr) { + throw Error("The passed socket is not a messanger"); + } this->receiveBuffer.resize(bufferCapacity); }; void AsyncMessanger::serviceIteration() { this->receiveBuffer.resize(this->receiveBuffer.capacity()); auto pr = std::make_pair(this->receiveBuffer.data(), this->receiveBuffer.capacity()); - /* auto recvBytes = this->recvPtr->receive(pr, std::chrono::milliseconds(0)); + auto recvBytes = this->messPtr->receive(pr, std::chrono::milliseconds(0)); if (recvBytes != this->receiveBuffer.capacity()) { this->receiveBuffer.resize(recvBytes); - }*/ + } this->Talker::notify(pr); } - - inline bool AsyncMessanger::send(const std::pair& message) { - //return this->sendPtr->send(message); - return true; - }; } \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp b/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp index 20bf4819..6bee799e 100644 --- a/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp +++ b/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp @@ -14,6 +14,6 @@ namespace sck::async { void AsyncTcpServer::serviceIteration() { auto client = dynamic_cast(this->wrapped.get())->acceptClient(); - //this->Talker::notify(std::move(client)); + this->Talker::notify(std::move(client)); } } From 5f3e5f857857df44ded2b1358415e2d2a76e422e Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 6 May 2021 22:25:00 +0200 Subject: [PATCH 055/228] implementing --- .../include/messanger/AsyncMessanger.h | 1 + Utils/include/Asker.h | 4 +- Utils/include/AsyncResponder.h | 33 +++++++++++++++++ Utils/include/{Names.h => NamesMap.h} | 10 ++--- Utils/include/Responder.h | 2 +- Utils/include/ResponderAsync.h | 35 ------------------ Utils/src/AsyncResponder.cpp | 31 ++++++++++++++++ Utils/src/{Names.cpp => NamesMap.cpp} | 12 +++--- Utils/src/Responder.cpp | 2 +- Utils/src/ResponderAsync.cpp | 37 ------------------- 10 files changed, 80 insertions(+), 87 deletions(-) create mode 100644 Utils/include/AsyncResponder.h rename Utils/include/{Names.h => NamesMap.h} (83%) delete mode 100644 Utils/include/ResponderAsync.h create mode 100644 Utils/src/AsyncResponder.cpp rename Utils/src/{Names.cpp => NamesMap.cpp} (69%) delete mode 100644 Utils/src/ResponderAsync.cpp diff --git a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h index 4644bcaf..94139025 100644 --- a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h +++ b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h @@ -34,6 +34,7 @@ namespace sck::async { std::vector receiveBuffer; + protected: Messanger* messPtr; }; } diff --git a/Utils/include/Asker.h b/Utils/include/Asker.h index 84c389e2..829e2df8 100644 --- a/Utils/include/Asker.h +++ b/Utils/include/Asker.h @@ -9,7 +9,7 @@ #define SAMPLE_ASKER_H #include -#include +#include #include namespace sck::sample { @@ -24,7 +24,7 @@ namespace sck::sample { private: std::unique_ptr socket; - Names cursor; + NamesMap cursor; char recvBuffer[1000]; }; } diff --git a/Utils/include/AsyncResponder.h b/Utils/include/AsyncResponder.h new file mode 100644 index 00000000..8b7c8d9b --- /dev/null +++ b/Utils/include/AsyncResponder.h @@ -0,0 +1,33 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef SAMPLE_ASYNCRESPONDER_H +#define SAMPLE_ASYNCRESPONDER_H + +#ifdef ASYNCH_ENABLED +#include +#include + +namespace sck::sample { + class AsyncResponder + : public sck::async::AsyncMessanger + , public sck::async::MessangerListener + , public sck::async::ErrorListener { + public: + AsyncResponder(std::unique_ptr socket); + + private: + void handle(const std::pair& message) final; + + void handle(const sck::Error& error) final; + + void handle(const std::exception& error) final; + }; +} +#endif + +#endif \ No newline at end of file diff --git a/Utils/include/Names.h b/Utils/include/NamesMap.h similarity index 83% rename from Utils/include/Names.h rename to Utils/include/NamesMap.h index e7e6e4bf..095e965c 100644 --- a/Utils/include/Names.h +++ b/Utils/include/NamesMap.h @@ -5,23 +5,23 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef SAMPLE_NAMES_H -#define SAMPLE_NAMES_H +#ifndef SAMPLE_NAMESMAP_H +#define SAMPLE_NAMESMAP_H #include #include namespace sck::sample { - class Names { + class NamesMap { public: - Names(); + NamesMap(); inline const std::string& getCursorName() const { return this->cursor->first; }; inline const std::string& getCursorSurname() const { return this->cursor->second; }; static const std::string& getSurname(const std::string& name); - Names& operator++(); + NamesMap& operator++(); private: static const std::map namesSurnames; diff --git a/Utils/include/Responder.h b/Utils/include/Responder.h index e5fc6afe..8d2973f8 100644 --- a/Utils/include/Responder.h +++ b/Utils/include/Responder.h @@ -9,7 +9,7 @@ #define SAMPLE_RESPONDER_H #include -#include +#include #include namespace sck::sample { diff --git a/Utils/include/ResponderAsync.h b/Utils/include/ResponderAsync.h deleted file mode 100644 index bb00123f..00000000 --- a/Utils/include/ResponderAsync.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef SAMPLE_RESPONDER_ASYNC_H -#define SAMPLE_RESPONDER_ASYNC_H - -#ifdef ASYNCH_ENABLED -#include -#include - -class ResponderAsync - : public sck::async::MessageListener - , public sck::async::ErrorListener { -public: - ResponderAsync(std::unique_ptr socket); - - inline bool isRunning() const { return this->running; }; - -private: - void handle(const std::pair& message) final; - - void handle(const sck::Error& error) final; - - void handle(const std::exception& error) final; - - std::unique_ptr asyncSocket; - std::atomic_bool running; -}; -#endif - -#endif \ No newline at end of file diff --git a/Utils/src/AsyncResponder.cpp b/Utils/src/AsyncResponder.cpp new file mode 100644 index 00000000..6128ecbd --- /dev/null +++ b/Utils/src/AsyncResponder.cpp @@ -0,0 +1,31 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifdef ASYNCH_ENABLED +#include + +namespace sck::sample { + AsyncResponder::AsyncResponder(std::unique_ptr socket) + : AsyncMessanger(std::move(socket), 1000) { + } + + void AsyncResponder::handle(const std::pair& message) { + std::string recStr(message.first, message.second); + const std::string surname = NamesMap::getSurname(recStr); + this->messPtr->send({ surname.data(), surname.size() }); + } + + void AsyncResponder::handle(const sck::Error& error) { + this->close(); + }; + + void AsyncResponder::handle(const std::exception& error) { + this->close(); + }; +} + +#endif diff --git a/Utils/src/Names.cpp b/Utils/src/NamesMap.cpp similarity index 69% rename from Utils/src/Names.cpp rename to Utils/src/NamesMap.cpp index e2e891f4..da2af734 100644 --- a/Utils/src/Names.cpp +++ b/Utils/src/NamesMap.cpp @@ -5,14 +5,14 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include namespace sck::sample { - Names::Names() { + NamesMap::NamesMap() { this->cursor = namesSurnames.begin(); } - const std::map Names::namesSurnames = { + const std::map NamesMap::namesSurnames = { {"Luciano", "Pavarotti"}, {"Gengis", "Khan"}, {"Giulio", "Cesare"}, @@ -20,9 +20,9 @@ namespace sck::sample { {"Immanuel", "Kant"} }; - const std::string Names::unknown = "unknown"; + const std::string NamesMap::unknown = "unknown"; - Names& Names::operator++() { + NamesMap& NamesMap::operator++() { ++this->cursor; if (this->cursor == namesSurnames.end()) { this->cursor = namesSurnames.begin(); @@ -30,7 +30,7 @@ namespace sck::sample { return *this; } - const std::string& Names::getSurname(const std::string& name) { + const std::string& NamesMap::getSurname(const std::string& name) { auto it = namesSurnames.find(name); if (it == namesSurnames.end()) return unknown; return it->second; diff --git a/Utils/src/Responder.cpp b/Utils/src/Responder.cpp index 69ea6941..41eb822c 100644 --- a/Utils/src/Responder.cpp +++ b/Utils/src/Responder.cpp @@ -19,7 +19,7 @@ namespace sck::sample { std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); std::string recStr(temp.first, recvBytes); - const std::string surname = Names::getSurname(recStr); + const std::string surname = NamesMap::getSurname(recStr); this->socket->send({ surname.data(), surname.size() }); } diff --git a/Utils/src/ResponderAsync.cpp b/Utils/src/ResponderAsync.cpp deleted file mode 100644 index f8ee84ad..00000000 --- a/Utils/src/ResponderAsync.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifdef ASYNCH_ENABLED -#include - -ResponderAsync::ResponderAsync(std::unique_ptr socket) { - this->running = true; - this->asyncSocket = std::make_unique(std::move(socket), 1000); - this->asyncSocket->resetErrorListener(this); - this->asyncSocket->resetListener(this); - this->asyncSocket->open(std::chrono::milliseconds(0)); - if(!this->asyncSocket->isOpen()) { - this->running = false; - } -} - -void ResponderAsync::handle(const std::pair& message) { - std::string recStr(message.first, message.second); - const std::string surname = Names::getSurname(recStr); - this->asyncSocket->send({ surname.data(), surname.size() }); -} - -void ResponderAsync::handle(const sck::Error& error){ - this->asyncSocket->close(); - this->running = false; -}; - -void ResponderAsync::handle(const std::exception& error) { - this->asyncSocket->close(); - this->running = false; -}; -#endif From 49af2e9884ef2b68988f02b97d0822b6f7fb1841 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 7 May 2021 11:39:55 +0200 Subject: [PATCH 056/228] implementing tests --- .../include/core/components/ChannelAware.h | 2 +- .../src/core/components/ChannelAware.cpp | 15 +++ Tests/CMakeLists.txt | 25 ++-- Tests/Test01-tcp-synch.cpp | 126 ++++++++++++++++-- Tests/UtilsTest/CMakeLists.txt | 8 ++ Tests/UtilsTest/include/PortFactory.h | 20 +++ Tests/UtilsTest/src/PortFactory.cpp | 23 ++++ Utils/include/Asker.h | 2 + Utils/include/Logger.h | 1 - Utils/include/Responder.h | 2 + Utils/src/Asker.cpp | 6 + Utils/src/Logger.cpp | 19 ++- Utils/src/Responder.cpp | 7 + 13 files changed, 229 insertions(+), 27 deletions(-) create mode 100644 CrossSocket/SynchSocket/src/core/components/ChannelAware.cpp create mode 100644 Tests/UtilsTest/CMakeLists.txt create mode 100644 Tests/UtilsTest/include/PortFactory.h create mode 100644 Tests/UtilsTest/src/PortFactory.cpp diff --git a/CrossSocket/SynchSocket/include/core/components/ChannelAware.h b/CrossSocket/SynchSocket/include/core/components/ChannelAware.h index 094ad515..08d13b17 100644 --- a/CrossSocket/SynchSocket/include/core/components/ChannelAware.h +++ b/CrossSocket/SynchSocket/include/core/components/ChannelAware.h @@ -15,7 +15,7 @@ namespace sck { class ChannelAware { public: - virtual ~ChannelAware() = default; + virtual ~ChannelAware(); protected: ChannelAware() = default; diff --git a/CrossSocket/SynchSocket/src/core/components/ChannelAware.cpp b/CrossSocket/SynchSocket/src/core/components/ChannelAware.cpp new file mode 100644 index 00000000..07474439 --- /dev/null +++ b/CrossSocket/SynchSocket/src/core/components/ChannelAware.cpp @@ -0,0 +1,15 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include "../../Channel.h" + +namespace sck { + ChannelAware::~ChannelAware() { + this->channel.reset(); + } +} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index f6494420..cab9724d 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -3,16 +3,25 @@ if (WIN32) endif (WIN32) add_subdirectory(GoogleTest/) -function(MakeTest NAME) -add_executable(${NAME} ${NAME}.cpp) +add_subdirectory(UtilsTest) -target_link_libraries(${NAME} -PUBLIC - Utils - gtest -) +function(MakeTest NAME) + add_executable(${NAME} ${NAME}.cpp) -install(TARGETS ${NAME}) + target_link_libraries(${NAME} + PUBLIC + Utils + UtilsTest + gtest + ) + + find_package(OpenMP) + target_link_libraries(${NAME} + PUBLIC + OpenMP::OpenMP_CXX + ) + + install(TARGETS ${NAME}) endfunction() MakeTest(Test01-tcp-synch) diff --git a/Tests/Test01-tcp-synch.cpp b/Tests/Test01-tcp-synch.cpp index bad06300..af70e46b 100644 --- a/Tests/Test01-tcp-synch.cpp +++ b/Tests/Test01-tcp-synch.cpp @@ -1,24 +1,128 @@ #include #include #include -#include +#include +#include +#include +#include using namespace sck; using namespace sck::tcp; -TEST(TcpSynch, establishConnection) { - const std::uint16_t port = 10000; +// 1 to 1 - TcpClient client(*sck::Ip::createLocalHost(port)); - std::thread t([&]() { - client.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(client.isOpen()); - }); +TEST(TcpSynch, OpenClose) { + const std::uint16_t port = sample::PortFactory::makePort(); - TcpServer server(port); - server.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(server.isOpen()); + #pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // server + std::unique_ptr acceptedClient; + { + TcpServer server(port); + server.open(std::chrono::milliseconds(0)); +#pragma omp barrier + EXPECT_TRUE(server.isOpen()); + acceptedClient = server.acceptClient(); + server.close(); + EXPECT_FALSE(server.isOpen()); + } + EXPECT_TRUE(acceptedClient->isOpen()); +#pragma omp barrier + acceptedClient->close(); + EXPECT_FALSE(acceptedClient->isOpen()); + } + else { + // client + TcpClient client(*sck::Ip::createLocalHost(port)); +#pragma omp barrier + client.open(std::chrono::milliseconds(0)); + EXPECT_TRUE(client.isOpen()); +#pragma omp barrier + client.close(); + EXPECT_FALSE(client.isOpen()); + } + } } +TEST(TcpSynch, ClientAsker_ServerResponder) { + const std::uint16_t port = sample::PortFactory::makePort(); + const std::size_t cycles = 5; + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // server + std::unique_ptr acceptedClient; + { + TcpServer server(port); + server.open(std::chrono::milliseconds(0)); +#pragma omp barrier + EXPECT_TRUE(server.isOpen()); + acceptedClient = server.acceptClient(); + server.close(); + EXPECT_FALSE(server.isOpen()); + } + EXPECT_TRUE(acceptedClient->isOpen()); + + sample::Responder responder(std::move(acceptedClient)); + responder.respond(cycles); +#pragma omp barrier + } + else { + // client + std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); +#pragma omp barrier + client->open(std::chrono::milliseconds(0)); + EXPECT_TRUE(client->isOpen()); + + sample::Asker asker(std::move(client)); + asker.ask(cycles); +#pragma omp barrier + } + } +} + +TEST(TcpSynch, ClientResponder_ServerAsker) { + const std::uint16_t port = sample::PortFactory::makePort(); + const std::size_t cycles = 5; + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // server + std::unique_ptr acceptedClient; + { + TcpServer server(port); + server.open(std::chrono::milliseconds(0)); +#pragma omp barrier + EXPECT_TRUE(server.isOpen()); + acceptedClient = server.acceptClient(); + server.close(); + EXPECT_FALSE(server.isOpen()); + } + EXPECT_TRUE(acceptedClient->isOpen()); + + sample::Asker asker(std::move(acceptedClient)); + asker.ask(cycles); +#pragma omp barrier + } + else { + // client + std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); +#pragma omp barrier + client->open(std::chrono::milliseconds(0)); + EXPECT_TRUE(client->isOpen()); + + sample::Responder responder(std::move(client)); + responder.respond(cycles); +#pragma omp barrier + } + } +} + +// 1 to many + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/Tests/UtilsTest/CMakeLists.txt b/Tests/UtilsTest/CMakeLists.txt new file mode 100644 index 00000000..66aeb1cc --- /dev/null +++ b/Tests/UtilsTest/CMakeLists.txt @@ -0,0 +1,8 @@ +set(PROJECT_SHORTNAME "UtilsTest") + +MakeLibrary(${PROJECT_SHORTNAME} include) + +target_link_libraries(${PROJECT_SHORTNAME} + PUBLIC + Synch-Cross-Socket +) diff --git a/Tests/UtilsTest/include/PortFactory.h b/Tests/UtilsTest/include/PortFactory.h new file mode 100644 index 00000000..d8dba9a8 --- /dev/null +++ b/Tests/UtilsTest/include/PortFactory.h @@ -0,0 +1,20 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef SAMPLE_PORT_FACTORY_H +#define SAMPLE_PORT_FACTORY_H + +#include + +namespace sck::sample { + class PortFactory { + public: + static std::uint16_t makePort(); + }; +} + +#endif \ No newline at end of file diff --git a/Tests/UtilsTest/src/PortFactory.cpp b/Tests/UtilsTest/src/PortFactory.cpp new file mode 100644 index 00000000..37b2ec02 --- /dev/null +++ b/Tests/UtilsTest/src/PortFactory.cpp @@ -0,0 +1,23 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +namespace sck::sample { + constexpr std::uint16_t INITIAL_PORT = 9999; + constexpr std::uint16_t DELTA_PORT = 10; + + static std::mutex portMtx; + static std::uint16_t port = INITIAL_PORT; + + std::uint16_t PortFactory::makePort() { + std::lock_guard lk(portMtx); + ++port; + return port; + } +} diff --git a/Utils/include/Asker.h b/Utils/include/Asker.h index 829e2df8..b495a71a 100644 --- a/Utils/include/Asker.h +++ b/Utils/include/Asker.h @@ -20,6 +20,8 @@ namespace sck::sample { void ask(); + void ask(const std::size_t times); + void askForever(const std::chrono::milliseconds& sampleTime); private: diff --git a/Utils/include/Logger.h b/Utils/include/Logger.h index a759766c..1376b5cb 100644 --- a/Utils/include/Logger.h +++ b/Utils/include/Logger.h @@ -10,7 +10,6 @@ #include #include -#include #include namespace sck::sample { diff --git a/Utils/include/Responder.h b/Utils/include/Responder.h index 8d2973f8..f474bddf 100644 --- a/Utils/include/Responder.h +++ b/Utils/include/Responder.h @@ -20,6 +20,8 @@ namespace sck::sample { void respond(); + void respond(const std::size_t times); + void respondForever(); private: diff --git a/Utils/src/Asker.cpp b/Utils/src/Asker.cpp index d895aea9..8a3117de 100644 --- a/Utils/src/Asker.cpp +++ b/Utils/src/Asker.cpp @@ -28,6 +28,12 @@ namespace sck::sample { ++this->cursor; } + void Asker::ask(const std::size_t times) { + for (std::size_t k = 0; k < times; ++k) { + this->ask(); + } + } + void Asker::askForever(const std::chrono::milliseconds& sampleTime) { while (true) { this->ask(); diff --git a/Utils/src/Logger.cpp b/Utils/src/Logger.cpp index 324acfaa..f05b966b 100644 --- a/Utils/src/Logger.cpp +++ b/Utils/src/Logger.cpp @@ -8,16 +8,23 @@ #include #include #include +#include namespace sck::sample { - static std::set logNamesReserved; + static std::map namesUsed; - Logger::Logger(const std::string& logName) - : logName(logName) { - if (logNamesReserved.find(logName) != logNamesReserved.end()) { - throw Error("Names already reserved"); + std::string makeName(const std::string& logName) { + auto it = namesUsed.find(logName); + if (it == namesUsed.end()) { + it = namesUsed.emplace(logName, 1).first; + } + else { + ++it->second; } - logNamesReserved.emplace(logName); + return logName + std::to_string(it->second); + } + Logger::Logger(const std::string& logName) + : logName(makeName(logName)) { } static std::mutex coutMtx; diff --git a/Utils/src/Responder.cpp b/Utils/src/Responder.cpp index 41eb822c..700d75a6 100644 --- a/Utils/src/Responder.cpp +++ b/Utils/src/Responder.cpp @@ -20,9 +20,16 @@ namespace sck::sample { std::string recStr(temp.first, recvBytes); const std::string surname = NamesMap::getSurname(recStr); + this->log("request: ", recStr, " response: ", surname); this->socket->send({ surname.data(), surname.size() }); } + void Responder::respond(const std::size_t times) { + for (std::size_t k = 0; k < times; ++k) { + this->respond(); + } + } + void Responder::respondForever() { while (true) { this->respond(); From ccd3c771afe1563692f583c2e35fb16526613767 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 7 May 2021 11:49:26 +0200 Subject: [PATCH 057/228] unit test and artifacts workflow split --- .../{action.yml => installArtifacts.yml} | 16 +----- .github/workflows/runTests.yml | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+), 14 deletions(-) rename .github/workflows/{action.yml => installArtifacts.yml} (89%) create mode 100644 .github/workflows/runTests.yml diff --git a/.github/workflows/action.yml b/.github/workflows/installArtifacts.yml similarity index 89% rename from .github/workflows/action.yml rename to .github/workflows/installArtifacts.yml index e848ec38..afc52d7e 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/installArtifacts.yml @@ -1,4 +1,4 @@ -name: C/C++ CI +name: Binaries Compilation on: push: @@ -19,8 +19,6 @@ jobs: with: cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=gcc;-DCMAKE_CXX_COMPILER=g++;-DBUILD_SAMPLES=OFF submodule_update: ON - run_tests: ON - unit_test_build: -Dtest=ON - name: Install artifacts run: cmake --install ./build - uses: actions/upload-artifact@v2 @@ -38,8 +36,6 @@ jobs: with: cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=gcc;-DCMAKE_CXX_COMPILER=g++;-DBUILD_SAMPLES=OFF;-DLIB_OPT=ON submodule_update: ON - run_tests: ON - unit_test_build: -Dtest=ON - name: Install artifacts run: cmake --install ./build - uses: actions/upload-artifact@v2 @@ -57,8 +53,6 @@ jobs: with: cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=clang;-DCMAKE_CXX_COMPILER=clang++;-DBUILD_SAMPLES=OFF submodule_update: ON - run_tests: ON - unit_test_build: -Dtest=ON - name: Install artifacts run: cmake --install ./build - uses: actions/upload-artifact@v2 @@ -76,8 +70,6 @@ jobs: with: cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=clang;-DCMAKE_CXX_COMPILER=clang++;-DBUILD_SAMPLES=OFF;-DLIB_OPT=ON submodule_update: ON - run_tests: ON - unit_test_build: -Dtest=ON - name: Install artifacts run: cmake --install ./build - uses: actions/upload-artifact@v2 @@ -95,8 +87,6 @@ jobs: with: cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./build/artifacts/;-DBUILD_SAMPLES=OFF submodule_update: ON - run_tests: ON - unit_test_build: -Dtest=ON - name: Install artifacts run: cmake --install ./build - uses: actions/upload-artifact@v2 @@ -114,11 +104,9 @@ jobs: with: cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./build/artifacts/;-DBUILD_SAMPLES=OFF;-DLIB_OPT=ON submodule_update: ON - run_tests: ON - unit_test_build: -Dtest=ON - name: Install artifacts run: cmake --install ./build - uses: actions/upload-artifact@v2 with: path: build/artifacts - name: Win32_VisualStudio_shared.zip + name: Win32_VisualStudio_shared.zip \ No newline at end of file diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml new file mode 100644 index 00000000..10884afe --- /dev/null +++ b/.github/workflows/runTests.yml @@ -0,0 +1,55 @@ +name: Unit Tests + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + ubuntu-gcc-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.0.0 + - name: Build project + uses: nicledomaS/cmake_build_action@v1.3 + with: + cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=gcc;-DCMAKE_CXX_COMPILER=g++;-DBUILD_SAMPLES=OFF;-DBUILD_TESTS=ON + submodule_update: ON + - name: Install artifacts + run: cmake --install ./build + - name: Test01-tcp-synch + run: ./artifacts/bin/Test01-tcp-synch + + ubuntu-clang-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.0.0 + - name: Build project + uses: nicledomaS/cmake_build_action@v1.3 + with: + cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=clang;-DCMAKE_CXX_COMPILER=clang++;-DBUILD_SAMPLES=OFF;-DBUILD_TESTS=ON + submodule_update: ON + - name: Install artifacts + run: cmake --install ./build + - name: Test01-tcp-synch + run: ./artifacts/bin/Test01-tcp-synch + + windows-vs-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.0.0 + - name: Build project + uses: nicledomaS/cmake_build_action@v1.3 + with: + cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./build/artifacts/;-DBUILD_SAMPLES=OFF;-DBUILD_TESTS=ON + submodule_update: ON + - name: Install artifacts + run: cmake --install ./build + - name: Test01-tcp-synch + run: ./artifacts/bin/Test01-tcp-synch \ No newline at end of file From c81ed36824cc6444fe2b08079b88d19d10162b1c Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 7 May 2021 14:00:12 +0200 Subject: [PATCH 058/228] implementing tests --- Tests/CMakeLists.txt | 2 +- Tests/Test01-tcp-synch.cpp | 53 ++++++++++-- Tests/Test02-tcp-asynch.cpp | 142 +++++++++++++++++++++++++++++++++ Utils/include/AsyncResponder.h | 6 +- Utils/src/AsyncResponder.cpp | 4 +- 5 files changed, 197 insertions(+), 10 deletions(-) diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index cab9724d..9b3038c5 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -26,4 +26,4 @@ endfunction() MakeTest(Test01-tcp-synch) -# MakeTest(Test02-tcp-asynch) +MakeTest(Test02-tcp-asynch) diff --git a/Tests/Test01-tcp-synch.cpp b/Tests/Test01-tcp-synch.cpp index af70e46b..41894993 100644 --- a/Tests/Test01-tcp-synch.cpp +++ b/Tests/Test01-tcp-synch.cpp @@ -3,13 +3,9 @@ #include #include #include -#include -#include using namespace sck; using namespace sck::tcp; -// 1 to 1 - TEST(TcpSynch, OpenClose) { const std::uint16_t port = sample::PortFactory::makePort(); @@ -45,6 +41,53 @@ TEST(TcpSynch, OpenClose) { } } +TEST(TcpSynch, OpenCloseManyClients) { + const std::uint16_t port = sample::PortFactory::makePort(); + const std::size_t clientsNumb = 5; + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // server + TcpServer server(port); + server.open(std::chrono::milliseconds(0)); +#pragma omp barrier + EXPECT_TRUE(server.isOpen()); + + std::list> acceptedClients; + for (std::size_t k = 0; k < clientsNumb; ++k) { + acceptedClients.emplace_back(server.acceptClient()); + EXPECT_TRUE(acceptedClients.back()->isOpen()); + } + server.close(); + EXPECT_FALSE(server.isOpen()); +#pragma omp barrier + for (auto it = acceptedClients.begin(); it != acceptedClients.end(); ++it) { + (*it)->close(); + EXPECT_FALSE((*it)->isOpen()); + } + } + else { + // client +#pragma omp barrier + std::list> clients; + for (std::size_t k = 0; k < clientsNumb; ++k) { + clients.emplace_back(std::make_unique(*sck::Ip::createLocalHost(port))); + clients.back()->open(std::chrono::milliseconds(0)); + EXPECT_TRUE(clients.back()->isOpen()); + } +#pragma omp barrier + for (auto it = clients.begin(); it != clients.end(); ++it) { + (*it)->close(); + EXPECT_FALSE((*it)->isOpen()); + } + } + } +} + +#include +#include + TEST(TcpSynch, ClientAsker_ServerResponder) { const std::uint16_t port = sample::PortFactory::makePort(); const std::size_t cycles = 5; @@ -121,8 +164,6 @@ TEST(TcpSynch, ClientResponder_ServerAsker) { } } -// 1 to many - int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/Tests/Test02-tcp-asynch.cpp b/Tests/Test02-tcp-asynch.cpp index e69de29b..2ae02348 100644 --- a/Tests/Test02-tcp-asynch.cpp +++ b/Tests/Test02-tcp-asynch.cpp @@ -0,0 +1,142 @@ +#include +#include +#include +#include +#include +using namespace sck; +using namespace sck::tcp; + +#include +#include + +TEST(TcpAsynch, OpenCloseAcceptSynch) { + const std::uint16_t port = sample::PortFactory::makePort(); + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // server + std::unique_ptr acceptedClient; + { + TcpServer server(port); + server.open(std::chrono::milliseconds(0)); +#pragma omp barrier + EXPECT_TRUE(server.isOpen()); + acceptedClient = server.acceptClient(); + server.close(); + EXPECT_FALSE(server.isOpen()); + } + async::AsyncMessanger asynchResponder(std::move(acceptedClient), 500); + EXPECT_TRUE(asynchResponder.isOpen()); +#pragma omp barrier + asynchResponder.close(); + EXPECT_FALSE(asynchResponder.isOpen()); + } + else { + // client + TcpClient client(*sck::Ip::createLocalHost(port)); +#pragma omp barrier + client.open(std::chrono::milliseconds(0)); + EXPECT_TRUE(client.isOpen()); +#pragma omp barrier + client.close(); + EXPECT_FALSE(client.isOpen()); + } + } +} + +TEST(TcpAsynch, ClientAsker_ServerResponder) { + const std::uint16_t port = sample::PortFactory::makePort(); + const std::size_t cycles = 5; + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // server + std::unique_ptr acceptedClient; + { + TcpServer server(port); + server.open(std::chrono::milliseconds(0)); +#pragma omp barrier + EXPECT_TRUE(server.isOpen()); + acceptedClient = server.acceptClient(); + server.close(); + EXPECT_FALSE(server.isOpen()); + } + sample::AsyncResponder asynchResponder(std::move(acceptedClient)); + EXPECT_TRUE(asynchResponder.isOpen()); +#pragma omp barrier + asynchResponder.close(); + EXPECT_TRUE(!asynchResponder.isOpen()); + } + else { + // client + std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); +#pragma omp barrier + client->open(std::chrono::milliseconds(0)); + EXPECT_TRUE(client->isOpen()); + + sample::Asker asker(std::move(client)); + asker.ask(cycles); +#pragma omp barrier + } + } +} + +#include + +class AsynchAcceptor + : public async::AsyncTcpServer + , private async::TcpServerListener + , private async::ErrorListener +{ +public: + AsynchAcceptor(const std::uint16_t port) : AsyncTcpServer(std::make_unique(port)) {}; + + inline const std::list>& getAccepted() const { return this->accepted; }; + +private: + void handle(std::unique_ptr clientHandler) final { + EXPECT_TRUE(clientHandler->isOpen()); + this->accepted.emplace_back(std::move(clientHandler)); + }; + + void handle(const sck::Error& error) final {}; + + void handle(const std::exception& error) final {}; + + std::list> accepted; +}; + +TEST(TcpAsynch, AsynchTcpServerAcceptor) { + const std::uint16_t port = sample::PortFactory::makePort(); + const std::size_t cycles = 5; + const std::size_t clientsNumb = 5; + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // acceptor + AsynchAcceptor acceptor(port); + acceptor.open(std::chrono::milliseconds(0)); + EXPECT_TRUE(acceptor.isOpen()); +#pragma omp barrier + EXPECT_EQ(acceptor.getAccepted().size(), cycles); + } + else { + // client + std::list clients; + for (std::size_t k = 0; k < clientsNumb; ++k) { + clients.emplace_back(*sck::Ip::createLocalHost(port)); + clients.back().open(std::chrono::milliseconds(0)); + EXPECT_TRUE(clients.back().isOpen()); + } +#pragma omp barrier + } + } +} + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Utils/include/AsyncResponder.h b/Utils/include/AsyncResponder.h index 8b7c8d9b..d6f80a4d 100644 --- a/Utils/include/AsyncResponder.h +++ b/Utils/include/AsyncResponder.h @@ -11,12 +11,14 @@ #ifdef ASYNCH_ENABLED #include #include +#include namespace sck::sample { class AsyncResponder : public sck::async::AsyncMessanger - , public sck::async::MessangerListener - , public sck::async::ErrorListener { + , protected sck::async::MessangerListener + , protected sck::async::ErrorListener + , public Logger { public: AsyncResponder(std::unique_ptr socket); diff --git a/Utils/src/AsyncResponder.cpp b/Utils/src/AsyncResponder.cpp index 6128ecbd..e65c995f 100644 --- a/Utils/src/AsyncResponder.cpp +++ b/Utils/src/AsyncResponder.cpp @@ -10,12 +10,14 @@ namespace sck::sample { AsyncResponder::AsyncResponder(std::unique_ptr socket) - : AsyncMessanger(std::move(socket), 1000) { + : AsyncMessanger(std::move(socket), 1000) + , Logger("AsynchResponder") { } void AsyncResponder::handle(const std::pair& message) { std::string recStr(message.first, message.second); const std::string surname = NamesMap::getSurname(recStr); + this->log("request: ", recStr, " response: ", surname); this->messPtr->send({ surname.data(), surname.size() }); } From ecdd226bd6fcbef1f2251bc51b93fe571085f06e Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 7 May 2021 16:47:13 +0200 Subject: [PATCH 059/228] restoring samples --- CMakeLists.txt | 1 + .../SynchSocket/include/core/Connection.h | 1 - CrossSocket/SynchSocket/include/core/Socket.h | 4 +- .../core/components/RemoteAddressAware.h | 2 +- .../SynchSocket/include/tcp/TcpClient.h | 2 +- .../SynchSocket/include/tcp/TcpServer.h | 7 ++-- .../SynchSocket/include/udp/UdpConnection.h | 1 - Samples/CMakeLists.txt | 10 ++++- Samples/Tcp/CMakeLists.txt | 38 +++++++------------ ...ent.cpp => SampleTcp-01-server-client.cpp} | 0 ...ts.cpp => SampleTcp-02-server-clients.cpp} | 0 ...pp => SampleTcp-03-asyncserver-client.cpp} | 2 +- ...repeater.cpp => SampleTcp-04-repeater.cpp} | 2 +- Samples/Tcp/{applications => }/TcpClient.cpp | 2 +- .../Tcp/{applications => }/TcpRepeater.cpp | 2 +- Samples/Tcp/{applications => }/TcpServer.cpp | 6 +-- .../Tcp/{applications => }/TcpServerAsync.cpp | 6 +-- Samples/Udp/CMakeLists.txt | 19 ++++------ Samples/Udp/Sample-01-client-client.cpp | 11 ------ Samples/Udp/Sample-02-client-server.cpp | 11 ------ Samples/Udp/SampleUdp-01-client-client.cpp | 11 ++++++ Samples/Udp/SampleUdp-02-client-server.cpp | 11 ++++++ .../UdpClientAsker.cpp => UdpAsker.cpp} | 6 +-- ...dpClientResponder.cpp => UdpResponder.cpp} | 6 +-- .../UdpServer.cpp => UdpServerResponder.cpp} | 2 +- cmake/MakeSample.cmake | 14 +++++++ 26 files changed, 91 insertions(+), 86 deletions(-) rename Samples/Tcp/{Sample-01-server-client.cpp => SampleTcp-01-server-client.cpp} (100%) rename Samples/Tcp/{Sample-02-server-clients.cpp => SampleTcp-02-server-clients.cpp} (100%) rename Samples/Tcp/{Sample-04-asyncserver-client.cpp => SampleTcp-03-asyncserver-client.cpp} (86%) rename Samples/Tcp/{Sample-03-repeater.cpp => SampleTcp-04-repeater.cpp} (88%) rename Samples/Tcp/{applications => }/TcpClient.cpp (97%) rename Samples/Tcp/{applications => }/TcpRepeater.cpp (96%) rename Samples/Tcp/{applications => }/TcpServer.cpp (90%) rename Samples/Tcp/{applications => }/TcpServerAsync.cpp (87%) delete mode 100644 Samples/Udp/Sample-01-client-client.cpp delete mode 100644 Samples/Udp/Sample-02-client-server.cpp create mode 100644 Samples/Udp/SampleUdp-01-client-client.cpp create mode 100644 Samples/Udp/SampleUdp-02-client-server.cpp rename Samples/Udp/{applications/UdpClientAsker.cpp => UdpAsker.cpp} (90%) rename Samples/Udp/{applications/UdpClientResponder.cpp => UdpResponder.cpp} (86%) rename Samples/Udp/{applications/UdpServer.cpp => UdpServerResponder.cpp} (93%) create mode 100644 cmake/MakeSample.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 07a9dc5f..a7e3bcea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ set(WITH_SOURCE_TREE ON) include(GroupSources) include(AutoCollect) include(MakeLibrary) +include(MakeSample) project(CrossSocket) add_subdirectory(CrossSocket) diff --git a/CrossSocket/SynchSocket/include/core/Connection.h b/CrossSocket/SynchSocket/include/core/Connection.h index de22ea48..7447a12e 100644 --- a/CrossSocket/SynchSocket/include/core/Connection.h +++ b/CrossSocket/SynchSocket/include/core/Connection.h @@ -28,7 +28,6 @@ namespace sck { */ void openSteps() override; - private: inline sck::Family getFamily() const final { return this->remoteAddress->getFamily(); }; }; } diff --git a/CrossSocket/SynchSocket/include/core/Socket.h b/CrossSocket/SynchSocket/include/core/Socket.h index 0ec0d6d2..f5fae19e 100644 --- a/CrossSocket/SynchSocket/include/core/Socket.h +++ b/CrossSocket/SynchSocket/include/core/Socket.h @@ -42,8 +42,8 @@ namespace sck { class SocketOpenable : public Socket , public Openable - , public FamilyAware - , public ProtocolAware { + , virtual public FamilyAware + , virtual public ProtocolAware { public: /** * @brief When something goes wrong inside the method, close is diff --git a/CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h b/CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h index 4d1e178e..c2c620d8 100644 --- a/CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h +++ b/CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h @@ -13,7 +13,7 @@ namespace sck { class RemoteAddressAware { - protected: + public: /** * @brief returns the address of the remote entity connected with this socket */ diff --git a/CrossSocket/SynchSocket/include/tcp/TcpClient.h b/CrossSocket/SynchSocket/include/tcp/TcpClient.h index 01a90919..e74461fe 100644 --- a/CrossSocket/SynchSocket/include/tcp/TcpClient.h +++ b/CrossSocket/SynchSocket/include/tcp/TcpClient.h @@ -24,7 +24,7 @@ namespace sck::tcp { */ explicit TcpClient(const sck::Ip& remoteAddress); - private: + protected: inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; }; } diff --git a/CrossSocket/SynchSocket/include/tcp/TcpServer.h b/CrossSocket/SynchSocket/include/tcp/TcpServer.h index 65328949..0c561dee 100644 --- a/CrossSocket/SynchSocket/include/tcp/TcpServer.h +++ b/CrossSocket/SynchSocket/include/tcp/TcpServer.h @@ -37,12 +37,13 @@ namespace sck::tcp { */ std::unique_ptr acceptClient(); + protected: + inline sck::Family getFamily() const final { return this->family; }; + inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; + private: void openSteps() override; - inline sck::Family getFamily() const final { return this->family; }; - inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; - std::uint16_t port; sck::Family family; }; diff --git a/CrossSocket/SynchSocket/include/udp/UdpConnection.h b/CrossSocket/SynchSocket/include/udp/UdpConnection.h index ebd2f778..9c7f0b9c 100644 --- a/CrossSocket/SynchSocket/include/udp/UdpConnection.h +++ b/CrossSocket/SynchSocket/include/udp/UdpConnection.h @@ -36,7 +36,6 @@ namespace sck::udp { void openSteps() override; - private: inline sck::Protocol getProtocol() const final { return Protocol::UDP; }; }; } diff --git a/Samples/CMakeLists.txt b/Samples/CMakeLists.txt index ddea8e59..472f1567 100644 --- a/Samples/CMakeLists.txt +++ b/Samples/CMakeLists.txt @@ -1,3 +1,9 @@ -add_subdirectory(Tcp) +option(BUILD_SAMPLES_TCP "Tcp samples enabled (ON) or not (OFF)" ON) +if(BUILD_SAMPLES_TCP) + add_subdirectory(Tcp) +endif() -add_subdirectory(Udp) +option(BUILD_SAMPLES_UDP "Udp samples enabled (ON) or not (OFF)" ON) +if(BUILD_SAMPLES_UDP) + add_subdirectory(Udp) +endif() diff --git a/Samples/Tcp/CMakeLists.txt b/Samples/Tcp/CMakeLists.txt index dedf7bdf..57529861 100644 --- a/Samples/Tcp/CMakeLists.txt +++ b/Samples/Tcp/CMakeLists.txt @@ -1,35 +1,23 @@ project("Tcp-Samples") -function(MakeSample NAME) +MakeSample(TcpClient) -endfunction() +MakeSample(TcpServer) -function(MakeInstallableSample NAME) +MakeSample(TcpServerAsync) -endfunction() +MakeSample(TcpRepeater) -add_executable(TcpClient TcpClient.cpp) -target_link_libraries(TcpClient Sample-Utils) +## samples launchers ## -add_executable(TcpServer TcpServer.cpp) -target_link_libraries(TcpServer Sample-Utils) +add_executable(SampleTcp-01-server-client SampleTcp-01-server-client.cpp) +add_dependencies(SampleTcp-01-server-client TcpClient TcpServer) -add_executable(TcpServerAsync TcpServerAsync.cpp) -target_link_libraries(TcpServerAsync Sample-Utils) +add_executable(SampleTcp-02-server-clients SampleTcp-02-server-clients.cpp) +add_dependencies(SampleTcp-02-server-clients TcpClient TcpServer) -add_executable(TcpRepeater TcpRepeater.cpp) -target_link_libraries(TcpRepeater Sample-Utils) +add_executable(SampleTcp-03-asyncserver-client SampleTcp-03-asyncserver-client.cpp) +add_dependencies(SampleTcp-03-asyncserver-client TcpClient TcpServerAsync) -## launchers ## - -add_executable(Tcp01Launcher Launcher-01-server-client.cpp) -add_dependencies(Tcp01Launcher TcpClient TcpServer) - -add_executable(Tcp02Launcher Launcher-02-server-clients.cpp) -add_dependencies(Tcp02Launcher TcpClient TcpServer) - -add_executable(Tcp03Launcher Launcher-03-repeater.cpp) -add_dependencies(Tcp03Launcher TcpClient TcpServer TcpRepeater) - -add_executable(Tcp04Launcher Launcher-04-asyncserver-client.cpp) -add_dependencies(Tcp04Launcher TcpClient TcpServerAsync) +add_executable(SampleTcp-04-repeater SampleTcp-04-repeater.cpp) +add_dependencies(SampleTcp-04-repeater TcpClient TcpServer TcpRepeater) diff --git a/Samples/Tcp/Sample-01-server-client.cpp b/Samples/Tcp/SampleTcp-01-server-client.cpp similarity index 100% rename from Samples/Tcp/Sample-01-server-client.cpp rename to Samples/Tcp/SampleTcp-01-server-client.cpp diff --git a/Samples/Tcp/Sample-02-server-clients.cpp b/Samples/Tcp/SampleTcp-02-server-clients.cpp similarity index 100% rename from Samples/Tcp/Sample-02-server-clients.cpp rename to Samples/Tcp/SampleTcp-02-server-clients.cpp diff --git a/Samples/Tcp/Sample-04-asyncserver-client.cpp b/Samples/Tcp/SampleTcp-03-asyncserver-client.cpp similarity index 86% rename from Samples/Tcp/Sample-04-asyncserver-client.cpp rename to Samples/Tcp/SampleTcp-03-asyncserver-client.cpp index cb22682c..4a2abf62 100644 --- a/Samples/Tcp/Sample-04-asyncserver-client.cpp +++ b/Samples/Tcp/SampleTcp-03-asyncserver-client.cpp @@ -2,7 +2,7 @@ int main() { - Launcher lnc("Launcher04"); + Launcher lnc("Launcher03"); lnc.addProcess("TcpServerAsync", "45000"); lnc.addProcessSleep(std::make_pair("TcpClient", 1), "45000", "250"); lnc(); diff --git a/Samples/Tcp/Sample-03-repeater.cpp b/Samples/Tcp/SampleTcp-04-repeater.cpp similarity index 88% rename from Samples/Tcp/Sample-03-repeater.cpp rename to Samples/Tcp/SampleTcp-04-repeater.cpp index 56a4ee42..b6e9555a 100644 --- a/Samples/Tcp/Sample-03-repeater.cpp +++ b/Samples/Tcp/SampleTcp-04-repeater.cpp @@ -2,7 +2,7 @@ int main() { - Launcher lnc("Launcher03"); + Launcher lnc("Launcher04"); lnc.addProcess("TcpServer","3000"); lnc.addProcessSleep(std::make_pair("TcpRepeater", 1)); lnc.addProcessSleep(std::make_pair("TcpClient", 1), "4000"); diff --git a/Samples/Tcp/applications/TcpClient.cpp b/Samples/Tcp/TcpClient.cpp similarity index 97% rename from Samples/Tcp/applications/TcpClient.cpp rename to Samples/Tcp/TcpClient.cpp index 61c615c3..63f6e41e 100644 --- a/Samples/Tcp/applications/TcpClient.cpp +++ b/Samples/Tcp/TcpClient.cpp @@ -49,7 +49,7 @@ int main(int argc, char **argv){ } cout << "connection opened" << endl; - Asker ask(std::move(client)); + sck::sample::Asker ask(std::move(client)); ask.askForever(rate); return EXIT_SUCCESS; diff --git a/Samples/Tcp/applications/TcpRepeater.cpp b/Samples/Tcp/TcpRepeater.cpp similarity index 96% rename from Samples/Tcp/applications/TcpRepeater.cpp rename to Samples/Tcp/TcpRepeater.cpp index e982816f..4f262152 100644 --- a/Samples/Tcp/applications/TcpRepeater.cpp +++ b/Samples/Tcp/TcpRepeater.cpp @@ -25,7 +25,7 @@ int main(){ } // accepting client - std::unique_ptr connection2Client; + std::unique_ptr connection2Client; { sck::tcp::TcpServer server(4000); server.open(std::chrono::milliseconds(0)); diff --git a/Samples/Tcp/applications/TcpServer.cpp b/Samples/Tcp/TcpServer.cpp similarity index 90% rename from Samples/Tcp/applications/TcpServer.cpp rename to Samples/Tcp/TcpServer.cpp index 9180aa84..1fd1f527 100644 --- a/Samples/Tcp/applications/TcpServer.cpp +++ b/Samples/Tcp/TcpServer.cpp @@ -43,14 +43,14 @@ int main(int argc, char** argv){ } cout << "connection opened" << endl; - std::list responders; + std::list responders; std::list respThreads; for (std::size_t c = 0; c < clientNumbers; ++c) { //accept the client responders.emplace_back(server.acceptClient()); cout << "new client connected" << endl; - respThreads.emplace_back([&responders]() { - Responder* respRef = &responders.back(); + sck::sample::Responder* respRef = &responders.back(); + respThreads.emplace_back([respRef]() { respRef->respondForever(); }); } diff --git a/Samples/Tcp/applications/TcpServerAsync.cpp b/Samples/Tcp/TcpServerAsync.cpp similarity index 87% rename from Samples/Tcp/applications/TcpServerAsync.cpp rename to Samples/Tcp/TcpServerAsync.cpp index 1e5b7a5b..f5ec9d1c 100644 --- a/Samples/Tcp/applications/TcpServerAsync.cpp +++ b/Samples/Tcp/TcpServerAsync.cpp @@ -5,7 +5,7 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include #include #include @@ -36,8 +36,8 @@ int main(int argc, char** argv){ auto clientConenction = server.acceptClient(); cout << "client accepted" << endl; - ResponderAsync responder(std::move(clientConenction)); - while(responder.isRunning()) { + sck::sample::AsyncResponder responder(std::move(clientConenction)); + while(responder.isOpen()) { } return EXIT_SUCCESS; diff --git a/Samples/Udp/CMakeLists.txt b/Samples/Udp/CMakeLists.txt index d77a1f28..e97d4309 100644 --- a/Samples/Udp/CMakeLists.txt +++ b/Samples/Udp/CMakeLists.txt @@ -1,18 +1,15 @@ project("Udp-Samples") -add_executable(UdpClientAsker UdpClientAsker.cpp) -target_link_libraries(UdpClientAsker Sample-Utils) +MakeSample(UdpAsker) -add_executable(UdpClientResponder UdpClientResponder.cpp) -target_link_libraries(UdpClientResponder Sample-Utils) +MakeSample(UdpResponder) -add_executable(UdpServer UdpServer.cpp) -target_link_libraries(UdpServer Sample-Utils) +MakeSample(UdpServerResponder) -## launchers ## +## samples launchers ## -add_executable(Udp01Launcher Launcher-01-client-client.cpp) -add_dependencies(Udp01Launcher UdpClientAsker UdpClientResponder) +add_executable(SampleUdp-01-client-client SampleUdp-01-client-client.cpp) +add_dependencies(SampleUdp-01-client-client UdpAsker UdpResponder) -add_executable(Udp02Launcher Launcher-02-client-server.cpp) -add_dependencies(Udp02Launcher UdpClientAsker UdpServer) +add_executable(SampleUdp-02-client-server SampleUdp-02-client-server.cpp) +add_dependencies(SampleUdp-02-client-server UdpAsker UdpServerResponder) diff --git a/Samples/Udp/Sample-01-client-client.cpp b/Samples/Udp/Sample-01-client-client.cpp deleted file mode 100644 index cd2d41a2..00000000 --- a/Samples/Udp/Sample-01-client-client.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "../ProcessLauncher.h" - -int main() { - - Launcher lnc("Launcher01"); - lnc.addProcess("UdpClientResponder", "10000", "15000"); - lnc.addProcessSleep(std::make_pair("UdpClientAsker", 1), "15000", "250", "10000"); - lnc(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Udp/Sample-02-client-server.cpp b/Samples/Udp/Sample-02-client-server.cpp deleted file mode 100644 index 675a5f34..00000000 --- a/Samples/Udp/Sample-02-client-server.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "../ProcessLauncher.h" - -int main() { - - Launcher lnc("Launcher02"); - lnc.addProcess("UdpServer", "35000"); - lnc.addProcessSleep(std::make_pair("UdpClientAsker", 1), "35000", "250", "30000", "1"); - lnc(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples/Udp/SampleUdp-01-client-client.cpp b/Samples/Udp/SampleUdp-01-client-client.cpp new file mode 100644 index 00000000..c3d88f67 --- /dev/null +++ b/Samples/Udp/SampleUdp-01-client-client.cpp @@ -0,0 +1,11 @@ +#include "../ProcessLauncher.h" + +int main() { + + Launcher lnc("Launcher01"); + lnc.addProcess("UdpResponder", "10000", "15000"); + lnc.addProcessSleep(std::make_pair("UdpAsker", 1), "15000", "250", "10000"); + lnc(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/SampleUdp-02-client-server.cpp b/Samples/Udp/SampleUdp-02-client-server.cpp new file mode 100644 index 00000000..c84e60f7 --- /dev/null +++ b/Samples/Udp/SampleUdp-02-client-server.cpp @@ -0,0 +1,11 @@ +#include "../ProcessLauncher.h" + +int main() { + + Launcher lnc("Launcher02"); + lnc.addProcess("UdpServerResponder", "35000"); + lnc.addProcessSleep(std::make_pair("UdpAsker", 1), "35000", "250", "30000", "1"); + lnc(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/applications/UdpClientAsker.cpp b/Samples/Udp/UdpAsker.cpp similarity index 90% rename from Samples/Udp/applications/UdpClientAsker.cpp rename to Samples/Udp/UdpAsker.cpp index df27694f..7694c0d8 100644 --- a/Samples/Udp/applications/UdpClientAsker.cpp +++ b/Samples/Udp/UdpAsker.cpp @@ -6,7 +6,7 @@ **/ #include -#include +#include #include using namespace std; @@ -49,7 +49,7 @@ int main(int argc, char **argv){ return EXIT_FAILURE; } - std::unique_ptr client = std::make_unique(*serverAddress, port); + std::unique_ptr client = std::make_unique(*serverAddress, port); cout << "Expecting connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; // blocking open @@ -62,7 +62,7 @@ int main(int argc, char **argv){ cout << "handshake sent" << endl; } - Asker ask(std::move(client)); + sck::sample::Asker ask(std::move(client)); ask.askForever(rate); return EXIT_SUCCESS; diff --git a/Samples/Udp/applications/UdpClientResponder.cpp b/Samples/Udp/UdpResponder.cpp similarity index 86% rename from Samples/Udp/applications/UdpClientResponder.cpp rename to Samples/Udp/UdpResponder.cpp index 216a4f9a..1daa7fd8 100644 --- a/Samples/Udp/applications/UdpClientResponder.cpp +++ b/Samples/Udp/UdpResponder.cpp @@ -6,7 +6,7 @@ **/ #include -#include +#include #include using namespace std; @@ -38,14 +38,14 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - std::unique_ptr client = std::make_unique(*serverAddress, port); + std::unique_ptr client = std::make_unique(*serverAddress, port); cout << "Expecting connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; // blocking open client->open(std::chrono::milliseconds(0)); cout << "connection opened" << endl; - Responder responder(std::move(client)); + sck::sample::Responder responder(std::move(client)); responder.respondForever(); return EXIT_SUCCESS; diff --git a/Samples/Udp/applications/UdpServer.cpp b/Samples/Udp/UdpServerResponder.cpp similarity index 93% rename from Samples/Udp/applications/UdpServer.cpp rename to Samples/Udp/UdpServerResponder.cpp index e721e8ca..6bfe030a 100644 --- a/Samples/Udp/applications/UdpServer.cpp +++ b/Samples/Udp/UdpServerResponder.cpp @@ -27,7 +27,7 @@ int main(int argc, char** argv) { server->open(std::chrono::milliseconds(0)); cout << "connection opened" << endl; - Responder responder(std::move(server)); + sck::sample::Responder responder(std::move(server)); responder.respondForever(); return EXIT_SUCCESS; diff --git a/cmake/MakeSample.cmake b/cmake/MakeSample.cmake new file mode 100644 index 00000000..74302e1b --- /dev/null +++ b/cmake/MakeSample.cmake @@ -0,0 +1,14 @@ +function(MakeSample NAME) + add_executable(${NAME} ${NAME}.cpp) + + target_link_libraries(${NAME} + PUBLIC + Utils + ) +endfunction() + +# function(MakeInstallableSample NAME) + # MakeSample(${NAME}) + + # install(TARGETS ${NAME}) +# endfunction() From 04b2cbb29641aa96172e4a50297e8060b9db532c Mon Sep 17 00:00:00 2001 From: AndreaC Date: Sat, 8 May 2021 00:15:19 +0200 Subject: [PATCH 060/228] implementing --- CMakeLists.txt | 2 ++ CrossSocket/SynchSocket/src/core/Messanger.cpp | 2 +- Samples/Tcp/CMakeLists.txt | 12 ++++-------- Samples/Udp/CMakeLists.txt | 6 ++---- ...ient-client.cpp => SampleUdp-01-connection.cpp} | 0 ...2-client-server.cpp => SampleUdp-02-server.cpp} | 0 Tests/Test01-tcp-synch.cpp | 1 + Tests/Test02-tcp-asynch.cpp | 1 + cmake/MakeSample.cmake | 14 ++++++++++---- 9 files changed, 21 insertions(+), 17 deletions(-) rename Samples/Udp/{SampleUdp-01-client-client.cpp => SampleUdp-01-connection.cpp} (100%) rename Samples/Udp/{SampleUdp-02-client-server.cpp => SampleUdp-02-server.cpp} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7e3bcea..b1788671 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,8 @@ include(AutoCollect) include(MakeLibrary) include(MakeSample) +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + project(CrossSocket) add_subdirectory(CrossSocket) diff --git a/CrossSocket/SynchSocket/src/core/Messanger.cpp b/CrossSocket/SynchSocket/src/core/Messanger.cpp index 88125e25..43f97f5f 100644 --- a/CrossSocket/SynchSocket/src/core/Messanger.cpp +++ b/CrossSocket/SynchSocket/src/core/Messanger.cpp @@ -25,7 +25,7 @@ namespace sck { else { tv.tv_usec = std::chrono::duration_cast(this->actualTimeOut).count(); } - if (::setsockopt(**this->channelMsg, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(struct timeval)) < 0) { + if (::setsockopt(**this->channel, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(struct timeval)) < 0) { #endif throwWithCode("can't set timeout"); } diff --git a/Samples/Tcp/CMakeLists.txt b/Samples/Tcp/CMakeLists.txt index 57529861..47519ff0 100644 --- a/Samples/Tcp/CMakeLists.txt +++ b/Samples/Tcp/CMakeLists.txt @@ -10,14 +10,10 @@ MakeSample(TcpRepeater) ## samples launchers ## -add_executable(SampleTcp-01-server-client SampleTcp-01-server-client.cpp) -add_dependencies(SampleTcp-01-server-client TcpClient TcpServer) +MakeLaunch(SampleTcp-01-server-client TcpClient TcpServer) -add_executable(SampleTcp-02-server-clients SampleTcp-02-server-clients.cpp) -add_dependencies(SampleTcp-02-server-clients TcpClient TcpServer) +MakeLaunch(SampleTcp-02-server-clients TcpClient TcpServer) -add_executable(SampleTcp-03-asyncserver-client SampleTcp-03-asyncserver-client.cpp) -add_dependencies(SampleTcp-03-asyncserver-client TcpClient TcpServerAsync) +MakeLaunch(SampleTcp-03-asyncserver-client TcpClient TcpServerAsync) -add_executable(SampleTcp-04-repeater SampleTcp-04-repeater.cpp) -add_dependencies(SampleTcp-04-repeater TcpClient TcpServer TcpRepeater) +MakeLaunch(SampleTcp-04-repeater TcpClient TcpServer TcpRepeater) diff --git a/Samples/Udp/CMakeLists.txt b/Samples/Udp/CMakeLists.txt index e97d4309..e7e4acb9 100644 --- a/Samples/Udp/CMakeLists.txt +++ b/Samples/Udp/CMakeLists.txt @@ -8,8 +8,6 @@ MakeSample(UdpServerResponder) ## samples launchers ## -add_executable(SampleUdp-01-client-client SampleUdp-01-client-client.cpp) -add_dependencies(SampleUdp-01-client-client UdpAsker UdpResponder) +MakeLaunch(SampleUdp-01-connection UdpAsker UdpResponder) -add_executable(SampleUdp-02-client-server SampleUdp-02-client-server.cpp) -add_dependencies(SampleUdp-02-client-server UdpAsker UdpServerResponder) +MakeLaunch(SampleUdp-02-server UdpAsker UdpServerResponder) diff --git a/Samples/Udp/SampleUdp-01-client-client.cpp b/Samples/Udp/SampleUdp-01-connection.cpp similarity index 100% rename from Samples/Udp/SampleUdp-01-client-client.cpp rename to Samples/Udp/SampleUdp-01-connection.cpp diff --git a/Samples/Udp/SampleUdp-02-client-server.cpp b/Samples/Udp/SampleUdp-02-server.cpp similarity index 100% rename from Samples/Udp/SampleUdp-02-client-server.cpp rename to Samples/Udp/SampleUdp-02-server.cpp diff --git a/Tests/Test01-tcp-synch.cpp b/Tests/Test01-tcp-synch.cpp index 41894993..85218661 100644 --- a/Tests/Test01-tcp-synch.cpp +++ b/Tests/Test01-tcp-synch.cpp @@ -3,6 +3,7 @@ #include #include #include +#include using namespace sck; using namespace sck::tcp; diff --git a/Tests/Test02-tcp-asynch.cpp b/Tests/Test02-tcp-asynch.cpp index 2ae02348..e984706c 100644 --- a/Tests/Test02-tcp-asynch.cpp +++ b/Tests/Test02-tcp-asynch.cpp @@ -3,6 +3,7 @@ #include #include #include +#include using namespace sck; using namespace sck::tcp; diff --git a/cmake/MakeSample.cmake b/cmake/MakeSample.cmake index 74302e1b..f3be14b9 100644 --- a/cmake/MakeSample.cmake +++ b/cmake/MakeSample.cmake @@ -5,10 +5,16 @@ function(MakeSample NAME) PUBLIC Utils ) + + set_target_properties(${NAME} PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=%PATH%;${CMAKE_INSTALL_PREFIX}/bin/") + + install(TARGETS ${NAME}) endfunction() -# function(MakeInstallableSample NAME) - # MakeSample(${NAME}) +function(MakeLaunch NAME) + add_executable(${NAME} ${NAME}.cpp) - # install(TARGETS ${NAME}) -# endfunction() + add_dependencies(${NAME} ${ARGV}) + + install(TARGETS ${NAME}) +endfunction() From a6a0792a450a92b504ddb01cf2d51d94470906cf Mon Sep 17 00:00:00 2001 From: Andrea Date: Sat, 8 May 2021 11:10:43 +0200 Subject: [PATCH 061/228] launchers modified --- Samples/{ProcessLauncher.h => Launcher.h} | 38 +++++++++++++++---- Samples/Tcp/SampleTcp-01-server-client.cpp | 6 +-- Samples/Tcp/SampleTcp-02-server-clients.cpp | 6 +-- .../Tcp/SampleTcp-03-asyncserver-client.cpp | 6 +-- Samples/Tcp/SampleTcp-04-repeater.cpp | 6 +-- Samples/Udp/SampleUdp-01-connection.cpp | 6 +-- Samples/Udp/SampleUdp-02-server.cpp | 6 +-- 7 files changed, 49 insertions(+), 25 deletions(-) rename Samples/{ProcessLauncher.h => Launcher.h} (78%) diff --git a/Samples/ProcessLauncher.h b/Samples/Launcher.h similarity index 78% rename from Samples/ProcessLauncher.h rename to Samples/Launcher.h index 24283c5e..60735996 100644 --- a/Samples/ProcessLauncher.h +++ b/Samples/Launcher.h @@ -52,9 +52,10 @@ class Launcher { /** * @brief Print the script and runs it */ - void operator()() const; + void run() const; private: + std::string writeScript() const; template void parseArgs(std::list& parsed , const std::string& arg, Args ... args) { @@ -83,7 +84,7 @@ class Launcher { #include -void Launcher::operator()() const { +std::string Launcher::writeScript() const { std::string name = this->nameFile; #ifdef _WIN32 name += ".bat"; @@ -108,23 +109,46 @@ void Launcher::operator()() const { addSleep(it->msSleep); #ifdef _WIN32 f << std::endl << "start \"\" \"" << it->processName << "\""; - for(auto a : it->processArguments) f << " \"" << a << "\""; + for (auto a : it->processArguments) f << " \"" << a << "\""; #elif __linux__ f << std::endl << "gnome-terminal -x sh -c \"./" << it->processName; - for(auto a : it->processArguments) f << " " << a; + for (auto a : it->processArguments) f << " " << a; f << "; bash\""; #endif } addSleep(it->msSleep); #ifdef _WIN32 f << std::endl << "\"" << this->commands.back().processName << "\""; - for(auto a : this->commands.back().processArguments) f << " \"" << a << "\""; + for (auto a : this->commands.back().processArguments) f << " \"" << a << "\""; #elif __linux__ f << std::endl << "./" << this->commands.back().processName; - for(auto a : this->commands.back().processArguments) f << " " << a; + for (auto a : this->commands.back().processArguments) f << " " << a; #endif - f.close(); + return name; +} + +#include + +void Launcher::run() const { + std::string scriptName; + // run + std::stringstream runCmd; +#if __linux__ + runCmd << "sh ./"; +#endif + scriptName = this->writeScript(); + runCmd << scriptName; + std::system(runCmd.str().c_str()); + // delete file + std::stringstream delCmd; +#ifdef _WIN32 + delCmd << "del "; +#elif __linux__ + delCmd << "rm "; +#endif + delCmd << scriptName; + std::system(delCmd.str().c_str()); }; #endif \ No newline at end of file diff --git a/Samples/Tcp/SampleTcp-01-server-client.cpp b/Samples/Tcp/SampleTcp-01-server-client.cpp index c0d712cc..eb4fc7ba 100644 --- a/Samples/Tcp/SampleTcp-01-server-client.cpp +++ b/Samples/Tcp/SampleTcp-01-server-client.cpp @@ -1,11 +1,11 @@ -#include "../ProcessLauncher.h" +#include "../Launcher.h" int main() { - Launcher lnc("Launcher01"); + Launcher lnc("Launcher"); lnc.addProcess("TcpServer", "20000"); lnc.addProcessSleep(std::make_pair("TcpClient", 1), "20000"); - lnc(); + lnc.run(); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/SampleTcp-02-server-clients.cpp b/Samples/Tcp/SampleTcp-02-server-clients.cpp index 63d273c4..32a2d6c0 100644 --- a/Samples/Tcp/SampleTcp-02-server-clients.cpp +++ b/Samples/Tcp/SampleTcp-02-server-clients.cpp @@ -1,12 +1,12 @@ -#include "../ProcessLauncher.h" +#include "../Launcher.h" int main() { - Launcher lnc("Launcher02"); + Launcher lnc("Launcher"); lnc.addProcess("TcpServer", "25000"); lnc.addProcessSleep(std::make_pair("TcpClient", 1), "25000", "100"); lnc.addProcess("TcpClient","25000", "500"); - lnc(); + lnc.run(); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/SampleTcp-03-asyncserver-client.cpp b/Samples/Tcp/SampleTcp-03-asyncserver-client.cpp index 4a2abf62..adf9ce4c 100644 --- a/Samples/Tcp/SampleTcp-03-asyncserver-client.cpp +++ b/Samples/Tcp/SampleTcp-03-asyncserver-client.cpp @@ -1,11 +1,11 @@ -#include "../ProcessLauncher.h" +#include "../Launcher.h" int main() { - Launcher lnc("Launcher03"); + Launcher lnc("Launcher"); lnc.addProcess("TcpServerAsync", "45000"); lnc.addProcessSleep(std::make_pair("TcpClient", 1), "45000", "250"); - lnc(); + lnc.run(); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Tcp/SampleTcp-04-repeater.cpp b/Samples/Tcp/SampleTcp-04-repeater.cpp index b6e9555a..0c802b8d 100644 --- a/Samples/Tcp/SampleTcp-04-repeater.cpp +++ b/Samples/Tcp/SampleTcp-04-repeater.cpp @@ -1,12 +1,12 @@ -#include "../ProcessLauncher.h" +#include "../Launcher.h" int main() { - Launcher lnc("Launcher04"); + Launcher lnc("Launcher"); lnc.addProcess("TcpServer","3000"); lnc.addProcessSleep(std::make_pair("TcpRepeater", 1)); lnc.addProcessSleep(std::make_pair("TcpClient", 1), "4000"); - lnc(); + lnc.run(); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Udp/SampleUdp-01-connection.cpp b/Samples/Udp/SampleUdp-01-connection.cpp index c3d88f67..02e744a7 100644 --- a/Samples/Udp/SampleUdp-01-connection.cpp +++ b/Samples/Udp/SampleUdp-01-connection.cpp @@ -1,11 +1,11 @@ -#include "../ProcessLauncher.h" +#include "../Launcher.h" int main() { - Launcher lnc("Launcher01"); + Launcher lnc("Launcher"); lnc.addProcess("UdpResponder", "10000", "15000"); lnc.addProcessSleep(std::make_pair("UdpAsker", 1), "15000", "250", "10000"); - lnc(); + lnc.run(); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Samples/Udp/SampleUdp-02-server.cpp b/Samples/Udp/SampleUdp-02-server.cpp index c84e60f7..ecd8d556 100644 --- a/Samples/Udp/SampleUdp-02-server.cpp +++ b/Samples/Udp/SampleUdp-02-server.cpp @@ -1,11 +1,11 @@ -#include "../ProcessLauncher.h" +#include "../Launcher.h" int main() { - Launcher lnc("Launcher02"); + Launcher lnc("Launcher"); lnc.addProcess("UdpServerResponder", "35000"); lnc.addProcessSleep(std::make_pair("UdpAsker", 1), "35000", "250", "30000", "1"); - lnc(); + lnc.run(); return EXIT_SUCCESS; } \ No newline at end of file From eceb9bf532595793c7ac53d5947a5086b3b35d8d Mon Sep 17 00:00:00 2001 From: Andrea Date: Sat, 8 May 2021 14:14:17 +0200 Subject: [PATCH 062/228] samples fixed on Windows --- .../AsynchSocket/include/core/Talker.h | 2 +- .../AsynchSocket/src/core/AsyncSocket.cpp | 12 ++-- .../src/messanger/AsyncMessanger.cpp | 1 + Samples/Tcp/CMakeLists.txt | 2 +- Samples/Tcp/SampleTcp-02-server-clients.cpp | 2 +- ...p => SampleTcp-03-asyncserver-clients.cpp} | 5 +- Samples/Tcp/TcpServer.cpp | 17 ++++-- Samples/Tcp/TcpServerAsync.cpp | 33 +++++++++-- Samples/Udp/CMakeLists.txt | 8 ++- Samples/Udp/SampleUdp-02-asyncconnection.cpp | 11 ++++ ...-02-server.cpp => SampleUdp-03-server.cpp} | 2 +- Samples/Udp/UdpAsker.cpp | 2 +- Samples/Udp/UdpResponder.cpp | 2 +- Samples/Udp/UdpResponderAsync.cpp | 55 +++++++++++++++++++ .../{UdpServerResponder.cpp => UdpServer.cpp} | 0 Utils/include/Responder.h | 3 +- Utils/src/AsyncResponder.cpp | 2 + Utils/src/Responder.cpp | 2 +- 18 files changed, 130 insertions(+), 31 deletions(-) rename Samples/Tcp/{SampleTcp-03-asyncserver-client.cpp => SampleTcp-03-asyncserver-clients.cpp} (61%) create mode 100644 Samples/Udp/SampleUdp-02-asyncconnection.cpp rename Samples/Udp/{SampleUdp-02-server.cpp => SampleUdp-03-server.cpp} (79%) create mode 100644 Samples/Udp/UdpResponderAsync.cpp rename Samples/Udp/{UdpServerResponder.cpp => UdpServer.cpp} (100%) diff --git a/CrossSocket/AsynchSocket/include/core/Talker.h b/CrossSocket/AsynchSocket/include/core/Talker.h index 1547590f..c2dbf5be 100644 --- a/CrossSocket/AsynchSocket/include/core/Talker.h +++ b/CrossSocket/AsynchSocket/include/core/Talker.h @@ -15,7 +15,7 @@ namespace sck::async { template class Talker { public: - void resetListener(std::shared_ptr listener) { + void resetListener(Listener* listener) { if (nullptr == listener) { throw Error("The passed listener is empty"); } diff --git a/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp b/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp index 1221d478..374dc859 100644 --- a/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp +++ b/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp @@ -10,9 +10,6 @@ namespace sck::async { AsyncSocket::AsyncSocket(std::unique_ptr socket) : SocketDecorator(std::move(socket)) { - if (this->wrapped->isOpen()) { - this->spawnService(); - } }; void AsyncSocket::spawnService() { @@ -46,18 +43,19 @@ namespace sck::async { if (nullptr != this->service) { throw Error("The socket was already opened"); } - this->SocketDecorator::open(timeout); - if (this->wrapped->isOpen()) { - this->spawnService(); + // open the wrapped socket when necessary + if (!this->wrapped->isOpen()) { + this->SocketDecorator::open(timeout); } + this->spawnService(); }; void AsyncSocket::close() { + this->wrapped->close(); if (nullptr == this->service) { return; } this->serviceLife = false; - this->wrapped->close(); if (this->service->joinable()) { this->service->join(); } diff --git a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp index da5afd13..a8a1cc3b 100644 --- a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp +++ b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp @@ -26,6 +26,7 @@ namespace sck::async { if (recvBytes != this->receiveBuffer.capacity()) { this->receiveBuffer.resize(recvBytes); } + pr.second = recvBytes; this->Talker::notify(pr); } } \ No newline at end of file diff --git a/Samples/Tcp/CMakeLists.txt b/Samples/Tcp/CMakeLists.txt index 47519ff0..78e87111 100644 --- a/Samples/Tcp/CMakeLists.txt +++ b/Samples/Tcp/CMakeLists.txt @@ -14,6 +14,6 @@ MakeLaunch(SampleTcp-01-server-client TcpClient TcpServer) MakeLaunch(SampleTcp-02-server-clients TcpClient TcpServer) -MakeLaunch(SampleTcp-03-asyncserver-client TcpClient TcpServerAsync) +MakeLaunch(SampleTcp-03-asyncserver-clients TcpClient TcpServerAsync) MakeLaunch(SampleTcp-04-repeater TcpClient TcpServer TcpRepeater) diff --git a/Samples/Tcp/SampleTcp-02-server-clients.cpp b/Samples/Tcp/SampleTcp-02-server-clients.cpp index 32a2d6c0..281d22fa 100644 --- a/Samples/Tcp/SampleTcp-02-server-clients.cpp +++ b/Samples/Tcp/SampleTcp-02-server-clients.cpp @@ -3,7 +3,7 @@ int main() { Launcher lnc("Launcher"); - lnc.addProcess("TcpServer", "25000"); + lnc.addProcess("TcpServer", "25000", "2"); lnc.addProcessSleep(std::make_pair("TcpClient", 1), "25000", "100"); lnc.addProcess("TcpClient","25000", "500"); lnc.run(); diff --git a/Samples/Tcp/SampleTcp-03-asyncserver-client.cpp b/Samples/Tcp/SampleTcp-03-asyncserver-clients.cpp similarity index 61% rename from Samples/Tcp/SampleTcp-03-asyncserver-client.cpp rename to Samples/Tcp/SampleTcp-03-asyncserver-clients.cpp index adf9ce4c..37738609 100644 --- a/Samples/Tcp/SampleTcp-03-asyncserver-client.cpp +++ b/Samples/Tcp/SampleTcp-03-asyncserver-clients.cpp @@ -3,8 +3,9 @@ int main() { Launcher lnc("Launcher"); - lnc.addProcess("TcpServerAsync", "45000"); - lnc.addProcessSleep(std::make_pair("TcpClient", 1), "45000", "250"); + lnc.addProcess("TcpServerAsync", "45000", "2"); + lnc.addProcessSleep(std::make_pair("TcpClient", 1), "45000", "100"); + lnc.addProcess("TcpClient", "45000", "500"); lnc.run(); return EXIT_SUCCESS; diff --git a/Samples/Tcp/TcpServer.cpp b/Samples/Tcp/TcpServer.cpp index 1fd1f527..b89188ef 100644 --- a/Samples/Tcp/TcpServer.cpp +++ b/Samples/Tcp/TcpServer.cpp @@ -47,16 +47,21 @@ int main(int argc, char** argv){ std::list respThreads; for (std::size_t c = 0; c < clientNumbers; ++c) { //accept the client + cout << "waiting client " << c << endl; responders.emplace_back(server.acceptClient()); cout << "new client connected" << endl; - sck::sample::Responder* respRef = &responders.back(); - respThreads.emplace_back([respRef]() { - respRef->respondForever(); - }); + respThreads.emplace_back(&sck::sample::Responder::respondForever, &responders.back()); } - for (auto it = respThreads.begin(); it != respThreads.end(); ++it) { - it->join(); + while (!respThreads.empty()) { + auto it = respThreads.begin(); + while (it != respThreads.end()) { + if (it->joinable()) { + it->join(); + it = respThreads.erase(it); + } + else ++it; + } } return EXIT_SUCCESS; diff --git a/Samples/Tcp/TcpServerAsync.cpp b/Samples/Tcp/TcpServerAsync.cpp index f5ec9d1c..8cd3965d 100644 --- a/Samples/Tcp/TcpServerAsync.cpp +++ b/Samples/Tcp/TcpServerAsync.cpp @@ -11,15 +11,24 @@ #include using namespace std; -int main(int argc, char** argv){ +int main(int argc, char** argv) { cout << "----------------------- Server Async -----------------------" << endl; if (argc == 1) { - cout << "correct syntax is: 'port to bind'" << endl; + cout << "correct syntax is: 'port to bind', 'number of clients to accept'" << endl; return EXIT_FAILURE; } std::uint16_t port = std::atoi(argv[1]); + std::size_t clientNumbers = 1; + if (argc > 2) { + clientNumbers = std::atoi(argv[2]); + } + if (0 == clientNumbers) { + cout << "invalid number of clients" << endl; + return EXIT_FAILURE; + } + cout << "clients excepted " << clientNumbers << endl; //build the server object sck::tcp::TcpServer server(port); @@ -33,11 +42,23 @@ int main(int argc, char** argv){ } cout << "connection opened" << endl; - auto clientConenction = server.acceptClient(); - cout << "client accepted" << endl; + std::list responders; + for (std::size_t c = 0; c < clientNumbers; ++c) { + //accept the client + cout << "waiting client " << c << endl; + responders.emplace_back(server.acceptClient()); + cout << "new client connected" << endl; + responders.back().open(std::chrono::milliseconds(0)); + } - sck::sample::AsyncResponder responder(std::move(clientConenction)); - while(responder.isOpen()) { + while (!responders.empty()) { + auto it = responders.begin(); + while (it != responders.end()) { + if (!it->isOpen()) { + it = responders.erase(it); + } + else ++it; + } } return EXIT_SUCCESS; diff --git a/Samples/Udp/CMakeLists.txt b/Samples/Udp/CMakeLists.txt index e7e4acb9..86ba7ac4 100644 --- a/Samples/Udp/CMakeLists.txt +++ b/Samples/Udp/CMakeLists.txt @@ -4,10 +4,14 @@ MakeSample(UdpAsker) MakeSample(UdpResponder) -MakeSample(UdpServerResponder) +MakeSample(UdpResponderAsync) + +MakeSample(UdpServer) ## samples launchers ## MakeLaunch(SampleUdp-01-connection UdpAsker UdpResponder) -MakeLaunch(SampleUdp-02-server UdpAsker UdpServerResponder) +MakeLaunch(SampleUdp-02-asyncconnection UdpAsker UdpResponderAsync) + +MakeLaunch(SampleUdp-03-server UdpAsker UdpServer) diff --git a/Samples/Udp/SampleUdp-02-asyncconnection.cpp b/Samples/Udp/SampleUdp-02-asyncconnection.cpp new file mode 100644 index 00000000..7e14dc65 --- /dev/null +++ b/Samples/Udp/SampleUdp-02-asyncconnection.cpp @@ -0,0 +1,11 @@ +#include "../Launcher.h" + +int main() { + + Launcher lnc("Launcher"); + lnc.addProcess("UdpResponderAsync", "10000", "15000"); + lnc.addProcessSleep(std::make_pair("UdpAsker", 1), "15000", "250", "10000"); + lnc.run(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/SampleUdp-02-server.cpp b/Samples/Udp/SampleUdp-03-server.cpp similarity index 79% rename from Samples/Udp/SampleUdp-02-server.cpp rename to Samples/Udp/SampleUdp-03-server.cpp index ecd8d556..d5f19db3 100644 --- a/Samples/Udp/SampleUdp-02-server.cpp +++ b/Samples/Udp/SampleUdp-03-server.cpp @@ -3,7 +3,7 @@ int main() { Launcher lnc("Launcher"); - lnc.addProcess("UdpServerResponder", "35000"); + lnc.addProcess("UdpServer", "35000"); lnc.addProcessSleep(std::make_pair("UdpAsker", 1), "35000", "250", "30000", "1"); lnc.run(); diff --git a/Samples/Udp/UdpAsker.cpp b/Samples/Udp/UdpAsker.cpp index 7694c0d8..b9bae222 100644 --- a/Samples/Udp/UdpAsker.cpp +++ b/Samples/Udp/UdpAsker.cpp @@ -11,7 +11,7 @@ using namespace std; int main(int argc, char **argv){ - cout << "----------------------- Client Asker -----------------------" << endl; + cout << "----------------------- Connection Asker -----------------------" << endl; if (argc == 1) { cout << "correct syntax is: 'server port', 'rate', 'port to reserve', 'send initial handshake flag 0/1', 'server host'" << endl; diff --git a/Samples/Udp/UdpResponder.cpp b/Samples/Udp/UdpResponder.cpp index 1daa7fd8..00fbb081 100644 --- a/Samples/Udp/UdpResponder.cpp +++ b/Samples/Udp/UdpResponder.cpp @@ -11,7 +11,7 @@ using namespace std; int main(int argc, char** argv) { - cout << "----------------------- Client Responder -----------------------" << endl; + cout << "----------------------- Connection Responder -----------------------" << endl; if (argc == 1) { cout << "correct syntax is: 'server port', 'port to reserve', 'server host'" << endl; diff --git a/Samples/Udp/UdpResponderAsync.cpp b/Samples/Udp/UdpResponderAsync.cpp new file mode 100644 index 00000000..e8858adb --- /dev/null +++ b/Samples/Udp/UdpResponderAsync.cpp @@ -0,0 +1,55 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include +using namespace std; + +int main(int argc, char** argv) { + cout << "----------------------- Connection Responder -----------------------" << endl; + + if (argc == 1) { + cout << "correct syntax is: 'server port', 'port to reserve', 'server host'" << endl; + return EXIT_FAILURE; + } + + std::uint16_t serverPort = std::atoi(argv[1]); + + std::uint16_t port = 0; + if (argc > 2) { + port = std::atoi(argv[2]); + } + cout << "port reserved by this udp " << port << endl; + + sck::IpPtr serverAddress; + if (argc > 3) { + serverAddress = sck::Ip::create(std::string(argv[3]), serverPort); + } + else { + serverAddress = sck::Ip::createLocalHost(serverPort); + } + if (nullptr == serverAddress) { + cout << "invalid server address" << endl; + return EXIT_FAILURE; + } + + std::unique_ptr client = std::make_unique(*serverAddress, port); + cout << "Expecting connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; + + // blocking open + client->open(std::chrono::milliseconds(0)); + cout << "connection opened" << endl; + + sck::sample::AsyncResponder responder(std::move(client)); + responder.open(std::chrono::milliseconds(0)); + + while (responder.isOpen()) { + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Samples/Udp/UdpServerResponder.cpp b/Samples/Udp/UdpServer.cpp similarity index 100% rename from Samples/Udp/UdpServerResponder.cpp rename to Samples/Udp/UdpServer.cpp diff --git a/Utils/include/Responder.h b/Utils/include/Responder.h index f474bddf..a64e8808 100644 --- a/Utils/include/Responder.h +++ b/Utils/include/Responder.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace sck::sample { class Responder @@ -26,7 +27,7 @@ namespace sck::sample { private: std::unique_ptr socket; - char recvBuffer[1000]; + std::array recvBuffer; }; } diff --git a/Utils/src/AsyncResponder.cpp b/Utils/src/AsyncResponder.cpp index e65c995f..d593ca4d 100644 --- a/Utils/src/AsyncResponder.cpp +++ b/Utils/src/AsyncResponder.cpp @@ -12,6 +12,8 @@ namespace sck::sample { AsyncResponder::AsyncResponder(std::unique_ptr socket) : AsyncMessanger(std::move(socket), 1000) , Logger("AsynchResponder") { + this->Talker::resetListener(this); + this->Talker::resetListener(this); } void AsyncResponder::handle(const std::pair& message) { diff --git a/Utils/src/Responder.cpp b/Utils/src/Responder.cpp index 700d75a6..e15d18ca 100644 --- a/Utils/src/Responder.cpp +++ b/Utils/src/Responder.cpp @@ -15,7 +15,7 @@ namespace sck::sample { } void Responder::respond() { - std::pair temp = { &this->recvBuffer[0], 1000 }; + std::pair temp = { this->recvBuffer.data(), this->recvBuffer.size() }; std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); std::string recStr(temp.first, recvBytes); From 94cab0d40dac30442839077fa2572463acf1d9fd Mon Sep 17 00:00:00 2001 From: AndreaC Date: Sat, 8 May 2021 16:13:35 +0200 Subject: [PATCH 063/228] samples fixed in Linux --- CrossSocket/AsynchSocket/include/core/AsyncSocket.h | 4 +++- .../AsynchSocket/include/messanger/AsyncMessanger.h | 4 +++- .../AsynchSocket/include/tcpServer/AsyncTcpServer.h | 4 +++- Samples/Tcp/CMakeLists.txt | 8 ++++++-- Samples/Udp/CMakeLists.txt | 8 ++++++-- Utils/src/AsyncResponder.cpp | 5 +++-- 6 files changed, 24 insertions(+), 9 deletions(-) diff --git a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h index e0e7892b..95815ae9 100644 --- a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h +++ b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h @@ -15,9 +15,11 @@ #include namespace sck::async { + typedef Talker ErrorTalker; + class AsyncSocket : public SocketDecorator - , public Talker { + , public ErrorTalker { public: virtual ~AsyncSocket() { this->close(); }; diff --git a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h index 94139025..b3e54e5e 100644 --- a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h +++ b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h @@ -15,6 +15,8 @@ #include namespace sck::async { + typedef Talker MessageTalker; + /** * @brief An asynchronous client can be any kind of socket that is a MessangerConcrete, that keeps receive messages * inside a private thread stored by this class. From the outside it is possible to send messages to the remote host @@ -22,7 +24,7 @@ namespace sck::async { */ class AsyncMessanger : public AsyncSocket - , public Talker + , public MessageTalker , public SendCapable { public: AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity); diff --git a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h index 6a2e817f..7820adc4 100644 --- a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h +++ b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h @@ -12,6 +12,8 @@ #include namespace sck::async { + typedef Talker TcpClientHandlerTalker; + /** * @brief An asynchronous tcp server keeps accpeting new clients * inside a private thread stored by this class. From the outside it is possible to subscribe to the @@ -19,7 +21,7 @@ namespace sck::async { */ class AsyncTcpServer : public AsyncSocket - , public Talker { + , public TcpClientHandlerTalker { public: explicit AsyncTcpServer(std::unique_ptr server); diff --git a/Samples/Tcp/CMakeLists.txt b/Samples/Tcp/CMakeLists.txt index 78e87111..84f9ca24 100644 --- a/Samples/Tcp/CMakeLists.txt +++ b/Samples/Tcp/CMakeLists.txt @@ -4,7 +4,9 @@ MakeSample(TcpClient) MakeSample(TcpServer) -MakeSample(TcpServerAsync) +if(COMPILE_ASYNCH) + MakeSample(TcpServerAsync) +endif() MakeSample(TcpRepeater) @@ -14,6 +16,8 @@ MakeLaunch(SampleTcp-01-server-client TcpClient TcpServer) MakeLaunch(SampleTcp-02-server-clients TcpClient TcpServer) -MakeLaunch(SampleTcp-03-asyncserver-clients TcpClient TcpServerAsync) +if(COMPILE_ASYNCH) + MakeLaunch(SampleTcp-03-asyncserver-clients TcpClient TcpServerAsync) +endif() MakeLaunch(SampleTcp-04-repeater TcpClient TcpServer TcpRepeater) diff --git a/Samples/Udp/CMakeLists.txt b/Samples/Udp/CMakeLists.txt index 86ba7ac4..a62bcc64 100644 --- a/Samples/Udp/CMakeLists.txt +++ b/Samples/Udp/CMakeLists.txt @@ -4,7 +4,9 @@ MakeSample(UdpAsker) MakeSample(UdpResponder) -MakeSample(UdpResponderAsync) +if(COMPILE_ASYNCH) + MakeSample(UdpResponderAsync) +endif() MakeSample(UdpServer) @@ -12,6 +14,8 @@ MakeSample(UdpServer) MakeLaunch(SampleUdp-01-connection UdpAsker UdpResponder) -MakeLaunch(SampleUdp-02-asyncconnection UdpAsker UdpResponderAsync) +if(COMPILE_ASYNCH) + MakeLaunch(SampleUdp-02-asyncconnection UdpAsker UdpResponderAsync) +endif() MakeLaunch(SampleUdp-03-server UdpAsker UdpServer) diff --git a/Utils/src/AsyncResponder.cpp b/Utils/src/AsyncResponder.cpp index d593ca4d..d36f07e3 100644 --- a/Utils/src/AsyncResponder.cpp +++ b/Utils/src/AsyncResponder.cpp @@ -12,8 +12,9 @@ namespace sck::sample { AsyncResponder::AsyncResponder(std::unique_ptr socket) : AsyncMessanger(std::move(socket), 1000) , Logger("AsynchResponder") { - this->Talker::resetListener(this); - this->Talker::resetListener(this); + + this->sck::async::MessageTalker::resetListener(this); + this->sck::async::ErrorTalker::resetListener(this); } void AsyncResponder::handle(const std::pair& message) { From 6c5a8660a403008c41eccfcade26028058cea0d1 Mon Sep 17 00:00:00 2001 From: AndreaC Date: Sat, 8 May 2021 17:12:12 +0200 Subject: [PATCH 064/228] implementing tests --- .github/workflows/runTests.yml | 20 ++- .../AsynchSocket/include/core/AsyncSocket.h | 3 + .../include/core/SocketDecorator.h | 2 +- Tests/CMakeLists.txt | 4 + Tests/Test01-udp-synch.cpp | 122 ++++++++++++++++++ Tests/Test02-tcp-asynch.cpp | 20 ++- Tests/Test02-udp-asynch.cpp | 51 ++++++++ 7 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 Tests/Test01-udp-synch.cpp create mode 100644 Tests/Test02-udp-asynch.cpp diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index 10884afe..a97c47f0 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -23,6 +23,12 @@ jobs: run: cmake --install ./build - name: Test01-tcp-synch run: ./artifacts/bin/Test01-tcp-synch + - name: Test01-udp-synch + run: ./artifacts/bin/Test01-udp-synch + - name: Test02-tcp-asynch + run: ./artifacts/bin/Test02-tcp-asynch + - name: Test02-udp-asynch + run: ./artifacts/bin/Test02-udp-asynch ubuntu-clang-tests: runs-on: ubuntu-latest @@ -38,6 +44,12 @@ jobs: run: cmake --install ./build - name: Test01-tcp-synch run: ./artifacts/bin/Test01-tcp-synch + - name: Test01-udp-synch + run: ./artifacts/bin/Test01-udp-synch + - name: Test02-tcp-asynch + run: ./artifacts/bin/Test02-tcp-asynch + - name: Test02-udp-asynch + run: ./artifacts/bin/Test02-udp-asynch windows-vs-tests: runs-on: ubuntu-latest @@ -52,4 +64,10 @@ jobs: - name: Install artifacts run: cmake --install ./build - name: Test01-tcp-synch - run: ./artifacts/bin/Test01-tcp-synch \ No newline at end of file + run: ./artifacts/bin/Test01-tcp-synch + - name: Test01-udp-synch + run: ./artifacts/bin/Test01-udp-synch + - name: Test02-tcp-asynch + run: ./artifacts/bin/Test02-tcp-asynch + - name: Test02-udp-asynch + run: ./artifacts/bin/Test02-udp-asynch \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h index 95815ae9..b52f295a 100644 --- a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h +++ b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h @@ -23,6 +23,9 @@ namespace sck::async { public: virtual ~AsyncSocket() { this->close(); }; + // true when the internal service was spawned (non basta che socket interno sia aperto) + inline bool isOpen() const override { return this->serviceLife; }; + // the wrapped socket is opened and the service is spawned void open(const std::chrono::milliseconds& timeout) final; diff --git a/CrossSocket/SynchSocket/include/core/SocketDecorator.h b/CrossSocket/SynchSocket/include/core/SocketDecorator.h index 972fdc5c..520f6b44 100644 --- a/CrossSocket/SynchSocket/include/core/SocketDecorator.h +++ b/CrossSocket/SynchSocket/include/core/SocketDecorator.h @@ -16,7 +16,7 @@ namespace sck { , public Openable , public Closable { public: - inline bool isOpen() const final { return this->wrapped->isOpen(); }; + inline bool isOpen() const override { return this->wrapped->isOpen(); }; inline void close() override { this->wrapped->close(); }; diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 9b3038c5..ce517b11 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -27,3 +27,7 @@ endfunction() MakeTest(Test01-tcp-synch) MakeTest(Test02-tcp-asynch) + +MakeTest(Test01-udp-synch) + +MakeTest(Test02-udp-asynch) diff --git a/Tests/Test01-udp-synch.cpp b/Tests/Test01-udp-synch.cpp new file mode 100644 index 00000000..191340ff --- /dev/null +++ b/Tests/Test01-udp-synch.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include +using namespace sck; +using namespace sck::udp; + +TEST(UdpSynch, OpenClose) { + const std::uint16_t portA = sample::PortFactory::makePort(); + const std::uint16_t portB = sample::PortFactory::makePort(); + + #pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // connection A + UdpConnection connectionA(*sck::Ip::createLocalHost(portB) , portA); + EXPECT_FALSE(connectionA.isOpen()); +#pragma omp barrier + connectionA.open(std::chrono::milliseconds(0)); + EXPECT_TRUE(connectionA.isOpen()); +#pragma omp barrier + connectionA.close(); + EXPECT_FALSE(connectionA.isOpen()); + } + else { + // connection B + UdpConnection connectionB(*sck::Ip::createLocalHost(portA) , portB); + EXPECT_FALSE(connectionB.isOpen()); +#pragma omp barrier + connectionB.open(std::chrono::milliseconds(0)); + EXPECT_TRUE(connectionB.isOpen()); +#pragma omp barrier + connectionB.close(); + EXPECT_FALSE(connectionB.isOpen()); + } + } +} + +#include +#include + +TEST(UdpSynch, Asker_Responder) { + const std::uint16_t portA = sample::PortFactory::makePort(); + const std::uint16_t portB = sample::PortFactory::makePort(); + const std::size_t cycles = 5; + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // connection A + std::unique_ptr connectionA = std::make_unique(*sck::Ip::createLocalHost(portB) , portA); + EXPECT_FALSE(connectionA->isOpen()); +#pragma omp barrier + connectionA->open(std::chrono::milliseconds(0)); + EXPECT_TRUE(connectionA->isOpen()); + + sample::Responder responder(std::move(connectionA)); + responder.respond(cycles); +#pragma omp barrier + } + else { + // conenction B + std::unique_ptr connectionB = std::make_unique(*sck::Ip::createLocalHost(portA) , portB); + EXPECT_FALSE(connectionB->isOpen()); +#pragma omp barrier + connectionB->open(std::chrono::milliseconds(0)); + EXPECT_TRUE(connectionB->isOpen()); + + sample::Asker asker(std::move(connectionB)); + asker.ask(cycles); +#pragma omp barrier + } + } +} + +#include + +TEST(UdpSynch, Asker_ServerResponder) { + const std::uint16_t portA = sample::PortFactory::makePort(); + const std::uint16_t portB = sample::PortFactory::makePort(); + const std::size_t cycles = 5; + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // server + std::unique_ptr server = std::make_unique(portA); + EXPECT_FALSE(server->isOpen()); +#pragma omp barrier + server->open(std::chrono::milliseconds(0)); + EXPECT_TRUE(server->isOpen()); + + sample::Responder responder(std::move(server)); + responder.respond(cycles); +#pragma omp barrier + } + else { + // connection + std::unique_ptr connection = std::make_unique(*sck::Ip::createLocalHost(portA) , portB); + EXPECT_FALSE(connection->isOpen()); +#pragma omp barrier + connection->open(std::chrono::milliseconds(0)); + EXPECT_TRUE(connection->isOpen()); + + // send some bytes to establish the connection + { + std::string mex = "hello"; + connection->send(std::make_pair(mex.data(), mex.size())); + } + + sample::Asker asker(std::move(connection)); + asker.ask(cycles); +#pragma omp barrier + } + } +} + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Tests/Test02-tcp-asynch.cpp b/Tests/Test02-tcp-asynch.cpp index e984706c..4928ac10 100644 --- a/Tests/Test02-tcp-asynch.cpp +++ b/Tests/Test02-tcp-asynch.cpp @@ -10,7 +10,7 @@ using namespace sck::tcp; #include #include -TEST(TcpAsynch, OpenCloseAcceptSynch) { +TEST(TcpAsync, OpenCloseAcceptSynch) { const std::uint16_t port = sample::PortFactory::makePort(); #pragma omp parallel num_threads(2) @@ -28,6 +28,8 @@ TEST(TcpAsynch, OpenCloseAcceptSynch) { EXPECT_FALSE(server.isOpen()); } async::AsyncMessanger asynchResponder(std::move(acceptedClient), 500); + EXPECT_FALSE(asynchResponder.isOpen()); + asynchResponder.open(std::chrono::milliseconds(0)); EXPECT_TRUE(asynchResponder.isOpen()); #pragma omp barrier asynchResponder.close(); @@ -46,7 +48,7 @@ TEST(TcpAsynch, OpenCloseAcceptSynch) { } } -TEST(TcpAsynch, ClientAsker_ServerResponder) { +TEST(TcpAsync, ClientAsker_ServerResponder) { const std::uint16_t port = sample::PortFactory::makePort(); const std::size_t cycles = 5; @@ -58,13 +60,16 @@ TEST(TcpAsynch, ClientAsker_ServerResponder) { { TcpServer server(port); server.open(std::chrono::milliseconds(0)); -#pragma omp barrier EXPECT_TRUE(server.isOpen()); +#pragma omp barrier acceptedClient = server.acceptClient(); server.close(); EXPECT_FALSE(server.isOpen()); } sample::AsyncResponder asynchResponder(std::move(acceptedClient)); + EXPECT_FALSE(asynchResponder.isOpen()); + asynchResponder.open(std::chrono::milliseconds(0)); +#pragma omp barrier EXPECT_TRUE(asynchResponder.isOpen()); #pragma omp barrier asynchResponder.close(); @@ -76,6 +81,7 @@ TEST(TcpAsynch, ClientAsker_ServerResponder) { #pragma omp barrier client->open(std::chrono::milliseconds(0)); EXPECT_TRUE(client->isOpen()); +#pragma omp barrier sample::Asker asker(std::move(client)); asker.ask(cycles); @@ -92,7 +98,9 @@ class AsynchAcceptor , private async::ErrorListener { public: - AsynchAcceptor(const std::uint16_t port) : AsyncTcpServer(std::make_unique(port)) {}; + AsynchAcceptor(const std::uint16_t port) : AsyncTcpServer(std::make_unique(port)) { + this->sck::async::TcpClientHandlerTalker::resetListener(this); + }; inline const std::list>& getAccepted() const { return this->accepted; }; @@ -109,7 +117,7 @@ class AsynchAcceptor std::list> accepted; }; -TEST(TcpAsynch, AsynchTcpServerAcceptor) { +TEST(TcpAsync, AsynchTcpServerAcceptor) { const std::uint16_t port = sample::PortFactory::makePort(); const std::size_t cycles = 5; const std::size_t clientsNumb = 5; @@ -120,11 +128,13 @@ TEST(TcpAsynch, AsynchTcpServerAcceptor) { // acceptor AsynchAcceptor acceptor(port); acceptor.open(std::chrono::milliseconds(0)); +#pragma omp barrier EXPECT_TRUE(acceptor.isOpen()); #pragma omp barrier EXPECT_EQ(acceptor.getAccepted().size(), cycles); } else { +#pragma omp barrier // client std::list clients; for (std::size_t k = 0; k < clientsNumb; ++k) { diff --git a/Tests/Test02-udp-asynch.cpp b/Tests/Test02-udp-asynch.cpp new file mode 100644 index 00000000..f1ec3c0d --- /dev/null +++ b/Tests/Test02-udp-asynch.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +using namespace sck; +using namespace sck::udp; + +#include +#include + +TEST(UdpAsync, Asker_Responder) { + const std::uint16_t portA = sample::PortFactory::makePort(); + const std::uint16_t portB = sample::PortFactory::makePort(); + const std::size_t cycles = 5; + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // connection A + std::unique_ptr connectionA = std::make_unique(*sck::Ip::createLocalHost(portB) , portA); + EXPECT_FALSE(connectionA->isOpen()); + connectionA->open(std::chrono::milliseconds(0)); + EXPECT_TRUE(connectionA->isOpen()); + sample::AsyncResponder asynchResponder(std::move(connectionA)); + EXPECT_FALSE(asynchResponder.isOpen()); + asynchResponder.open(std::chrono::milliseconds(0)); +#pragma omp barrier + EXPECT_TRUE(asynchResponder.isOpen()); +#pragma omp barrier + asynchResponder.close(); + EXPECT_TRUE(!asynchResponder.isOpen()); + } + else { + // connection B + std::unique_ptr connectionB = std::make_unique(*sck::Ip::createLocalHost(portA) , portB); + EXPECT_FALSE(connectionB->isOpen()); +#pragma omp barrier + connectionB->open(std::chrono::milliseconds(0)); + EXPECT_TRUE(connectionB->isOpen()); + sample::Asker asker(std::move(connectionB)); + asker.ask(cycles); +#pragma omp barrier + } + } +} + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From c91ebfcbaa233923c21ba71c703ebafdb26fd489 Mon Sep 17 00:00:00 2001 From: AndreaC Date: Sun, 9 May 2021 00:00:06 +0200 Subject: [PATCH 065/228] documentation updated --- .../AsynchSocket/include/core/AsyncSocket.h | 16 ++++++++-- .../AsynchSocket/include/core/Talker.h | 11 ++++++- .../include/messanger/AsyncMessanger.h | 7 +++-- .../include/tcpServer/AsyncTcpServer.h | 6 ++-- CrossSocket/SynchSocket/include/Error.h | 3 +- .../SynchSocket/include/core/Connection.h | 2 +- CrossSocket/SynchSocket/include/core/Socket.h | 2 +- .../include/core/components/Closable.h | 3 ++ .../include/core/components/Openable.h | 2 +- .../SynchSocket/include/udp/UdpServer.h | 4 +-- README.md | 16 ++++++---- Samples/Tcp/README.md | 28 +++++++----------- Samples/Udp/README.md | 29 +++++++++---------- Tests/Test02-tcp-asynch.cpp | 6 ++-- Tests/Test02-udp-asynch.cpp | 2 +- 15 files changed, 81 insertions(+), 56 deletions(-) diff --git a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h index b52f295a..49186b40 100644 --- a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h +++ b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h @@ -17,16 +17,28 @@ namespace sck::async { typedef Talker ErrorTalker; + /** + * @brief An asynchronous socket internally stores a service, which is a private thread using in some way the wrapped socket. + * The internal service is created and spawned everytime the object is opened and stop and destroyed everytime is closed. + */ class AsyncSocket : public SocketDecorator , public ErrorTalker { public: virtual ~AsyncSocket() { this->close(); }; - // true when the internal service was spawned (non basta che socket interno sia aperto) + /** + * @return true only when the internal service was already spawned. + * IMPORTANT!! the object might be built passing an already opened socket. However the + * service is not spawned inside the c'tor and calling this function after construction will return false, + * even if the passed socket was opened. It is however possible to call open to actually start the service. + */ inline bool isOpen() const override { return this->serviceLife; }; - // the wrapped socket is opened and the service is spawned + /** + * @brief tries to open the wrapped socket if not already opened and then spawn the internal service. + * @throw if the service was already spawned. + */ void open(const std::chrono::milliseconds& timeout) final; void close() final; diff --git a/CrossSocket/AsynchSocket/include/core/Talker.h b/CrossSocket/AsynchSocket/include/core/Talker.h index c2dbf5be..5aab4c79 100644 --- a/CrossSocket/AsynchSocket/include/core/Talker.h +++ b/CrossSocket/AsynchSocket/include/core/Talker.h @@ -12,9 +12,18 @@ #include namespace sck::async { + /** + * @brief An object able to notify to an external listener. + * After construction, no listener is assumed, meaning that calling notify will + * actually have no effect. + */ template class Talker { - public: + public: + /** + * @brief Sets the listener that should receive the notifications + * @param the new listener to set. Only 1 listener at a time can be supported + */ void resetListener(Listener* listener) { if (nullptr == listener) { throw Error("The passed listener is empty"); diff --git a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h index b3e54e5e..5f185801 100644 --- a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h +++ b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h @@ -18,9 +18,10 @@ namespace sck::async { typedef Talker MessageTalker; /** - * @brief An asynchronous client can be any kind of socket that is a MessangerConcrete, that keeps receive messages - * inside a private thread stored by this class. From the outside it is possible to send messages to the remote host - * or subscribe to the received messages by setting a MessageListener (calling AsyncDecorator::resetListener(...)) + * @brief An asynchronous messanger can be any kind of socket able to send and receive messages. + * This object stores such a messanger and keeps receive messages inside the private service stored by this class. + * From the outside it is only possible to send messages or subscribe to the received messages by setting the proper + * MessageListener. */ class AsyncMessanger : public AsyncSocket diff --git a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h index 7820adc4..5f0fbfb8 100644 --- a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h +++ b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h @@ -15,9 +15,9 @@ namespace sck::async { typedef Talker TcpClientHandlerTalker; /** - * @brief An asynchronous tcp server keeps accpeting new clients - * inside a private thread stored by this class. From the outside it is possible to subscribe to the - * accepted clients by setting a TcpServerListener (calling AsyncDecorator::resetListener(...)) + * @brief An asynchronous tcp server keep accept new clients + * inside the private service stored by this class. From the outside it is possible to subscribe to the + * accepted clients by setting the proper TcpServerListener */ class AsyncTcpServer : public AsyncSocket diff --git a/CrossSocket/SynchSocket/include/Error.h b/CrossSocket/SynchSocket/include/Error.h index fadb4af3..80f66be8 100644 --- a/CrossSocket/SynchSocket/include/Error.h +++ b/CrossSocket/SynchSocket/include/Error.h @@ -12,7 +12,8 @@ #include namespace sck { - /** @brief A runtime error that can be raised when using any object in sck:: + /** + * @brief A runtime error that can be raised when using any object in sck:: */ class Error : public std::runtime_error { public: diff --git a/CrossSocket/SynchSocket/include/core/Connection.h b/CrossSocket/SynchSocket/include/core/Connection.h index 7447a12e..f22aebea 100644 --- a/CrossSocket/SynchSocket/include/core/Connection.h +++ b/CrossSocket/SynchSocket/include/core/Connection.h @@ -19,7 +19,7 @@ namespace sck { , public RemoteAddressAware { protected: /** - * @param[in] the address of the server to hit + * @param[in] the address of the connection to reach */ explicit Connection(const sck::Ip& remoteAddress); diff --git a/CrossSocket/SynchSocket/include/core/Socket.h b/CrossSocket/SynchSocket/include/core/Socket.h index f5fae19e..ce33da72 100644 --- a/CrossSocket/SynchSocket/include/core/Socket.h +++ b/CrossSocket/SynchSocket/include/core/Socket.h @@ -32,7 +32,7 @@ namespace sck { explicit Socket(std::unique_ptr channel); /** - * @brief The methods containing the specific steps to perform to fully close a concrete socket + * @brief The methods containing the specific steps to perform in order to fully close a concrete socket */ virtual void closeSteps(); }; diff --git a/CrossSocket/SynchSocket/include/core/components/Closable.h b/CrossSocket/SynchSocket/include/core/components/Closable.h index 45662ee3..98e72055 100644 --- a/CrossSocket/SynchSocket/include/core/components/Closable.h +++ b/CrossSocket/SynchSocket/include/core/components/Closable.h @@ -11,6 +11,9 @@ namespace sck { class Closable { public: + /** + * @brief close the object + */ virtual void close() = 0; }; } diff --git a/CrossSocket/SynchSocket/include/core/components/Openable.h b/CrossSocket/SynchSocket/include/core/components/Openable.h index 624ff7fd..f7ef08da 100644 --- a/CrossSocket/SynchSocket/include/core/components/Openable.h +++ b/CrossSocket/SynchSocket/include/core/components/Openable.h @@ -15,7 +15,7 @@ namespace sck { public: /** * @brief Tries to open the object, until the passed timeout, after which the object spontaneously close itself. - * @param timeout to assume for the open operation. When passing 0, an infite timeout is assumed + * @param timeout to assume for the open operation. When passing 0, an infinite timeout is assumed */ virtual void open(const std::chrono::milliseconds& timeout) = 0; }; diff --git a/CrossSocket/SynchSocket/include/udp/UdpServer.h b/CrossSocket/SynchSocket/include/udp/UdpServer.h index 70add4ca..045ea49f 100644 --- a/CrossSocket/SynchSocket/include/udp/UdpServer.h +++ b/CrossSocket/SynchSocket/include/udp/UdpServer.h @@ -12,8 +12,8 @@ namespace sck::udp { /** - * @brief A UdpServer is an UdpClient, with the possibility to deduce the remoteAddress when calling open, - * by setting as target the first UdpClient that hits this socket, sending + * @brief A UdpServer extends the UdpConnection class, since it has the possibility to deduce the remoteAddress when calling open, + * by setting as target the first UdpConnection that hits this socket, sending * at least a 1 byte (or more) message. * IMPORTANT!!! The first message sent to the server will be lost. */ diff --git a/README.md b/README.md index 704f7228..490ec399 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ -This repository contains the minimal functionalities to create and use from **C++** both **tcp** and **udp** socket in a +This repository contains the minimal functionalities to create and use from **C++** both **tcp** and **udp** sockets in a completely platform independent way. **Content** - * The libarary is contained in the ./CrossSocket folder - * Samples showing the library usage are contained in the ./Samples folder. ATTENTION!!! Samples execution might be blocked by your firewall + * The core packages are contained in the ./CrossSocket folder: + * SynchSocket implements the functionalities to create and use standard tcp and udp connections + * AsynchSocket contains implementation of asynchronous sockets, i.e. objects having a private thread service that can use the socket. + + * Samples showing the library usage are contained in the ./Samples folder. ATTENTION!!! The Samples execution might be blocked the first time by your firewall. **Build from sources** Use [CMake](https://cmake.org) to configure and compile the library and the samples. -**Run the examples** +**Run the Samples** Check the *README.md* inside Samples/Tcp/ and Samples/Udp/ @@ -18,4 +21,7 @@ Check the *README.md* inside Samples/Tcp/ and Samples/Udp/ You can simply download and use the binaries of the latest master version [here](https://github.com/andreacasalino/Cross-Platform-Socket/actions/runs/640613596) -If you have found this library usefull, remember put a **star** on it +**What Else?** + +Have fun and leave a **star**. + diff --git a/Samples/Tcp/README.md b/Samples/Tcp/README.md index 8c6ef8fa..1dcac2dc 100644 --- a/Samples/Tcp/README.md +++ b/Samples/Tcp/README.md @@ -1,34 +1,28 @@ -This folder contains some samples using tcp connections. -In order to execute each sample more than 1 processes needs to be run. -You don't need to manually do that, since some appropriate launcher scripts -can be run to do all the work. To create the launchers you need to compile and run: -**Tcp01Launcher**, **Tcp02Launcher** or **Tcp03Launcher** -according to the sample you are interested in. -After running one of the above application a *.bat* or *.sh* script (according to -which is your system) is created: you can execute that script from the command line -to run the corresponding example +This folder contains some samples showing how to create and use tcp sockets (with server or client roles). +In order to execute each sample more than 1 processes need to be run. +You don't need to manually do that, since this is done running one of the launcher application named SampleTcp-xx-... **Samples description** - *Sample01, run: + *SampleTcp-01-server-client, run: * a Tcp server ready to accept 1 client * a Tcp client that connects to the server and exchange messages - *Sample02, run: + *SampleTcp-02-server-clients, run: * a Tcp server ready to accept 2 client * a first Tcp client that connects to the server and exchange messages with an high frequency * a second Tcp client that connects to the server and exchange messages with a low frequency - *Sample03, run: + *SampleTcp-03-asyncserver-clients, run: + + * a Tcp server asynchronous accepting 1 single client + * a Tcp client that connects to the async server and exchange messages + + *SampleTcp-04-repeater, run: * a Tcp server ready to accept 1 client * a Tcp client that connects to an intermediate repeater * a repeater creating 2 connections in order to receive the request of the client and forward them to the server, sending back the response - *Sample04, run: - - * a Tcp server asynchronous accepting 1 single client - * a Tcp client that connects to the async server and exchange messages - diff --git a/Samples/Udp/README.md b/Samples/Udp/README.md index 62e0409a..219db85b 100644 --- a/Samples/Udp/README.md +++ b/Samples/Udp/README.md @@ -1,21 +1,20 @@ -This folder contains some samples using udp connections. -In order to execute each sample more than 1 processes needs to be run. -You don't need to manually do that, since some appropriate launcher scripts -can be run to do all the work. To create the launchers you need to compile and run: -**Udp01Launcher** or **Udp02Launcher** -according to the sample you are interested in. -After running one of the above application a *.bat* or *.sh* script (according to -which is your system) is created: you can execute that script from the command line -to run the corresponding example +This folder contains some samples showing how to create and use udp connections. +In order to execute each sample more than 1 processes need to be run. +You don't need to manually do that, since this is done running one of the launcher application named SampleUdp-xx-... **Samples description** - *Sample01, run: + *SampleUdp-01-connection, run: - * a first UdpClient reserve a specific port and it is ready to respond to another UdpClient - * a second UdpClient exchanging messages with the first + * a first UdpConnection reserving a specific port, ready to respond to another UdpConnection + * a second UdpConnection exchanging messages with the first - *Sample02, run: + *SampleUdp-02-asyncconnection, run: + + * a first UdpConnection reserving a specific port, ready to asynchrously respond to another UdpClient + * a second UdpConnection exchanging messages with the first + + *SampleUdp-03-server, run: - * a UdpServer ready to receive an initial message from a client to detect it - * a UdpClient that connects to the server by sending one message and exchange messages \ No newline at end of file + * a UdpServer ready to receive an initial message from another peer to detect it + * a UdpConnection that connects to the server by sending one message and exchange messages diff --git a/Tests/Test02-tcp-asynch.cpp b/Tests/Test02-tcp-asynch.cpp index 4928ac10..23f005cd 100644 --- a/Tests/Test02-tcp-asynch.cpp +++ b/Tests/Test02-tcp-asynch.cpp @@ -10,7 +10,7 @@ using namespace sck::tcp; #include #include -TEST(TcpAsync, OpenCloseAcceptSynch) { +TEST(TcpAsync, DISABLED_OpenCloseAcceptSynch) { const std::uint16_t port = sample::PortFactory::makePort(); #pragma omp parallel num_threads(2) @@ -48,7 +48,7 @@ TEST(TcpAsync, OpenCloseAcceptSynch) { } } -TEST(TcpAsync, ClientAsker_ServerResponder) { +TEST(TcpAsync, DISABLED_ClientAsker_ServerResponder) { const std::uint16_t port = sample::PortFactory::makePort(); const std::size_t cycles = 5; @@ -117,7 +117,7 @@ class AsynchAcceptor std::list> accepted; }; -TEST(TcpAsync, AsynchTcpServerAcceptor) { +TEST(TcpAsync, DISABLED_AsynchTcpServerAcceptor) { const std::uint16_t port = sample::PortFactory::makePort(); const std::size_t cycles = 5; const std::size_t clientsNumb = 5; diff --git a/Tests/Test02-udp-asynch.cpp b/Tests/Test02-udp-asynch.cpp index f1ec3c0d..ec3b24f2 100644 --- a/Tests/Test02-udp-asynch.cpp +++ b/Tests/Test02-udp-asynch.cpp @@ -9,7 +9,7 @@ using namespace sck::udp; #include #include -TEST(UdpAsync, Asker_Responder) { +TEST(UdpAsync, DISABLED_Asker_Responder) { const std::uint16_t portA = sample::PortFactory::makePort(); const std::uint16_t portB = sample::PortFactory::makePort(); const std::size_t cycles = 5; From 94770178acb1197108adf5ddb9545ea14cda9c32 Mon Sep 17 00:00:00 2001 From: AndreaC Date: Sun, 9 May 2021 00:04:17 +0200 Subject: [PATCH 066/228] minor changes --- .github/workflows/runTests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index a97c47f0..7888cfa1 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -64,10 +64,10 @@ jobs: - name: Install artifacts run: cmake --install ./build - name: Test01-tcp-synch - run: ./artifacts/bin/Test01-tcp-synch + run: ./build/artifacts/bin/Test01-tcp-synch - name: Test01-udp-synch - run: ./artifacts/bin/Test01-udp-synch + run: ./build/artifacts/bin/Test01-udp-synch - name: Test02-tcp-asynch - run: ./artifacts/bin/Test02-tcp-asynch + run: ./build/artifacts/bin/Test02-tcp-asynch - name: Test02-udp-asynch - run: ./artifacts/bin/Test02-udp-asynch \ No newline at end of file + run: ./build/artifacts/bin/Test02-udp-asynch \ No newline at end of file From f0ea51fd11f9e244d6e2c4c5f5b8feee82a18cf7 Mon Sep 17 00:00:00 2001 From: AndreaC Date: Sun, 9 May 2021 21:27:06 +0200 Subject: [PATCH 067/228] implementing --- .../include/{PortFactory.h => Common.h} | 7 ++++-- Tests/UtilsTest/include/TcpCommon.h | 22 +++++++++++++++++++ Tests/UtilsTest/include/UdpCommon.h | 19 ++++++++++++++++ .../src/{PortFactory.cpp => Common.cpp} | 9 +++++++- Tests/UtilsTest/src/TcpCommon.cpp | 0 Tests/UtilsTest/src/UdpCommon.cpp | 21 ++++++++++++++++++ 6 files changed, 75 insertions(+), 3 deletions(-) rename Tests/UtilsTest/include/{PortFactory.h => Common.h} (68%) create mode 100644 Tests/UtilsTest/include/TcpCommon.h create mode 100644 Tests/UtilsTest/include/UdpCommon.h rename Tests/UtilsTest/src/{PortFactory.cpp => Common.cpp} (65%) create mode 100644 Tests/UtilsTest/src/TcpCommon.cpp create mode 100644 Tests/UtilsTest/src/UdpCommon.cpp diff --git a/Tests/UtilsTest/include/PortFactory.h b/Tests/UtilsTest/include/Common.h similarity index 68% rename from Tests/UtilsTest/include/PortFactory.h rename to Tests/UtilsTest/include/Common.h index d8dba9a8..382cd48f 100644 --- a/Tests/UtilsTest/include/PortFactory.h +++ b/Tests/UtilsTest/include/Common.h @@ -5,16 +5,19 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef SAMPLE_PORT_FACTORY_H -#define SAMPLE_PORT_FACTORY_H +#ifndef SAMPLE_COMMON_H +#define SAMPLE_COMMON_H #include +#include namespace sck::sample { class PortFactory { public: static std::uint16_t makePort(); }; + + void open(Openable& socket); } #endif \ No newline at end of file diff --git a/Tests/UtilsTest/include/TcpCommon.h b/Tests/UtilsTest/include/TcpCommon.h new file mode 100644 index 00000000..56666aad --- /dev/null +++ b/Tests/UtilsTest/include/TcpCommon.h @@ -0,0 +1,22 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef SAMPLE_TCP_COMMON_H +#define SAMPLE_TCP_COMMON_H + +#include +#include + +namespace sck::sample { + typedef std::unique_ptr<> UdpConnectionPtr; + + void open(const ); + + std::unique_ptr accept(); +} + +#endif \ No newline at end of file diff --git a/Tests/UtilsTest/include/UdpCommon.h b/Tests/UtilsTest/include/UdpCommon.h new file mode 100644 index 00000000..a33d3099 --- /dev/null +++ b/Tests/UtilsTest/include/UdpCommon.h @@ -0,0 +1,19 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef SAMPLE_UDP_COMMON_H +#define SAMPLE_UDP_COMMON_H + +#include + +namespace sck::sample { + typedef std::unique_ptr UdpConnectionPtr; + + std::pair makeOpenUdpConnections(const std::size_t portA, const std::size_t portB); +} + +#endif \ No newline at end of file diff --git a/Tests/UtilsTest/src/PortFactory.cpp b/Tests/UtilsTest/src/Common.cpp similarity index 65% rename from Tests/UtilsTest/src/PortFactory.cpp rename to Tests/UtilsTest/src/Common.cpp index 37b2ec02..3f96698f 100644 --- a/Tests/UtilsTest/src/PortFactory.cpp +++ b/Tests/UtilsTest/src/Common.cpp @@ -5,8 +5,10 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include +#include +#include namespace sck::sample { constexpr std::uint16_t INITIAL_PORT = 9999; @@ -20,4 +22,9 @@ namespace sck::sample { ++port; return port; } + + void open(Openable& socket) { + socket.open(std::chrono::milliseconds(0)); + EXPECT_TRUE(dynamic_cast(&socket)->isOpen()); + } } diff --git a/Tests/UtilsTest/src/TcpCommon.cpp b/Tests/UtilsTest/src/TcpCommon.cpp new file mode 100644 index 00000000..e69de29b diff --git a/Tests/UtilsTest/src/UdpCommon.cpp b/Tests/UtilsTest/src/UdpCommon.cpp new file mode 100644 index 00000000..e0fb6a02 --- /dev/null +++ b/Tests/UtilsTest/src/UdpCommon.cpp @@ -0,0 +1,21 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +namespace sck::sample { + std::pair makeOpenUdpConnections(const std::size_t portA, const std::size_t portB) { + UdpConnectionPtr connA = std::make_unique(Ip::createLocalHost(portB) , portA); + UdpConnectionPtr connB = std::make_unique(Ip::createLocalHost(portA) , portB); + + open(*connA); + open(*connB); + + return std::make_pair(std::move(connA), std::move(connB)); + }; +} From 313f47e902a7ce9db3ff69b5397c2a8e3ba08b24 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 9 May 2021 22:46:04 +0200 Subject: [PATCH 068/228] fixing tests --- Tests/CMakeLists.txt | 9 +--- Tests/Test01-tcp-synch.cpp | 79 +++++------------------------ Tests/UtilsTest/CMakeLists.txt | 4 ++ Tests/UtilsTest/include/Common.h | 4 +- Tests/UtilsTest/include/TcpCommon.h | 10 ++-- Tests/UtilsTest/include/UdpCommon.h | 3 +- Tests/UtilsTest/src/Common.cpp | 4 +- Tests/UtilsTest/src/TcpCommon.cpp | 35 +++++++++++++ Tests/UtilsTest/src/UdpCommon.cpp | 11 ++-- 9 files changed, 71 insertions(+), 88 deletions(-) diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index ce517b11..75840337 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -12,15 +12,8 @@ function(MakeTest NAME) PUBLIC Utils UtilsTest - gtest ) - - find_package(OpenMP) - target_link_libraries(${NAME} - PUBLIC - OpenMP::OpenMP_CXX - ) - + install(TARGETS ${NAME}) endfunction() diff --git a/Tests/Test01-tcp-synch.cpp b/Tests/Test01-tcp-synch.cpp index 85218661..8e3927fd 100644 --- a/Tests/Test01-tcp-synch.cpp +++ b/Tests/Test01-tcp-synch.cpp @@ -1,9 +1,6 @@ #include -#include -#include -#include +#include #include -#include using namespace sck; using namespace sck::tcp; @@ -14,17 +11,7 @@ TEST(TcpSynch, OpenClose) { { if (0 == omp_get_thread_num()) { // server - std::unique_ptr acceptedClient; - { - TcpServer server(port); - server.open(std::chrono::milliseconds(0)); -#pragma omp barrier - EXPECT_TRUE(server.isOpen()); - acceptedClient = server.acceptClient(); - server.close(); - EXPECT_FALSE(server.isOpen()); - } - EXPECT_TRUE(acceptedClient->isOpen()); + auto acceptedClient = sample::accept(port); #pragma omp barrier acceptedClient->close(); EXPECT_FALSE(acceptedClient->isOpen()); @@ -32,9 +19,7 @@ TEST(TcpSynch, OpenClose) { else { // client TcpClient client(*sck::Ip::createLocalHost(port)); -#pragma omp barrier - client.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(client.isOpen()); + sample::openTcpClient(client); #pragma omp barrier client.close(); EXPECT_FALSE(client.isOpen()); @@ -50,18 +35,9 @@ TEST(TcpSynch, OpenCloseManyClients) { { if (0 == omp_get_thread_num()) { // server - TcpServer server(port); - server.open(std::chrono::milliseconds(0)); -#pragma omp barrier - EXPECT_TRUE(server.isOpen()); - - std::list> acceptedClients; - for (std::size_t k = 0; k < clientsNumb; ++k) { - acceptedClients.emplace_back(server.acceptClient()); - EXPECT_TRUE(acceptedClients.back()->isOpen()); - } - server.close(); - EXPECT_FALSE(server.isOpen()); + tcp::TcpServer server(port); + sample::openSocket(server); + auto acceptedClients = sample::accept(server, clientsNumb); #pragma omp barrier for (auto it = acceptedClients.begin(); it != acceptedClients.end(); ++it) { (*it)->close(); @@ -70,12 +46,11 @@ TEST(TcpSynch, OpenCloseManyClients) { } else { // client -#pragma omp barrier - std::list> clients; + std::vector> clients; + clients.reserve(clientsNumb); for (std::size_t k = 0; k < clientsNumb; ++k) { clients.emplace_back(std::make_unique(*sck::Ip::createLocalHost(port))); - clients.back()->open(std::chrono::milliseconds(0)); - EXPECT_TRUE(clients.back()->isOpen()); + sample::openTcpClient(*clients.back()); } #pragma omp barrier for (auto it = clients.begin(); it != clients.end(); ++it) { @@ -97,18 +72,7 @@ TEST(TcpSynch, ClientAsker_ServerResponder) { { if (0 == omp_get_thread_num()) { // server - std::unique_ptr acceptedClient; - { - TcpServer server(port); - server.open(std::chrono::milliseconds(0)); -#pragma omp barrier - EXPECT_TRUE(server.isOpen()); - acceptedClient = server.acceptClient(); - server.close(); - EXPECT_FALSE(server.isOpen()); - } - EXPECT_TRUE(acceptedClient->isOpen()); - + auto acceptedClient = sample::accept(port); sample::Responder responder(std::move(acceptedClient)); responder.respond(cycles); #pragma omp barrier @@ -116,10 +80,7 @@ TEST(TcpSynch, ClientAsker_ServerResponder) { else { // client std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); -#pragma omp barrier - client->open(std::chrono::milliseconds(0)); - EXPECT_TRUE(client->isOpen()); - + sample::openTcpClient(*client); sample::Asker asker(std::move(client)); asker.ask(cycles); #pragma omp barrier @@ -135,18 +96,7 @@ TEST(TcpSynch, ClientResponder_ServerAsker) { { if (0 == omp_get_thread_num()) { // server - std::unique_ptr acceptedClient; - { - TcpServer server(port); - server.open(std::chrono::milliseconds(0)); -#pragma omp barrier - EXPECT_TRUE(server.isOpen()); - acceptedClient = server.acceptClient(); - server.close(); - EXPECT_FALSE(server.isOpen()); - } - EXPECT_TRUE(acceptedClient->isOpen()); - + auto acceptedClient = sample::accept(port); sample::Asker asker(std::move(acceptedClient)); asker.ask(cycles); #pragma omp barrier @@ -154,10 +104,7 @@ TEST(TcpSynch, ClientResponder_ServerAsker) { else { // client std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); -#pragma omp barrier - client->open(std::chrono::milliseconds(0)); - EXPECT_TRUE(client->isOpen()); - + sample::openTcpClient(*client); sample::Responder responder(std::move(client)); responder.respond(cycles); #pragma omp barrier diff --git a/Tests/UtilsTest/CMakeLists.txt b/Tests/UtilsTest/CMakeLists.txt index 66aeb1cc..03e6b099 100644 --- a/Tests/UtilsTest/CMakeLists.txt +++ b/Tests/UtilsTest/CMakeLists.txt @@ -2,7 +2,11 @@ set(PROJECT_SHORTNAME "UtilsTest") MakeLibrary(${PROJECT_SHORTNAME} include) +find_package(OpenMP) + target_link_libraries(${PROJECT_SHORTNAME} PUBLIC Synch-Cross-Socket + gtest + OpenMP::OpenMP_CXX ) diff --git a/Tests/UtilsTest/include/Common.h b/Tests/UtilsTest/include/Common.h index 382cd48f..3446a0ca 100644 --- a/Tests/UtilsTest/include/Common.h +++ b/Tests/UtilsTest/include/Common.h @@ -9,7 +9,7 @@ #define SAMPLE_COMMON_H #include -#include +#include namespace sck::sample { class PortFactory { @@ -17,7 +17,7 @@ namespace sck::sample { static std::uint16_t makePort(); }; - void open(Openable& socket); + void openSocket(SocketOpenable& socket); } #endif \ No newline at end of file diff --git a/Tests/UtilsTest/include/TcpCommon.h b/Tests/UtilsTest/include/TcpCommon.h index 56666aad..c9bf0081 100644 --- a/Tests/UtilsTest/include/TcpCommon.h +++ b/Tests/UtilsTest/include/TcpCommon.h @@ -10,13 +10,17 @@ #include #include +#include +#include namespace sck::sample { - typedef std::unique_ptr<> UdpConnectionPtr; + typedef std::unique_ptr TcpClientHndlrPtr; - void open(const ); + std::vector accept(tcp::TcpServer& server, const std::size_t clients); - std::unique_ptr accept(); + TcpClientHndlrPtr accept(const std::uint16_t port); + + void openTcpClient(tcp::TcpClient& client); } #endif \ No newline at end of file diff --git a/Tests/UtilsTest/include/UdpCommon.h b/Tests/UtilsTest/include/UdpCommon.h index a33d3099..beefb6a3 100644 --- a/Tests/UtilsTest/include/UdpCommon.h +++ b/Tests/UtilsTest/include/UdpCommon.h @@ -9,11 +9,12 @@ #define SAMPLE_UDP_COMMON_H #include +#include namespace sck::sample { typedef std::unique_ptr UdpConnectionPtr; - std::pair makeOpenUdpConnections(const std::size_t portA, const std::size_t portB); + std::pair makeOpenedUdpConnections(const std::uint16_t portA, const std::uint16_t portB); } #endif \ No newline at end of file diff --git a/Tests/UtilsTest/src/Common.cpp b/Tests/UtilsTest/src/Common.cpp index 3f96698f..03edbcd1 100644 --- a/Tests/UtilsTest/src/Common.cpp +++ b/Tests/UtilsTest/src/Common.cpp @@ -23,8 +23,8 @@ namespace sck::sample { return port; } - void open(Openable& socket) { + void openSocket(SocketOpenable& socket) { socket.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(dynamic_cast(&socket)->isOpen()); + EXPECT_TRUE(socket.isOpen()); } } diff --git a/Tests/UtilsTest/src/TcpCommon.cpp b/Tests/UtilsTest/src/TcpCommon.cpp index e69de29b..54a1ec33 100644 --- a/Tests/UtilsTest/src/TcpCommon.cpp +++ b/Tests/UtilsTest/src/TcpCommon.cpp @@ -0,0 +1,35 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include + +namespace sck::sample { + std::vector accept(tcp::TcpServer& server, const std::size_t clients) { + std::vector clientPtrs; + clientPtrs.reserve(clients); + for (std::size_t k = 0; k < clients; ++k) { +#pragma omp barrier + clientPtrs.emplace_back(server.acceptClient()); + EXPECT_TRUE(clientPtrs.back()->isOpen()); + } + return clientPtrs; + } + + TcpClientHndlrPtr accept(const std::uint16_t port) { + tcp::TcpServer server(port); + openSocket(server); + return std::move(accept(server, 1).front()); + } + + void openTcpClient(tcp::TcpClient& client) { + EXPECT_FALSE(client.isOpen()); +#pragma omp barrier + openSocket(client); + } +} diff --git a/Tests/UtilsTest/src/UdpCommon.cpp b/Tests/UtilsTest/src/UdpCommon.cpp index e0fb6a02..af5445d8 100644 --- a/Tests/UtilsTest/src/UdpCommon.cpp +++ b/Tests/UtilsTest/src/UdpCommon.cpp @@ -6,15 +6,14 @@ **/ #include -#include namespace sck::sample { - std::pair makeOpenUdpConnections(const std::size_t portA, const std::size_t portB) { - UdpConnectionPtr connA = std::make_unique(Ip::createLocalHost(portB) , portA); - UdpConnectionPtr connB = std::make_unique(Ip::createLocalHost(portA) , portB); + std::pair makeOpenedUdpConnections(const std::uint16_t portA, const std::uint16_t portB) { + UdpConnectionPtr connA = std::make_unique(*Ip::createLocalHost(portB) , portA); + UdpConnectionPtr connB = std::make_unique(*Ip::createLocalHost(portA) , portB); - open(*connA); - open(*connB); + openSocket(*connA); + openSocket(*connB); return std::make_pair(std::move(connA), std::move(connB)); }; From 7754962a907261c1215a3b355fad578b502ad8c6 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 10 May 2021 00:16:52 +0200 Subject: [PATCH 069/228] tests fixed --- .../include/tcpServer/AsyncTcpServer.h | 4 +- Tests/Test01-tcp-synch.cpp | 12 +-- Tests/Test01-udp-synch.cpp | 55 +++---------- Tests/Test02-tcp-asynch.cpp | 79 ++++++------------- Tests/Test02-udp-asynch.cpp | 29 ++----- Tests/UtilsTest/include/Common.h | 7 ++ Tests/UtilsTest/src/Common.cpp | 27 ++++++- Utils/src/AsyncResponder.cpp | 4 +- 8 files changed, 84 insertions(+), 133 deletions(-) diff --git a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h index 5f0fbfb8..d81aab10 100644 --- a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h +++ b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h @@ -25,8 +25,8 @@ namespace sck::async { public: explicit AsyncTcpServer(std::unique_ptr server); - private: - void serviceIteration() final; + protected: + void serviceIteration() override; }; } diff --git a/Tests/Test01-tcp-synch.cpp b/Tests/Test01-tcp-synch.cpp index 8e3927fd..a041582a 100644 --- a/Tests/Test01-tcp-synch.cpp +++ b/Tests/Test01-tcp-synch.cpp @@ -13,16 +13,14 @@ TEST(TcpSynch, OpenClose) { // server auto acceptedClient = sample::accept(port); #pragma omp barrier - acceptedClient->close(); - EXPECT_FALSE(acceptedClient->isOpen()); + sample::closeSocket(*acceptedClient); } else { // client TcpClient client(*sck::Ip::createLocalHost(port)); sample::openTcpClient(client); #pragma omp barrier - client.close(); - EXPECT_FALSE(client.isOpen()); + sample::closeSocket(client); } } } @@ -40,8 +38,7 @@ TEST(TcpSynch, OpenCloseManyClients) { auto acceptedClients = sample::accept(server, clientsNumb); #pragma omp barrier for (auto it = acceptedClients.begin(); it != acceptedClients.end(); ++it) { - (*it)->close(); - EXPECT_FALSE((*it)->isOpen()); + sample::closeSocket(**it); } } else { @@ -54,8 +51,7 @@ TEST(TcpSynch, OpenCloseManyClients) { } #pragma omp barrier for (auto it = clients.begin(); it != clients.end(); ++it) { - (*it)->close(); - EXPECT_FALSE((*it)->isOpen()); + sample::closeSocket(**it); } } } diff --git a/Tests/Test01-udp-synch.cpp b/Tests/Test01-udp-synch.cpp index 191340ff..e2f7186c 100644 --- a/Tests/Test01-udp-synch.cpp +++ b/Tests/Test01-udp-synch.cpp @@ -1,38 +1,25 @@ #include -#include -#include +#include #include -#include using namespace sck; using namespace sck::udp; TEST(UdpSynch, OpenClose) { - const std::uint16_t portA = sample::PortFactory::makePort(); - const std::uint16_t portB = sample::PortFactory::makePort(); + auto connections = sample::makeOpenedUdpConnections(sample::PortFactory::makePort(), sample::PortFactory::makePort()); #pragma omp parallel num_threads(2) { if (0 == omp_get_thread_num()) { // connection A - UdpConnection connectionA(*sck::Ip::createLocalHost(portB) , portA); - EXPECT_FALSE(connectionA.isOpen()); -#pragma omp barrier - connectionA.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(connectionA.isOpen()); + sample::openSocket(*connections.first); #pragma omp barrier - connectionA.close(); - EXPECT_FALSE(connectionA.isOpen()); + sample::closeSocket(*connections.first); } else { // connection B - UdpConnection connectionB(*sck::Ip::createLocalHost(portA) , portB); - EXPECT_FALSE(connectionB.isOpen()); + sample::openSocket(*connections.second); #pragma omp barrier - connectionB.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(connectionB.isOpen()); -#pragma omp barrier - connectionB.close(); - EXPECT_FALSE(connectionB.isOpen()); + sample::closeSocket(*connections.second); } } } @@ -41,33 +28,20 @@ TEST(UdpSynch, OpenClose) { #include TEST(UdpSynch, Asker_Responder) { - const std::uint16_t portA = sample::PortFactory::makePort(); - const std::uint16_t portB = sample::PortFactory::makePort(); + auto connections = sample::makeOpenedUdpConnections(sample::PortFactory::makePort(), sample::PortFactory::makePort()); const std::size_t cycles = 5; #pragma omp parallel num_threads(2) { if (0 == omp_get_thread_num()) { // connection A - std::unique_ptr connectionA = std::make_unique(*sck::Ip::createLocalHost(portB) , portA); - EXPECT_FALSE(connectionA->isOpen()); -#pragma omp barrier - connectionA->open(std::chrono::milliseconds(0)); - EXPECT_TRUE(connectionA->isOpen()); - - sample::Responder responder(std::move(connectionA)); + sample::Responder responder(std::move(connections.first)); responder.respond(cycles); #pragma omp barrier } else { // conenction B - std::unique_ptr connectionB = std::make_unique(*sck::Ip::createLocalHost(portA) , portB); - EXPECT_FALSE(connectionB->isOpen()); -#pragma omp barrier - connectionB->open(std::chrono::milliseconds(0)); - EXPECT_TRUE(connectionB->isOpen()); - - sample::Asker asker(std::move(connectionB)); + sample::Asker asker(std::move(connections.second)); asker.ask(cycles); #pragma omp barrier } @@ -76,7 +50,7 @@ TEST(UdpSynch, Asker_Responder) { #include -TEST(UdpSynch, Asker_ServerResponder) { +TEST(UdpSynch, DIAsker_ServerResponder) { const std::uint16_t portA = sample::PortFactory::makePort(); const std::uint16_t portB = sample::PortFactory::makePort(); const std::size_t cycles = 5; @@ -86,11 +60,9 @@ TEST(UdpSynch, Asker_ServerResponder) { if (0 == omp_get_thread_num()) { // server std::unique_ptr server = std::make_unique(portA); - EXPECT_FALSE(server->isOpen()); -#pragma omp barrier server->open(std::chrono::milliseconds(0)); EXPECT_TRUE(server->isOpen()); - +#pragma omp barrier sample::Responder responder(std::move(server)); responder.respond(cycles); #pragma omp barrier @@ -98,17 +70,14 @@ TEST(UdpSynch, Asker_ServerResponder) { else { // connection std::unique_ptr connection = std::make_unique(*sck::Ip::createLocalHost(portA) , portB); - EXPECT_FALSE(connection->isOpen()); -#pragma omp barrier connection->open(std::chrono::milliseconds(0)); EXPECT_TRUE(connection->isOpen()); - // send some bytes to establish the connection { std::string mex = "hello"; connection->send(std::make_pair(mex.data(), mex.size())); } - +#pragma omp barrier sample::Asker asker(std::move(connection)); asker.ask(cycles); #pragma omp barrier diff --git a/Tests/Test02-tcp-asynch.cpp b/Tests/Test02-tcp-asynch.cpp index 23f005cd..b4f090ca 100644 --- a/Tests/Test02-tcp-asynch.cpp +++ b/Tests/Test02-tcp-asynch.cpp @@ -1,7 +1,5 @@ #include -#include -#include -#include +#include #include #include using namespace sck; @@ -10,45 +8,29 @@ using namespace sck::tcp; #include #include -TEST(TcpAsync, DISABLED_OpenCloseAcceptSynch) { +TEST(TcpAsync, OpenCloseAcceptSynch) { const std::uint16_t port = sample::PortFactory::makePort(); #pragma omp parallel num_threads(2) { if (0 == omp_get_thread_num()) { // server - std::unique_ptr acceptedClient; - { - TcpServer server(port); - server.open(std::chrono::milliseconds(0)); -#pragma omp barrier - EXPECT_TRUE(server.isOpen()); - acceptedClient = server.acceptClient(); - server.close(); - EXPECT_FALSE(server.isOpen()); - } - async::AsyncMessanger asynchResponder(std::move(acceptedClient), 500); - EXPECT_FALSE(asynchResponder.isOpen()); - asynchResponder.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(asynchResponder.isOpen()); + auto acceptedClient = sample::accept(port); + sample::AsyncResponder asynchResponder(std::move(acceptedClient)); + sample::openSocketDecorator(asynchResponder); #pragma omp barrier - asynchResponder.close(); - EXPECT_FALSE(asynchResponder.isOpen()); + sample::closeSocketDecorator(asynchResponder); } else { // client TcpClient client(*sck::Ip::createLocalHost(port)); + sample::openTcpClient(client); #pragma omp barrier - client.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(client.isOpen()); -#pragma omp barrier - client.close(); - EXPECT_FALSE(client.isOpen()); } } } -TEST(TcpAsync, DISABLED_ClientAsker_ServerResponder) { +TEST(TcpAsync, ClientAsker_ServerResponder) { const std::uint16_t port = sample::PortFactory::makePort(); const std::size_t cycles = 5; @@ -56,33 +38,15 @@ TEST(TcpAsync, DISABLED_ClientAsker_ServerResponder) { { if (0 == omp_get_thread_num()) { // server - std::unique_ptr acceptedClient; - { - TcpServer server(port); - server.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(server.isOpen()); -#pragma omp barrier - acceptedClient = server.acceptClient(); - server.close(); - EXPECT_FALSE(server.isOpen()); - } + auto acceptedClient = sample::accept(port); sample::AsyncResponder asynchResponder(std::move(acceptedClient)); - EXPECT_FALSE(asynchResponder.isOpen()); - asynchResponder.open(std::chrono::milliseconds(0)); -#pragma omp barrier - EXPECT_TRUE(asynchResponder.isOpen()); + sample::openSocketDecorator(asynchResponder); #pragma omp barrier - asynchResponder.close(); - EXPECT_TRUE(!asynchResponder.isOpen()); } else { // client std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); -#pragma omp barrier - client->open(std::chrono::milliseconds(0)); - EXPECT_TRUE(client->isOpen()); -#pragma omp barrier - + sample::openTcpClient(*client); sample::Asker asker(std::move(client)); asker.ask(cycles); #pragma omp barrier @@ -102,10 +66,14 @@ class AsynchAcceptor this->sck::async::TcpClientHandlerTalker::resetListener(this); }; - inline const std::list>& getAccepted() const { return this->accepted; }; + inline std::size_t getAcceptedNumber() const { + std::lock_guard lk(this->acceptedMtx); + return this->accepted.size(); + }; private: void handle(std::unique_ptr clientHandler) final { + std::lock_guard lk(this->acceptedMtx); EXPECT_TRUE(clientHandler->isOpen()); this->accepted.emplace_back(std::move(clientHandler)); }; @@ -114,35 +82,38 @@ class AsynchAcceptor void handle(const std::exception& error) final {}; + mutable std::mutex acceptedMtx; std::list> accepted; }; -TEST(TcpAsync, DISABLED_AsynchTcpServerAcceptor) { +TEST(TcpAsync, AsynchTcpServerAcceptor) { const std::uint16_t port = sample::PortFactory::makePort(); - const std::size_t cycles = 5; const std::size_t clientsNumb = 5; + const std::chrono::milliseconds timeOut = std::chrono::seconds(5); + #pragma omp parallel num_threads(2) { if (0 == omp_get_thread_num()) { // acceptor AsynchAcceptor acceptor(port); acceptor.open(std::chrono::milliseconds(0)); -#pragma omp barrier EXPECT_TRUE(acceptor.isOpen()); #pragma omp barrier - EXPECT_EQ(acceptor.getAccepted().size(), cycles); + auto tic = std::chrono::high_resolution_clock::now(); + while (acceptor.getAcceptedNumber() != clientsNumb) { + EXPECT_LE(std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - tic).count(), timeOut.count()); + } } else { -#pragma omp barrier // client +#pragma omp barrier std::list clients; for (std::size_t k = 0; k < clientsNumb; ++k) { clients.emplace_back(*sck::Ip::createLocalHost(port)); clients.back().open(std::chrono::milliseconds(0)); EXPECT_TRUE(clients.back().isOpen()); } -#pragma omp barrier } } } diff --git a/Tests/Test02-udp-asynch.cpp b/Tests/Test02-udp-asynch.cpp index ec3b24f2..6cc96da0 100644 --- a/Tests/Test02-udp-asynch.cpp +++ b/Tests/Test02-udp-asynch.cpp @@ -1,6 +1,5 @@ #include -#include -#include +#include #include #include using namespace sck; @@ -9,36 +8,22 @@ using namespace sck::udp; #include #include -TEST(UdpAsync, DISABLED_Asker_Responder) { - const std::uint16_t portA = sample::PortFactory::makePort(); - const std::uint16_t portB = sample::PortFactory::makePort(); +TEST(UdpAsync, Asker_Responder) { + auto connections = sample::makeOpenedUdpConnections(sample::PortFactory::makePort(), sample::PortFactory::makePort()); const std::size_t cycles = 5; #pragma omp parallel num_threads(2) { if (0 == omp_get_thread_num()) { // connection A - std::unique_ptr connectionA = std::make_unique(*sck::Ip::createLocalHost(portB) , portA); - EXPECT_FALSE(connectionA->isOpen()); - connectionA->open(std::chrono::milliseconds(0)); - EXPECT_TRUE(connectionA->isOpen()); - sample::AsyncResponder asynchResponder(std::move(connectionA)); - EXPECT_FALSE(asynchResponder.isOpen()); - asynchResponder.open(std::chrono::milliseconds(0)); + sample::AsyncResponder asynchResponder(std::move(connections.first)); + sample::openSocketDecorator(asynchResponder); #pragma omp barrier - EXPECT_TRUE(asynchResponder.isOpen()); -#pragma omp barrier - asynchResponder.close(); - EXPECT_TRUE(!asynchResponder.isOpen()); + sample::closeSocketDecorator(asynchResponder); } else { // connection B - std::unique_ptr connectionB = std::make_unique(*sck::Ip::createLocalHost(portA) , portB); - EXPECT_FALSE(connectionB->isOpen()); -#pragma omp barrier - connectionB->open(std::chrono::milliseconds(0)); - EXPECT_TRUE(connectionB->isOpen()); - sample::Asker asker(std::move(connectionB)); + sample::Asker asker(std::move(connections.second)); asker.ask(cycles); #pragma omp barrier } diff --git a/Tests/UtilsTest/include/Common.h b/Tests/UtilsTest/include/Common.h index 3446a0ca..34a10230 100644 --- a/Tests/UtilsTest/include/Common.h +++ b/Tests/UtilsTest/include/Common.h @@ -10,6 +10,7 @@ #include #include +#include namespace sck::sample { class PortFactory { @@ -18,6 +19,12 @@ namespace sck::sample { }; void openSocket(SocketOpenable& socket); + + void closeSocket(Socket& socket); + + void openSocketDecorator(SocketDecorator& socket); + + void closeSocketDecorator(SocketDecorator& socket); } #endif \ No newline at end of file diff --git a/Tests/UtilsTest/src/Common.cpp b/Tests/UtilsTest/src/Common.cpp index 03edbcd1..785af04c 100644 --- a/Tests/UtilsTest/src/Common.cpp +++ b/Tests/UtilsTest/src/Common.cpp @@ -23,8 +23,31 @@ namespace sck::sample { return port; } + template + void open(T& openable) { + openable.open(std::chrono::milliseconds(0)); + EXPECT_TRUE(openable.isOpen()); + } + void openSocket(SocketOpenable& socket) { - socket.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(socket.isOpen()); + open(socket); + } + + void openSocketDecorator(SocketDecorator& socket) { + open(socket); + } + + template + void close(T& closable) { + closable.close(); + EXPECT_FALSE(closable.isOpen()); + } + + void closeSocket(Socket& socket) { + close(socket); + } + + void closeSocketDecorator(SocketDecorator& socket) { + close(socket); } } diff --git a/Utils/src/AsyncResponder.cpp b/Utils/src/AsyncResponder.cpp index d36f07e3..68ba0c93 100644 --- a/Utils/src/AsyncResponder.cpp +++ b/Utils/src/AsyncResponder.cpp @@ -25,11 +25,11 @@ namespace sck::sample { } void AsyncResponder::handle(const sck::Error& error) { - this->close(); + //this->close(); }; void AsyncResponder::handle(const std::exception& error) { - this->close(); + //this->close(); }; } From ecdefafccac6c282a1eeb4b8c5d9c0e4c5085855 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 10 May 2021 00:22:20 +0200 Subject: [PATCH 070/228] minor changes --- README.md | 5 ++++- Samples/Tcp/README.md | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 490ec399..28d9acad 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +![binaries_compilation](https://github.com/andreacasalino/Cross-Platform-Socket/actions/workflows/installArtifacts.yml/badge.svg) +![binaries_compilation](https://github.com/andreacasalino/Cross-Platform-Socket/actions/workflows/runTests.yml/badge.svg) + This repository contains the minimal functionalities to create and use from **C++** both **tcp** and **udp** sockets in a completely platform independent way. @@ -15,7 +18,7 @@ Use [CMake](https://cmake.org) to configure and compile the library and the samp **Run the Samples** -Check the *README.md* inside Samples/Tcp/ and Samples/Udp/ +Check the *README.md* inside Samples/Tcp/ and Samples/Udp/ to understand the samples purpose and how to run them **Download compiled binaries** diff --git a/Samples/Tcp/README.md b/Samples/Tcp/README.md index 1dcac2dc..16903f20 100644 --- a/Samples/Tcp/README.md +++ b/Samples/Tcp/README.md @@ -17,7 +17,7 @@ You don't need to manually do that, since this is done running one of the launch *SampleTcp-03-asyncserver-clients, run: - * a Tcp server asynchronous accepting 1 single client + * a Tcp server accepting 1 single client and asynchronously exchange message with it * a Tcp client that connects to the async server and exchange messages *SampleTcp-04-repeater, run: From 1596f54a7980fa161e6d790cd175a2e33aca47cd Mon Sep 17 00:00:00 2001 From: AndreaC Date: Tue, 11 May 2021 23:06:27 +0200 Subject: [PATCH 071/228] udp server test disabled --- Tests/Test01-udp-synch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Test01-udp-synch.cpp b/Tests/Test01-udp-synch.cpp index e2f7186c..28db9a42 100644 --- a/Tests/Test01-udp-synch.cpp +++ b/Tests/Test01-udp-synch.cpp @@ -50,7 +50,7 @@ TEST(UdpSynch, Asker_Responder) { #include -TEST(UdpSynch, DIAsker_ServerResponder) { +TEST(UdpSynch, DISABLED_Asker_ServerResponder) { const std::uint16_t portA = sample::PortFactory::makePort(); const std::uint16_t portB = sample::PortFactory::makePort(); const std::size_t cycles = 5; From 2cf5918090bee738949898219340d7457358d489 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sat, 15 May 2021 20:11:24 +0200 Subject: [PATCH 072/228] improving existing code --- .../AsynchSocket/include/core/AsyncSocket.h | 2 +- .../include/messanger/AsyncMessanger.h | 3 +- .../AsynchSocket/src/core/AsyncSocket.cpp | 2 +- .../src/messanger/AsyncMessanger.cpp | 5 +-- .../SynchSocket/include/core/Connection.h | 17 +++++++- .../SynchSocket/include/core/SocketClosable.h | 35 +++++++++++++++++ .../include/core/SocketDecorator.h | 7 ++-- .../core/{Socket.h => SocketOpenable.h} | 37 ++++-------------- .../SynchSocket/include/tcp/TcpClient.h | 2 +- .../SynchSocket/include/tcp/TcpServer.h | 7 ++-- .../SynchSocket/include/udp/UdpConnection.h | 2 +- .../SynchSocket/src/core/Connection.cpp | 10 +++-- .../SynchSocket/src/core/SocketClosable.cpp | 36 +++++++++++++++++ .../SynchSocket/src/core/SocketDecorator.cpp | 2 +- .../core/{Socket.cpp => SocketOpenable.cpp} | 39 +------------------ CrossSocket/SynchSocket/src/tcp/TcpClient.cpp | 2 +- CrossSocket/SynchSocket/src/tcp/TcpServer.cpp | 8 ++-- .../SynchSocket/src/udp/UdpConnection.cpp | 4 +- CrossSocket/SynchSocket/src/udp/UdpServer.cpp | 2 +- CrossSocket/TypedSocket/CMakeLists.txt | 8 ++++ .../include/core/TypedAsyncSocket.h | 0 .../include/core/TypedReceiveCapable.h | 21 ++++++++++ .../include/core/TypedSendCapable.h | 21 ++++++++++ .../TypedSocket/include/core/TypedSocket.h | 26 +++++++++++++ .../include/core/components/Decoder.h | 0 .../include/core/components/Encoder.h | 0 .../include/core/components/MessangerAware.h | 0 27 files changed, 201 insertions(+), 97 deletions(-) create mode 100644 CrossSocket/SynchSocket/include/core/SocketClosable.h rename CrossSocket/SynchSocket/include/core/{Socket.h => SocketOpenable.h} (61%) create mode 100644 CrossSocket/SynchSocket/src/core/SocketClosable.cpp rename CrossSocket/SynchSocket/src/core/{Socket.cpp => SocketOpenable.cpp} (60%) create mode 100644 CrossSocket/TypedSocket/CMakeLists.txt create mode 100644 CrossSocket/TypedSocket/include/core/TypedAsyncSocket.h create mode 100644 CrossSocket/TypedSocket/include/core/TypedReceiveCapable.h create mode 100644 CrossSocket/TypedSocket/include/core/TypedSendCapable.h create mode 100644 CrossSocket/TypedSocket/include/core/TypedSocket.h create mode 100644 CrossSocket/TypedSocket/include/core/components/Decoder.h create mode 100644 CrossSocket/TypedSocket/include/core/components/Encoder.h create mode 100644 CrossSocket/TypedSocket/include/core/components/MessangerAware.h diff --git a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h index 49186b40..3b0f60c9 100644 --- a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h +++ b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h @@ -44,7 +44,7 @@ namespace sck::async { void close() final; protected: - AsyncSocket(std::unique_ptr socket); + AsyncSocket(std::unique_ptr socket); virtual void serviceIteration() = 0; diff --git a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h index 5f185801..9a35d72c 100644 --- a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h +++ b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h @@ -9,6 +9,7 @@ #define _CROSS_SOCKET_ASYNCMESSANGER_H #include +#include #include #include #include @@ -28,7 +29,7 @@ namespace sck::async { , public MessageTalker , public SendCapable { public: - AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity); + AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity); inline bool send(const std::pair& message) final { return this->messPtr->send(message); }; diff --git a/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp b/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp index 374dc859..61e058f2 100644 --- a/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp +++ b/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp @@ -8,7 +8,7 @@ #include namespace sck::async { - AsyncSocket::AsyncSocket(std::unique_ptr socket) + AsyncSocket::AsyncSocket(std::unique_ptr socket) : SocketDecorator(std::move(socket)) { }; diff --git a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp index a8a1cc3b..452abc05 100644 --- a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp +++ b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp @@ -10,12 +10,9 @@ #include namespace sck::async { - AsyncMessanger::AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) + AsyncMessanger::AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) : AsyncSocket(std::move(messanger)) { this->messPtr = dynamic_cast (this->wrapped.get()); - if (nullptr == this->messPtr) { - throw Error("The passed socket is not a messanger"); - } this->receiveBuffer.resize(bufferCapacity); }; diff --git a/CrossSocket/SynchSocket/include/core/Connection.h b/CrossSocket/SynchSocket/include/core/Connection.h index f22aebea..b008b624 100644 --- a/CrossSocket/SynchSocket/include/core/Connection.h +++ b/CrossSocket/SynchSocket/include/core/Connection.h @@ -8,13 +8,14 @@ #ifndef _CROSS_SOCKET_CONNECTION_H_ #define _CROSS_SOCKET_CONNECTION_H_ -#include +#include +#include #include #include namespace sck { class Connection - : public SocketOpenable + : public SocketClosable , public Messanger , public RemoteAddressAware { protected: @@ -22,6 +23,18 @@ namespace sck { * @param[in] the address of the connection to reach */ explicit Connection(const sck::Ip& remoteAddress); + }; + + + + class ConnectionOpenable + : public Connection + , public SocketOpenable { + protected: + /** + * @param[in] the address of the connection to reach + */ + explicit ConnectionOpenable(const sck::Ip& remoteAddress); /** * @brief connect is internally called diff --git a/CrossSocket/SynchSocket/include/core/SocketClosable.h b/CrossSocket/SynchSocket/include/core/SocketClosable.h new file mode 100644 index 00000000..07f0b53a --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/SocketClosable.h @@ -0,0 +1,35 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_SOCKETCLOSABLE_H_ +#define _CROSS_SOCKET_SOCKETCLOSABLE_H_ + +#include +#include +#include + +namespace sck { + class SocketClosable + : virtual public ChannelAware + , virtual public StateAware + , virtual public Closable { + public: + virtual ~SocketClosable() override; + + bool isOpen() const final; + + void close() final; + + protected: + /** + * @brief The methods containing the specific steps to perform in order to fully close a concrete socket + */ + virtual void closeSteps(); + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/SocketDecorator.h b/CrossSocket/SynchSocket/include/core/SocketDecorator.h index 520f6b44..6d5396d7 100644 --- a/CrossSocket/SynchSocket/include/core/SocketDecorator.h +++ b/CrossSocket/SynchSocket/include/core/SocketDecorator.h @@ -8,7 +8,8 @@ #ifndef _CROSS_SOCKET_SOCKETDECORATOR_H_ #define _CROSS_SOCKET_SOCKETDECORATOR_H_ -#include +#include +#include namespace sck { class SocketDecorator @@ -27,9 +28,9 @@ namespace sck { void open(const std::chrono::milliseconds& timeout) override; protected: - SocketDecorator(std::unique_ptr channel); + SocketDecorator(std::unique_ptr channel); - std::unique_ptr wrapped; + std::unique_ptr wrapped; }; } diff --git a/CrossSocket/SynchSocket/include/core/Socket.h b/CrossSocket/SynchSocket/include/core/SocketOpenable.h similarity index 61% rename from CrossSocket/SynchSocket/include/core/Socket.h rename to CrossSocket/SynchSocket/include/core/SocketOpenable.h index ce33da72..f69c5e75 100644 --- a/CrossSocket/SynchSocket/include/core/Socket.h +++ b/CrossSocket/SynchSocket/include/core/SocketOpenable.h @@ -5,46 +5,27 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef _CROSS_SOCKET_SOCKET_H_ -#define _CROSS_SOCKET_SOCKET_H_ +#ifndef _CROSS_SOCKET_SOCKETOPENABLE_H_ +#define _CROSS_SOCKET_SOCKETOPENABLE_H_ #include +#include #include #include -#include #include #include -#include namespace sck { - class Socket - : virtual public ChannelAware - , public StateAware - , public Closable { - public: - virtual ~Socket() override; - - bool isOpen() const final; - - void close() final; - - protected: - explicit Socket(std::unique_ptr channel); - - /** - * @brief The methods containing the specific steps to perform in order to fully close a concrete socket - */ - virtual void closeSteps(); - }; - - - class SocketOpenable - : public Socket + : virtual public ChannelAware + , virtual public StateAware , public Openable + , virtual protected Closable , virtual public FamilyAware , virtual public ProtocolAware { public: + virtual ~SocketOpenable() override; + /** * @brief When something goes wrong inside the method, close is * internally called, leaving the object in a closed status. @@ -52,8 +33,6 @@ namespace sck { void open(const std::chrono::milliseconds& timeout) final; protected: - explicit SocketOpenable(std::unique_ptr channel); - /** * @brief The methods containing the specific steps to perform to fully open a concrete socket */ diff --git a/CrossSocket/SynchSocket/include/tcp/TcpClient.h b/CrossSocket/SynchSocket/include/tcp/TcpClient.h index e74461fe..2a0f0597 100644 --- a/CrossSocket/SynchSocket/include/tcp/TcpClient.h +++ b/CrossSocket/SynchSocket/include/tcp/TcpClient.h @@ -17,7 +17,7 @@ namespace sck::tcp { * it should be ready to listen and accept this client */ class TcpClient - : public Connection { + : public ConnectionOpenable { public: /** * @param[in] the address of the server to reach diff --git a/CrossSocket/SynchSocket/include/tcp/TcpServer.h b/CrossSocket/SynchSocket/include/tcp/TcpServer.h index 0c561dee..8c13901c 100644 --- a/CrossSocket/SynchSocket/include/tcp/TcpServer.h +++ b/CrossSocket/SynchSocket/include/tcp/TcpServer.h @@ -8,7 +8,7 @@ #ifndef _CROSS_SOCKET_TCPSERVER_H_ #define _CROSS_SOCKET_TCPSERVER_H_ -#include +#include #include #include #include @@ -22,6 +22,7 @@ namespace sck::tcp { */ class TcpServer : public SocketOpenable + , public SocketClosable , public BindCapable { public: /** @@ -49,9 +50,7 @@ namespace sck::tcp { }; class TcpClientHandler - : public Socket - , public Messanger - , public RemoteAddressAware { + : public Connection { friend class TcpServer; protected: TcpClientHandler(std::unique_ptr channel, const sck::Ip& remoteAddress); diff --git a/CrossSocket/SynchSocket/include/udp/UdpConnection.h b/CrossSocket/SynchSocket/include/udp/UdpConnection.h index 9c7f0b9c..6a9fbffe 100644 --- a/CrossSocket/SynchSocket/include/udp/UdpConnection.h +++ b/CrossSocket/SynchSocket/include/udp/UdpConnection.h @@ -22,7 +22,7 @@ namespace sck::udp { * @brief interface for a standard udp connection. */ class UdpConnection - : public Connection + : public ConnectionOpenable , public BindCapable { public: /** diff --git a/CrossSocket/SynchSocket/src/core/Connection.cpp b/CrossSocket/SynchSocket/src/core/Connection.cpp index 0ed293ce..0354c740 100644 --- a/CrossSocket/SynchSocket/src/core/Connection.cpp +++ b/CrossSocket/SynchSocket/src/core/Connection.cpp @@ -10,12 +10,16 @@ #include namespace sck { - Connection::Connection(const sck::Ip& remoteAddress) - : SocketOpenable(std::make_unique()) { + Connection::Connection(const sck::Ip& remoteAddress) { this->remoteAddress = std::make_unique(remoteAddress); + this->channel = std::make_unique(); } - void Connection::openSteps() { + ConnectionOpenable::ConnectionOpenable(const sck::Ip& remoteAddress) + : Connection(remoteAddress) { + } + + void ConnectionOpenable::openSteps() { if (sck::Family::IP_V4 == this->getFamily()) { //v4 family auto addr = convertIpv4(*this->remoteAddress); diff --git a/CrossSocket/SynchSocket/src/core/SocketClosable.cpp b/CrossSocket/SynchSocket/src/core/SocketClosable.cpp new file mode 100644 index 00000000..57c20c3a --- /dev/null +++ b/CrossSocket/SynchSocket/src/core/SocketClosable.cpp @@ -0,0 +1,36 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include "../Channel.h" + +namespace sck { + bool SocketClosable::isOpen() const { + return this->channel->isOpen(); + }; + + SocketClosable::~SocketClosable() { + if (this->isOpen()) { + this->close(); + } + } + + void SocketClosable::close() { + if (!this->isOpen()) { + return; + } + try { + this->closeSteps(); + } + catch (...) { + } + } + + void SocketClosable::closeSteps() { + this->channel->close(); + } +} diff --git a/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp b/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp index 17a1be53..f395d6c1 100644 --- a/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp +++ b/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp @@ -9,7 +9,7 @@ #include namespace sck { - SocketDecorator::SocketDecorator(std::unique_ptr channel) + SocketDecorator::SocketDecorator(std::unique_ptr channel) : wrapped(std::move(channel)) { if (nullptr == this->wrapped) { throw Error("The decorator can't wrap a nullptr"); diff --git a/CrossSocket/SynchSocket/src/core/Socket.cpp b/CrossSocket/SynchSocket/src/core/SocketOpenable.cpp similarity index 60% rename from CrossSocket/SynchSocket/src/core/Socket.cpp rename to CrossSocket/SynchSocket/src/core/SocketOpenable.cpp index a843c36f..67a42413 100644 --- a/CrossSocket/SynchSocket/src/core/Socket.cpp +++ b/CrossSocket/SynchSocket/src/core/SocketOpenable.cpp @@ -5,50 +5,13 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include "../Channel.h" -#include #include #include #include namespace sck { - Socket::Socket(std::unique_ptr channel) { - if (nullptr == channel) { - throw Error("found null channel when building the socket object"); - } - this->channel = std::move(channel); - } - - bool Socket::isOpen() const { - return this->channel->isOpen(); - }; - - Socket::~Socket() { - if (this->isOpen()) { - this->close(); - } - } - - void Socket::close() { - if (!this->isOpen()) { - return; - } - try { - this->closeSteps(); - } - catch (...) { - } - } - - void Socket::closeSteps() { - this->channel->close(); - } - - SocketOpenable::SocketOpenable(std::unique_ptr channel) - : Socket(std::move(channel)) { - } - void SocketOpenable::open(const std::chrono::milliseconds& timeout) { if (this->isOpen()) { return; diff --git a/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp b/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp index 93fac67b..ec7afe3e 100644 --- a/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp +++ b/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp @@ -10,6 +10,6 @@ namespace sck::tcp { TcpClient::TcpClient(const sck::Ip& remoteAddress) - : Connection(remoteAddress) { + : ConnectionOpenable(remoteAddress) { } } diff --git a/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp b/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp index 2a3843cd..52d31003 100644 --- a/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp +++ b/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp @@ -13,14 +13,14 @@ namespace sck::tcp { constexpr std::size_t LISTEN_BACKLOG = 50; TcpClientHandler::TcpClientHandler(std::unique_ptr channel, const sck::Ip& remoteAddress) - : Socket(std::move(channel)) { - this->remoteAddress = std::make_unique(remoteAddress); + : Connection(remoteAddress) { + this->channel = std::move(channel); } TcpServer::TcpServer(const std::uint16_t& port, const Family& family) - : SocketOpenable(std::make_unique()) - , port(port) + : port(port) , family(family) { + this->channel = std::make_unique(); } std::unique_ptr TcpServer::acceptClient() { diff --git a/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp b/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp index 5ae308e9..bd03ceda 100644 --- a/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp +++ b/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp @@ -11,12 +11,12 @@ namespace sck::udp { UdpConnection::UdpConnection(const sck::Ip& remoteAddress, const std::uint16_t& localPort) - : Connection(remoteAddress) + : ConnectionOpenable(remoteAddress) , port(localPort) { } void UdpConnection::openSteps() { this->bindToPort(this->port); - this->Connection::openSteps(); + this->ConnectionOpenable::openSteps(); } } \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/udp/UdpServer.cpp b/CrossSocket/SynchSocket/src/udp/UdpServer.cpp index 7ae6284e..48dbd197 100644 --- a/CrossSocket/SynchSocket/src/udp/UdpServer.cpp +++ b/CrossSocket/SynchSocket/src/udp/UdpServer.cpp @@ -45,6 +45,6 @@ namespace sck::udp { throw Error(remoteAddr.sa_data, " is an invalid data for udp serer remote address"); } this->remoteAddress = std::move(remoteConverted); - this->Connection::openSteps(); + this->ConnectionOpenable::openSteps(); } } \ No newline at end of file diff --git a/CrossSocket/TypedSocket/CMakeLists.txt b/CrossSocket/TypedSocket/CMakeLists.txt new file mode 100644 index 00000000..2da036e2 --- /dev/null +++ b/CrossSocket/TypedSocket/CMakeLists.txt @@ -0,0 +1,8 @@ +set(PROJECT_SHORTNAME "Typed-Cross-Socket") + +MakeLibrary(${PROJECT_SHORTNAME} include) + +target_link_libraries(${PROJECT_SHORTNAME} +PUBLIC + Synch-Cross-Socket +) diff --git a/CrossSocket/TypedSocket/include/core/TypedAsyncSocket.h b/CrossSocket/TypedSocket/include/core/TypedAsyncSocket.h new file mode 100644 index 00000000..e69de29b diff --git a/CrossSocket/TypedSocket/include/core/TypedReceiveCapable.h b/CrossSocket/TypedSocket/include/core/TypedReceiveCapable.h new file mode 100644 index 00000000..ab5f07f4 --- /dev/null +++ b/CrossSocket/TypedSocket/include/core/TypedReceiveCapable.h @@ -0,0 +1,21 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_TYPEDRECEIVECAPABLE_H_ +#define _CROSS_SOCKET_TYPEDRECEIVECAPABLE_H_ + +#include + +namespace sck::typed { + template + class ReceiveCapable { + public: + virtual std::size_t receive(T& recipient, const std::size_t size, const std::chrono::milliseconds& timeout) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/TypedSendCapable.h b/CrossSocket/TypedSocket/include/core/TypedSendCapable.h new file mode 100644 index 00000000..c5e2eb58 --- /dev/null +++ b/CrossSocket/TypedSocket/include/core/TypedSendCapable.h @@ -0,0 +1,21 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_TYPEDSENDCAPABLE_H_ +#define _CROSS_SOCKET_TYPEDSENDCAPABLE_H_ + +#include + +namespace sck::typed { + template + class SendCapable { + public: + virtual bool send(const T& message) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/TypedSocket.h b/CrossSocket/TypedSocket/include/core/TypedSocket.h new file mode 100644 index 00000000..9d88dedc --- /dev/null +++ b/CrossSocket/TypedSocket/include/core/TypedSocket.h @@ -0,0 +1,26 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_TYPEDSOCKET_H_ +#define _CROSS_SOCKET_TYPEDSOCKET_H_ + +#include +#include +#include + +namespace sck::typed { + template + class TypedSocket + : public SocketDecorator + , public ReceiveCapable + , public SendCapable { + public: + TypedSocket(std::unique_ptr channel); + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/components/Decoder.h b/CrossSocket/TypedSocket/include/core/components/Decoder.h new file mode 100644 index 00000000..e69de29b diff --git a/CrossSocket/TypedSocket/include/core/components/Encoder.h b/CrossSocket/TypedSocket/include/core/components/Encoder.h new file mode 100644 index 00000000..e69de29b diff --git a/CrossSocket/TypedSocket/include/core/components/MessangerAware.h b/CrossSocket/TypedSocket/include/core/components/MessangerAware.h new file mode 100644 index 00000000..e69de29b From 1e0af8728e80b81a76f004b45379ff4c21556e7f Mon Sep 17 00:00:00 2001 From: Andrea Date: Sat, 15 May 2021 23:08:49 +0200 Subject: [PATCH 073/228] implementing typed connections --- CrossSocket/CMakeLists.txt | 5 +++ .../SynchSocket/include/core/SocketOpenable.h | 2 - ...pedAsyncSocket.h => TypedAsyncMessanger.h} | 0 .../TypedSocket/include/core/TypedMessanger.h | 33 +++++++++++++++ .../TypedSocket/include/core/TypedReceiver.h | 41 +++++++++++++++++++ .../TypedSocket/include/core/TypedSender.h | 41 +++++++++++++++++++ .../TypedSocket/include/core/TypedSocket.h | 26 ------------ .../include/core/components/Decoder.h | 22 ++++++++++ .../include/core/components/Encoder.h | 22 ++++++++++ .../include/core/components/MessangerAware.h | 0 .../{ => components}/TypedReceiveCapable.h | 2 +- .../core/{ => components}/TypedSendCapable.h | 0 Tests/UtilsTest/include/Common.h | 5 ++- Tests/UtilsTest/src/Common.cpp | 2 +- Utils/include/AsyncResponder.h | 2 +- Utils/src/AsyncResponder.cpp | 2 +- 16 files changed, 171 insertions(+), 34 deletions(-) rename CrossSocket/TypedSocket/include/core/{TypedAsyncSocket.h => TypedAsyncMessanger.h} (100%) create mode 100644 CrossSocket/TypedSocket/include/core/TypedMessanger.h create mode 100644 CrossSocket/TypedSocket/include/core/TypedReceiver.h create mode 100644 CrossSocket/TypedSocket/include/core/TypedSender.h delete mode 100644 CrossSocket/TypedSocket/include/core/TypedSocket.h delete mode 100644 CrossSocket/TypedSocket/include/core/components/MessangerAware.h rename CrossSocket/TypedSocket/include/core/{ => components}/TypedReceiveCapable.h (72%) rename CrossSocket/TypedSocket/include/core/{ => components}/TypedSendCapable.h (100%) diff --git a/CrossSocket/CMakeLists.txt b/CrossSocket/CMakeLists.txt index 26ad4789..4212ae8c 100644 --- a/CrossSocket/CMakeLists.txt +++ b/CrossSocket/CMakeLists.txt @@ -4,3 +4,8 @@ option(COMPILE_ASYNCH "Compile the asynchronous cross socket package" ON) if(COMPILE_ASYNCH) add_subdirectory(AsynchSocket) endif() + +option(COMPILE_TYPED "Compile the typed cross socket package" ON) +if(COMPILE_TYPED) +add_subdirectory(TypedSocket) +endif() diff --git a/CrossSocket/SynchSocket/include/core/SocketOpenable.h b/CrossSocket/SynchSocket/include/core/SocketOpenable.h index f69c5e75..45c28013 100644 --- a/CrossSocket/SynchSocket/include/core/SocketOpenable.h +++ b/CrossSocket/SynchSocket/include/core/SocketOpenable.h @@ -24,8 +24,6 @@ namespace sck { , virtual public FamilyAware , virtual public ProtocolAware { public: - virtual ~SocketOpenable() override; - /** * @brief When something goes wrong inside the method, close is * internally called, leaving the object in a closed status. diff --git a/CrossSocket/TypedSocket/include/core/TypedAsyncSocket.h b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h similarity index 100% rename from CrossSocket/TypedSocket/include/core/TypedAsyncSocket.h rename to CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h diff --git a/CrossSocket/TypedSocket/include/core/TypedMessanger.h b/CrossSocket/TypedSocket/include/core/TypedMessanger.h new file mode 100644 index 00000000..52f2f9ee --- /dev/null +++ b/CrossSocket/TypedSocket/include/core/TypedMessanger.h @@ -0,0 +1,33 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_TYPEDMESSANGER_H_ +#define _CROSS_SOCKET_TYPEDMESSANGER_H_ + +#include +#include +#include +#include + +namespace sck::typed { + template + class TypedSocket + : public SocketDecorator + , public TypedSender + , public TypedReceiver { + public: + TypedSocket(std::unique_ptr channel) + : SocketDecorator(std::move(channel)) + , TypedSender() + , TypedReceiver() { + this->sender = dynamic_cast(this->wrapped.get()); + this->receiver = dynamic_cast(this->wrapped.get()); + }; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/TypedReceiver.h b/CrossSocket/TypedSocket/include/core/TypedReceiver.h new file mode 100644 index 00000000..4f4bff14 --- /dev/null +++ b/CrossSocket/TypedSocket/include/core/TypedReceiver.h @@ -0,0 +1,41 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_TYPEDRECEIVER_H_ +#define _CROSS_SOCKET_TYPEDRECEIVER_H_ + +#include +#include +#include +#include +#include + +namespace sck::typed { + template + class TypedReceiver + : public ReceiveCapable + , public Decoder_ { + static_assert(std::is_base_of, Decoder_>::value, "Not valid Decoder_ type"); + public: + bool receive(T& recipient, const std::size_t size, const std::chrono::milliseconds& timeout) override { + std::lock_guard lk(this->recvBufferMtx); + this->recvBuffer.resize(size); + std::size_t recvBytes = this->receiver->receive(std::make_pair(this->recvBuffer.data(), this->recvBuffer.size()) , timeout); + this->recvBuffer.resize(recvBytes); + return this->decode(this->recvBuffer, recipient); + } + + protected: + sck::ReceiveCapable* receiver; + + private: + std::mutex recvBufferMtx; + std::string recvBuffer; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/TypedSender.h b/CrossSocket/TypedSocket/include/core/TypedSender.h new file mode 100644 index 00000000..584169fe --- /dev/null +++ b/CrossSocket/TypedSocket/include/core/TypedSender.h @@ -0,0 +1,41 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_TYPEDSENDER_H_ +#define _CROSS_SOCKET_TYPEDSENDER_H_ + +#include +#include +#include +#include +#include + +namespace sck::typed { + template + class TypedSender + : public SendCapable + , public Encoder_ { + static_assert(std::is_base_of, Encoder_>::value, "Not valid Encoder_ type"); + public: + bool send(const T& message) override { + std::lock_guard lk(this->sendBufferMtx); + if (!this->encode(this->sendBuffer, message)) { + return false; + } + return this->sender->send(std::make_pair(this->sendBuffer.data(), this->sendBuffer.size())); + }; + + protected: + sck::SendCapable* sender; + + private: + std::mutex sendBufferMtx; + std::string sendBuffer; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/TypedSocket.h b/CrossSocket/TypedSocket/include/core/TypedSocket.h deleted file mode 100644 index 9d88dedc..00000000 --- a/CrossSocket/TypedSocket/include/core/TypedSocket.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TYPEDSOCKET_H_ -#define _CROSS_SOCKET_TYPEDSOCKET_H_ - -#include -#include -#include - -namespace sck::typed { - template - class TypedSocket - : public SocketDecorator - , public ReceiveCapable - , public SendCapable { - public: - TypedSocket(std::unique_ptr channel); - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/components/Decoder.h b/CrossSocket/TypedSocket/include/core/components/Decoder.h index e69de29b..0a9b9fe5 100644 --- a/CrossSocket/TypedSocket/include/core/components/Decoder.h +++ b/CrossSocket/TypedSocket/include/core/components/Decoder.h @@ -0,0 +1,22 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_DECODER_H_ +#define _CROSS_SOCKET_DECODER_H_ + +#include +#include + +namespace sck { + template + class Decoder { + protected: + virtual bool decode(const std::string& buffer, T& message) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/components/Encoder.h b/CrossSocket/TypedSocket/include/core/components/Encoder.h index e69de29b..5ff617a1 100644 --- a/CrossSocket/TypedSocket/include/core/components/Encoder.h +++ b/CrossSocket/TypedSocket/include/core/components/Encoder.h @@ -0,0 +1,22 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_ENCODER_H_ +#define _CROSS_SOCKET_ENCODER_H_ + +#include +#include + +namespace sck { + template + class Encoder { + protected: + virtual bool encode(std::string& buffer, const T& message) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/components/MessangerAware.h b/CrossSocket/TypedSocket/include/core/components/MessangerAware.h deleted file mode 100644 index e69de29b..00000000 diff --git a/CrossSocket/TypedSocket/include/core/TypedReceiveCapable.h b/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h similarity index 72% rename from CrossSocket/TypedSocket/include/core/TypedReceiveCapable.h rename to CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h index ab5f07f4..ffcdf07e 100644 --- a/CrossSocket/TypedSocket/include/core/TypedReceiveCapable.h +++ b/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h @@ -14,7 +14,7 @@ namespace sck::typed { template class ReceiveCapable { public: - virtual std::size_t receive(T& recipient, const std::size_t size, const std::chrono::milliseconds& timeout) = 0; + virtual bool receive(T& recipient, const std::size_t size, const std::chrono::milliseconds& timeout) = 0; }; } diff --git a/CrossSocket/TypedSocket/include/core/TypedSendCapable.h b/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h similarity index 100% rename from CrossSocket/TypedSocket/include/core/TypedSendCapable.h rename to CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h diff --git a/Tests/UtilsTest/include/Common.h b/Tests/UtilsTest/include/Common.h index 34a10230..00e8acaf 100644 --- a/Tests/UtilsTest/include/Common.h +++ b/Tests/UtilsTest/include/Common.h @@ -9,7 +9,8 @@ #define SAMPLE_COMMON_H #include -#include +#include +#include #include namespace sck::sample { @@ -20,7 +21,7 @@ namespace sck::sample { void openSocket(SocketOpenable& socket); - void closeSocket(Socket& socket); + void closeSocket(SocketClosable& socket); void openSocketDecorator(SocketDecorator& socket); diff --git a/Tests/UtilsTest/src/Common.cpp b/Tests/UtilsTest/src/Common.cpp index 785af04c..619b01cc 100644 --- a/Tests/UtilsTest/src/Common.cpp +++ b/Tests/UtilsTest/src/Common.cpp @@ -43,7 +43,7 @@ namespace sck::sample { EXPECT_FALSE(closable.isOpen()); } - void closeSocket(Socket& socket) { + void closeSocket(SocketClosable& socket) { close(socket); } diff --git a/Utils/include/AsyncResponder.h b/Utils/include/AsyncResponder.h index d6f80a4d..5409a652 100644 --- a/Utils/include/AsyncResponder.h +++ b/Utils/include/AsyncResponder.h @@ -20,7 +20,7 @@ namespace sck::sample { , protected sck::async::ErrorListener , public Logger { public: - AsyncResponder(std::unique_ptr socket); + AsyncResponder(std::unique_ptr socket); private: void handle(const std::pair& message) final; diff --git a/Utils/src/AsyncResponder.cpp b/Utils/src/AsyncResponder.cpp index 68ba0c93..72f1f4c2 100644 --- a/Utils/src/AsyncResponder.cpp +++ b/Utils/src/AsyncResponder.cpp @@ -9,7 +9,7 @@ #include namespace sck::sample { - AsyncResponder::AsyncResponder(std::unique_ptr socket) + AsyncResponder::AsyncResponder(std::unique_ptr socket) : AsyncMessanger(std::move(socket), 1000) , Logger("AsynchResponder") { From dd92a3c6de0e52c6689ed00518cdd5baf1d6f87a Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 16 May 2021 12:04:57 +0200 Subject: [PATCH 074/228] implementing typed messangers --- .../include/messanger/AsyncMessanger.h | 12 +++---- .../src/messanger/AsyncMessanger.cpp | 17 ++++----- .../include/core/components/Buffer.h | 35 +++++++++++++++++++ .../src/core/components/Buffer.cpp | 32 +++++++++++++++++ CrossSocket/TypedSocket/CMakeLists.txt | 16 ++++++--- .../include/core/TypedAsyncMessanger.h | 30 ++++++++++++++++ .../TypedSocket/include/core/TypedMessanger.h | 20 +++++++---- .../include/core/TypedMessangerListener.h | 19 ++++++++++ .../TypedSocket/include/core/TypedReceiver.h | 26 +++++++++----- .../TypedSocket/include/core/TypedSender.h | 13 +++---- .../include/core/components/Decoder.h | 1 - .../include/core/components/Encoder.h | 1 - .../core/components/TypedReceiveCapable.h | 2 +- .../core/components/TypedSendCapable.h | 2 -- 14 files changed, 177 insertions(+), 49 deletions(-) create mode 100644 CrossSocket/SynchSocket/include/core/components/Buffer.h create mode 100644 CrossSocket/SynchSocket/src/core/components/Buffer.cpp create mode 100644 CrossSocket/TypedSocket/include/core/TypedMessangerListener.h diff --git a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h index 9a35d72c..638d69e7 100644 --- a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h +++ b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace sck::async { @@ -27,19 +28,18 @@ namespace sck::async { class AsyncMessanger : public AsyncSocket , public MessageTalker - , public SendCapable { + , public SendCapable + , public Buffer { public: AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity); inline bool send(const std::pair& message) final { return this->messPtr->send(message); }; - private: - void serviceIteration() final; - - std::vector receiveBuffer; - protected: Messanger* messPtr; + + private: + void serviceIteration() final; }; } diff --git a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp index 452abc05..f69178b4 100644 --- a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp +++ b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp @@ -11,19 +11,16 @@ namespace sck::async { AsyncMessanger::AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) - : AsyncSocket(std::move(messanger)) { + : AsyncSocket(std::move(messanger)) + , Buffer(bufferCapacity) { this->messPtr = dynamic_cast (this->wrapped.get()); - this->receiveBuffer.resize(bufferCapacity); }; void AsyncMessanger::serviceIteration() { - this->receiveBuffer.resize(this->receiveBuffer.capacity()); - auto pr = std::make_pair(this->receiveBuffer.data(), this->receiveBuffer.capacity()); - auto recvBytes = this->messPtr->receive(pr, std::chrono::milliseconds(0)); - if (recvBytes != this->receiveBuffer.capacity()) { - this->receiveBuffer.resize(recvBytes); - } - pr.second = recvBytes; - this->Talker::notify(pr); + this->resetBufferSize(); + auto buffer = this->getBuffer(); + auto recvBytes = this->messPtr->receive(buffer, std::chrono::milliseconds(0)); + buffer.second = recvBytes; + this->Talker::notify(buffer); } } \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/Buffer.h b/CrossSocket/SynchSocket/include/core/components/Buffer.h new file mode 100644 index 00000000..1ad57fc7 --- /dev/null +++ b/CrossSocket/SynchSocket/include/core/components/Buffer.h @@ -0,0 +1,35 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_BUFFER_H_ +#define _CROSS_SOCKET_BUFFER_H_ + +#include +#include + +namespace sck { + class Buffer { + protected: + Buffer(const std::size_t capacity); + + inline std::pair getBuffer() { return std::make_pair(this->buffer.data(), this->buffer.size()); }; + + inline const std::string& getStringBuffer() const { return this->buffer; }; + + // with 0 reset to size equal to capacity + // throw if the new size is higher than the max capacity + void resetBufferSize(const std::size_t newSize = 0); + + void resetBufferCapacity(const std::size_t newCapacity); + + private: + std::string buffer; + std::size_t bufferCapacity; + }; +} + +#endif diff --git a/CrossSocket/SynchSocket/src/core/components/Buffer.cpp b/CrossSocket/SynchSocket/src/core/components/Buffer.cpp new file mode 100644 index 00000000..294c0221 --- /dev/null +++ b/CrossSocket/SynchSocket/src/core/components/Buffer.cpp @@ -0,0 +1,32 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +namespace sck { + Buffer::Buffer(const std::size_t capacity) { + this->resetBufferCapacity(capacity); + } + + void Buffer::resetBufferSize(const std::size_t newSize) { + if (0 == newSize) { + this->buffer.resize(this->bufferCapacity); + } + else if(newSize > this->bufferCapacity) { + throw Error("New buffer size can't exceed buffer capacity"); + } + this->buffer.resize(newSize); + } + + void Buffer::resetBufferCapacity(const std::size_t newCapacity) { + if (0 == newCapacity) { + throw Error("null capacity is invalid"); + } + this->bufferCapacity = newCapacity; + } +} diff --git a/CrossSocket/TypedSocket/CMakeLists.txt b/CrossSocket/TypedSocket/CMakeLists.txt index 2da036e2..c371f083 100644 --- a/CrossSocket/TypedSocket/CMakeLists.txt +++ b/CrossSocket/TypedSocket/CMakeLists.txt @@ -2,7 +2,15 @@ set(PROJECT_SHORTNAME "Typed-Cross-Socket") MakeLibrary(${PROJECT_SHORTNAME} include) -target_link_libraries(${PROJECT_SHORTNAME} -PUBLIC - Synch-Cross-Socket -) +if(COMPILE_ASYNCH) + target_link_libraries(${PROJECT_SHORTNAME} + PUBLIC + Asynch-Cross-Socket + ) + target_compile_definitions(${PROJECT_SHORTNAME} PUBLIC ASYNCH_ENABLED) +else() + target_link_libraries(${PROJECT_SHORTNAME} + PUBLIC + Synch-Cross-Socket + ) +endif() \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h index e69de29b..cd699d7a 100644 --- a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h +++ b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h @@ -0,0 +1,30 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifdef ASYNCH_ENABLED +#ifndef _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ +#define _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ + +#include +#include +#include +#include + +namespace sck::typed { + template + class TypedAsynchMessanger + : protected AsyncMessanger + , public Talker> + , public TypedSender + , public Decoder_ { + public: + TypedAsynchMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity); + }; +} + +#endif +#endif diff --git a/CrossSocket/TypedSocket/include/core/TypedMessanger.h b/CrossSocket/TypedSocket/include/core/TypedMessanger.h index 52f2f9ee..fabc9d17 100644 --- a/CrossSocket/TypedSocket/include/core/TypedMessanger.h +++ b/CrossSocket/TypedSocket/include/core/TypedMessanger.h @@ -14,20 +14,26 @@ #include namespace sck::typed { - template - class TypedSocket + template + class TypedMessanger : public SocketDecorator - , public TypedSender - , public TypedReceiver { + , public TypedSender + , public TypedReceiver { public: - TypedSocket(std::unique_ptr channel) + TypedMessanger(std::unique_ptr channel, const std::size_t bufferCapacity) : SocketDecorator(std::move(channel)) - , TypedSender() - , TypedReceiver() { + , TypedReceiver(bufferCapacity) { this->sender = dynamic_cast(this->wrapped.get()); this->receiver = dynamic_cast(this->wrapped.get()); }; }; + + //template + //class TypedMessangerSingleT + // : public TypedMessanger { + //public: + + //}; } #endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/TypedMessangerListener.h b/CrossSocket/TypedSocket/include/core/TypedMessangerListener.h new file mode 100644 index 00000000..645e15df --- /dev/null +++ b/CrossSocket/TypedSocket/include/core/TypedMessangerListener.h @@ -0,0 +1,19 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef _CROSS_SOCKET_TYPEDMESSANGERLISTENER_H_ +#define _CROSS_SOCKET_TYPEDMESSANGERLISTENER_H_ + +namespace sck::async { + template + class TypedMessangerListener { + public: + virtual void handle(const T& message) = 0; + }; +} + +#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/TypedReceiver.h b/CrossSocket/TypedSocket/include/core/TypedReceiver.h index 4f4bff14..a7af3b52 100644 --- a/CrossSocket/TypedSocket/include/core/TypedReceiver.h +++ b/CrossSocket/TypedSocket/include/core/TypedReceiver.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -18,23 +19,32 @@ namespace sck::typed { template class TypedReceiver : public ReceiveCapable + , private Buffer , public Decoder_ { static_assert(std::is_base_of, Decoder_>::value, "Not valid Decoder_ type"); public: - bool receive(T& recipient, const std::size_t size, const std::chrono::milliseconds& timeout) override { - std::lock_guard lk(this->recvBufferMtx); - this->recvBuffer.resize(size); - std::size_t recvBytes = this->receiver->receive(std::make_pair(this->recvBuffer.data(), this->recvBuffer.size()) , timeout); - this->recvBuffer.resize(recvBytes); - return this->decode(this->recvBuffer, recipient); + bool receive(T& recipient, const std::chrono::milliseconds& timeout) final { + std::lock_guard lk(this->bufferMtx); + this->resetBufferSize(); + auto buffer = this->getBuffer(); + std::size_t recvBytes = this->receiver->receive(buffer , timeout); + buffer.second = recvBytes; + return this->decode(this->getStringBuffer(), recipient); } + void setBufferCapacity(const std::size_t bufferCapacity) { + std::lock_guard lk(this->bufferMtx); + this->Buffer::resetBufferCapacity(bufferCapacity); + }; + + protected: + TypedReceiver(const std::size_t bufferCapacity); + protected: sck::ReceiveCapable* receiver; private: - std::mutex recvBufferMtx; - std::string recvBuffer; + std::mutex bufferMtx; }; } diff --git a/CrossSocket/TypedSocket/include/core/TypedSender.h b/CrossSocket/TypedSocket/include/core/TypedSender.h index 584169fe..1cd23dd3 100644 --- a/CrossSocket/TypedSocket/include/core/TypedSender.h +++ b/CrossSocket/TypedSocket/include/core/TypedSender.h @@ -12,7 +12,6 @@ #include #include #include -#include namespace sck::typed { template @@ -21,20 +20,16 @@ namespace sck::typed { , public Encoder_ { static_assert(std::is_base_of, Encoder_>::value, "Not valid Encoder_ type"); public: - bool send(const T& message) override { - std::lock_guard lk(this->sendBufferMtx); - if (!this->encode(this->sendBuffer, message)) { + bool send(const T& message) final { + std::string sendBuffer; + if (!this->encode(sendBuffer, message)) { return false; } - return this->sender->send(std::make_pair(this->sendBuffer.data(), this->sendBuffer.size())); + return this->sender->send(std::make_pair(sendBuffer.data(), sendBuffer.size())); }; protected: sck::SendCapable* sender; - - private: - std::mutex sendBufferMtx; - std::string sendBuffer; }; } diff --git a/CrossSocket/TypedSocket/include/core/components/Decoder.h b/CrossSocket/TypedSocket/include/core/components/Decoder.h index 0a9b9fe5..f91aa618 100644 --- a/CrossSocket/TypedSocket/include/core/components/Decoder.h +++ b/CrossSocket/TypedSocket/include/core/components/Decoder.h @@ -8,7 +8,6 @@ #ifndef _CROSS_SOCKET_DECODER_H_ #define _CROSS_SOCKET_DECODER_H_ -#include #include namespace sck { diff --git a/CrossSocket/TypedSocket/include/core/components/Encoder.h b/CrossSocket/TypedSocket/include/core/components/Encoder.h index 5ff617a1..c08ef284 100644 --- a/CrossSocket/TypedSocket/include/core/components/Encoder.h +++ b/CrossSocket/TypedSocket/include/core/components/Encoder.h @@ -8,7 +8,6 @@ #ifndef _CROSS_SOCKET_ENCODER_H_ #define _CROSS_SOCKET_ENCODER_H_ -#include #include namespace sck { diff --git a/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h b/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h index ffcdf07e..2a184f19 100644 --- a/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h +++ b/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h @@ -14,7 +14,7 @@ namespace sck::typed { template class ReceiveCapable { public: - virtual bool receive(T& recipient, const std::size_t size, const std::chrono::milliseconds& timeout) = 0; + virtual bool receive(T& recipient, const std::chrono::milliseconds& timeout) = 0; }; } diff --git a/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h b/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h index c5e2eb58..61680e24 100644 --- a/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h +++ b/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h @@ -8,8 +8,6 @@ #ifndef _CROSS_SOCKET_TYPEDSENDCAPABLE_H_ #define _CROSS_SOCKET_TYPEDSENDCAPABLE_H_ -#include - namespace sck::typed { template class SendCapable { From 7c902154bc8e71b40511863f5114ee86b20b8aad Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 16 May 2021 23:22:14 +0200 Subject: [PATCH 075/228] implementing typed connections --- .../include/core/TypedAsyncMessanger.h | 31 +++++++++++++++---- .../include/core/TypedMessangerListener.h | 2 +- .../TypedSocket/include/core/TypedReceiver.h | 4 ++- .../include/core/components/Decoder.h | 2 +- .../include/core/components/Encoder.h | 2 +- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h index cd699d7a..92b9fd3f 100644 --- a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h +++ b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h @@ -10,19 +10,38 @@ #define _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ #include -#include +#include #include #include namespace sck::typed { - template class TypedAsynchMessanger - : protected AsyncMessanger - , public Talker> + : protected async::AsyncMessanger + , protected async::MessangerListener + , public async::Talker> , public TypedSender - , public Decoder_ { + , protected Decoder_ { public: - TypedAsynchMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity); + TypedAsynchMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) + : AsyncMessanger(std::move(messanger), bufferCapacity) { + this->async::MessageTalker::resetListener(this); + }; + + inline bool isOpen() const { return this->async::AsyncMessanger::isOpen(); }; + + inline void close() { this->async::AsyncMessanger::close(); }; + + inline void open(const std::chrono::milliseconds& timeout) { this->async::AsyncMessanger::open(timeout); }; + + inline void resetErrorListener(async::ErrorListener* listener) { this->async::AsyncMessanger::resetListener(listener); }; + + protected: + void handle(const std::pair& message) final { + // bool decode(const std::string& buffer, T& message) + RecvT typed; + this->Decoder_::decode(std::string(message.first, message.second), typed); + this->notify(typed); + } }; } diff --git a/CrossSocket/TypedSocket/include/core/TypedMessangerListener.h b/CrossSocket/TypedSocket/include/core/TypedMessangerListener.h index 645e15df..ff3d30b5 100644 --- a/CrossSocket/TypedSocket/include/core/TypedMessangerListener.h +++ b/CrossSocket/TypedSocket/include/core/TypedMessangerListener.h @@ -8,7 +8,7 @@ #ifndef _CROSS_SOCKET_TYPEDMESSANGERLISTENER_H_ #define _CROSS_SOCKET_TYPEDMESSANGERLISTENER_H_ -namespace sck::async { +namespace sck::typed { template class TypedMessangerListener { public: diff --git a/CrossSocket/TypedSocket/include/core/TypedReceiver.h b/CrossSocket/TypedSocket/include/core/TypedReceiver.h index a7af3b52..45bd7855 100644 --- a/CrossSocket/TypedSocket/include/core/TypedReceiver.h +++ b/CrossSocket/TypedSocket/include/core/TypedReceiver.h @@ -38,7 +38,9 @@ namespace sck::typed { }; protected: - TypedReceiver(const std::size_t bufferCapacity); + TypedReceiver(const std::size_t bufferCapacity) + : Buffer(bufferCapacity) { + }; protected: sck::ReceiveCapable* receiver; diff --git a/CrossSocket/TypedSocket/include/core/components/Decoder.h b/CrossSocket/TypedSocket/include/core/components/Decoder.h index f91aa618..97e6a4df 100644 --- a/CrossSocket/TypedSocket/include/core/components/Decoder.h +++ b/CrossSocket/TypedSocket/include/core/components/Decoder.h @@ -10,7 +10,7 @@ #include -namespace sck { +namespace sck::typed { template class Decoder { protected: diff --git a/CrossSocket/TypedSocket/include/core/components/Encoder.h b/CrossSocket/TypedSocket/include/core/components/Encoder.h index c08ef284..bd425b16 100644 --- a/CrossSocket/TypedSocket/include/core/components/Encoder.h +++ b/CrossSocket/TypedSocket/include/core/components/Encoder.h @@ -10,7 +10,7 @@ #include -namespace sck { +namespace sck::typed { template class Encoder { protected: From 6c9cdac42a3d1e6b2535af682a82b8af817b0226 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 19 May 2021 12:16:08 +0200 Subject: [PATCH 076/228] typed tests introduced --- .../src/core/components/Buffer.cpp | 1 + .../include/core/TypedAsyncMessanger.h | 98 +++++++++---------- .../TypedSocket/include/core/TypedMessanger.h | 16 ++- .../TypedSocket/include/core/TypedReceiver.h | 8 +- .../TypedSocket/include/core/TypedSender.h | 2 +- .../include/core/components/Decoder.h | 2 +- .../include/core/components/Encoder.h | 2 +- .../core/components/TypedReceiveCapable.h | 2 +- .../core/components/TypedSendCapable.h | 2 +- .../TypedSocket/src/core/TypedMessanger.cpp | 15 +++ Tests/CMakeLists.txt | 2 + Tests/Test03-typed.cpp | 74 ++++++++++++++ Tests/UtilsTest/CMakeLists.txt | 2 +- Tests/UtilsTest/include/NamesCoding.h | 29 ++++++ Tests/UtilsTest/src/NamesCoding.cpp | 50 ++++++++++ Utils/include/NamesMap.h | 2 + 16 files changed, 237 insertions(+), 70 deletions(-) create mode 100644 CrossSocket/TypedSocket/src/core/TypedMessanger.cpp create mode 100644 Tests/Test03-typed.cpp create mode 100644 Tests/UtilsTest/include/NamesCoding.h create mode 100644 Tests/UtilsTest/src/NamesCoding.cpp diff --git a/CrossSocket/SynchSocket/src/core/components/Buffer.cpp b/CrossSocket/SynchSocket/src/core/components/Buffer.cpp index 294c0221..0551bdea 100644 --- a/CrossSocket/SynchSocket/src/core/components/Buffer.cpp +++ b/CrossSocket/SynchSocket/src/core/components/Buffer.cpp @@ -16,6 +16,7 @@ namespace sck { void Buffer::resetBufferSize(const std::size_t newSize) { if (0 == newSize) { this->buffer.resize(this->bufferCapacity); + return; } else if(newSize > this->bufferCapacity) { throw Error("New buffer size can't exceed buffer capacity"); diff --git a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h index 92b9fd3f..3a177e85 100644 --- a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h +++ b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h @@ -1,49 +1,49 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifdef ASYNCH_ENABLED -#ifndef _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ -#define _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ - -#include -#include -#include -#include - -namespace sck::typed { - class TypedAsynchMessanger - : protected async::AsyncMessanger - , protected async::MessangerListener - , public async::Talker> - , public TypedSender - , protected Decoder_ { - public: - TypedAsynchMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) - : AsyncMessanger(std::move(messanger), bufferCapacity) { - this->async::MessageTalker::resetListener(this); - }; - - inline bool isOpen() const { return this->async::AsyncMessanger::isOpen(); }; - - inline void close() { this->async::AsyncMessanger::close(); }; - - inline void open(const std::chrono::milliseconds& timeout) { this->async::AsyncMessanger::open(timeout); }; - - inline void resetErrorListener(async::ErrorListener* listener) { this->async::AsyncMessanger::resetListener(listener); }; - - protected: - void handle(const std::pair& message) final { - // bool decode(const std::string& buffer, T& message) - RecvT typed; - this->Decoder_::decode(std::string(message.first, message.second), typed); - this->notify(typed); - } - }; -} - -#endif -#endif +///** +// * Author: Andrea Casalino +// * Created: 01.28.2020 +// * +// * report any bug to andrecasa91@gmail.com. +// **/ +// +//#ifdef ASYNCH_ENABLED +//#ifndef _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ +//#define _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ +// +//#include +//#include +//#include +//#include +// +//namespace sck::typed { +// class TypedAsynchMessanger +// : protected async::AsyncMessanger +// , protected async::MessangerListener +// , public async::Talker> +// , public TypedSender +// , protected Decoder_ { +// public: +// TypedAsynchMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) +// : AsyncMessanger(std::move(messanger), bufferCapacity) { +// this->async::MessageTalker::resetListener(this); +// }; +// +// inline bool isOpen() const { return this->async::AsyncMessanger::isOpen(); }; +// +// inline void close() { this->async::AsyncMessanger::close(); }; +// +// inline void open(const std::chrono::milliseconds& timeout) { this->async::AsyncMessanger::open(timeout); }; +// +// inline void resetErrorListener(async::ErrorListener* listener) { this->async::AsyncMessanger::resetListener(listener); }; +// +// protected: +// void handle(const std::pair& message) final { +// // bool decode(const std::string& buffer, T& message) +// RecvT typed; +// this->Decoder_::decode(std::string(message.first, message.second), typed); +// this->notify(typed); +// } +// }; +//} +// +//#endif +//#endif diff --git a/CrossSocket/TypedSocket/include/core/TypedMessanger.h b/CrossSocket/TypedSocket/include/core/TypedMessanger.h index fabc9d17..2559c718 100644 --- a/CrossSocket/TypedSocket/include/core/TypedMessanger.h +++ b/CrossSocket/TypedSocket/include/core/TypedMessanger.h @@ -14,6 +14,8 @@ #include namespace sck::typed { + std::pair extractComponents(SocketClosable* connection); + template class TypedMessanger : public SocketDecorator @@ -23,17 +25,11 @@ namespace sck::typed { TypedMessanger(std::unique_ptr channel, const std::size_t bufferCapacity) : SocketDecorator(std::move(channel)) , TypedReceiver(bufferCapacity) { - this->sender = dynamic_cast(this->wrapped.get()); - this->receiver = dynamic_cast(this->wrapped.get()); + auto components = extractComponents(this->wrapped.get()); + this->sender = components.first; + this->receiver = components.second; }; }; - - //template - //class TypedMessangerSingleT - // : public TypedMessanger { - //public: - - //}; } -#endif \ No newline at end of file +#endif diff --git a/CrossSocket/TypedSocket/include/core/TypedReceiver.h b/CrossSocket/TypedSocket/include/core/TypedReceiver.h index 45bd7855..76c9b094 100644 --- a/CrossSocket/TypedSocket/include/core/TypedReceiver.h +++ b/CrossSocket/TypedSocket/include/core/TypedReceiver.h @@ -18,17 +18,15 @@ namespace sck::typed { template class TypedReceiver - : public ReceiveCapable + : public TypedReceiveCapable , private Buffer , public Decoder_ { static_assert(std::is_base_of, Decoder_>::value, "Not valid Decoder_ type"); public: bool receive(T& recipient, const std::chrono::milliseconds& timeout) final { std::lock_guard lk(this->bufferMtx); - this->resetBufferSize(); - auto buffer = this->getBuffer(); - std::size_t recvBytes = this->receiver->receive(buffer , timeout); - buffer.second = recvBytes; + this->Buffer::resetBufferSize(); + this->Buffer::resetBufferSize(this->receiver->receive(this->getBuffer(), timeout)); return this->decode(this->getStringBuffer(), recipient); } diff --git a/CrossSocket/TypedSocket/include/core/TypedSender.h b/CrossSocket/TypedSocket/include/core/TypedSender.h index 1cd23dd3..d6dc77bb 100644 --- a/CrossSocket/TypedSocket/include/core/TypedSender.h +++ b/CrossSocket/TypedSocket/include/core/TypedSender.h @@ -16,7 +16,7 @@ namespace sck::typed { template class TypedSender - : public SendCapable + : public TypedSendCapable , public Encoder_ { static_assert(std::is_base_of, Encoder_>::value, "Not valid Encoder_ type"); public: diff --git a/CrossSocket/TypedSocket/include/core/components/Decoder.h b/CrossSocket/TypedSocket/include/core/components/Decoder.h index 97e6a4df..f452d6d3 100644 --- a/CrossSocket/TypedSocket/include/core/components/Decoder.h +++ b/CrossSocket/TypedSocket/include/core/components/Decoder.h @@ -14,7 +14,7 @@ namespace sck::typed { template class Decoder { protected: - virtual bool decode(const std::string& buffer, T& message) = 0; + virtual bool decode(const std::string& buffer, T& message) const = 0; }; } diff --git a/CrossSocket/TypedSocket/include/core/components/Encoder.h b/CrossSocket/TypedSocket/include/core/components/Encoder.h index bd425b16..80572073 100644 --- a/CrossSocket/TypedSocket/include/core/components/Encoder.h +++ b/CrossSocket/TypedSocket/include/core/components/Encoder.h @@ -14,7 +14,7 @@ namespace sck::typed { template class Encoder { protected: - virtual bool encode(std::string& buffer, const T& message) = 0; + virtual bool encode(std::string& buffer, const T& message) const = 0; }; } diff --git a/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h b/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h index 2a184f19..8edc9d43 100644 --- a/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h +++ b/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h @@ -12,7 +12,7 @@ namespace sck::typed { template - class ReceiveCapable { + class TypedReceiveCapable { public: virtual bool receive(T& recipient, const std::chrono::milliseconds& timeout) = 0; }; diff --git a/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h b/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h index 61680e24..afeae560 100644 --- a/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h +++ b/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h @@ -10,7 +10,7 @@ namespace sck::typed { template - class SendCapable { + class TypedSendCapable { public: virtual bool send(const T& message) = 0; }; diff --git a/CrossSocket/TypedSocket/src/core/TypedMessanger.cpp b/CrossSocket/TypedSocket/src/core/TypedMessanger.cpp new file mode 100644 index 00000000..6c3677ed --- /dev/null +++ b/CrossSocket/TypedSocket/src/core/TypedMessanger.cpp @@ -0,0 +1,15 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +namespace sck::typed { + std::pair extractComponents(SocketClosable* connection) { + return std::make_pair(dynamic_cast(connection), + dynamic_cast(connection)); + } +} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 75840337..2497aec2 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -24,3 +24,5 @@ MakeTest(Test02-tcp-asynch) MakeTest(Test01-udp-synch) MakeTest(Test02-udp-asynch) + +MakeTest(Test03-typed) diff --git a/Tests/Test03-typed.cpp b/Tests/Test03-typed.cpp new file mode 100644 index 00000000..165f0ef1 --- /dev/null +++ b/Tests/Test03-typed.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +using namespace sck; +using namespace sck::tcp; + +#include +#include +#include +using namespace sck::typed; + +sample::Names getNames() { + sample::Names names; + sample::NamesMap map; + for (std::size_t k = 0; k < sample::NamesMap::size(); ++k) { + names.push_back(map.getCursorName()); + ++map; + } + return names; +}; + +sample::Names getSurnames() { + sample::Names surnames; + sample::NamesMap map; + for (std::size_t k = 0; k < sample::NamesMap::size(); ++k) { + surnames.push_back(map.getCursorSurname()); + ++map; + } + return surnames; +}; + +TEST(TcpTyped, ClientAsker_ServerResponder) { + const std::uint16_t port = sample::PortFactory::makePort(); + const std::size_t cycles = 5; + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // server + auto acceptedClient = sample::accept(port); + TypedMessanger messanger(std::move(acceptedClient), 2500); + // exchange typed messages + for (std::size_t k = 0; k < cycles; ++k) { + sample::Names request; + EXPECT_TRUE(messanger.receive(request, std::chrono::milliseconds(0))); + sample::Names response; + for (auto it = request.begin(); it != request.end(); ++it) { + response.push_back(sample::NamesMap::getSurname(*it)); + } + EXPECT_TRUE(messanger.send(response)); + } +#pragma omp barrier + } + else { + // client + std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); + sample::openTcpClient(*client); + TypedMessanger messanger(std::move(client), 2500); + // exchange typed messages + for (std::size_t k = 0; k < cycles; ++k) { + EXPECT_TRUE(messanger.send(getNames())); + sample::Names response; + EXPECT_TRUE(messanger.receive(response, std::chrono::milliseconds(0))); + EXPECT_EQ(response, getSurnames()); + } +#pragma omp barrier + } + } +} + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Tests/UtilsTest/CMakeLists.txt b/Tests/UtilsTest/CMakeLists.txt index 03e6b099..356d1a18 100644 --- a/Tests/UtilsTest/CMakeLists.txt +++ b/Tests/UtilsTest/CMakeLists.txt @@ -6,7 +6,7 @@ find_package(OpenMP) target_link_libraries(${PROJECT_SHORTNAME} PUBLIC - Synch-Cross-Socket + Typed-Cross-Socket gtest OpenMP::OpenMP_CXX ) diff --git a/Tests/UtilsTest/include/NamesCoding.h b/Tests/UtilsTest/include/NamesCoding.h new file mode 100644 index 00000000..06661b66 --- /dev/null +++ b/Tests/UtilsTest/include/NamesCoding.h @@ -0,0 +1,29 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifndef SAMPLE_NAMESCODING_H +#define SAMPLE_NAMESCODING_H + +#include +#include +#include + +namespace sck::sample { + typedef std::list Names; + + class NamesDecoder : public typed::Decoder { + protected: + bool decode(const std::string& buffer, Names& message) const final; + }; + + class NamesEncoder : public typed::Encoder { + protected: + bool encode(std::string& buffer, const Names& message) const final; + }; +} + +#endif diff --git a/Tests/UtilsTest/src/NamesCoding.cpp b/Tests/UtilsTest/src/NamesCoding.cpp new file mode 100644 index 00000000..befceb24 --- /dev/null +++ b/Tests/UtilsTest/src/NamesCoding.cpp @@ -0,0 +1,50 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +namespace sck::sample { + bool NamesDecoder::decode(const std::string& buffer, Names& message) const { + message.clear(); + if (buffer.empty()) { + return true; + } + std::list posSeparator; + for (std::size_t k = 0; k < buffer.size(); ++k) { + if (';' == buffer[k]) { + posSeparator.push_back(k); + } + } + if (posSeparator.empty()) { + message = {buffer}; + return true; + } + std::size_t posPrev = 0; + for (auto it = posSeparator.begin(); it != posSeparator.end(); ++it) { + message.push_back(std::string(buffer, posPrev, *it - posPrev)); + posPrev = *it + 1; + } + if (posPrev < buffer.size()) { + message.push_back(std::string(buffer, posPrev)); + } + return true; + } + + bool NamesEncoder::encode(std::string& buffer, const Names& message) const { + std::stringstream stream; + auto it = message.begin(); + stream << *it; + ++it; + for (it; it != message.end(); ++it) { + stream << ';'; + stream << *it; + } + buffer = stream.str(); + return true; + } +} diff --git a/Utils/include/NamesMap.h b/Utils/include/NamesMap.h index 095e965c..9dde4303 100644 --- a/Utils/include/NamesMap.h +++ b/Utils/include/NamesMap.h @@ -23,6 +23,8 @@ namespace sck::sample { NamesMap& operator++(); + inline static std::size_t size() { return namesSurnames.size(); }; + private: static const std::map namesSurnames; static const std::string unknown; From a0f0bff0836891e91dd37786e53801c5ce06176e Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 19 May 2021 13:09:31 +0200 Subject: [PATCH 077/228] implementing tests --- .../include/core/TypedAsyncMessanger.h | 104 +++++++++--------- Tests/Test03-typed.cpp | 89 ++++++++++++--- 2 files changed, 131 insertions(+), 62 deletions(-) diff --git a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h index 3a177e85..fe9b089e 100644 --- a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h +++ b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h @@ -1,49 +1,55 @@ -///** -// * Author: Andrea Casalino -// * Created: 01.28.2020 -// * -// * report any bug to andrecasa91@gmail.com. -// **/ -// -//#ifdef ASYNCH_ENABLED -//#ifndef _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ -//#define _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ -// -//#include -//#include -//#include -//#include -// -//namespace sck::typed { -// class TypedAsynchMessanger -// : protected async::AsyncMessanger -// , protected async::MessangerListener -// , public async::Talker> -// , public TypedSender -// , protected Decoder_ { -// public: -// TypedAsynchMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) -// : AsyncMessanger(std::move(messanger), bufferCapacity) { -// this->async::MessageTalker::resetListener(this); -// }; -// -// inline bool isOpen() const { return this->async::AsyncMessanger::isOpen(); }; -// -// inline void close() { this->async::AsyncMessanger::close(); }; -// -// inline void open(const std::chrono::milliseconds& timeout) { this->async::AsyncMessanger::open(timeout); }; -// -// inline void resetErrorListener(async::ErrorListener* listener) { this->async::AsyncMessanger::resetListener(listener); }; -// -// protected: -// void handle(const std::pair& message) final { -// // bool decode(const std::string& buffer, T& message) -// RecvT typed; -// this->Decoder_::decode(std::string(message.first, message.second), typed); -// this->notify(typed); -// } -// }; -//} -// -//#endif -//#endif +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifdef ASYNCH_ENABLED +#ifndef _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ +#define _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ + +#include +#include +#include +#include + +namespace sck::typed { + template + class TypedAsynchMessanger + : public StateAware + , public Openable + , public Closable + , protected async::MessangerListener + , public TypedSender + , public async::Talker> + , protected Decoder_ { + public: + TypedAsynchMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) + : asyncMessanger(std::move(messanger), bufferCapacity) { + this->sender = &this->asyncMessanger; + }; + + inline bool isOpen() const override { return this->asyncMessanger.isOpen(); }; + + inline void close() override { this->asyncMessanger.close(); }; + + inline void open(const std::chrono::milliseconds& timeout) override { this->asyncMessanger.open(timeout); }; + + inline void resetErrorListener(async::ErrorListener* listener) { this->asyncMessanger.resetListener(listener); }; + + protected: + void handle(const std::pair& message) final { + // bool decode(const std::string& buffer, T& message) + RecvT typed; + this->Decoder_::decode(std::string(message.first, message.second), typed); + this->notify(typed); + } + + private: + async::AsyncMessanger asyncMessanger; + }; +} + +#endif +#endif diff --git a/Tests/Test03-typed.cpp b/Tests/Test03-typed.cpp index 165f0ef1..7fcaebb4 100644 --- a/Tests/Test03-typed.cpp +++ b/Tests/Test03-typed.cpp @@ -6,7 +6,9 @@ using namespace sck::tcp; #include #include +#include #include +#include using namespace sck::typed; sample::Names getNames() { @@ -29,7 +31,24 @@ sample::Names getSurnames() { return surnames; }; -TEST(TcpTyped, ClientAsker_ServerResponder) { +sample::Names getResponse(const sample::Names& request) { + sample::Names response; + for (auto it = request.begin(); it != request.end(); ++it) { + response.push_back(sample::NamesMap::getSurname(*it)); + } + return response; +}; + +void doRequests(TypedMessanger& messanger, const std::size_t cycles) { + for (std::size_t k = 0; k < cycles; ++k) { + EXPECT_TRUE(messanger.send(getNames())); + sample::Names response; + EXPECT_TRUE(messanger.receive(response, std::chrono::milliseconds(0))); + EXPECT_EQ(response, getSurnames()); + } +} + +TEST(SyncTyped, ClientAsker_ServerResponder) { const std::uint16_t port = sample::PortFactory::makePort(); const std::size_t cycles = 5; @@ -43,11 +62,7 @@ TEST(TcpTyped, ClientAsker_ServerResponder) { for (std::size_t k = 0; k < cycles; ++k) { sample::Names request; EXPECT_TRUE(messanger.receive(request, std::chrono::milliseconds(0))); - sample::Names response; - for (auto it = request.begin(); it != request.end(); ++it) { - response.push_back(sample::NamesMap::getSurname(*it)); - } - EXPECT_TRUE(messanger.send(response)); + EXPECT_TRUE(messanger.send(getResponse(request))); } #pragma omp barrier } @@ -56,13 +71,61 @@ TEST(TcpTyped, ClientAsker_ServerResponder) { std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); sample::openTcpClient(*client); TypedMessanger messanger(std::move(client), 2500); - // exchange typed messages - for (std::size_t k = 0; k < cycles; ++k) { - EXPECT_TRUE(messanger.send(getNames())); - sample::Names response; - EXPECT_TRUE(messanger.receive(response, std::chrono::milliseconds(0))); - EXPECT_EQ(response, getSurnames()); - } + doRequests(messanger, cycles); +#pragma omp barrier + } + } +} + + +class TypedAsyncResponder + : public TypedAsynchMessanger + , public TypedMessangerListener + , protected sck::async::ErrorListener + , public sample::Logger { +public: + TypedAsyncResponder(std::unique_ptr messanger, const std::size_t& bufferCapacity) + : TypedAsynchMessanger(std::move(messanger), bufferCapacity) + , Logger("TypedAsyncResponder") { + this->resetErrorListener(this); + this->resetListener(this); + }; + +private: + void handle(const sample::Names& message) final { + sample::Names response = getResponse(message); + this->send(response); + //std::string recStr(message.first, message.second); + //const std::string surname = NamesMap::getSurname(recStr); + //this->log("request: ", recStr, " response: ", surname); + //this->messPtr->send({ surname.data(), surname.size() }); + }; + + void handle(const sck::Error& error) final {}; + + void handle(const std::exception& error) final {}; +}; + +TEST(AsyncTyped, ClientAsker_ServerResponder) { + const std::uint16_t port = sample::PortFactory::makePort(); + const std::size_t cycles = 5; + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // server + auto acceptedClient = sample::accept(port); + TypedAsynchMessanger asynchResponder(std::move(acceptedClient), 2500); + open(asynchResponder); +#pragma omp barrier + asynchResponder.close(); + } + else { + // client + std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); + sample::openTcpClient(*client); + TypedMessanger messanger(std::move(client), 2500); + doRequests(messanger, cycles); #pragma omp barrier } } From 6d8e8819157422a9b4f42aa6384c77b7290b8be3 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 19 May 2021 16:17:08 +0200 Subject: [PATCH 078/228] implementing tests --- .../include/core/TypedAsyncMessanger.h | 3 +- Tests/Test01-tcp-synch.cpp | 10 ++-- Tests/Test01-udp-synch.cpp | 8 ++-- Tests/Test02-tcp-asynch.cpp | 6 +-- Tests/Test02-udp-asynch.cpp | 4 +- Tests/Test03-typed.cpp | 47 ++++++++++++------- Tests/UtilsTest/include/Common.h | 22 +++++---- .../include/{NamesCoding.h => NamesCoder.h} | 4 +- Tests/UtilsTest/src/Common.cpp | 29 ------------ .../src/{NamesCoding.cpp => NamesCoder.cpp} | 2 +- Tests/UtilsTest/src/TcpCommon.cpp | 4 +- Tests/UtilsTest/src/UdpCommon.cpp | 4 +- 12 files changed, 64 insertions(+), 79 deletions(-) rename Tests/UtilsTest/include/{NamesCoding.h => NamesCoder.h} (91%) rename Tests/UtilsTest/src/{NamesCoding.cpp => NamesCoder.cpp} (98%) diff --git a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h index fe9b089e..c857ca6f 100644 --- a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h +++ b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h @@ -36,11 +36,10 @@ namespace sck::typed { inline void open(const std::chrono::milliseconds& timeout) override { this->asyncMessanger.open(timeout); }; - inline void resetErrorListener(async::ErrorListener* listener) { this->asyncMessanger.resetListener(listener); }; + inline void resetErrorListener(async::ErrorListener* listener) { this->asyncMessanger.async::ErrorTalker::resetListener(listener); }; protected: void handle(const std::pair& message) final { - // bool decode(const std::string& buffer, T& message) RecvT typed; this->Decoder_::decode(std::string(message.first, message.second), typed); this->notify(typed); diff --git a/Tests/Test01-tcp-synch.cpp b/Tests/Test01-tcp-synch.cpp index a041582a..d6a115d8 100644 --- a/Tests/Test01-tcp-synch.cpp +++ b/Tests/Test01-tcp-synch.cpp @@ -13,14 +13,14 @@ TEST(TcpSynch, OpenClose) { // server auto acceptedClient = sample::accept(port); #pragma omp barrier - sample::closeSocket(*acceptedClient); + sample::close(*acceptedClient); } else { // client TcpClient client(*sck::Ip::createLocalHost(port)); sample::openTcpClient(client); #pragma omp barrier - sample::closeSocket(client); + sample::close(client); } } } @@ -34,11 +34,11 @@ TEST(TcpSynch, OpenCloseManyClients) { if (0 == omp_get_thread_num()) { // server tcp::TcpServer server(port); - sample::openSocket(server); + sample::open(server); auto acceptedClients = sample::accept(server, clientsNumb); #pragma omp barrier for (auto it = acceptedClients.begin(); it != acceptedClients.end(); ++it) { - sample::closeSocket(**it); + sample::close(**it); } } else { @@ -51,7 +51,7 @@ TEST(TcpSynch, OpenCloseManyClients) { } #pragma omp barrier for (auto it = clients.begin(); it != clients.end(); ++it) { - sample::closeSocket(**it); + sample::close(**it); } } } diff --git a/Tests/Test01-udp-synch.cpp b/Tests/Test01-udp-synch.cpp index 28db9a42..843c20e2 100644 --- a/Tests/Test01-udp-synch.cpp +++ b/Tests/Test01-udp-synch.cpp @@ -11,15 +11,15 @@ TEST(UdpSynch, OpenClose) { { if (0 == omp_get_thread_num()) { // connection A - sample::openSocket(*connections.first); + sample::open(*connections.first); #pragma omp barrier - sample::closeSocket(*connections.first); + sample::close(*connections.first); } else { // connection B - sample::openSocket(*connections.second); + sample::open(*connections.second); #pragma omp barrier - sample::closeSocket(*connections.second); + sample::close(*connections.second); } } } diff --git a/Tests/Test02-tcp-asynch.cpp b/Tests/Test02-tcp-asynch.cpp index b4f090ca..64b46dbe 100644 --- a/Tests/Test02-tcp-asynch.cpp +++ b/Tests/Test02-tcp-asynch.cpp @@ -17,9 +17,9 @@ TEST(TcpAsync, OpenCloseAcceptSynch) { // server auto acceptedClient = sample::accept(port); sample::AsyncResponder asynchResponder(std::move(acceptedClient)); - sample::openSocketDecorator(asynchResponder); + sample::open(asynchResponder); #pragma omp barrier - sample::closeSocketDecorator(asynchResponder); + sample::close(asynchResponder); } else { // client @@ -40,7 +40,7 @@ TEST(TcpAsync, ClientAsker_ServerResponder) { // server auto acceptedClient = sample::accept(port); sample::AsyncResponder asynchResponder(std::move(acceptedClient)); - sample::openSocketDecorator(asynchResponder); + sample::open(asynchResponder); #pragma omp barrier } else { diff --git a/Tests/Test02-udp-asynch.cpp b/Tests/Test02-udp-asynch.cpp index 6cc96da0..da0cc4a4 100644 --- a/Tests/Test02-udp-asynch.cpp +++ b/Tests/Test02-udp-asynch.cpp @@ -17,9 +17,9 @@ TEST(UdpAsync, Asker_Responder) { if (0 == omp_get_thread_num()) { // connection A sample::AsyncResponder asynchResponder(std::move(connections.first)); - sample::openSocketDecorator(asynchResponder); + sample::open(asynchResponder); #pragma omp barrier - sample::closeSocketDecorator(asynchResponder); + sample::close(asynchResponder); } else { // connection B diff --git a/Tests/Test03-typed.cpp b/Tests/Test03-typed.cpp index 7fcaebb4..1d56856f 100644 --- a/Tests/Test03-typed.cpp +++ b/Tests/Test03-typed.cpp @@ -7,11 +7,13 @@ using namespace sck::tcp; #include #include #include -#include +#include #include using namespace sck::typed; -sample::Names getNames() { +constexpr std::size_t BUFFER_CAPACITY = 2500; + +sample::Names getAllNames() { sample::Names names; sample::NamesMap map; for (std::size_t k = 0; k < sample::NamesMap::size(); ++k) { @@ -21,7 +23,7 @@ sample::Names getNames() { return names; }; -sample::Names getSurnames() { +sample::Names getAllSurnames() { sample::Names surnames; sample::NamesMap map; for (std::size_t k = 0; k < sample::NamesMap::size(); ++k) { @@ -41,14 +43,14 @@ sample::Names getResponse(const sample::Names& request) { void doRequests(TypedMessanger& messanger, const std::size_t cycles) { for (std::size_t k = 0; k < cycles; ++k) { - EXPECT_TRUE(messanger.send(getNames())); + EXPECT_TRUE(messanger.send(getAllNames())); sample::Names response; EXPECT_TRUE(messanger.receive(response, std::chrono::milliseconds(0))); - EXPECT_EQ(response, getSurnames()); + EXPECT_EQ(response, getAllSurnames()); } } -TEST(SyncTyped, ClientAsker_ServerResponder) { +TEST(Typed, Sync) { const std::uint16_t port = sample::PortFactory::makePort(); const std::size_t cycles = 5; @@ -57,7 +59,7 @@ TEST(SyncTyped, ClientAsker_ServerResponder) { if (0 == omp_get_thread_num()) { // server auto acceptedClient = sample::accept(port); - TypedMessanger messanger(std::move(acceptedClient), 2500); + TypedMessanger messanger(std::move(acceptedClient), BUFFER_CAPACITY); // exchange typed messages for (std::size_t k = 0; k < cycles; ++k) { sample::Names request; @@ -70,7 +72,7 @@ TEST(SyncTyped, ClientAsker_ServerResponder) { // client std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); sample::openTcpClient(*client); - TypedMessanger messanger(std::move(client), 2500); + TypedMessanger messanger(std::move(client), BUFFER_CAPACITY); doRequests(messanger, cycles); #pragma omp barrier } @@ -78,14 +80,25 @@ TEST(SyncTyped, ClientAsker_ServerResponder) { } +std::string toString(const sample::Names& names) { + std::stringstream stream; + auto it = names.begin(); + stream << *it; + ++it; + for (it; it != names.end(); ++it) { + stream << ';' << *it; + } + return stream.str(); +}; + class TypedAsyncResponder : public TypedAsynchMessanger , public TypedMessangerListener , protected sck::async::ErrorListener , public sample::Logger { public: - TypedAsyncResponder(std::unique_ptr messanger, const std::size_t& bufferCapacity) - : TypedAsynchMessanger(std::move(messanger), bufferCapacity) + TypedAsyncResponder(std::unique_ptr messanger) + : TypedAsynchMessanger(std::move(messanger), BUFFER_CAPACITY) , Logger("TypedAsyncResponder") { this->resetErrorListener(this); this->resetListener(this); @@ -93,12 +106,10 @@ class TypedAsyncResponder private: void handle(const sample::Names& message) final { + this->log("request: ", toString(message)); sample::Names response = getResponse(message); + this->log("response: ", toString(response)); this->send(response); - //std::string recStr(message.first, message.second); - //const std::string surname = NamesMap::getSurname(recStr); - //this->log("request: ", recStr, " response: ", surname); - //this->messPtr->send({ surname.data(), surname.size() }); }; void handle(const sck::Error& error) final {}; @@ -106,7 +117,7 @@ class TypedAsyncResponder void handle(const std::exception& error) final {}; }; -TEST(AsyncTyped, ClientAsker_ServerResponder) { +TEST(Typed, Async) { const std::uint16_t port = sample::PortFactory::makePort(); const std::size_t cycles = 5; @@ -115,16 +126,16 @@ TEST(AsyncTyped, ClientAsker_ServerResponder) { if (0 == omp_get_thread_num()) { // server auto acceptedClient = sample::accept(port); - TypedAsynchMessanger asynchResponder(std::move(acceptedClient), 2500); + TypedAsyncResponder asynchResponder(std::move(acceptedClient)); open(asynchResponder); #pragma omp barrier - asynchResponder.close(); + close(asynchResponder); } else { // client std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); sample::openTcpClient(*client); - TypedMessanger messanger(std::move(client), 2500); + TypedMessanger messanger(std::move(client), BUFFER_CAPACITY); doRequests(messanger, cycles); #pragma omp barrier } diff --git a/Tests/UtilsTest/include/Common.h b/Tests/UtilsTest/include/Common.h index 00e8acaf..05c04508 100644 --- a/Tests/UtilsTest/include/Common.h +++ b/Tests/UtilsTest/include/Common.h @@ -9,9 +9,9 @@ #define SAMPLE_COMMON_H #include -#include -#include -#include +#include +#include +#include namespace sck::sample { class PortFactory { @@ -19,13 +19,17 @@ namespace sck::sample { static std::uint16_t makePort(); }; - void openSocket(SocketOpenable& socket); + template + void open(T& openable) { + openable.open(std::chrono::milliseconds(0)); + EXPECT_TRUE(openable.isOpen()); + } - void closeSocket(SocketClosable& socket); - - void openSocketDecorator(SocketDecorator& socket); - - void closeSocketDecorator(SocketDecorator& socket); + template + void close(T& closable) { + closable.close(); + EXPECT_FALSE(closable.isOpen()); + }; } #endif \ No newline at end of file diff --git a/Tests/UtilsTest/include/NamesCoding.h b/Tests/UtilsTest/include/NamesCoder.h similarity index 91% rename from Tests/UtilsTest/include/NamesCoding.h rename to Tests/UtilsTest/include/NamesCoder.h index 06661b66..2d353038 100644 --- a/Tests/UtilsTest/include/NamesCoding.h +++ b/Tests/UtilsTest/include/NamesCoder.h @@ -5,8 +5,8 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef SAMPLE_NAMESCODING_H -#define SAMPLE_NAMESCODING_H +#ifndef SAMPLE_NAMESCODER_H +#define SAMPLE_NAMESCODER_H #include #include diff --git a/Tests/UtilsTest/src/Common.cpp b/Tests/UtilsTest/src/Common.cpp index 619b01cc..b506d119 100644 --- a/Tests/UtilsTest/src/Common.cpp +++ b/Tests/UtilsTest/src/Common.cpp @@ -7,7 +7,6 @@ #include #include -#include #include namespace sck::sample { @@ -22,32 +21,4 @@ namespace sck::sample { ++port; return port; } - - template - void open(T& openable) { - openable.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(openable.isOpen()); - } - - void openSocket(SocketOpenable& socket) { - open(socket); - } - - void openSocketDecorator(SocketDecorator& socket) { - open(socket); - } - - template - void close(T& closable) { - closable.close(); - EXPECT_FALSE(closable.isOpen()); - } - - void closeSocket(SocketClosable& socket) { - close(socket); - } - - void closeSocketDecorator(SocketDecorator& socket) { - close(socket); - } } diff --git a/Tests/UtilsTest/src/NamesCoding.cpp b/Tests/UtilsTest/src/NamesCoder.cpp similarity index 98% rename from Tests/UtilsTest/src/NamesCoding.cpp rename to Tests/UtilsTest/src/NamesCoder.cpp index befceb24..45d74169 100644 --- a/Tests/UtilsTest/src/NamesCoding.cpp +++ b/Tests/UtilsTest/src/NamesCoder.cpp @@ -5,7 +5,7 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include namespace sck::sample { diff --git a/Tests/UtilsTest/src/TcpCommon.cpp b/Tests/UtilsTest/src/TcpCommon.cpp index 54a1ec33..6d046ae4 100644 --- a/Tests/UtilsTest/src/TcpCommon.cpp +++ b/Tests/UtilsTest/src/TcpCommon.cpp @@ -23,13 +23,13 @@ namespace sck::sample { TcpClientHndlrPtr accept(const std::uint16_t port) { tcp::TcpServer server(port); - openSocket(server); + open(server); return std::move(accept(server, 1).front()); } void openTcpClient(tcp::TcpClient& client) { EXPECT_FALSE(client.isOpen()); #pragma omp barrier - openSocket(client); + open(client); } } diff --git a/Tests/UtilsTest/src/UdpCommon.cpp b/Tests/UtilsTest/src/UdpCommon.cpp index af5445d8..9fd7a0f0 100644 --- a/Tests/UtilsTest/src/UdpCommon.cpp +++ b/Tests/UtilsTest/src/UdpCommon.cpp @@ -12,8 +12,8 @@ namespace sck::sample { UdpConnectionPtr connA = std::make_unique(*Ip::createLocalHost(portB) , portA); UdpConnectionPtr connB = std::make_unique(*Ip::createLocalHost(portA) , portB); - openSocket(*connA); - openSocket(*connB); + open(*connA); + open(*connB); return std::make_pair(std::move(connA), std::move(connB)); }; From c319c4335d29acc72ed48b12db22f36f2a73d9a9 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 19 May 2021 17:41:28 +0200 Subject: [PATCH 079/228] typed tests implemented --- .../TypedSocket/include/core/TypedAsyncMessanger.h | 6 ++++-- Tests/Test03-typed.cpp | 12 +++++++----- Tests/UtilsTest/src/NamesCoder.cpp | 12 +++++++----- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h index c857ca6f..4da2d8d2 100644 --- a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h +++ b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h @@ -28,6 +28,7 @@ namespace sck::typed { TypedAsynchMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) : asyncMessanger(std::move(messanger), bufferCapacity) { this->sender = &this->asyncMessanger; + this->asyncMessanger.async::MessageTalker::resetListener(this); }; inline bool isOpen() const override { return this->asyncMessanger.isOpen(); }; @@ -41,8 +42,9 @@ namespace sck::typed { protected: void handle(const std::pair& message) final { RecvT typed; - this->Decoder_::decode(std::string(message.first, message.second), typed); - this->notify(typed); + if (this->Decoder_::decode(std::string(message.first, message.second), typed)) { + this->notify(typed); + } } private: diff --git a/Tests/Test03-typed.cpp b/Tests/Test03-typed.cpp index 1d56856f..11b968e3 100644 --- a/Tests/Test03-typed.cpp +++ b/Tests/Test03-typed.cpp @@ -82,11 +82,13 @@ TEST(Typed, Sync) { std::string toString(const sample::Names& names) { std::stringstream stream; - auto it = names.begin(); - stream << *it; - ++it; - for (it; it != names.end(); ++it) { - stream << ';' << *it; + if (!names.empty()) { + auto it = names.begin(); + stream << *it; + ++it; + for (it; it != names.end(); ++it) { + stream << ';' << *it; + } } return stream.str(); }; diff --git a/Tests/UtilsTest/src/NamesCoder.cpp b/Tests/UtilsTest/src/NamesCoder.cpp index 45d74169..c8e9097e 100644 --- a/Tests/UtilsTest/src/NamesCoder.cpp +++ b/Tests/UtilsTest/src/NamesCoder.cpp @@ -37,12 +37,14 @@ namespace sck::sample { bool NamesEncoder::encode(std::string& buffer, const Names& message) const { std::stringstream stream; - auto it = message.begin(); - stream << *it; - ++it; - for (it; it != message.end(); ++it) { - stream << ';'; + if (!message.empty()) { + auto it = message.begin(); stream << *it; + ++it; + for (it; it != message.end(); ++it) { + stream << ';'; + stream << *it; + } } buffer = stream.str(); return true; From 2570a5ba1743235412d39f9a6414bbdcf5ac88a0 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 19 May 2021 17:44:42 +0200 Subject: [PATCH 080/228] tests added to pipeline --- .github/workflows/runTests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index 7888cfa1..7fa13abf 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -29,6 +29,8 @@ jobs: run: ./artifacts/bin/Test02-tcp-asynch - name: Test02-udp-asynch run: ./artifacts/bin/Test02-udp-asynch + - name: Test03-typed + run: ./artifacts/bin/Test03-typed ubuntu-clang-tests: runs-on: ubuntu-latest @@ -50,6 +52,8 @@ jobs: run: ./artifacts/bin/Test02-tcp-asynch - name: Test02-udp-asynch run: ./artifacts/bin/Test02-udp-asynch + - name: Test03-typed + run: ./artifacts/bin/Test03-typed windows-vs-tests: runs-on: ubuntu-latest @@ -70,4 +74,6 @@ jobs: - name: Test02-tcp-asynch run: ./build/artifacts/bin/Test02-tcp-asynch - name: Test02-udp-asynch - run: ./build/artifacts/bin/Test02-udp-asynch \ No newline at end of file + run: ./build/artifacts/bin/Test02-udp-asynch + - name: Test03-typed + run: ./build/artifacts/bin/Test03-typed \ No newline at end of file From 83a6207db3e9f1dc297d61fe129bd36ee28bf886 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 19 May 2021 17:51:14 +0200 Subject: [PATCH 081/228] fix compile error --- CrossSocket/SynchSocket/include/core/Messanger.h | 2 +- .../SynchSocket/include/core/components/ReceiveCapable.h | 2 +- CrossSocket/SynchSocket/src/core/Messanger.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CrossSocket/SynchSocket/include/core/Messanger.h b/CrossSocket/SynchSocket/include/core/Messanger.h index 2eaaf12c..90ffae4b 100644 --- a/CrossSocket/SynchSocket/include/core/Messanger.h +++ b/CrossSocket/SynchSocket/include/core/Messanger.h @@ -19,7 +19,7 @@ namespace sck { , virtual public ReceiveCapable , virtual public SendCapable { public: - std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) final; + std::size_t receive(const std::pair& message, const std::chrono::milliseconds& timeout) final; bool send(const std::pair& message) final; diff --git a/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h b/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h index a74afa8d..4620d254 100644 --- a/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h +++ b/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h @@ -22,7 +22,7 @@ namespace sck { * @param[in] the timeout to consider * @return the number of received bytes actually received and copied into message (can be also less than the buffer size) */ - virtual std::size_t receive(std::pair& message, const std::chrono::milliseconds& timeout) = 0; + virtual std::size_t receive(const std::pair& message, const std::chrono::milliseconds& timeout) = 0; }; } diff --git a/CrossSocket/SynchSocket/src/core/Messanger.cpp b/CrossSocket/SynchSocket/src/core/Messanger.cpp index 43f97f5f..eb1682c5 100644 --- a/CrossSocket/SynchSocket/src/core/Messanger.cpp +++ b/CrossSocket/SynchSocket/src/core/Messanger.cpp @@ -9,7 +9,7 @@ #include "../Channel.h" namespace sck { - std::size_t Messanger::receive(std::pair& message, const std::chrono::milliseconds& timeout) { + std::size_t Messanger::receive(const std::pair& message, const std::chrono::milliseconds& timeout) { std::lock_guard recvLock(this->receiveMtx); if (timeout.count() != this->actualTimeOut.count()) { //set new timeout From 6dbf11ce71256d17cc351635d4bba1416516f420 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 19 May 2021 20:05:48 +0200 Subject: [PATCH 082/228] restructure of cmake prj --- CrossSocket/AsynchSocket/CMakeLists.txt | 5 +++ CrossSocket/TypedSocket/CMakeLists.txt | 8 +++- .../include/core/TypedAsyncMessanger.h | 1 + Tests/Test03-typed.cpp | 39 +++++++++---------- Tests/UtilsTest/CMakeLists.txt | 2 +- Tests/UtilsTest/include/NamesCoder.h | 29 -------------- Utils/CMakeLists.txt | 8 +++- Utils/include/Asker.h | 2 +- Utils/include/AsyncResponder.h | 2 +- Utils/include/{NamesMap.h => Names.h} | 23 ++++++++++- Utils/include/Responder.h | 2 +- .../src/NamesCoder.cpp => Utils/src/Names.cpp | 36 +++++++++++++++-- Utils/src/NamesMap.cpp | 38 ------------------ 13 files changed, 95 insertions(+), 100 deletions(-) delete mode 100644 Tests/UtilsTest/include/NamesCoder.h rename Utils/include/{NamesMap.h => Names.h} (57%) rename Tests/UtilsTest/src/NamesCoder.cpp => Utils/src/Names.cpp (55%) delete mode 100644 Utils/src/NamesMap.cpp diff --git a/CrossSocket/AsynchSocket/CMakeLists.txt b/CrossSocket/AsynchSocket/CMakeLists.txt index f5f34354..5b8c3728 100644 --- a/CrossSocket/AsynchSocket/CMakeLists.txt +++ b/CrossSocket/AsynchSocket/CMakeLists.txt @@ -6,3 +6,8 @@ target_link_libraries(${PROJECT_SHORTNAME} PUBLIC Synch-Cross-Socket ) + +target_compile_definitions(${PROJECT_SHORTNAME} +PUBLIC + ASYNCH_ENABLED +) diff --git a/CrossSocket/TypedSocket/CMakeLists.txt b/CrossSocket/TypedSocket/CMakeLists.txt index c371f083..f40d314b 100644 --- a/CrossSocket/TypedSocket/CMakeLists.txt +++ b/CrossSocket/TypedSocket/CMakeLists.txt @@ -7,10 +7,14 @@ if(COMPILE_ASYNCH) PUBLIC Asynch-Cross-Socket ) - target_compile_definitions(${PROJECT_SHORTNAME} PUBLIC ASYNCH_ENABLED) else() target_link_libraries(${PROJECT_SHORTNAME} PUBLIC Synch-Cross-Socket ) -endif() \ No newline at end of file +endif() + +target_compile_definitions(${PROJECT_SHORTNAME} +PUBLIC + TYPED_ENABLED +) diff --git a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h index 4da2d8d2..201c2ba3 100644 --- a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h +++ b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h @@ -24,6 +24,7 @@ namespace sck::typed { , public TypedSender , public async::Talker> , protected Decoder_ { + static_assert(std::is_base_of, Decoder_>::value, "Not valid Decoder_ type"); public: TypedAsynchMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) : asyncMessanger(std::move(messanger), bufferCapacity) { diff --git a/Tests/Test03-typed.cpp b/Tests/Test03-typed.cpp index 11b968e3..fc1134c5 100644 --- a/Tests/Test03-typed.cpp +++ b/Tests/Test03-typed.cpp @@ -4,17 +4,16 @@ using namespace sck; using namespace sck::tcp; -#include +#include #include #include -#include #include using namespace sck::typed; constexpr std::size_t BUFFER_CAPACITY = 2500; -sample::Names getAllNames() { - sample::Names names; +sample::NamesCollection getAllNames() { + sample::NamesCollection names; sample::NamesMap map; for (std::size_t k = 0; k < sample::NamesMap::size(); ++k) { names.push_back(map.getCursorName()); @@ -23,8 +22,8 @@ sample::Names getAllNames() { return names; }; -sample::Names getAllSurnames() { - sample::Names surnames; +sample::NamesCollection getAllSurnames() { + sample::NamesCollection surnames; sample::NamesMap map; for (std::size_t k = 0; k < sample::NamesMap::size(); ++k) { surnames.push_back(map.getCursorSurname()); @@ -33,18 +32,18 @@ sample::Names getAllSurnames() { return surnames; }; -sample::Names getResponse(const sample::Names& request) { - sample::Names response; +sample::NamesCollection getResponse(const sample::NamesCollection& request) { + sample::NamesCollection response; for (auto it = request.begin(); it != request.end(); ++it) { response.push_back(sample::NamesMap::getSurname(*it)); } return response; }; -void doRequests(TypedMessanger& messanger, const std::size_t cycles) { +void doRequests(TypedMessanger& messanger, const std::size_t cycles) { for (std::size_t k = 0; k < cycles; ++k) { EXPECT_TRUE(messanger.send(getAllNames())); - sample::Names response; + sample::NamesCollection response; EXPECT_TRUE(messanger.receive(response, std::chrono::milliseconds(0))); EXPECT_EQ(response, getAllSurnames()); } @@ -59,10 +58,10 @@ TEST(Typed, Sync) { if (0 == omp_get_thread_num()) { // server auto acceptedClient = sample::accept(port); - TypedMessanger messanger(std::move(acceptedClient), BUFFER_CAPACITY); + TypedMessanger messanger(std::move(acceptedClient), BUFFER_CAPACITY); // exchange typed messages for (std::size_t k = 0; k < cycles; ++k) { - sample::Names request; + sample::NamesCollection request; EXPECT_TRUE(messanger.receive(request, std::chrono::milliseconds(0))); EXPECT_TRUE(messanger.send(getResponse(request))); } @@ -72,7 +71,7 @@ TEST(Typed, Sync) { // client std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); sample::openTcpClient(*client); - TypedMessanger messanger(std::move(client), BUFFER_CAPACITY); + TypedMessanger messanger(std::move(client), BUFFER_CAPACITY); doRequests(messanger, cycles); #pragma omp barrier } @@ -80,7 +79,7 @@ TEST(Typed, Sync) { } -std::string toString(const sample::Names& names) { +std::string toString(const sample::NamesCollection& names) { std::stringstream stream; if (!names.empty()) { auto it = names.begin(); @@ -94,22 +93,22 @@ std::string toString(const sample::Names& names) { }; class TypedAsyncResponder - : public TypedAsynchMessanger - , public TypedMessangerListener + : public TypedAsynchMessanger + , public TypedMessangerListener , protected sck::async::ErrorListener , public sample::Logger { public: TypedAsyncResponder(std::unique_ptr messanger) - : TypedAsynchMessanger(std::move(messanger), BUFFER_CAPACITY) + : TypedAsynchMessanger(std::move(messanger), BUFFER_CAPACITY) , Logger("TypedAsyncResponder") { this->resetErrorListener(this); this->resetListener(this); }; private: - void handle(const sample::Names& message) final { + void handle(const sample::NamesCollection& message) final { this->log("request: ", toString(message)); - sample::Names response = getResponse(message); + sample::NamesCollection response = getResponse(message); this->log("response: ", toString(response)); this->send(response); }; @@ -137,7 +136,7 @@ TEST(Typed, Async) { // client std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); sample::openTcpClient(*client); - TypedMessanger messanger(std::move(client), BUFFER_CAPACITY); + TypedMessanger messanger(std::move(client), BUFFER_CAPACITY); doRequests(messanger, cycles); #pragma omp barrier } diff --git a/Tests/UtilsTest/CMakeLists.txt b/Tests/UtilsTest/CMakeLists.txt index 356d1a18..03e6b099 100644 --- a/Tests/UtilsTest/CMakeLists.txt +++ b/Tests/UtilsTest/CMakeLists.txt @@ -6,7 +6,7 @@ find_package(OpenMP) target_link_libraries(${PROJECT_SHORTNAME} PUBLIC - Typed-Cross-Socket + Synch-Cross-Socket gtest OpenMP::OpenMP_CXX ) diff --git a/Tests/UtilsTest/include/NamesCoder.h b/Tests/UtilsTest/include/NamesCoder.h deleted file mode 100644 index 2d353038..00000000 --- a/Tests/UtilsTest/include/NamesCoder.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef SAMPLE_NAMESCODER_H -#define SAMPLE_NAMESCODER_H - -#include -#include -#include - -namespace sck::sample { - typedef std::list Names; - - class NamesDecoder : public typed::Decoder { - protected: - bool decode(const std::string& buffer, Names& message) const final; - }; - - class NamesEncoder : public typed::Encoder { - protected: - bool encode(std::string& buffer, const Names& message) const final; - }; -} - -#endif diff --git a/Utils/CMakeLists.txt b/Utils/CMakeLists.txt index 3b99c416..18b3a6cc 100644 --- a/Utils/CMakeLists.txt +++ b/Utils/CMakeLists.txt @@ -7,10 +7,16 @@ if(COMPILE_ASYNCH) PUBLIC Asynch-Cross-Socket ) - target_compile_definitions(${PROJECT_SHORTNAME} PUBLIC ASYNCH_ENABLED) else() target_link_libraries(${PROJECT_SHORTNAME} PUBLIC Synch-Cross-Socket ) endif() + +if(COMPILE_TYPED) + target_link_libraries(${PROJECT_SHORTNAME} + PUBLIC + Typed-Cross-Socket + ) +endif() diff --git a/Utils/include/Asker.h b/Utils/include/Asker.h index b495a71a..2aba71d8 100644 --- a/Utils/include/Asker.h +++ b/Utils/include/Asker.h @@ -9,7 +9,7 @@ #define SAMPLE_ASKER_H #include -#include +#include #include namespace sck::sample { diff --git a/Utils/include/AsyncResponder.h b/Utils/include/AsyncResponder.h index 5409a652..065dc65d 100644 --- a/Utils/include/AsyncResponder.h +++ b/Utils/include/AsyncResponder.h @@ -10,7 +10,7 @@ #ifdef ASYNCH_ENABLED #include -#include +#include #include namespace sck::sample { diff --git a/Utils/include/NamesMap.h b/Utils/include/Names.h similarity index 57% rename from Utils/include/NamesMap.h rename to Utils/include/Names.h index 9dde4303..7b282ef5 100644 --- a/Utils/include/NamesMap.h +++ b/Utils/include/Names.h @@ -5,11 +5,16 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef SAMPLE_NAMESMAP_H -#define SAMPLE_NAMESMAP_H +#ifndef SAMPLE_NAMES_H +#define SAMPLE_NAMES_H #include #include +#ifdef TYPED_ENABLED +#include +#include +#include +#endif namespace sck::sample { class NamesMap { @@ -31,6 +36,20 @@ namespace sck::sample { std::map::const_iterator cursor; }; + +#ifdef TYPED_ENABLED + typedef std::list NamesCollection; + + class NamesDecoder : public typed::Decoder { + protected: + bool decode(const std::string& buffer, NamesCollection& message) const final; + }; + + class NamesEncoder : public typed::Encoder { + protected: + bool encode(std::string& buffer, const NamesCollection& message) const final; + }; +#endif } #endif diff --git a/Utils/include/Responder.h b/Utils/include/Responder.h index a64e8808..ce3365cc 100644 --- a/Utils/include/Responder.h +++ b/Utils/include/Responder.h @@ -9,7 +9,7 @@ #define SAMPLE_RESPONDER_H #include -#include +#include #include #include diff --git a/Tests/UtilsTest/src/NamesCoder.cpp b/Utils/src/Names.cpp similarity index 55% rename from Tests/UtilsTest/src/NamesCoder.cpp rename to Utils/src/Names.cpp index c8e9097e..18c5343c 100644 --- a/Tests/UtilsTest/src/NamesCoder.cpp +++ b/Utils/src/Names.cpp @@ -5,11 +5,39 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include #include namespace sck::sample { - bool NamesDecoder::decode(const std::string& buffer, Names& message) const { + NamesMap::NamesMap() { + this->cursor = namesSurnames.begin(); + } + + const std::map NamesMap::namesSurnames = { + {"Luciano", "Pavarotti"}, + {"Gengis", "Khan"}, + {"Giulio", "Cesare"}, + {"Theodor", "Roosvelt"}, + {"Immanuel", "Kant"} + }; + + const std::string NamesMap::unknown = "unknown"; + + NamesMap& NamesMap::operator++() { + ++this->cursor; + if (this->cursor == namesSurnames.end()) { + this->cursor = namesSurnames.begin(); + } + return *this; + } + + const std::string& NamesMap::getSurname(const std::string& name) { + auto it = namesSurnames.find(name); + if (it == namesSurnames.end()) return unknown; + return it->second; + } + + bool NamesDecoder::decode(const std::string& buffer, NamesCollection& message) const { message.clear(); if (buffer.empty()) { return true; @@ -21,7 +49,7 @@ namespace sck::sample { } } if (posSeparator.empty()) { - message = {buffer}; + message = { buffer }; return true; } std::size_t posPrev = 0; @@ -35,7 +63,7 @@ namespace sck::sample { return true; } - bool NamesEncoder::encode(std::string& buffer, const Names& message) const { + bool NamesEncoder::encode(std::string& buffer, const NamesCollection& message) const { std::stringstream stream; if (!message.empty()) { auto it = message.begin(); diff --git a/Utils/src/NamesMap.cpp b/Utils/src/NamesMap.cpp deleted file mode 100644 index da2af734..00000000 --- a/Utils/src/NamesMap.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -namespace sck::sample { - NamesMap::NamesMap() { - this->cursor = namesSurnames.begin(); - } - - const std::map NamesMap::namesSurnames = { - {"Luciano", "Pavarotti"}, - {"Gengis", "Khan"}, - {"Giulio", "Cesare"}, - {"Theodor", "Roosvelt"}, - {"Immanuel", "Kant"} - }; - - const std::string NamesMap::unknown = "unknown"; - - NamesMap& NamesMap::operator++() { - ++this->cursor; - if (this->cursor == namesSurnames.end()) { - this->cursor = namesSurnames.begin(); - } - return *this; - } - - const std::string& NamesMap::getSurname(const std::string& name) { - auto it = namesSurnames.find(name); - if (it == namesSurnames.end()) return unknown; - return it->second; - } -} From 6b4295b51fa40dade82fd77328880e11133c5a28 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 19 May 2021 20:23:56 +0200 Subject: [PATCH 083/228] readme improved --- README.md | 5 +++-- Utils/src/Names.cpp | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 28d9acad..9fa0e23f 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,10 @@ completely platform independent way. * The core packages are contained in the ./CrossSocket folder: * SynchSocket implements the functionalities to create and use standard tcp and udp connections - * AsynchSocket contains implementation of asynchronous sockets, i.e. objects having a private thread service that can use the socket. + * AsynchSocket contains implementation of asynchronous sockets, i.e. sockets storing a private service constantly receiving new messages. It is possible to subscribe to the received messages (and react to it) by attaching a listener to the asynchronous socket. + * TypedSocket contains usefull functionalities to build typed messangers, i.e. sockets exchanging complex data structures. These functionalities can be used in combination with [Google Protocol Buffers](https://developers.google.com/protocol-buffers/docs/cpptutorial) or similar implementations like [NanoPb](https://jpa.kapsi.fi/nanopb/) - * Samples showing the library usage are contained in the ./Samples folder. ATTENTION!!! The Samples execution might be blocked the first time by your firewall. + * Samples showing the library usage are contained in the ./Samples folder. 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.) **Build from sources** diff --git a/Utils/src/Names.cpp b/Utils/src/Names.cpp index 18c5343c..6f16f212 100644 --- a/Utils/src/Names.cpp +++ b/Utils/src/Names.cpp @@ -37,6 +37,7 @@ namespace sck::sample { return it->second; } +#ifdef TYPED_ENABLED bool NamesDecoder::decode(const std::string& buffer, NamesCollection& message) const { message.clear(); if (buffer.empty()) { @@ -77,4 +78,5 @@ namespace sck::sample { buffer = stream.str(); return true; } +#endif } From 47bba6360eefb0693fdd01a0577dbf8608dfc870 Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Fri, 21 May 2021 16:25:58 +0200 Subject: [PATCH 084/228] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9fa0e23f..016319a8 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,20 @@ ![binaries_compilation](https://github.com/andreacasalino/Cross-Platform-Socket/actions/workflows/runTests.yml/badge.svg) This repository contains the minimal functionalities to create and use from **C++** both **tcp** and **udp** sockets in a -completely platform independent way. +completely platform independent way. You can decide to compile just the very core package named SynchSocket, with the raw socket implementations or compile also the more advanced ones. **Content** * The core packages are contained in the ./CrossSocket folder: - * SynchSocket implements the functionalities to create and use standard tcp and udp connections - * AsynchSocket contains implementation of asynchronous sockets, i.e. sockets storing a private service constantly receiving new messages. It is possible to subscribe to the received messages (and react to it) by attaching a listener to the asynchronous socket. - * TypedSocket contains usefull functionalities to build typed messangers, i.e. sockets exchanging complex data structures. These functionalities can be used in combination with [Google Protocol Buffers](https://developers.google.com/protocol-buffers/docs/cpptutorial) or similar implementations like [NanoPb](https://jpa.kapsi.fi/nanopb/) + * SynchSocket implements the minimal functionalities to create and use raw tcp and udp connections, sending and receiving buffer of bytes + * AsynchSocket contains implementation of asynchronous sockets, i.e. sockets storing a private service constantly receiving new messages. It is possible to subscribe to the received messages (and react to it) by attaching a listener to the asynchronous socket. You can decide to not compile this package by setting to OFF the CMake option named COMPILE_ASYNCH + * TypedSocket contains usefull functionalities to build typed messangers, i.e. sockets exchanging complex data structures. These functionalities can be used in combination with [Google Protocol Buffers](https://developers.google.com/protocol-buffers/docs/cpptutorial) or similar implementations like [NanoPb](https://jpa.kapsi.fi/nanopb/) to automatically encode and decode data. You can decide to not compile this package by setting to OFF the CMake option named COMPILE_TYPED * Samples showing the library usage are contained in the ./Samples folder. 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.) **Build from sources** -Use [CMake](https://cmake.org) to configure and compile the library and the samples. +Use [CMake](https://cmake.org) to configure and compile the library and the samples. By setting to ON the CMake option LIB_OPT, the above 3 packages will be compiled as shared libraries, otherwise as static. **Run the Samples** From 00ad331ec0e3ba82b4ec745d21099db0d2e5c0d3 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 16 Jul 2021 16:03:50 +0200 Subject: [PATCH 085/228] improving yml file --- .github/workflows/installArtifacts.yml | 132 ++++++++----------------- .github/workflows/runTests.yml | 81 +++++---------- 2 files changed, 66 insertions(+), 147 deletions(-) diff --git a/.github/workflows/installArtifacts.yml b/.github/workflows/installArtifacts.yml index afc52d7e..e53dee49 100644 --- a/.github/workflows/installArtifacts.yml +++ b/.github/workflows/installArtifacts.yml @@ -9,104 +9,50 @@ on: - master jobs: - ubuntu-build-gcc-static: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Build project - uses: nicledomaS/cmake_build_action@v1.3 - with: - cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=gcc;-DCMAKE_CXX_COMPILER=g++;-DBUILD_SAMPLES=OFF - submodule_update: ON - - name: Install artifacts - run: cmake --install ./build - - uses: actions/upload-artifact@v2 - with: - path: artifacts - name: Ubuntu_GCC_static.tar.gz + binCompile: + strategy: + # max-parallel: 4 + matrix: + name: [ubuntu-gcc-static, ubuntu-clang-static, windows-VS-static,ubuntu-gcc-shared, ubuntu-clang-shared, windows-VS-shared] + include: + - name: ubuntu-gcc-static + os: ubuntu-latest + compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -G \"Unix Makefiles\"" + lib_opt: "" + - name: ubuntu-clang-static + os: ubuntu-latest + compiler_opt: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -G \"Unix Makefiles\"" + lib_opt: "" + - name: windows-VS-static + os: windows-latest + compiler_opt: "" + lib_opt: "" + - name: ubuntu-gcc-shared + os: ubuntu-latest + compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -G \"Unix Makefiles\"" + lib_opt: "-DLIB_OPT=ON" + - name: ubuntu-clang-shared + os: ubuntu-latest + compiler_opt: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -G \"Unix Makefiles\"" + lib_opt: "-DLIB_OPT=ON" + - name: windows-VS-shared + os: windows-latest + compiler_opt: "" + lib_opt: "-DLIB_OPT=ON" - ubuntu-build-gcc-shared: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v2.0.0 - - name: Build project - uses: nicledomaS/cmake_build_action@v1.3 - with: - cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=gcc;-DCMAKE_CXX_COMPILER=g++;-DBUILD_SAMPLES=OFF;-DLIB_OPT=ON - submodule_update: ON + - name: Checkout submodules + run: git submodule update --init --recursive + - name: CMake configure + run: cmake -B./build -DCMAKE_INSTALL_PREFIX:STRING=./artifacts/ -DBUILD_SAMPLES=OFF -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_BUILD_TYPE:STRING=Release ${{ matrix.compiler_opt }} ${{ matrix.lib_opt }} + - name: build release + run: cmake --build ./build --config Release - name: Install artifacts - run: cmake --install ./build + run: cmake --install ./build --config Release - uses: actions/upload-artifact@v2 with: path: artifacts - name: Ubuntu_GCC_shared.tar.gz - - ubuntu-build-clang-static: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Build project - uses: nicledomaS/cmake_build_action@v1.3 - with: - cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=clang;-DCMAKE_CXX_COMPILER=clang++;-DBUILD_SAMPLES=OFF - submodule_update: ON - - name: Install artifacts - run: cmake --install ./build - - uses: actions/upload-artifact@v2 - with: - path: artifacts - name: Ubuntu_CLANG_static.tar.gz - - ubuntu-build-clang-shared: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Build project - uses: nicledomaS/cmake_build_action@v1.3 - with: - cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=clang;-DCMAKE_CXX_COMPILER=clang++;-DBUILD_SAMPLES=OFF;-DLIB_OPT=ON - submodule_update: ON - - name: Install artifacts - run: cmake --install ./build - - uses: actions/upload-artifact@v2 - with: - path: artifacts - name: Ubuntu_CLANG_shared.tar.gz - - windows-build-vs-static: - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Build project - uses: nicledomaS/cmake_build_action@v1.3 - with: - cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./build/artifacts/;-DBUILD_SAMPLES=OFF - submodule_update: ON - - name: Install artifacts - run: cmake --install ./build - - uses: actions/upload-artifact@v2 - with: - path: build/artifacts - name: Win32_VisualStudio_static.zip - - windows-build-vs-shared: - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Build project - uses: nicledomaS/cmake_build_action@v1.3 - with: - cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./build/artifacts/;-DBUILD_SAMPLES=OFF;-DLIB_OPT=ON - submodule_update: ON - - name: Install artifacts - run: cmake --install ./build - - uses: actions/upload-artifact@v2 - with: - path: build/artifacts - name: Win32_VisualStudio_shared.zip \ No newline at end of file + name: ${{ matrix.name }} diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index 7fa13abf..c48060b5 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -9,18 +9,33 @@ on: - master jobs: - ubuntu-gcc-tests: - runs-on: ubuntu-latest + unitTests: + strategy: + matrix: + name: [ubuntu-gcc, ubuntu-clang, windows-VS] + include: + - name: ubuntu-gcc + os: ubuntu-latest + compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -G \"Unix Makefiles\"" + - name: ubuntu-clang + os: ubuntu-latest + compiler_opt: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -G \"Unix Makefiles\"" + - name: windows-VS + os: windows-latest + compiler_opt: "" + + runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v2.0.0 - - name: Build project - uses: nicledomaS/cmake_build_action@v1.3 - with: - cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=gcc;-DCMAKE_CXX_COMPILER=g++;-DBUILD_SAMPLES=OFF;-DBUILD_TESTS=ON - submodule_update: ON - - name: Install artifacts - run: cmake --install ./build + - name: Checkout submodules + run: git submodule update --init --recursive + - name: CMake configure + run: cmake -B./build -DCMAKE_INSTALL_PREFIX:STRING=./artifacts/ -DBUILD_SAMPLES=OFF -DBUILD_TESTS=ON -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_BUILD_TYPE:STRING=Release ${{ matrix.compiler_opt }} + - name: Build + run: cmake --build ./build --config Release + - name: Install + run: cmake --install ./build --config Release - name: Test01-tcp-synch run: ./artifacts/bin/Test01-tcp-synch - name: Test01-udp-synch @@ -31,49 +46,7 @@ jobs: run: ./artifacts/bin/Test02-udp-asynch - name: Test03-typed run: ./artifacts/bin/Test03-typed - - ubuntu-clang-tests: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Build project - uses: nicledomaS/cmake_build_action@v1.3 + - uses: actions/upload-artifact@v2 with: - cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./artifacts/;-DCMAKE_C_COMPILER=clang;-DCMAKE_CXX_COMPILER=clang++;-DBUILD_SAMPLES=OFF;-DBUILD_TESTS=ON - submodule_update: ON - - name: Install artifacts - run: cmake --install ./build - - name: Test01-tcp-synch - run: ./artifacts/bin/Test01-tcp-synch - - name: Test01-udp-synch - run: ./artifacts/bin/Test01-udp-synch - - name: Test02-tcp-asynch - run: ./artifacts/bin/Test02-tcp-asynch - - name: Test02-udp-asynch - run: ./artifacts/bin/Test02-udp-asynch - - name: Test03-typed - run: ./artifacts/bin/Test03-typed - - windows-vs-tests: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Build project - uses: nicledomaS/cmake_build_action@v1.3 - with: - cmake_args: -B./build;-DCMAKE_INSTALL_PREFIX:STRING=./build/artifacts/;-DBUILD_SAMPLES=OFF;-DBUILD_TESTS=ON - submodule_update: ON - - name: Install artifacts - run: cmake --install ./build - - name: Test01-tcp-synch - run: ./build/artifacts/bin/Test01-tcp-synch - - name: Test01-udp-synch - run: ./build/artifacts/bin/Test01-udp-synch - - name: Test02-tcp-asynch - run: ./build/artifacts/bin/Test02-tcp-asynch - - name: Test02-udp-asynch - run: ./build/artifacts/bin/Test02-udp-asynch - - name: Test03-typed - run: ./build/artifacts/bin/Test03-typed \ No newline at end of file + path: artifacts + name: ${{ matrix.name }} From 1942a9a99c5eab58811a4438f5ba0f39d231bef2 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 22 Nov 2021 21:16:40 +0000 Subject: [PATCH 086/228] fetch gtest --- .gitmodules | 3 --- Tests/CMakeLists.txt | 8 +++++++- 2 files changed, 7 insertions(+), 4 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 0a6a6211..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "Tests/GoogleTest"] - path = Tests/GoogleTest - url = https://github.com/google/googletest diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 2497aec2..31b3a0b1 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -1,7 +1,13 @@ if (WIN32) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) endif (WIN32) -add_subdirectory(GoogleTest/) +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG main +) +FetchContent_MakeAvailable(googletest) add_subdirectory(UtilsTest) From 81c41a0f5ece78391170c1f108dc69863177282b Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 22 Nov 2021 21:17:43 +0000 Subject: [PATCH 087/228] fecth gtest --- Tests/GoogleTest | 1 - 1 file changed, 1 deletion(-) delete mode 160000 Tests/GoogleTest diff --git a/Tests/GoogleTest b/Tests/GoogleTest deleted file mode 160000 index f5e592d8..00000000 --- a/Tests/GoogleTest +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f5e592d8ee5ffb1d9af5be7f715ce3576b8bf9c4 From 7073b3add1b5e51e2f07010524b4db89b5cd7ad5 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 6 May 2022 16:40:00 +0100 Subject: [PATCH 088/228] refactoring --- CMakeLists.txt | 26 ++-- TODO | 3 + src/CMakeLists.txt | 6 + src/SynchSocket/CMakeLists.txt | 10 ++ src/SynchSocket/header/SynchSocket/Address.h | 57 ++++++++ src/SynchSocket/header/SynchSocket/Error.h | 42 ++++++ src/SynchSocket/src/Address.cpp | 64 +++++++++ src/SynchSocket/src/Commons.cpp | 135 +++++++++++++++++++ src/SynchSocket/src/Commons.h | 91 +++++++++++++ 9 files changed, 421 insertions(+), 13 deletions(-) create mode 100644 TODO create mode 100644 src/CMakeLists.txt create mode 100644 src/SynchSocket/CMakeLists.txt create mode 100644 src/SynchSocket/header/SynchSocket/Address.h create mode 100644 src/SynchSocket/header/SynchSocket/Error.h create mode 100644 src/SynchSocket/src/Address.cpp create mode 100644 src/SynchSocket/src/Commons.cpp create mode 100644 src/SynchSocket/src/Commons.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b1788671..9285aa18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) option(LIB_OPT "Compile shared libraries (ON) or static (OFF)" OFF) -option(BUILD_SAMPLES "Build the samples showing how to use EFG" ON) -option(BUILD_TESTS "" OFF) +option(BUILD_MinimalCppSocket_SAMPLES "Build the samples showing how to use Minimal Cpp Socket" ON) +option(BUILD_MinimalCppSocket_TESTS "" OFF) # set macro-directory and find scripts set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") @@ -20,17 +20,17 @@ include(MakeSample) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") -project(CrossSocket) -add_subdirectory(CrossSocket) +project(MinimalCppSocket) +add_subdirectory(src) -if(BUILD_SAMPLES OR BUILD_TESTS) - add_subdirectory(Utils) -endif() +# if(BUILD_SAMPLES OR BUILD_TESTS) +# add_subdirectory(Utils) +# endif() -if(BUILD_SAMPLES) - add_subdirectory(Samples) -endif() +# if(BUILD_SAMPLES) +# add_subdirectory(Samples) +# endif() -if(BUILD_TESTS) - add_subdirectory(Tests) -endif() +# if(BUILD_TESTS) +# add_subdirectory(Tests) +# endif() diff --git a/TODO b/TODO new file mode 100644 index 00000000..ed25d1c1 --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +use inet_pton also in windows + + replace all std::uint16_t with Port diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 00000000..5d7465c8 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(SynchSocket) + +# option(COMPILE_ASYNCH "Compile the asynchronous cross socket package" ON) +# if(COMPILE_ASYNCH) +# add_subdirectory(AsynchSocket) +# endif() diff --git a/src/SynchSocket/CMakeLists.txt b/src/SynchSocket/CMakeLists.txt new file mode 100644 index 00000000..6834ca17 --- /dev/null +++ b/src/SynchSocket/CMakeLists.txt @@ -0,0 +1,10 @@ +set(PROJECT_SHORTNAME "SynchSocket") + +MakeLibrary(${PROJECT_SHORTNAME} header) + +if(WIN32) + target_link_libraries(${PROJECT_SHORTNAME} PRIVATE wsock32 ws2_32) +endif() + +find_package(Threads) +target_link_libraries(${PROJECT_SHORTNAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) diff --git a/src/SynchSocket/header/SynchSocket/Address.h b/src/SynchSocket/header/SynchSocket/Address.h new file mode 100644 index 00000000..d0e52eac --- /dev/null +++ b/src/SynchSocket/header/SynchSocket/Address.h @@ -0,0 +1,57 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include + +namespace MinCppSock { +/** + * @brief The address family. Refer to + * https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzab6/address.htm + */ +enum AddressFamily { IP_V4, IP_V6 }; + +using Port = std::uint16_t; + +/** + * @brief representation of a network ip address + */ +class Address { +public: + /** + * @brief Internally the protocol Family is deduced according to the hostIp + * content. + * @return nullptr if the host is invalid, otherwise a smart pointer storing a + * usable ip + */ + Address(const std::string &hostIp, const Port &port); + + /** + * @return an ipv4 or ipv6 with localhost as host and the passed port + */ + static Address + makeLocalHost(const Port &port, + const AddressFamily &family = AddressFamily::IP_V4); + + const std::string &getHost() const { return this->host; }; + const Port &getPort() const { return this->port; }; + const AddressFamily &getFamily() const { return this->family; }; + + bool operator==(const Address &o) const; + +private: + Address() = default; + + std::string host; + Port port; + AddressFamily family; +}; + +std::string to_string(const Address &subject); +} // namespace MinCppSock diff --git a/src/SynchSocket/header/SynchSocket/Error.h b/src/SynchSocket/header/SynchSocket/Error.h new file mode 100644 index 00000000..2cd7c4e4 --- /dev/null +++ b/src/SynchSocket/header/SynchSocket/Error.h @@ -0,0 +1,42 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include + +namespace MinCppSock { +/** + * @brief A runtime error that can be raised when using any object in sck:: + */ +class Error : public std::runtime_error { +public: + Error(const std::string &what) : std::runtime_error(what){}; + + template Error(Args... args) : Error(merge(args...)) {} + +private: + template static std::string merge(Args... args) { + std::stringstream stream; + merge(stream, args...); + return stream.str(); + }; + + template + static void merge(std::stringstream &stream, const T ¤t, + Args... remaining) { + stream << current; + merge(stream, remaining...); + }; + + template + static void merge(std::stringstream &stream, const T &back) { + stream << back; + }; +}; +} // namespace MinCppSock diff --git a/src/SynchSocket/src/Address.cpp b/src/SynchSocket/src/Address.cpp new file mode 100644 index 00000000..5358034c --- /dev/null +++ b/src/SynchSocket/src/Address.cpp @@ -0,0 +1,64 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +#include "Commons.h" + +#include + +namespace MinCppSock { +Address::Address(const std::string &hostIp, const Port &port) { + this->host = hostIp; + this->port = port; + + if (std::nullopt != makeSocketIp4(host, port)) { + this->family = AddressFamily::IP_V4; + return; + } + + if (std::nullopt != makeSocketIp6(host, port)) { + this->family = AddressFamily::IP_V6; + return; + } + + throw Error{hostIp, " is a not recognized address"}; +} + +namespace { +static const std::string LOCALHOST_IPv4 = "127.0.0.1"; +static const std::string LOCALHOST_IPv6 = "::1"; +} // namespace + +Address Address::makeLocalHost(const std::uint16_t &port, + const AddressFamily &family) { + Address result; + result.port = port; + result.family = family; + switch (family) { + case AddressFamily::IP_V4: + result.host = LOCALHOST_IPv4; + return result; + case AddressFamily::IP_V6: + result.host = LOCALHOST_IPv6; + return result; + } + throw Error{"unrecognized family address"}; +} + +bool Address::operator==(const Address &o) const { + return (this->host == o.host) && (this->port == o.port) && + (this->family == o.family); +} + +std::string to_string(const Address &subject) { + std::stringstream stream; + stream << subject.getHost() << ':' << subject.getPort(); + return stream.str(); +} +} // namespace MinCppSock diff --git a/src/SynchSocket/src/Commons.cpp b/src/SynchSocket/src/Commons.cpp new file mode 100644 index 00000000..5701e648 --- /dev/null +++ b/src/SynchSocket/src/Commons.cpp @@ -0,0 +1,135 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include "Commons.h" +#include + +namespace MinCppSock { +int getLastErrorCode() { +#ifdef _WIN32 + return WSAGetLastError(); +#else + return static_cast(errno); +#endif +} + +void throwWithLastErrorCode(const std::string &what) { + throw Error(what, " , error code: ", getLastErrorCode()); +} + +std::optional makeSocketIp4(const std::string &raw_address, + const Port &port) { + std::optional result; + auto &result_ref = result.emplace(); + // set everything to 0 first + ::memset(&result_ref, 0, sizeof(SocketIp4)); + result_ref.sin_family = AF_INET; + result_ref.sin_port = htons(port); + + // try address conversion +#if !defined(_WIN32) + in_addr ia; + if (1 == ::inet_pton(AF_INET, raw_address.c_str(), &ia)) { + result_ref.sin_addr.s_addr = ia.s_addr; + return result; + } +#endif + + addrinfo *res, hints = addrinfo{}; + hints.ai_family = AF_INET; + hints.ai_socktype = 0; + hints.ai_protocol = 0; + + int gai_err = ::getaddrinfo(raw_address.c_str(), NULL, &hints, &res); + +#if !defined(_WIN32) + if (gai_err == EAI_SYSTEM) { + return std::nullopt; + } +#endif + if (gai_err != 0) { + return std::nullopt; + } + + auto ipv4 = reinterpret_cast(res->ai_addr); + result_ref.sin_addr.s_addr = ipv4->sin_addr.s_addr; + ::freeaddrinfo(res); + return result; +} + +std::optional makeSocketIp6(const std::string &raw_address, + const Port &port) { + std::optional result; + auto &result_ref = result.emplace(); + // set everything to 0 first + ::memset(&result_ref, 0, sizeof(SocketIp6)); + result_ref.sin6_family = AF_INET6; + result_ref.sin6_flowinfo = 0; + result_ref.sin6_port = htons(port); + + // try address conversion +#if !defined(_WIN32) + in6_addr ia; + if (1 == ::inet_pton(AF_INET6, raw_address.c_str(), &ia)) { + result_ref.sin6_addr = ia; + return result; + } +#endif + + addrinfo *res, hints = addrinfo{}; + hints.ai_family = AF_INET6; + hints.ai_socktype = 0; + hints.ai_protocol = 0; + + int gai_err = ::getaddrinfo(raw_address.c_str(), NULL, &hints, &res); + +#if !defined(_WIN32) + if (gai_err == EAI_SYSTEM) { + return std::nullopt; + } +#endif + if (gai_err != 0) { + return std::nullopt; + } + + auto ipv6 = reinterpret_cast(res->ai_addr); + result_ref.sin6_addr = ipv6->sin6_addr; + ::freeaddrinfo(res); + return result; +} + +Address make_address(const SocketIp &address) { + // refer to + // https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu + std::string ip; + Port port; + if (AF_INET == address.sa_family) { + // ipv4 address + // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to + // an ip that has no sense + ip = std::string( + ::inet_ntoa(reinterpret_cast(&address)->sin_addr)); + port = ntohs(reinterpret_cast(&address)->sin_port); + } else { + // ipv6 address + char temp[INET6_ADDRSTRLEN]; // this is the longest one + // refer to + // https://www.gnu.org/software/libc/manual/html_node/Host-Address-Functions.html + ::memset(temp, 0, INET6_ADDRSTRLEN); + ::inet_ntop(address.sa_family, &address, temp, INET6_ADDRSTRLEN); + ip = std::string(temp, INET6_ADDRSTRLEN); + port = ntohs(reinterpret_cast(&address)->sin6_port); + } + return Address{ip, port}; +} +} // namespace MinCppSock \ No newline at end of file diff --git a/src/SynchSocket/src/Commons.h b/src/SynchSocket/src/Commons.h new file mode 100644 index 00000000..5b60e36c --- /dev/null +++ b/src/SynchSocket/src/Commons.h @@ -0,0 +1,91 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include + +#include + +#ifdef _WIN32 +#include +#include +#include +#include +#define SCK_INVALID_SOCKET INVALID_SOCKET +#define SCK_SOCKET_ERROR SOCKET_ERROR +#else +#include +#include +#include //memset +#include +#include //close +#define SCK_INVALID_SOCKET -1 +#define SCK_SOCKET_ERROR -1 +#endif + +namespace MinCppSock { +/** + * socket handle + */ +#ifdef _WIN32 +using SocketHandler = SOCKET; +#else +using SocketHandler = int; +#endif +/** + * @brief representation of a generic socket address + */ +#ifdef _WIN32 +using SocketIp = SOCKADDR; +#else +using SocketIp = sockaddr; +#endif +/** + * @brief representation of an ipv4 socket address + */ +#ifdef _WIN32 +using SocketIp4 = SOCKADDR_IN; +#else +using SocketIp4 = sockaddr_in; +#endif +/** + * @brief representation of an ipv6 socket address + */ +#ifdef _WIN32 +using SocketIp6 = SOCKADDR_IN6; +#else +using SocketIp6 = sockaddr_in6; +#endif + +/** + * @brief returns the last error code raised by the socket API + */ +int getLastErrorCode(); + +void throwWithLastErrorCode(const std::string &what); + +/** + * @brief checks the address syntax and in case + * it's valid as an ipv4, creates the socket API representation + * of the address + */ +std::optional makeSocketIp4(const std::string &raw_address, + const Port &port); +/** + * @brief checks the address syntax and in case + * it's valid as an ipv6, creates the socket API representation + * of the address + */ +std::optional makeSocketIp6(const std::string &raw_address, + const Port &port); +/** + * @brief Convert a SocketAddress_t into an Address, internally + * deducing the family. + */ +Address make_address(const SocketIp &address); +} // namespace MinCppSock From f2bd81c083c6820a4dde5ad82c643285b92a44b7 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 6 May 2022 17:36:35 +0100 Subject: [PATCH 089/228] refactoring --- TODO | 4 +- .../header/SynchSocket/detail/Socket.h | 54 +++++++++++ src/SynchSocket/src/Commons.cpp | 92 +++++++++++++++++++ src/SynchSocket/src/Commons.h | 77 ++++++++++++++-- src/SynchSocket/src/detail/Socket.cpp | 0 5 files changed, 218 insertions(+), 9 deletions(-) create mode 100644 src/SynchSocket/header/SynchSocket/detail/Socket.h create mode 100644 src/SynchSocket/src/detail/Socket.cpp diff --git a/TODO b/TODO index ed25d1c1..f8ca59f2 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,5 @@ use inet_pton also in windows - replace all std::uint16_t with Port +replace all std::uint16_t with Port + +check if there is a way to check if an external passed socket was open or not in SocketHandler::open(const SocketHandlerType &) diff --git a/src/SynchSocket/header/SynchSocket/detail/Socket.h b/src/SynchSocket/header/SynchSocket/detail/Socket.h new file mode 100644 index 00000000..67f0d423 --- /dev/null +++ b/src/SynchSocket/header/SynchSocket/detail/Socket.h @@ -0,0 +1,54 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include + +namespace MinCppSock { +enum Protocol { UDP, TCP }; + +// class ProtocolAware { +// public: +// const Protocol &getProtocol() const { return protocol; }; + +// protected: +// ProtocolAware(const Protocol &protocol); + +// private: +// const Protocol protocol; +// }; + +using Buffer = std::string; + +class SocketHandler; +class SocketHandlerAware { +public: + virtual ~SocketHandlerAware(); + +protected: + SocketHandlerAware(); + + const SocketHandler &socketHandler() const; + SocketHandler &socketHandler(); + +private: + std::unique_ptr channel; +}; + +class Closable { +public: + virtual ~Closable() = default; + + /** + * @brief close the object + */ + virtual void close() = 0; +}; + +} // namespace MinCppSock diff --git a/src/SynchSocket/src/Commons.cpp b/src/SynchSocket/src/Commons.cpp index 5701e648..60a5a717 100644 --- a/src/SynchSocket/src/Commons.cpp +++ b/src/SynchSocket/src/Commons.cpp @@ -132,4 +132,96 @@ Address make_address(const SocketIp &address) { } return Address{ip, port}; } + +#ifdef _WIN32 +std::size_t Channel::SocketHandlerFactory::handlerCounter = 0; +std::mutex Channel::SocketHandlerFactory::handlerCounterMtx; + +void Channel::SocketHandlerFactory::beforeOpen() { + std::lock_guard hndLck(handlerCounterMtx); + ++handlerCounter; + if (1 == handlerCounter) { + // first socket opened + WSADATA wsa; + WSAStartup(MAKEWORD(2, 0), &wsa); + } +} + +void Channel::SocketHandlerFactory::afterClose() { + std::lock_guard hndLck(handlerCounterMtx); + --handlerCounter; + if (0 == handlerCounter) { + // last socket closed + WSACleanup(); + } +} +#endif + +SocketHandler::~SocketHandler() { close(); } + +void SocketHandler::reset(const SocketHandlerType &hndl) { + if (socket_id != SCK_INVALID_SOCKET) { + close(); + } + + this->socket_id = hndl; +} + +namespace { +int to_int(const AddressFamily &family) { + switch (family) { + case AddressFamily::IP_V4: + return static_cast(AF_INET); + case AddressFamily::IP_V6: + return static_cast(AF_INET6); + } + throw Error("unknown address family type"); +} +} // namespace + +void SocketHandler::reset(const Protocol &type, const AddressFamily &family) { + if (socket_id != SCK_INVALID_SOCKET) { + close(); + } + +#ifdef _WIN32 + SocketHandlerFactory::beforeOpen(); +#endif + + switch (type) { + case Protocol::TCP: + this->socket_id = ::socket(to_int(family), SOCK_STREAM, 0); + if (this->socket_id == SCK_INVALID_SOCKET) { + this->close(); + throwWithLastErrorCode("Stream socket could not be created"); + } + break; + case Protocol::UDP: + this->socket_id = ::socket(to_int(family), SOCK_DGRAM, 0); + if (this->socket_id == SCK_INVALID_SOCKET) { + this->close(); + throwWithLastErrorCode("DataGram socket could not be created"); + } + break; + default: + throw Error("unknown protocol type"); + } +} + +void SocketHandler::close() { + if (socket_id == SCK_INVALID_SOCKET) { + return; + } +#ifdef _WIN32 + shutdown(this->socket_id, 2); + closesocket(this->socket_id); +#else + ::shutdown(this->socket_id, SHUT_RDWR); + ::close(this->socket_id); +#endif + this->socket_id = SCK_INVALID_SOCKET; +#ifdef _WIN32 + SocketHandlerFactory::afterClose(); +#endif +} } // namespace MinCppSock \ No newline at end of file diff --git a/src/SynchSocket/src/Commons.h b/src/SynchSocket/src/Commons.h index 5b60e36c..e90696b6 100644 --- a/src/SynchSocket/src/Commons.h +++ b/src/SynchSocket/src/Commons.h @@ -8,7 +8,9 @@ #pragma once #include +#include +#include #include #ifdef _WIN32 @@ -29,14 +31,6 @@ #endif namespace MinCppSock { -/** - * socket handle - */ -#ifdef _WIN32 -using SocketHandler = SOCKET; -#else -using SocketHandler = int; -#endif /** * @brief representation of a generic socket address */ @@ -88,4 +82,71 @@ std::optional makeSocketIp6(const std::string &raw_address, * deducing the family. */ Address make_address(const SocketIp &address); + +/** + * socket handle + */ +#ifdef _WIN32 +using SocketHandlerType = SOCKET; +#else +using SocketHandlerType = int; +#endif + +/** + * An object storing a socket API handler and containing the minimal + * functionalities for interacting with it. + */ +class SocketHandler { +public: + SocketHandler(const SocketHandler &) = delete; + SocketHandler &operator=(const SocketHandler &) = delete; + + /** + * @brief a closed socket is created + */ + SocketHandler() = default; + + ~SocketHandler(); + + bool empty() const { return socket_id == SCK_INVALID_SOCKET; } + + /** + * @brief internally creates a new socket + */ + void reset(const Protocol &type, const AddressFamily &family); + + /** + * @brief the passed handler should be already created externally + * by the socket api + */ + void reset(const SocketHandlerType &hndl); + + /** + * @brief close and shutdown the current socket + */ + void close(); + + const SocketHandlerType &access() const { return socket_id; }; + +private: + SocketHandlerType socket_id = SCK_INVALID_SOCKET; + +#ifdef _WIN32 + class SocketHandlerFactory { + public: + /** + * @brief If we are about to open the first socket, WSAStartup() is invoked + */ + static void beforeOpen(); + /** + * @brief If we are closing the last socket, WSACleanup() is invoked + */ + static void afterClose(); + + private: + static std::mutex handlerCounterMtx; + static std::size_t handlerCounter; + }; +#endif +}; } // namespace MinCppSock diff --git a/src/SynchSocket/src/detail/Socket.cpp b/src/SynchSocket/src/detail/Socket.cpp new file mode 100644 index 00000000..e69de29b From de1f224607fc1af66aacb08af675f7daa5db435d Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 6 May 2022 22:43:42 +0100 Subject: [PATCH 090/228] refactoring --- TODO | 4 ++ src/CMakeLists.txt | 17 +++--- src/SynchSocket/CMakeLists.txt | 10 ---- .../header/SynchSocket/detail/Socket.h | 54 ------------------- .../MinimalSocket}/Address.h | 13 ++++- .../MinimalSocket}/Error.h | 7 +-- .../MinimalSocket/core/Receiver.h} | 0 src/header/MinimalSocket/core/Sender.h | 30 +++++++++++ src/header/MinimalSocket/core/Socket.h | 35 ++++++++++++ src/{SynchSocket => }/src/Address.cpp | 22 ++++++-- src/{SynchSocket => }/src/Commons.cpp | 19 +++---- src/{SynchSocket => }/src/Commons.h | 31 +++++------ src/src/core/Receiver.cpp | 0 src/src/core/Sender.cpp | 0 src/src/core/Socket.cpp | 0 15 files changed, 137 insertions(+), 105 deletions(-) delete mode 100644 src/SynchSocket/CMakeLists.txt delete mode 100644 src/SynchSocket/header/SynchSocket/detail/Socket.h rename src/{SynchSocket/header/SynchSocket => header/MinimalSocket}/Address.h (83%) rename src/{SynchSocket/header/SynchSocket => header/MinimalSocket}/Error.h (86%) rename src/{SynchSocket/src/detail/Socket.cpp => header/MinimalSocket/core/Receiver.h} (100%) create mode 100644 src/header/MinimalSocket/core/Sender.h create mode 100644 src/header/MinimalSocket/core/Socket.h rename src/{SynchSocket => }/src/Address.cpp (75%) rename src/{SynchSocket => }/src/Commons.cpp (93%) rename src/{SynchSocket => }/src/Commons.h (82%) create mode 100644 src/src/core/Receiver.cpp create mode 100644 src/src/core/Sender.cpp create mode 100644 src/src/core/Socket.cpp diff --git a/TODO b/TODO index f8ca59f2..edf62603 100644 --- a/TODO +++ b/TODO @@ -3,3 +3,7 @@ use inet_pton also in windows replace all std::uint16_t with Port check if there is a way to check if an external passed socket was open or not in SocketHandler::open(const SocketHandlerType &) + +support for bluetooth connection + +variant per vari stati di udp connection diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5d7465c8..430c68ab 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,11 @@ -add_subdirectory(SynchSocket) - -# option(COMPILE_ASYNCH "Compile the asynchronous cross socket package" ON) -# if(COMPILE_ASYNCH) -# add_subdirectory(AsynchSocket) -# endif() +set(PROJECT_SHORTNAME "MinimalSocket") + +MakeLibrary(${PROJECT_SHORTNAME} header) + +if(WIN32) + target_link_libraries(${PROJECT_SHORTNAME} PRIVATE wsock32 ws2_32) +endif() + +# find_package(Threads) +# target_link_libraries(${PROJECT_SHORTNAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) + diff --git a/src/SynchSocket/CMakeLists.txt b/src/SynchSocket/CMakeLists.txt deleted file mode 100644 index 6834ca17..00000000 --- a/src/SynchSocket/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -set(PROJECT_SHORTNAME "SynchSocket") - -MakeLibrary(${PROJECT_SHORTNAME} header) - -if(WIN32) - target_link_libraries(${PROJECT_SHORTNAME} PRIVATE wsock32 ws2_32) -endif() - -find_package(Threads) -target_link_libraries(${PROJECT_SHORTNAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) diff --git a/src/SynchSocket/header/SynchSocket/detail/Socket.h b/src/SynchSocket/header/SynchSocket/detail/Socket.h deleted file mode 100644 index 67f0d423..00000000 --- a/src/SynchSocket/header/SynchSocket/detail/Socket.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#pragma once - -#include -#include - -namespace MinCppSock { -enum Protocol { UDP, TCP }; - -// class ProtocolAware { -// public: -// const Protocol &getProtocol() const { return protocol; }; - -// protected: -// ProtocolAware(const Protocol &protocol); - -// private: -// const Protocol protocol; -// }; - -using Buffer = std::string; - -class SocketHandler; -class SocketHandlerAware { -public: - virtual ~SocketHandlerAware(); - -protected: - SocketHandlerAware(); - - const SocketHandler &socketHandler() const; - SocketHandler &socketHandler(); - -private: - std::unique_ptr channel; -}; - -class Closable { -public: - virtual ~Closable() = default; - - /** - * @brief close the object - */ - virtual void close() = 0; -}; - -} // namespace MinCppSock diff --git a/src/SynchSocket/header/SynchSocket/Address.h b/src/header/MinimalSocket/Address.h similarity index 83% rename from src/SynchSocket/header/SynchSocket/Address.h rename to src/header/MinimalSocket/Address.h index d0e52eac..46ec4439 100644 --- a/src/SynchSocket/header/SynchSocket/Address.h +++ b/src/header/MinimalSocket/Address.h @@ -10,7 +10,7 @@ #include #include -namespace MinCppSock { +namespace MinimalSocket { /** * @brief The address family. Refer to * https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzab6/address.htm @@ -54,4 +54,13 @@ class Address { }; std::string to_string(const Address &subject); -} // namespace MinCppSock + +/** + * @return nullopt in case the address in invalid + */ +std::optional +deduceAddressFamily(const std::string &host_address); + +bool isValidAddress(const std::string &host_address); + +} // namespace MinimalSocket diff --git a/src/SynchSocket/header/SynchSocket/Error.h b/src/header/MinimalSocket/Error.h similarity index 86% rename from src/SynchSocket/header/SynchSocket/Error.h rename to src/header/MinimalSocket/Error.h index 2cd7c4e4..a1eb0637 100644 --- a/src/SynchSocket/header/SynchSocket/Error.h +++ b/src/header/MinimalSocket/Error.h @@ -10,10 +10,7 @@ #include #include -namespace MinCppSock { -/** - * @brief A runtime error that can be raised when using any object in sck:: - */ +namespace MinimalSocket { class Error : public std::runtime_error { public: Error(const std::string &what) : std::runtime_error(what){}; @@ -39,4 +36,4 @@ class Error : public std::runtime_error { stream << back; }; }; -} // namespace MinCppSock +} // namespace MinimalSocket diff --git a/src/SynchSocket/src/detail/Socket.cpp b/src/header/MinimalSocket/core/Receiver.h similarity index 100% rename from src/SynchSocket/src/detail/Socket.cpp rename to src/header/MinimalSocket/core/Receiver.h diff --git a/src/header/MinimalSocket/core/Sender.h b/src/header/MinimalSocket/core/Sender.h new file mode 100644 index 00000000..4ec3fa76 --- /dev/null +++ b/src/header/MinimalSocket/core/Sender.h @@ -0,0 +1,30 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include + +namespace MinimalSocket { +class Sender : public virtual Socket { +public: + /** + * @return true if the message was completely sent + * @param[in] the message to send + */ + bool send(const Buffer &message); +}; + +class SenderTo : public virtual Socket { +public: + /** + * @return true if the message was completely sent + * @param[in] the message to send + */ + bool sendTo(const Buffer &message, const Address &recipient); +}; +} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h new file mode 100644 index 00000000..4e5987ef --- /dev/null +++ b/src/header/MinimalSocket/core/Socket.h @@ -0,0 +1,35 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include + +namespace MinimalSocket { +using Buffer = std::string; + +class SocketIdWrapper; +class Socket { +public: + virtual ~Socket(); + + Socket(const Socket &) = delete; + Socket &operator=(const Socket &) = delete; + +protected: + Socket(); + + void stealIDWrapper(Socket &o); + + const SocketIdWrapper &getIDWrapper() const { return *socket_id_wrapper; }; + SocketIdWrapper &getIDWrapper() { return *socket_id_wrapper; }; + +private: + std::unique_ptr socket_id_wrapper; +}; +} // namespace MinimalSocket diff --git a/src/SynchSocket/src/Address.cpp b/src/src/Address.cpp similarity index 75% rename from src/SynchSocket/src/Address.cpp rename to src/src/Address.cpp index 5358034c..37f0c037 100644 --- a/src/SynchSocket/src/Address.cpp +++ b/src/src/Address.cpp @@ -5,14 +5,14 @@ * report any bug to andrecasa91@gmail.com. **/ -#include -#include +#include +#include #include "Commons.h" #include -namespace MinCppSock { +namespace MinimalSocket { Address::Address(const std::string &hostIp, const Port &port) { this->host = hostIp; this->port = port; @@ -61,4 +61,18 @@ std::string to_string(const Address &subject) { stream << subject.getHost() << ':' << subject.getPort(); return stream.str(); } -} // namespace MinCppSock + +std::optional +deduceAddressFamily(const std::string &host_address) { + try { + Address temp(host_address, 0); + return temp.getFamily(); + } catch (...) { + } + return std::nullopt; +} + +bool isValidAddress(const std::string &host_address) { + return deduceAddressFamily(host_address) != std::nullopt; +} +} // namespace MinimalSocket diff --git a/src/SynchSocket/src/Commons.cpp b/src/src/Commons.cpp similarity index 93% rename from src/SynchSocket/src/Commons.cpp rename to src/src/Commons.cpp index 60a5a717..f8b962e2 100644 --- a/src/SynchSocket/src/Commons.cpp +++ b/src/src/Commons.cpp @@ -12,9 +12,9 @@ #endif #include "Commons.h" -#include +#include -namespace MinCppSock { +namespace MinimalSocket { int getLastErrorCode() { #ifdef _WIN32 return WSAGetLastError(); @@ -157,9 +157,9 @@ void Channel::SocketHandlerFactory::afterClose() { } #endif -SocketHandler::~SocketHandler() { close(); } +SocketIdWrapper::~SocketIdWrapper() { close(); } -void SocketHandler::reset(const SocketHandlerType &hndl) { +void SocketIdWrapper::reset(const SocketID &hndl) { if (socket_id != SCK_INVALID_SOCKET) { close(); } @@ -179,7 +179,8 @@ int to_int(const AddressFamily &family) { } } // namespace -void SocketHandler::reset(const Protocol &type, const AddressFamily &family) { +void SocketIdWrapper::reset(const SocketType &type, + const AddressFamily &family) { if (socket_id != SCK_INVALID_SOCKET) { close(); } @@ -189,14 +190,14 @@ void SocketHandler::reset(const Protocol &type, const AddressFamily &family) { #endif switch (type) { - case Protocol::TCP: + case SocketType::TCP: this->socket_id = ::socket(to_int(family), SOCK_STREAM, 0); if (this->socket_id == SCK_INVALID_SOCKET) { this->close(); throwWithLastErrorCode("Stream socket could not be created"); } break; - case Protocol::UDP: + case SocketType::UDP: this->socket_id = ::socket(to_int(family), SOCK_DGRAM, 0); if (this->socket_id == SCK_INVALID_SOCKET) { this->close(); @@ -208,7 +209,7 @@ void SocketHandler::reset(const Protocol &type, const AddressFamily &family) { } } -void SocketHandler::close() { +void SocketIdWrapper::close() { if (socket_id == SCK_INVALID_SOCKET) { return; } @@ -224,4 +225,4 @@ void SocketHandler::close() { SocketHandlerFactory::afterClose(); #endif } -} // namespace MinCppSock \ No newline at end of file +} // namespace MinimalSocket \ No newline at end of file diff --git a/src/SynchSocket/src/Commons.h b/src/src/Commons.h similarity index 82% rename from src/SynchSocket/src/Commons.h rename to src/src/Commons.h index e90696b6..ac9785d8 100644 --- a/src/SynchSocket/src/Commons.h +++ b/src/src/Commons.h @@ -7,8 +7,7 @@ #pragma once -#include -#include +#include #include #include @@ -30,7 +29,7 @@ #define SCK_SOCKET_ERROR -1 #endif -namespace MinCppSock { +namespace MinimalSocket { /** * @brief representation of a generic socket address */ @@ -87,49 +86,51 @@ Address make_address(const SocketIp &address); * socket handle */ #ifdef _WIN32 -using SocketHandlerType = SOCKET; +using SocketID = SOCKET; #else -using SocketHandlerType = int; +using SocketID = int; #endif +enum SocketType { UDP, TCP }; + /** * An object storing a socket API handler and containing the minimal * functionalities for interacting with it. */ -class SocketHandler { +class SocketIdWrapper { public: - SocketHandler(const SocketHandler &) = delete; - SocketHandler &operator=(const SocketHandler &) = delete; + SocketIdWrapper(const SocketIdWrapper &) = delete; + SocketIdWrapper &operator=(const SocketIdWrapper &) = delete; /** * @brief a closed socket is created */ - SocketHandler() = default; + SocketIdWrapper() = default; - ~SocketHandler(); + ~SocketIdWrapper(); bool empty() const { return socket_id == SCK_INVALID_SOCKET; } /** * @brief internally creates a new socket */ - void reset(const Protocol &type, const AddressFamily &family); + void reset(const SocketType &type, const AddressFamily &family); /** * @brief the passed handler should be already created externally * by the socket api */ - void reset(const SocketHandlerType &hndl); + void reset(const SocketID &hndl); /** * @brief close and shutdown the current socket */ void close(); - const SocketHandlerType &access() const { return socket_id; }; + const SocketID &access() const { return socket_id; }; private: - SocketHandlerType socket_id = SCK_INVALID_SOCKET; + SocketID socket_id = SCK_INVALID_SOCKET; #ifdef _WIN32 class SocketHandlerFactory { @@ -149,4 +150,4 @@ class SocketHandler { }; #endif }; -} // namespace MinCppSock +} // namespace MinimalSocket diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp new file mode 100644 index 00000000..e69de29b From 3ba189ccfb0a70bb1d0346f5a094865443699dcc Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 6 May 2022 23:03:51 +0100 Subject: [PATCH 091/228] refactoring --- src/CMakeLists.txt | 5 +- src/header/MinimalSocket/{ => core}/Address.h | 0 src/header/MinimalSocket/core/Receiver.h | 58 +++++++++++++++++++ src/header/MinimalSocket/core/Sender.h | 10 +++- src/header/MinimalSocket/core/Socket.h | 6 +- src/src/Commons.h | 2 +- src/src/{ => core}/Address.cpp | 4 +- src/src/core/Sender.cpp | 23 ++++++++ src/src/core/Socket.cpp | 26 +++++++++ 9 files changed, 125 insertions(+), 9 deletions(-) rename src/header/MinimalSocket/{ => core}/Address.h (100%) rename src/src/{ => core}/Address.cpp (96%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 430c68ab..0fe080de 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,5 @@ if(WIN32) target_link_libraries(${PROJECT_SHORTNAME} PRIVATE wsock32 ws2_32) endif() -# find_package(Threads) -# target_link_libraries(${PROJECT_SHORTNAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) - +find_package(Threads) +target_link_libraries(${PROJECT_SHORTNAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) diff --git a/src/header/MinimalSocket/Address.h b/src/header/MinimalSocket/core/Address.h similarity index 100% rename from src/header/MinimalSocket/Address.h rename to src/header/MinimalSocket/core/Address.h diff --git a/src/header/MinimalSocket/core/Receiver.h b/src/header/MinimalSocket/core/Receiver.h index e69de29b..0cac0b5a 100644 --- a/src/header/MinimalSocket/core/Receiver.h +++ b/src/header/MinimalSocket/core/Receiver.h @@ -0,0 +1,58 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include + +#include +#include + +namespace MinimalSocket { +class Receiver : public virtual Socket { +public: + /** + * @param[in] the buffer that will receive the message: + * first element of the pair is the data pointer + * of the buffer second element of the pair is the buffer size A request to + * receive a message of maximal size equal to message.second will be forwarded + * to the socket api. + * @param[in] the timeout to consider + * @return the number of received bytes actually received and copied into + * message (can be also less than the buffer size) + */ + std::size_t receive(const Buffer &message, + const std::chrono::milliseconds &timeout); + +private: + std::mutex receive_mtx; +}; + +class ReceiverUnkownSender : public virtual Socket { +public: + /** + * @param[in] the buffer that will receive the message: + * first element of the pair is the data pointer + * of the buffer second element of the pair is the buffer size A request to + * receive a message of maximal size equal to message.second will be forwarded + * to the socket api. + * @param[in] the timeout to consider + * @return the number of received bytes actually received and copied into + * message (can be also less than the buffer size) + */ + struct ReceivedResult { + std::size_t got_bytes; + Address sender; + }; + ReceivedResult receive(const Buffer &message, + const std::chrono::milliseconds &timeout); + +private: + std::mutex receive_mtx; +}; +} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Sender.h b/src/header/MinimalSocket/core/Sender.h index 4ec3fa76..b9c5aa1c 100644 --- a/src/header/MinimalSocket/core/Sender.h +++ b/src/header/MinimalSocket/core/Sender.h @@ -7,7 +7,9 @@ #pragma once -#include +#include +#include +#include namespace MinimalSocket { class Sender : public virtual Socket { @@ -17,6 +19,9 @@ class Sender : public virtual Socket { * @param[in] the message to send */ bool send(const Buffer &message); + +private: + std::mutex send_mtx; }; class SenderTo : public virtual Socket { @@ -26,5 +31,8 @@ class SenderTo : public virtual Socket { * @param[in] the message to send */ bool sendTo(const Buffer &message, const Address &recipient); + +private: + std::mutex send_mtx; }; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 4e5987ef..5afede71 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -13,6 +13,8 @@ namespace MinimalSocket { using Buffer = std::string; +void setZeros(Buffer &subject); + class SocketIdWrapper; class Socket { public: @@ -26,8 +28,8 @@ class Socket { void stealIDWrapper(Socket &o); - const SocketIdWrapper &getIDWrapper() const { return *socket_id_wrapper; }; - SocketIdWrapper &getIDWrapper() { return *socket_id_wrapper; }; + const SocketIdWrapper &getIDWrapper() const; + SocketIdWrapper &getIDWrapper(); private: std::unique_ptr socket_id_wrapper; diff --git a/src/src/Commons.h b/src/src/Commons.h index ac9785d8..ebe9b9bf 100644 --- a/src/src/Commons.h +++ b/src/src/Commons.h @@ -7,7 +7,7 @@ #pragma once -#include +#include #include #include diff --git a/src/src/Address.cpp b/src/src/core/Address.cpp similarity index 96% rename from src/src/Address.cpp rename to src/src/core/Address.cpp index 37f0c037..5264c7d8 100644 --- a/src/src/Address.cpp +++ b/src/src/core/Address.cpp @@ -5,10 +5,10 @@ * report any bug to andrecasa91@gmail.com. **/ -#include #include +#include -#include "Commons.h" +#include "../Commons.h" #include diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index e69de29b..f5789adf 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -0,0 +1,23 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include "../Commons.h" + +namespace MinimalSocket { +bool Sender::send(const Buffer &message) { + std::scoped_lock lock(send_mtx); + int sentBytes = ::send(getIDWrapper().access(), message.data(), + static_cast(message.size()), 0); + if (sentBytes == SCK_SOCKET_ERROR) { + sentBytes = 0; + throwWithLastErrorCode("send failed"); + } + return (sentBytes == static_cast(message.size())); +} +} // namespace MinimalSocket diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index e69de29b..55bf0746 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -0,0 +1,26 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include "../Commons.h" + +namespace MinimalSocket { +Socket::~Socket() = default; + +Socket::Socket() { socket_id_wrapper = std::make_unique(); } + +void Socket::stealIDWrapper(Socket &o) { + this->socket_id_wrapper = std::move(o.socket_id_wrapper); + o.socket_id_wrapper = std::make_unique(); +} + +const SocketIdWrapper &Socket::getIDWrapper() const { + return *socket_id_wrapper; +} +SocketIdWrapper &Socket::getIDWrapper() { return *socket_id_wrapper; } +} // namespace MinimalSocket \ No newline at end of file From 75de69838cdd3b6831fd8cc2ba0df5808da87f34 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 7 May 2022 00:15:43 +0100 Subject: [PATCH 092/228] refactoring --- TODO | 2 + .../MinimalSocket/core/BindedPortAware.h | 23 +++++++ src/header/MinimalSocket/core/Receiver.h | 27 +++++--- .../MinimalSocket/core/RemoteAddressAware.h | 23 +++++++ src/header/MinimalSocket/core/Sender.h | 1 + src/header/MinimalSocket/core/Socket.h | 1 + src/header/MinimalSocket/tcp/TcpClient.h | 22 +++++++ src/header/MinimalSocket/tcp/TcpServer.h | 33 ++++++++++ src/header/MinimalSocket/udp/UdpSocket.h | 65 +++++++++++++++++++ src/src/Commons.h | 1 + src/src/core/Receiver.cpp | 58 +++++++++++++++++ src/src/core/Sender.cpp | 26 ++++++++ 12 files changed, 272 insertions(+), 10 deletions(-) create mode 100644 src/header/MinimalSocket/core/BindedPortAware.h create mode 100644 src/header/MinimalSocket/core/RemoteAddressAware.h create mode 100644 src/header/MinimalSocket/tcp/TcpClient.h create mode 100644 src/header/MinimalSocket/tcp/TcpServer.h create mode 100644 src/header/MinimalSocket/udp/UdpSocket.h diff --git a/TODO b/TODO index edf62603..2b6ca718 100644 --- a/TODO +++ b/TODO @@ -7,3 +7,5 @@ check if there is a way to check if an external passed socket was open or not in support for bluetooth connection variant per vari stati di udp connection + +add timeout per udp connect diff --git a/src/header/MinimalSocket/core/BindedPortAware.h b/src/header/MinimalSocket/core/BindedPortAware.h new file mode 100644 index 00000000..27267d2d --- /dev/null +++ b/src/header/MinimalSocket/core/BindedPortAware.h @@ -0,0 +1,23 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include + +namespace MinimalSocket { +class BindedPortAware { +public: + Port getBindedPort() const { return binded_port; } + +protected: + BindedPortAware(const Port &port) : binded_port(port){}; + +private: + const Port binded_port; +}; +} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Receiver.h b/src/header/MinimalSocket/core/Receiver.h index 0cac0b5a..519c35e5 100644 --- a/src/header/MinimalSocket/core/Receiver.h +++ b/src/header/MinimalSocket/core/Receiver.h @@ -7,13 +7,25 @@ #pragma once -#include -#include +#include +#include #include #include namespace MinimalSocket { +using ReceiveTimeout = std::chrono::milliseconds; + +static constexpr ReceiveTimeout NULL_TIMEOUT = ReceiveTimeout{0}; + +class ReceiveTimeOutAware : public virtual Socket { +protected: + void lazyUpdateReceiveTimeout(const ReceiveTimeout &timeout); + +private: + ReceiveTimeout receive_timeout = NULL_TIMEOUT; +}; + class Receiver : public virtual Socket { public: /** @@ -26,8 +38,7 @@ class Receiver : public virtual Socket { * @return the number of received bytes actually received and copied into * message (can be also less than the buffer size) */ - std::size_t receive(const Buffer &message, - const std::chrono::milliseconds &timeout); + void receive(Buffer &message, const ReceiveTimeout &timeout = NULL_TIMEOUT); private: std::mutex receive_mtx; @@ -45,12 +56,8 @@ class ReceiverUnkownSender : public virtual Socket { * @return the number of received bytes actually received and copied into * message (can be also less than the buffer size) */ - struct ReceivedResult { - std::size_t got_bytes; - Address sender; - }; - ReceivedResult receive(const Buffer &message, - const std::chrono::milliseconds &timeout); + Address receive(Buffer &message, + const ReceiveTimeout &timeout = NULL_TIMEOUT); private: std::mutex receive_mtx; diff --git a/src/header/MinimalSocket/core/RemoteAddressAware.h b/src/header/MinimalSocket/core/RemoteAddressAware.h new file mode 100644 index 00000000..c487843d --- /dev/null +++ b/src/header/MinimalSocket/core/RemoteAddressAware.h @@ -0,0 +1,23 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include + +namespace MinimalSocket { +class RemoteAddressAware { +public: + const Address &getRemoteAddress() const { return remote_address; } + +protected: + RemoteAddressAware(const Address &address) : remote_address(address){}; + +private: + const Address remote_address; +}; +} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Sender.h b/src/header/MinimalSocket/core/Sender.h index b9c5aa1c..2d6a02a8 100644 --- a/src/header/MinimalSocket/core/Sender.h +++ b/src/header/MinimalSocket/core/Sender.h @@ -9,6 +9,7 @@ #include #include + #include namespace MinimalSocket { diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 5afede71..7662cb89 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -9,6 +9,7 @@ #include #include +#include namespace MinimalSocket { using Buffer = std::string; diff --git a/src/header/MinimalSocket/tcp/TcpClient.h b/src/header/MinimalSocket/tcp/TcpClient.h new file mode 100644 index 00000000..2c593020 --- /dev/null +++ b/src/header/MinimalSocket/tcp/TcpClient.h @@ -0,0 +1,22 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include +#include + +namespace MinimalSocket::tcp { +class TcpClient : public Sender, public Receiver, public RemoteAddressAware { +public: + TcpClient(TcpClient &&o); + TcpClient &operator=(TcpClient &&o); + + TcpClient(const Address &server_address); +}; +} // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h new file mode 100644 index 00000000..adfb6cf5 --- /dev/null +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -0,0 +1,33 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include +#include +#include + +namespace MinimalSocket::tcp { +class TcpConnection : public Sender, + public Receiver, + public RemoteAddressAware { +public: + TcpConnection(TcpConnection &&o); + TcpConnection &operator=(TcpConnection &&o); +}; + +class TcpServer : public BindedPortAware { +public: + TcpServer(TcpServer &&o); + TcpServer &operator=(TcpServer &&o); + + TcpServer(const Port &port); + + TcpConnection &&acceptNewClient(); +}; +} // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h new file mode 100644 index 00000000..8ad0d9cb --- /dev/null +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -0,0 +1,65 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace MinimalSocket::udp { +class UdpSender; +class UdpBinded; +class UdpConnected; + +class UdpSender : public SenderTo { +public: + UdpSender(UdpSender &&o); + UdpSender &operator=(UdpSender &&o); + + UdpBinded &&bind(const Port &port); +}; + +class UdpBinded : public SenderTo, + public ReceiverUnkownSender, + public BindedPortAware { +public: + UdpBinded(UdpBinded &&o); + UdpBinded &operator=(UdpBinded &&o); + + UdpBinded(const Port &port); + UdpBinded(UdpSender &&previous_phase, const Port &port); + + UdpSender &&unbind(); + + UdpConnected &&connect(); // to first sending 1 byte + UdpConnected &&connect(const Address &remote_address); +}; + +class UdpConnected : public Sender, + public Receiver, + public BindedPortAware, + public RemoteAddressAware { +public: + UdpConnected(UdpConnected &&o); + UdpConnected &operator=(UdpConnected &&o); + + UdpConnected(const Port &port); // to first sending 1 byte + UdpConnected(const Port &port, const Address &remote_address); + UdpConnected(UdpBinded &&previous_phase, + const Port &port); // to first sending 1 byte + UdpConnected(UdpBinded &&previous_phase, const Port &port, + const Address &remote_address); + + UdpBinded &&disconnect(); +}; + +using UdpSocket = std::variant; +} // namespace MinimalSocket::udp diff --git a/src/src/Commons.h b/src/src/Commons.h index ebe9b9bf..120c7fa0 100644 --- a/src/src/Commons.h +++ b/src/src/Commons.h @@ -76,6 +76,7 @@ std::optional makeSocketIp4(const std::string &raw_address, */ std::optional makeSocketIp6(const std::string &raw_address, const Port &port); + /** * @brief Convert a SocketAddress_t into an Address, internally * deducing the family. diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index e69de29b..229721ac 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -0,0 +1,58 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include "../Commons.h" + +namespace MinimalSocket { +void ReceiveTimeOutAware::lazyUpdateReceiveTimeout( + const ReceiveTimeout &timeout) { + if (timeout == receive_timeout) { + return; + } + receive_timeout = timeout; + // set new timeout +#ifdef _WIN32 + auto tv = DWORD(this->receive_timeout.count()); + if (setsockopt(getIDWrapper().access(), SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), + sizeof(DWORD)) == SOCKET_ERROR) { +#else + struct timeval tv = {0, 0}; + if (this->receive_timeout.count() >= 1000) { + tv.tv_sec = + std::chrono::duration_cast(receive_timeout) + .count(); + } else { + tv.tv_usec = + std::chrono::duration_cast(receive_timeout) + .count(); + } + if (::setsockopt(getIDWrapper().access(), SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), + sizeof(struct timeval)) < 0) { +#endif + throwWithLastErrorCode("can't set timeout"); + } +} + +void Receiver::receive(Buffer &message, const ReceiveTimeout &timeout) { + std::lock_guard recvLock(receive_mtx); + int recvBytes = ::recv(getIDWrapper().access(), message.data(), + static_cast(message.size()), 0); + if (recvBytes == SCK_SOCKET_ERROR) { + recvBytes = 0; + throwWithLastErrorCode("receive failed"); + } + if (recvBytes > message.size()) { + // if here, the message received is probably corrupted + recvBytes = 0; + } + message.resize(recvBytes); +} +} // namespace MinimalSocket diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index f5789adf..cb733de3 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -20,4 +20,30 @@ bool Sender::send(const Buffer &message) { } return (sentBytes == static_cast(message.size())); } + +bool SenderTo::sendTo(const Buffer &message, const Address &recipient) { + std::scoped_lock lock(send_mtx); + int sentBytes; + switch (recipient.getFamily()) { + case AddressFamily::IP_V4: { + auto socketIp4 = makeSocketIp4(recipient.getHost(), recipient.getPort()); + sentBytes = ::sendto(getIDWrapper().access(), message.data(), + static_cast(message.size()), 0, + reinterpret_cast(&socketIp4.value()), + sizeof(SocketIp4)); + } break; + case AddressFamily::IP_V6: { + auto socketIp6 = makeSocketIp6(recipient.getHost(), recipient.getPort()); + sentBytes = ::sendto(getIDWrapper().access(), message.data(), + static_cast(message.size()), 0, + reinterpret_cast(&socketIp6.value()), + sizeof(SocketIp6)); + } break; + } + if (sentBytes == SCK_SOCKET_ERROR) { + sentBytes = 0; + throwWithLastErrorCode("send to failed"); + } + return (sentBytes == static_cast(message.size())); +} } // namespace MinimalSocket From e22b153d53ed7d6d6a32badafe12ea38a18f4de5 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 7 May 2022 00:18:48 +0100 Subject: [PATCH 093/228] refactoring --- src/src/Commons.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/src/Commons.h b/src/src/Commons.h index 120c7fa0..9a1096df 100644 --- a/src/src/Commons.h +++ b/src/src/Commons.h @@ -151,4 +151,8 @@ class SocketIdWrapper { }; #endif }; + +void bind(const SocketID &socket_id, const Port &port); + +void connect(const SocketID &socket_id, const Address &remote_address); } // namespace MinimalSocket From 1afd32fb970f0add264d63cc1a6face7080d53c0 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 7 May 2022 15:04:43 +0100 Subject: [PATCH 094/228] refactoring --- TODO | 10 ++ .../MinimalSocket/core/BindedPortAware.h | 7 +- .../MinimalSocket/core/RemoteAddressAware.h | 5 +- src/header/MinimalSocket/core/Socket.h | 26 ++++ src/header/MinimalSocket/tcp/TcpClient.h | 8 +- src/header/MinimalSocket/tcp/TcpServer.h | 20 ++- src/header/MinimalSocket/udp/UdpSocket.h | 26 ++-- src/src/Commons.cpp | 127 +++++++++++++++++- src/src/Commons.h | 12 +- src/src/core/Address.cpp | 13 +- src/src/core/Sender.cpp | 36 ++--- src/src/core/Socket.cpp | 14 ++ src/src/tcp/TcpClient.cpp | 39 ++++++ src/src/tcp/TcpServer.cpp | 75 +++++++++++ 14 files changed, 366 insertions(+), 52 deletions(-) create mode 100644 src/src/tcp/TcpClient.cpp create mode 100644 src/src/tcp/TcpServer.cpp diff --git a/TODO b/TODO index 2b6ca718..8de267ac 100644 --- a/TODO +++ b/TODO @@ -9,3 +9,13 @@ support for bluetooth connection variant per vari stati di udp connection add timeout per udp connect + +in receive from, resize buffer a max size udp message + +open connection with timeout + +usare noexpect keyword in c'tors? + +pass number of max clients tcp server should accept + +mettere tutti constexpr static diff --git a/src/header/MinimalSocket/core/BindedPortAware.h b/src/header/MinimalSocket/core/BindedPortAware.h index 27267d2d..d65f3379 100644 --- a/src/header/MinimalSocket/core/BindedPortAware.h +++ b/src/header/MinimalSocket/core/BindedPortAware.h @@ -12,12 +12,15 @@ namespace MinimalSocket { class BindedPortAware { public: - Port getBindedPort() const { return binded_port; } + Port getPortToBind() const { return binded_port; } + + BindedPortAware(const BindedPortAware &) = default; + BindedPortAware &operator=(const BindedPortAware &) = default; protected: BindedPortAware(const Port &port) : binded_port(port){}; private: - const Port binded_port; + Port binded_port; }; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/RemoteAddressAware.h b/src/header/MinimalSocket/core/RemoteAddressAware.h index c487843d..7e36be8a 100644 --- a/src/header/MinimalSocket/core/RemoteAddressAware.h +++ b/src/header/MinimalSocket/core/RemoteAddressAware.h @@ -14,10 +14,13 @@ class RemoteAddressAware { public: const Address &getRemoteAddress() const { return remote_address; } + RemoteAddressAware(const RemoteAddressAware &) = default; + RemoteAddressAware &operator=(const RemoteAddressAware &) = default; + protected: RemoteAddressAware(const Address &address) : remote_address(address){}; private: - const Address remote_address; + Address remote_address; }; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 7662cb89..07779832 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -24,6 +25,8 @@ class Socket { Socket(const Socket &) = delete; Socket &operator=(const Socket &) = delete; + bool isNull() const; + protected: Socket(); @@ -35,4 +38,27 @@ class Socket { private: std::unique_ptr socket_id_wrapper; }; + +bool operator==(std::nullptr_t, const Socket &subject) { + return subject.isNull(); +} +bool operator==(const Socket &subject, std::nullptr_t) { + return subject.isNull(); +} + +class Openable { +public: + virtual ~Openable() = default; + + bool wasOpened() const { return opened; } + bool open(); + +protected: + Openable() = default; + + virtual bool open_() = 0; + +private: + std::atomic_bool opened = false; +}; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/tcp/TcpClient.h b/src/header/MinimalSocket/tcp/TcpClient.h index 2c593020..c3505c0b 100644 --- a/src/header/MinimalSocket/tcp/TcpClient.h +++ b/src/header/MinimalSocket/tcp/TcpClient.h @@ -12,11 +12,17 @@ #include namespace MinimalSocket::tcp { -class TcpClient : public Sender, public Receiver, public RemoteAddressAware { +class TcpClient : public Openable, + public Sender, + public Receiver, + public RemoteAddressAware { public: TcpClient(TcpClient &&o); TcpClient &operator=(TcpClient &&o); TcpClient(const Address &server_address); + +protected: + bool open_() override; }; } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index adfb6cf5..8c266c82 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -13,21 +13,35 @@ #include namespace MinimalSocket::tcp { +class TcpServer; + class TcpConnection : public Sender, public Receiver, public RemoteAddressAware { + friend class TcpServer; + public: TcpConnection(TcpConnection &&o); TcpConnection &operator=(TcpConnection &&o); + +private: + TcpConnection(const Address &remote_address); }; -class TcpServer : public BindedPortAware { +class TcpServer : public BindedPortAware, public Socket, public Openable { public: TcpServer(TcpServer &&o); TcpServer &operator=(TcpServer &&o); - TcpServer(const Port &port); + TcpServer(const Port &port, + const AddressFamily &kind_of_client = AddressFamily::IP_V4); + + TcpConnection acceptNewClient(); + +protected: + bool open_() override; - TcpConnection &&acceptNewClient(); +private: + AddressFamily kind_of_client_to_accept; }; } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 8ad0d9cb..2b82db99 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -12,21 +12,29 @@ #include #include -#include - namespace MinimalSocket::udp { +/** + * @brief refer to + * https://en.wikipedia.org/wiki/User_Datagram_Protocol#:~:text=The%20field%20size%20sets%20a,−%2020%20byte%20IP%20header). + */ +static constexpr std::size_t MAX_UDP_RECV_MESSAGE = 65507; + class UdpSender; class UdpBinded; class UdpConnected; +// can only send as no port was reserved class UdpSender : public SenderTo { public: UdpSender(UdpSender &&o); UdpSender &operator=(UdpSender &&o); - UdpBinded &&bind(const Port &port); + UdpSender() = default; + + UdpBinded bind(const Port &port); }; +// can send and receive (from anyonw hitting it) as a port was reserved class UdpBinded : public SenderTo, public ReceiverUnkownSender, public BindedPortAware { @@ -37,12 +45,12 @@ class UdpBinded : public SenderTo, UdpBinded(const Port &port); UdpBinded(UdpSender &&previous_phase, const Port &port); - UdpSender &&unbind(); - - UdpConnected &&connect(); // to first sending 1 byte - UdpConnected &&connect(const Address &remote_address); + UdpConnected connect(); // to first sending 1 byte + UdpConnected connect(const Address &remote_address); }; +// can send and receive only from the specific remote address the socket was +// connected to class UdpConnected : public Sender, public Receiver, public BindedPortAware, @@ -58,8 +66,6 @@ class UdpConnected : public Sender, UdpConnected(UdpBinded &&previous_phase, const Port &port, const Address &remote_address); - UdpBinded &&disconnect(); + UdpBinded disconnect(); }; - -using UdpSocket = std::variant; } // namespace MinimalSocket::udp diff --git a/src/src/Commons.cpp b/src/src/Commons.cpp index f8b962e2..f44f3ada 100644 --- a/src/src/Commons.cpp +++ b/src/src/Commons.cpp @@ -133,6 +133,22 @@ Address make_address(const SocketIp &address) { return Address{ip, port}; } +void address_case(const AddressFamily &family, + const std::function &ipv4_case, + const std::function &ipv6_case) { + switch (family) { + case AddressFamily::IP_V4: + ipv4_case(); + break; + case AddressFamily::IP_V6: + ipv6_case(); + break; + default: + throw Error{"Unrecognized AddressFamily"}; + break; + } +} + #ifdef _WIN32 std::size_t Channel::SocketHandlerFactory::handlerCounter = 0; std::mutex Channel::SocketHandlerFactory::handlerCounterMtx; @@ -169,13 +185,11 @@ void SocketIdWrapper::reset(const SocketID &hndl) { namespace { int to_int(const AddressFamily &family) { - switch (family) { - case AddressFamily::IP_V4: - return static_cast(AF_INET); - case AddressFamily::IP_V6: - return static_cast(AF_INET6); - } - throw Error("unknown address family type"); + int result; + address_case( + family, [&]() { result = static_cast(AF_INET); }, + [&]() { result = static_cast(AF_INET6); }); + return result; } } // namespace @@ -225,4 +239,103 @@ void SocketIdWrapper::close() { SocketHandlerFactory::afterClose(); #endif } + +namespace { +#ifdef _WIN32 +#define REBIND_OPTION SO_REUSEADDR +#else +#define REBIND_OPTION SO_REUSEPORT +#endif +} // namespace + +void bind(const SocketID &socket_id, const AddressFamily &family, + const Port &port) { + int reusePortOptVal = 1; + ::setsockopt(socket_id, SOL_SOCKET, REBIND_OPTION, + reinterpret_cast(&reusePortOptVal), + sizeof(int)); + + // bind the socket to the port + address_case( + family, + [&]() { + SocketIp4 addr; + ::memset(&addr, 0, sizeof(SocketIp4)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); +#ifdef _WIN32 + addr.sin_addr.s_addr = ADDR_ANY; +#else + addr.sin_addr.s_addr = htonl(INADDR_ANY); +#endif + if (::bind(socket_id, reinterpret_cast(&addr), + sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { + throwWithLastErrorCode("can't bind localhost on port: " + + std::to_string(port)); + } + }, + [&]() { + SocketIp6 addr; + ::memset(&addr, 0, sizeof(SocketIp6)); + addr.sin6_family = AF_INET6; + addr.sin6_flowinfo = 0; + addr.sin6_addr = + IN6ADDR_ANY_INIT; // apparently, there is no such a + // cross-system define for ipv6 addresses + addr.sin6_port = htons(port); + if (::bind(socket_id, reinterpret_cast(&addr), + sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { + throwWithLastErrorCode("can't bind localhost on port: " + + std::to_string(port)); + } + }); +} + +namespace { +static constexpr std::size_t LISTEN_BACKLOG = 50; +} + +void listen(const SocketID &socket_id) { + if (::listen(socket_id, LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { + throwWithLastErrorCode("Error: listening on reserved port"); + } +} + +void connect(const SocketID &socket_id, const Address &remote_address) { + address_case( + remote_address.getFamily(), + [&]() { + // v4 family + auto addr = + makeSocketIp4(remote_address.getHost(), remote_address.getPort()); + if (!addr) { + throw Error(to_string(remote_address), + " is an invalid server address"); + } + if (::connect(socket_id, reinterpret_cast(&(*addr)), + sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { + throwWithLastErrorCode("Connection can't be established"); + } + }, + [&]() { + // v6 family + auto addr = + makeSocketIp6(remote_address.getHost(), remote_address.getPort()); + if (!addr) { + throw Error(to_string(remote_address), + " is an invalid server address"); + } + if (::connect(socket_id, reinterpret_cast(&(*addr)), + sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { + throwWithLastErrorCode("Connection can't be established"); + } + }); +} } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/Commons.h b/src/src/Commons.h index 9a1096df..35681de8 100644 --- a/src/src/Commons.h +++ b/src/src/Commons.h @@ -10,6 +10,7 @@ #include #include +#include #include #ifdef _WIN32 @@ -83,6 +84,10 @@ std::optional makeSocketIp6(const std::string &raw_address, */ Address make_address(const SocketIp &address); +void address_case(const AddressFamily &family, + const std::function &ipv4_case, + const std::function &ipv6_case); + /** * socket handle */ @@ -110,8 +115,6 @@ class SocketIdWrapper { ~SocketIdWrapper(); - bool empty() const { return socket_id == SCK_INVALID_SOCKET; } - /** * @brief internally creates a new socket */ @@ -152,7 +155,10 @@ class SocketIdWrapper { #endif }; -void bind(const SocketID &socket_id, const Port &port); +void bind(const SocketID &socket_id, const AddressFamily &family, + const Port &port); + +void listen(const SocketID &socket_id); void connect(const SocketID &socket_id, const Address &remote_address); } // namespace MinimalSocket diff --git a/src/src/core/Address.cpp b/src/src/core/Address.cpp index 5264c7d8..5987a8a6 100644 --- a/src/src/core/Address.cpp +++ b/src/src/core/Address.cpp @@ -40,15 +40,10 @@ Address Address::makeLocalHost(const std::uint16_t &port, Address result; result.port = port; result.family = family; - switch (family) { - case AddressFamily::IP_V4: - result.host = LOCALHOST_IPv4; - return result; - case AddressFamily::IP_V6: - result.host = LOCALHOST_IPv6; - return result; - } - throw Error{"unrecognized family address"}; + address_case( + family, [&result]() { result.host = LOCALHOST_IPv4; }, + [&result]() { result.host = LOCALHOST_IPv6; }); + return result; } bool Address::operator==(const Address &o) const { diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index cb733de3..e776d7b7 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -24,22 +24,26 @@ bool Sender::send(const Buffer &message) { bool SenderTo::sendTo(const Buffer &message, const Address &recipient) { std::scoped_lock lock(send_mtx); int sentBytes; - switch (recipient.getFamily()) { - case AddressFamily::IP_V4: { - auto socketIp4 = makeSocketIp4(recipient.getHost(), recipient.getPort()); - sentBytes = ::sendto(getIDWrapper().access(), message.data(), - static_cast(message.size()), 0, - reinterpret_cast(&socketIp4.value()), - sizeof(SocketIp4)); - } break; - case AddressFamily::IP_V6: { - auto socketIp6 = makeSocketIp6(recipient.getHost(), recipient.getPort()); - sentBytes = ::sendto(getIDWrapper().access(), message.data(), - static_cast(message.size()), 0, - reinterpret_cast(&socketIp6.value()), - sizeof(SocketIp6)); - } break; - } + address_case( + recipient.getFamily(), + [&]() { + auto socketIp4 = + makeSocketIp4(recipient.getHost(), recipient.getPort()); + sentBytes = + ::sendto(getIDWrapper().access(), message.data(), + static_cast(message.size()), 0, + reinterpret_cast(&socketIp4.value()), + sizeof(SocketIp4)); + }, + [&]() { + auto socketIp6 = + makeSocketIp6(recipient.getHost(), recipient.getPort()); + sentBytes = + ::sendto(getIDWrapper().access(), message.data(), + static_cast(message.size()), 0, + reinterpret_cast(&socketIp6.value()), + sizeof(SocketIp6)); + }); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; throwWithLastErrorCode("send to failed"); diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 55bf0746..196ec5f1 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -5,6 +5,7 @@ * report any bug to andrecasa91@gmail.com. **/ +#include #include #include "../Commons.h" @@ -14,6 +15,10 @@ Socket::~Socket() = default; Socket::Socket() { socket_id_wrapper = std::make_unique(); } +bool Socket::isNull() const { + return socket_id_wrapper->access() == SCK_INVALID_SOCKET; +} + void Socket::stealIDWrapper(Socket &o) { this->socket_id_wrapper = std::move(o.socket_id_wrapper); o.socket_id_wrapper = std::make_unique(); @@ -23,4 +28,13 @@ const SocketIdWrapper &Socket::getIDWrapper() const { return *socket_id_wrapper; } SocketIdWrapper &Socket::getIDWrapper() { return *socket_id_wrapper; } + +bool Openable::open() { + if (opened) { + throw Error{"Already opened"}; + } + const bool success = open_(); + opened = success; + return success; +} } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/tcp/TcpClient.cpp b/src/src/tcp/TcpClient.cpp new file mode 100644 index 00000000..d33ece60 --- /dev/null +++ b/src/src/tcp/TcpClient.cpp @@ -0,0 +1,39 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +#include "../Commons.h" + +namespace MinimalSocket::tcp { +TcpClient::TcpClient(TcpClient &&o) : RemoteAddressAware(o) { + stealIDWrapper(o); +} +TcpClient &TcpClient::operator=(TcpClient &&o) { + static_cast(*this) = o; + stealIDWrapper(o); + return *this; +} + +TcpClient::TcpClient(const Address &server_address) + : RemoteAddressAware(server_address) {} + +bool TcpClient::open_() { + auto &socket = getIDWrapper(); + const auto &remote_address = getRemoteAddress(); + bool success = true; + try { + socket.reset(TCP, remote_address.getFamily()); + connect(socket.access(), remote_address); + } catch (const Error &) { + socket.close(); + success = false; + } + return success; +} +} // namespace MinimalSocket::tcp diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp new file mode 100644 index 00000000..e22f5a23 --- /dev/null +++ b/src/src/tcp/TcpServer.cpp @@ -0,0 +1,75 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +#include "../Commons.h" + +namespace MinimalSocket::tcp { +TcpServer::TcpServer(TcpServer &&o) : BindedPortAware(o) { stealIDWrapper(o); } +TcpServer &TcpServer::operator=(TcpServer &&o) { + static_cast(*this) = o; + stealIDWrapper(o); + return *this; +} + +TcpServer::TcpServer(const Port &port, const AddressFamily &kind_of_client) + : BindedPortAware(port), kind_of_client_to_accept(kind_of_client) {} + +bool TcpServer::open_() { + auto &socket = getIDWrapper(); + const auto port = getPortToBind(); + bool success = true; + try { + socket.reset(TCP, kind_of_client_to_accept); + bind(socket.access(), kind_of_client_to_accept, port); + listen(socket.access()); + } catch (const Error &) { + socket.close(); + success = false; + } + return success; +} + +TcpConnection TcpServer::acceptNewClient() { + if (!this->wasOpened()) { + throw Error("Tcp server was not opened before starting to accept clients"); + } + SocketIp acceptedClientAddress; +#ifdef _WIN32 + int acceptedAddressLength +#else + unsigned int acceptedAddressLength +#endif + = sizeof(SocketIp); + // accept: wait for a client to call connect and hit this server and get a + // pointer to this client. + SocketID accepted_client_socket_id = ::accept( + getIDWrapper().access(), &acceptedClientAddress, &acceptedAddressLength); + if (accepted_client_socket_id == SCK_INVALID_SOCKET) { + throwWithLastErrorCode("Error: accepting new client"); + } + + auto accepted_client_parsed_address = make_address(acceptedClientAddress); + TcpConnection result(accepted_client_parsed_address); + result.getIDWrapper().reset(accepted_client_socket_id); + return std::move(result); +} + +TcpConnection::TcpConnection(const Address &remote_address) + : RemoteAddressAware(remote_address) {} + +TcpConnection::TcpConnection(TcpConnection &&o) : RemoteAddressAware(o) { + stealIDWrapper(o); +} +TcpConnection &TcpConnection::operator=(TcpConnection &&o) { + static_cast(*this) = o; + stealIDWrapper(o); + return *this; +} +} // namespace MinimalSocket::tcp From cd51b2922a5a44941831f6430c18186f3f215ef0 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 7 May 2022 15:21:03 +0100 Subject: [PATCH 095/228] refactoring --- src/header/MinimalSocket/core/Socket.h | 2 +- src/header/MinimalSocket/udp/UdpSocket.h | 14 +++++++++++--- src/src/core/Socket.cpp | 6 +++--- src/src/tcp/TcpClient.cpp | 4 ++-- src/src/tcp/TcpServer.cpp | 10 ++++++---- src/src/udp/UdpSocket.cpp | 15 +++++++++++++++ 6 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 src/src/udp/UdpSocket.cpp diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 07779832..c7bff418 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -30,7 +30,7 @@ class Socket { protected: Socket(); - void stealIDWrapper(Socket &o); + static void transferIDWrapper(Socket &giver, Socket &recipient); const SocketIdWrapper &getIDWrapper() const; SocketIdWrapper &getIDWrapper(); diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 2b82db99..3a8c5895 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -29,7 +29,7 @@ class UdpSender : public SenderTo { UdpSender(UdpSender &&o); UdpSender &operator=(UdpSender &&o); - UdpSender() = default; + UdpSender(); UdpBinded bind(const Port &port); }; @@ -37,7 +37,8 @@ class UdpSender : public SenderTo { // can send and receive (from anyonw hitting it) as a port was reserved class UdpBinded : public SenderTo, public ReceiverUnkownSender, - public BindedPortAware { + public BindedPortAware, + public Openable { public: UdpBinded(UdpBinded &&o); UdpBinded &operator=(UdpBinded &&o); @@ -47,6 +48,9 @@ class UdpBinded : public SenderTo, UdpConnected connect(); // to first sending 1 byte UdpConnected connect(const Address &remote_address); + +protected: + bool open_() override; }; // can send and receive only from the specific remote address the socket was @@ -54,7 +58,8 @@ class UdpBinded : public SenderTo, class UdpConnected : public Sender, public Receiver, public BindedPortAware, - public RemoteAddressAware { + public RemoteAddressAware, + public Openable { public: UdpConnected(UdpConnected &&o); UdpConnected &operator=(UdpConnected &&o); @@ -67,5 +72,8 @@ class UdpConnected : public Sender, const Address &remote_address); UdpBinded disconnect(); + +protected: + bool open_() override; }; } // namespace MinimalSocket::udp diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 196ec5f1..87c1d234 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -19,9 +19,9 @@ bool Socket::isNull() const { return socket_id_wrapper->access() == SCK_INVALID_SOCKET; } -void Socket::stealIDWrapper(Socket &o) { - this->socket_id_wrapper = std::move(o.socket_id_wrapper); - o.socket_id_wrapper = std::make_unique(); +void Socket::transferIDWrapper(Socket &giver, Socket &recipient) { + recipient.socket_id_wrapper = std::move(giver.socket_id_wrapper); + giver.socket_id_wrapper = std::make_unique(); } const SocketIdWrapper &Socket::getIDWrapper() const { diff --git a/src/src/tcp/TcpClient.cpp b/src/src/tcp/TcpClient.cpp index d33ece60..ad35f7d1 100644 --- a/src/src/tcp/TcpClient.cpp +++ b/src/src/tcp/TcpClient.cpp @@ -12,11 +12,11 @@ namespace MinimalSocket::tcp { TcpClient::TcpClient(TcpClient &&o) : RemoteAddressAware(o) { - stealIDWrapper(o); + Socket::transferIDWrapper(o, *this); } TcpClient &TcpClient::operator=(TcpClient &&o) { static_cast(*this) = o; - stealIDWrapper(o); + Socket::transferIDWrapper(o, *this); return *this; } diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index e22f5a23..d8e37d29 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -11,10 +11,12 @@ #include "../Commons.h" namespace MinimalSocket::tcp { -TcpServer::TcpServer(TcpServer &&o) : BindedPortAware(o) { stealIDWrapper(o); } +TcpServer::TcpServer(TcpServer &&o) : BindedPortAware(o) { + Socket::transferIDWrapper(o, *this); +} TcpServer &TcpServer::operator=(TcpServer &&o) { static_cast(*this) = o; - stealIDWrapper(o); + Socket::transferIDWrapper(o, *this); return *this; } @@ -65,11 +67,11 @@ TcpConnection::TcpConnection(const Address &remote_address) : RemoteAddressAware(remote_address) {} TcpConnection::TcpConnection(TcpConnection &&o) : RemoteAddressAware(o) { - stealIDWrapper(o); + Socket::transferIDWrapper(o, *this); } TcpConnection &TcpConnection::operator=(TcpConnection &&o) { static_cast(*this) = o; - stealIDWrapper(o); + Socket::transferIDWrapper(o, *this); return *this; } } // namespace MinimalSocket::tcp diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp new file mode 100644 index 00000000..c79233ee --- /dev/null +++ b/src/src/udp/UdpSocket.cpp @@ -0,0 +1,15 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include "../Commons.h" + +namespace MinimalSocket::udp { +UdpSender::UdpSender() { getIDWrapper().reset(UDP, ); } + +} // namespace MinimalSocket::udp From 7ae23f4cdff7b90c2fd4b300bf80f2e5bb7602dd Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 7 May 2022 15:26:32 +0100 Subject: [PATCH 096/228] refactoring --- src/header/MinimalSocket/udp/UdpSocket.h | 54 ++++++++++++------------ src/src/udp/UdpSocket.cpp | 12 ++++++ 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 3a8c5895..5c5d305e 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -20,8 +20,8 @@ namespace MinimalSocket::udp { static constexpr std::size_t MAX_UDP_RECV_MESSAGE = 65507; class UdpSender; -class UdpBinded; -class UdpConnected; +class UdpBindable; +class UdpConnectable; // can only send as no port was reserved class UdpSender : public SenderTo { @@ -31,23 +31,23 @@ class UdpSender : public SenderTo { UdpSender(); - UdpBinded bind(const Port &port); + UdpBindable bind(const Port &port); }; // can send and receive (from anyonw hitting it) as a port was reserved -class UdpBinded : public SenderTo, - public ReceiverUnkownSender, - public BindedPortAware, - public Openable { +class UdpBindable : public SenderTo, + public ReceiverUnkownSender, + public BindedPortAware, + public Openable { public: - UdpBinded(UdpBinded &&o); - UdpBinded &operator=(UdpBinded &&o); + UdpBindable(UdpBindable &&o); + UdpBindable &operator=(UdpBindable &&o); - UdpBinded(const Port &port); - UdpBinded(UdpSender &&previous_phase, const Port &port); + UdpBindable(const Port &port); + UdpBindable(UdpSender &&previous_phase, const Port &port); - UdpConnected connect(); // to first sending 1 byte - UdpConnected connect(const Address &remote_address); + UdpConnectable connect(); // to first sending 1 byte + UdpConnectable connect(const Address &remote_address); protected: bool open_() override; @@ -55,23 +55,23 @@ class UdpBinded : public SenderTo, // can send and receive only from the specific remote address the socket was // connected to -class UdpConnected : public Sender, - public Receiver, - public BindedPortAware, - public RemoteAddressAware, - public Openable { +class UdpConnectable : public Sender, + public Receiver, + public BindedPortAware, + public RemoteAddressAware, + public Openable { public: - UdpConnected(UdpConnected &&o); - UdpConnected &operator=(UdpConnected &&o); + UdpConnectable(UdpConnectable &&o); + UdpConnectable &operator=(UdpConnectable &&o); - UdpConnected(const Port &port); // to first sending 1 byte - UdpConnected(const Port &port, const Address &remote_address); - UdpConnected(UdpBinded &&previous_phase, - const Port &port); // to first sending 1 byte - UdpConnected(UdpBinded &&previous_phase, const Port &port, - const Address &remote_address); + UdpConnectable(const Port &port); // to first sending 1 byte + UdpConnectable(const Port &port, const Address &remote_address); + UdpConnectable(UdpBindable &&previous_phase, + const Port &port); // to first sending 1 byte + UdpConnectable(UdpBindable &&previous_phase, const Port &port, + const Address &remote_address); - UdpBinded disconnect(); + UdpBindable disconnect(); protected: bool open_() override; diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index c79233ee..c37c26e3 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -12,4 +12,16 @@ namespace MinimalSocket::udp { UdpSender::UdpSender() { getIDWrapper().reset(UDP, ); } +UdpBindable UdpSender::bind(const Port &port) { + UdpBindable result(port); + Socket::transferIDWrapper(*this, result); + result.open(); + return std::move(result); +} + +UdpSender::UdpSender(UdpSender &&o) { Socket::transferIDWrapper(o, *this); } +UdpSender &UdpSender::operator=(UdpSender &&o) { + Socket::transferIDWrapper(o, *this); +} + } // namespace MinimalSocket::udp From 1072f2be12929fe258107ce3e92b8fe8050a0561 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 7 May 2022 20:31:22 +0100 Subject: [PATCH 097/228] refactoring --- .../MinimalSocket/core/BindedPortAware.h | 26 ------- .../MinimalSocket/core/RemoteAddressAware.h | 26 ------- src/header/MinimalSocket/core/Socket.h | 8 +-- src/header/MinimalSocket/core/SocketContext.h | 56 +++++++++++++++ src/header/MinimalSocket/tcp/TcpClient.h | 4 +- src/header/MinimalSocket/tcp/TcpServer.h | 17 +++-- src/header/MinimalSocket/udp/UdpSocket.h | 33 +++++---- src/src/core/Socket.cpp | 10 ++- src/src/tcp/TcpClient.cpp | 15 ++-- src/src/tcp/TcpServer.cpp | 28 ++++---- src/src/udp/UdpSocket.cpp | 72 +++++++++++++++++-- 11 files changed, 179 insertions(+), 116 deletions(-) delete mode 100644 src/header/MinimalSocket/core/BindedPortAware.h delete mode 100644 src/header/MinimalSocket/core/RemoteAddressAware.h create mode 100644 src/header/MinimalSocket/core/SocketContext.h diff --git a/src/header/MinimalSocket/core/BindedPortAware.h b/src/header/MinimalSocket/core/BindedPortAware.h deleted file mode 100644 index d65f3379..00000000 --- a/src/header/MinimalSocket/core/BindedPortAware.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#pragma once - -#include - -namespace MinimalSocket { -class BindedPortAware { -public: - Port getPortToBind() const { return binded_port; } - - BindedPortAware(const BindedPortAware &) = default; - BindedPortAware &operator=(const BindedPortAware &) = default; - -protected: - BindedPortAware(const Port &port) : binded_port(port){}; - -private: - Port binded_port; -}; -} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/RemoteAddressAware.h b/src/header/MinimalSocket/core/RemoteAddressAware.h deleted file mode 100644 index 7e36be8a..00000000 --- a/src/header/MinimalSocket/core/RemoteAddressAware.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#pragma once - -#include - -namespace MinimalSocket { -class RemoteAddressAware { -public: - const Address &getRemoteAddress() const { return remote_address; } - - RemoteAddressAware(const RemoteAddressAware &) = default; - RemoteAddressAware &operator=(const RemoteAddressAware &) = default; - -protected: - RemoteAddressAware(const Address &address) : remote_address(address){}; - -private: - Address remote_address; -}; -} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index c7bff418..37a72cce 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -46,19 +47,18 @@ bool operator==(const Socket &subject, std::nullptr_t) { return subject.isNull(); } -class Openable { +class Openable : public virtual Socket { public: - virtual ~Openable() = default; - bool wasOpened() const { return opened; } bool open(); protected: Openable() = default; - virtual bool open_() = 0; + virtual void open_() = 0; private: + std::mutex open_procedure_mtx; std::atomic_bool opened = false; }; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/SocketContext.h b/src/header/MinimalSocket/core/SocketContext.h new file mode 100644 index 00000000..2023688d --- /dev/null +++ b/src/header/MinimalSocket/core/SocketContext.h @@ -0,0 +1,56 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include + +namespace MinimalSocket { +class RemoteAddressAware { +public: + const Address &getRemoteAddress() const { return remote_address; } + + RemoteAddressAware(const RemoteAddressAware &) = default; + RemoteAddressAware &operator=(const RemoteAddressAware &) = default; + +protected: + RemoteAddressAware(const Address &address) : remote_address(address){}; + +private: + Address remote_address; +}; + +class PortToBindAware { +public: + Port getPortToBind() const { return port_to_bind; } + + PortToBindAware(const PortToBindAware &) = default; + PortToBindAware &operator=(const PortToBindAware &) = default; + +protected: + PortToBindAware(const Port &port) : port_to_bind(port){}; + +private: + Port port_to_bind; +}; + +class RemoteAddressFamilyAware { +public: + AddressFamily getRemoteAddressFamily() const { return remote_address_family; } + + RemoteAddressFamilyAware(const RemoteAddressFamilyAware &) = default; + RemoteAddressFamilyAware & + operator=(const RemoteAddressFamilyAware &) = default; + +protected: + RemoteAddressFamilyAware(const AddressFamily &family) + : remote_address_family(family){}; + +private: + AddressFamily remote_address_family; +}; +} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/tcp/TcpClient.h b/src/header/MinimalSocket/tcp/TcpClient.h index c3505c0b..baea3b60 100644 --- a/src/header/MinimalSocket/tcp/TcpClient.h +++ b/src/header/MinimalSocket/tcp/TcpClient.h @@ -8,8 +8,8 @@ #pragma once #include -#include #include +#include namespace MinimalSocket::tcp { class TcpClient : public Openable, @@ -23,6 +23,6 @@ class TcpClient : public Openable, TcpClient(const Address &server_address); protected: - bool open_() override; + void open_() override; }; } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index 8c266c82..d049eb1c 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -7,10 +7,9 @@ #pragma once -#include #include -#include #include +#include namespace MinimalSocket::tcp { class TcpServer; @@ -28,20 +27,20 @@ class TcpConnection : public Sender, TcpConnection(const Address &remote_address); }; -class TcpServer : public BindedPortAware, public Socket, public Openable { +class TcpServer : public PortToBindAware, + public RemoteAddressFamilyAware, + public virtual Socket, + public Openable { public: TcpServer(TcpServer &&o); TcpServer &operator=(TcpServer &&o); - TcpServer(const Port &port, - const AddressFamily &kind_of_client = AddressFamily::IP_V4); + TcpServer(const Port port_to_bind, + const AddressFamily &accepted_client_family = AddressFamily::IP_V4); TcpConnection acceptNewClient(); protected: - bool open_() override; - -private: - AddressFamily kind_of_client_to_accept; + void open_() override; }; } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 5c5d305e..4f8b3ffb 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -7,10 +7,9 @@ #pragma once -#include #include -#include #include +#include namespace MinimalSocket::udp { /** @@ -24,56 +23,56 @@ class UdpBindable; class UdpConnectable; // can only send as no port was reserved -class UdpSender : public SenderTo { +class UdpSender : public SenderTo, public RemoteAddressFamilyAware { public: UdpSender(UdpSender &&o); UdpSender &operator=(UdpSender &&o); - UdpSender(); + UdpSender( + const AddressFamily &accepted_connection_family = AddressFamily::IP_V4); - UdpBindable bind(const Port &port); + UdpBindable bind(const Port port_to_bind); }; // can send and receive (from anyonw hitting it) as a port was reserved class UdpBindable : public SenderTo, public ReceiverUnkownSender, - public BindedPortAware, + public PortToBindAware, + public RemoteAddressFamilyAware, public Openable { public: UdpBindable(UdpBindable &&o); UdpBindable &operator=(UdpBindable &&o); - UdpBindable(const Port &port); - UdpBindable(UdpSender &&previous_phase, const Port &port); + UdpBindable( + const Port port_to_bind, + const AddressFamily &accepted_connection_family = AddressFamily::IP_V4); - UdpConnectable connect(); // to first sending 1 byte + // throw in case address family is inconsistent UdpConnectable connect(const Address &remote_address); + UdpConnectable connect(); // to first sending 1 byte + protected: - bool open_() override; + void open_() override; }; // can send and receive only from the specific remote address the socket was // connected to class UdpConnectable : public Sender, public Receiver, - public BindedPortAware, + public PortToBindAware, public RemoteAddressAware, public Openable { public: UdpConnectable(UdpConnectable &&o); UdpConnectable &operator=(UdpConnectable &&o); - UdpConnectable(const Port &port); // to first sending 1 byte UdpConnectable(const Port &port, const Address &remote_address); - UdpConnectable(UdpBindable &&previous_phase, - const Port &port); // to first sending 1 byte - UdpConnectable(UdpBindable &&previous_phase, const Port &port, - const Address &remote_address); UdpBindable disconnect(); protected: - bool open_() override; + void open_() override; }; } // namespace MinimalSocket::udp diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 87c1d234..f0e2dac8 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -33,8 +33,14 @@ bool Openable::open() { if (opened) { throw Error{"Already opened"}; } - const bool success = open_(); - opened = success; + std::scoped_lock lock(open_procedure_mtx); + bool success = true; + try { + this->open_(); + } catch (const Error &) { + getIDWrapper().close(); + success = false; + } return success; } } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/tcp/TcpClient.cpp b/src/src/tcp/TcpClient.cpp index ad35f7d1..acca768b 100644 --- a/src/src/tcp/TcpClient.cpp +++ b/src/src/tcp/TcpClient.cpp @@ -23,17 +23,10 @@ TcpClient &TcpClient::operator=(TcpClient &&o) { TcpClient::TcpClient(const Address &server_address) : RemoteAddressAware(server_address) {} -bool TcpClient::open_() { +void TcpClient::open_() { auto &socket = getIDWrapper(); - const auto &remote_address = getRemoteAddress(); - bool success = true; - try { - socket.reset(TCP, remote_address.getFamily()); - connect(socket.access(), remote_address); - } catch (const Error &) { - socket.close(); - success = false; - } - return success; + const auto remote_address = getRemoteAddress(); + socket.reset(TCP, remote_address.getFamily()); + connect(socket.access(), remote_address); } } // namespace MinimalSocket::tcp diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index d8e37d29..1922af40 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -11,31 +11,29 @@ #include "../Commons.h" namespace MinimalSocket::tcp { -TcpServer::TcpServer(TcpServer &&o) : BindedPortAware(o) { +TcpServer::TcpServer(TcpServer &&o) + : PortToBindAware(o), RemoteAddressFamilyAware(o) { Socket::transferIDWrapper(o, *this); } TcpServer &TcpServer::operator=(TcpServer &&o) { - static_cast(*this) = o; + static_cast(*this) = o; + static_cast(*this) = o; Socket::transferIDWrapper(o, *this); return *this; } -TcpServer::TcpServer(const Port &port, const AddressFamily &kind_of_client) - : BindedPortAware(port), kind_of_client_to_accept(kind_of_client) {} +TcpServer::TcpServer(const Port port_to_bind, + const AddressFamily &accepted_client_family) + : PortToBindAware(port_to_bind), + RemoteAddressFamilyAware(accepted_client_family) {} -bool TcpServer::open_() { +void TcpServer::open_() { auto &socket = getIDWrapper(); const auto port = getPortToBind(); - bool success = true; - try { - socket.reset(TCP, kind_of_client_to_accept); - bind(socket.access(), kind_of_client_to_accept, port); - listen(socket.access()); - } catch (const Error &) { - socket.close(); - success = false; - } - return success; + const auto family = getRemoteAddressFamily(); + socket.reset(TCP, family); + bind(socket.access(), family, port); + listen(socket.access()); } TcpConnection TcpServer::acceptNewClient() { diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index c37c26e3..d0ba8f0e 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -5,23 +5,87 @@ * report any bug to andrecasa91@gmail.com. **/ +#include #include #include "../Commons.h" namespace MinimalSocket::udp { -UdpSender::UdpSender() { getIDWrapper().reset(UDP, ); } +UdpSender::UdpSender(const AddressFamily &accepted_connection_family) + : RemoteAddressFamilyAware(accepted_connection_family) { + getIDWrapper().reset(UDP, accepted_connection_family); +} -UdpBindable UdpSender::bind(const Port &port) { - UdpBindable result(port); +UdpBindable UdpSender::bind(const Port port_to_bind) { + UdpBindable result(port_to_bind, getRemoteAddressFamily()); Socket::transferIDWrapper(*this, result); result.open(); return std::move(result); } -UdpSender::UdpSender(UdpSender &&o) { Socket::transferIDWrapper(o, *this); } +UdpSender::UdpSender(UdpSender &&o) + : RemoteAddressFamilyAware(o.getRemoteAddressFamily()) { + Socket::transferIDWrapper(o, *this); +} UdpSender &UdpSender::operator=(UdpSender &&o) { Socket::transferIDWrapper(o, *this); + static_cast(*this) = o; + return *this; +} + +UdpBindable::UdpBindable(const Port port_to_bind, + const AddressFamily &accepted_connection_family) + : PortToBindAware(port_to_bind), + RemoteAddressFamilyAware(accepted_connection_family) { + getIDWrapper().reset(UDP, accepted_connection_family); +} + +void UdpBindable::open_() { + bind(getIDWrapper().access(), getRemoteAddressFamily(), getPortToBind()); +} + +UdpConnectable UdpBindable::connect(const Address &remote_address) { + if (remote_address.getFamily() != getRemoteAddressFamily()) { + throw Error{"Passed address has invalid family"}; + } + UdpConnectable result(getPortToBind(), remote_address); + Socket::transferIDWrapper(*this, result); + MinimalSocket::connect(getIDWrapper().access(), remote_address); + return std::move(result); +} + +UdpConnectable UdpBindable::connect() { + std::string buffer; + buffer.resize(MAX_UDP_RECV_MESSAGE); + auto sender_address = this->receive(buffer); + return connect(sender_address); +} + +UdpConnectable::UdpConnectable(const Port &port, const Address &remote_address) + : PortToBindAware(port), RemoteAddressAware(remote_address) {} + +UdpBindable UdpConnectable::disconnect() { + getIDWrapper().close(); + UdpBindable result(getPortToBind(), getRemoteAddress().getFamily()); + result.open(); + return std::move(result); +} + +UdpConnectable::UdpConnectable(UdpConnectable &&o) + : PortToBindAware(o), RemoteAddressAware(o) { + Socket::transferIDWrapper(o, *this); +} +UdpConnectable &UdpConnectable::operator=(UdpConnectable &&o) { + Socket::transferIDWrapper(o, *this); + static_cast(*this) = o; + static_cast(*this) = o; + return *this; } +void UdpConnectable::open_() { + const auto &socket_id = getIDWrapper().access(); + const auto &remote_address = getRemoteAddress(); + bind(socket_id, remote_address.getFamily(), getPortToBind()); + connect(socket_id, remote_address); +} } // namespace MinimalSocket::udp From 75b31b668af341e328bddb9d1cb86ba9ee729ee8 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 7 May 2022 20:54:42 +0100 Subject: [PATCH 098/228] preparing for tests --- CMakeLists.txt | 6 +- Tests/Test02-tcp-asynch.cpp | 124 --------------- Tests/Test02-udp-asynch.cpp | 36 ----- Tests/Test03-typed.cpp | 149 ------------------ Tests/UtilsTest/include/Common.h | 35 ---- Tests/UtilsTest/src/Common.cpp | 24 --- {Tests => tests}/CMakeLists.txt | 22 +-- .../Test00-ip.cpp => tests/Test00-address.cpp | 0 .../Test01-tcp.cpp | 0 .../Test01-udp.cpp | 0 {Tests => tests}/UtilsTest/CMakeLists.txt | 5 +- tests/UtilsTest/include/PortFactory.h | 24 +++ .../UtilsTest/include/TcpCommon.h | 0 .../UtilsTest/include/UdpCommon.h | 0 tests/UtilsTest/src/PortFactory.cpp | 24 +++ {Tests => tests}/UtilsTest/src/TcpCommon.cpp | 0 {Tests => tests}/UtilsTest/src/UdpCommon.cpp | 0 17 files changed, 60 insertions(+), 389 deletions(-) delete mode 100644 Tests/Test02-tcp-asynch.cpp delete mode 100644 Tests/Test02-udp-asynch.cpp delete mode 100644 Tests/Test03-typed.cpp delete mode 100644 Tests/UtilsTest/include/Common.h delete mode 100644 Tests/UtilsTest/src/Common.cpp rename {Tests => tests}/CMakeLists.txt (62%) rename Tests/Test00-ip.cpp => tests/Test00-address.cpp (100%) rename Tests/Test01-tcp-synch.cpp => tests/Test01-tcp.cpp (100%) rename Tests/Test01-udp-synch.cpp => tests/Test01-udp.cpp (100%) rename {Tests => tests}/UtilsTest/CMakeLists.txt (64%) create mode 100644 tests/UtilsTest/include/PortFactory.h rename {Tests => tests}/UtilsTest/include/TcpCommon.h (100%) rename {Tests => tests}/UtilsTest/include/UdpCommon.h (100%) create mode 100644 tests/UtilsTest/src/PortFactory.cpp rename {Tests => tests}/UtilsTest/src/TcpCommon.cpp (100%) rename {Tests => tests}/UtilsTest/src/UdpCommon.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9285aa18..d15c69fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,6 @@ add_subdirectory(src) # add_subdirectory(Samples) # endif() -# if(BUILD_TESTS) -# add_subdirectory(Tests) -# endif() +if(BUILD_MinimalCppSocket_TESTS) + add_subdirectory(tests) +endif() diff --git a/Tests/Test02-tcp-asynch.cpp b/Tests/Test02-tcp-asynch.cpp deleted file mode 100644 index 64b46dbe..00000000 --- a/Tests/Test02-tcp-asynch.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include -#include -#include -#include -using namespace sck; -using namespace sck::tcp; - -#include -#include - -TEST(TcpAsync, OpenCloseAcceptSynch) { - const std::uint16_t port = sample::PortFactory::makePort(); - -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // server - auto acceptedClient = sample::accept(port); - sample::AsyncResponder asynchResponder(std::move(acceptedClient)); - sample::open(asynchResponder); -#pragma omp barrier - sample::close(asynchResponder); - } - else { - // client - TcpClient client(*sck::Ip::createLocalHost(port)); - sample::openTcpClient(client); -#pragma omp barrier - } - } -} - -TEST(TcpAsync, ClientAsker_ServerResponder) { - const std::uint16_t port = sample::PortFactory::makePort(); - const std::size_t cycles = 5; - -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // server - auto acceptedClient = sample::accept(port); - sample::AsyncResponder asynchResponder(std::move(acceptedClient)); - sample::open(asynchResponder); -#pragma omp barrier - } - else { - // client - std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); - sample::openTcpClient(*client); - sample::Asker asker(std::move(client)); - asker.ask(cycles); -#pragma omp barrier - } - } -} - -#include - -class AsynchAcceptor - : public async::AsyncTcpServer - , private async::TcpServerListener - , private async::ErrorListener -{ -public: - AsynchAcceptor(const std::uint16_t port) : AsyncTcpServer(std::make_unique(port)) { - this->sck::async::TcpClientHandlerTalker::resetListener(this); - }; - - inline std::size_t getAcceptedNumber() const { - std::lock_guard lk(this->acceptedMtx); - return this->accepted.size(); - }; - -private: - void handle(std::unique_ptr clientHandler) final { - std::lock_guard lk(this->acceptedMtx); - EXPECT_TRUE(clientHandler->isOpen()); - this->accepted.emplace_back(std::move(clientHandler)); - }; - - void handle(const sck::Error& error) final {}; - - void handle(const std::exception& error) final {}; - - mutable std::mutex acceptedMtx; - std::list> accepted; -}; - -TEST(TcpAsync, AsynchTcpServerAcceptor) { - const std::uint16_t port = sample::PortFactory::makePort(); - const std::size_t clientsNumb = 5; - - const std::chrono::milliseconds timeOut = std::chrono::seconds(5); - -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // acceptor - AsynchAcceptor acceptor(port); - acceptor.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(acceptor.isOpen()); -#pragma omp barrier - auto tic = std::chrono::high_resolution_clock::now(); - while (acceptor.getAcceptedNumber() != clientsNumb) { - EXPECT_LE(std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - tic).count(), timeOut.count()); - } - } - else { - // client -#pragma omp barrier - std::list clients; - for (std::size_t k = 0; k < clientsNumb; ++k) { - clients.emplace_back(*sck::Ip::createLocalHost(port)); - clients.back().open(std::chrono::milliseconds(0)); - EXPECT_TRUE(clients.back().isOpen()); - } - } - } -} - -int main(int argc, char* argv[]) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/Tests/Test02-udp-asynch.cpp b/Tests/Test02-udp-asynch.cpp deleted file mode 100644 index da0cc4a4..00000000 --- a/Tests/Test02-udp-asynch.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include -#include -#include -using namespace sck; -using namespace sck::udp; - -#include -#include - -TEST(UdpAsync, Asker_Responder) { - auto connections = sample::makeOpenedUdpConnections(sample::PortFactory::makePort(), sample::PortFactory::makePort()); - const std::size_t cycles = 5; - -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // connection A - sample::AsyncResponder asynchResponder(std::move(connections.first)); - sample::open(asynchResponder); -#pragma omp barrier - sample::close(asynchResponder); - } - else { - // connection B - sample::Asker asker(std::move(connections.second)); - asker.ask(cycles); -#pragma omp barrier - } - } -} - -int main(int argc, char* argv[]) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/Tests/Test03-typed.cpp b/Tests/Test03-typed.cpp deleted file mode 100644 index fc1134c5..00000000 --- a/Tests/Test03-typed.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include -#include -#include -using namespace sck; -using namespace sck::tcp; - -#include -#include -#include -#include -using namespace sck::typed; - -constexpr std::size_t BUFFER_CAPACITY = 2500; - -sample::NamesCollection getAllNames() { - sample::NamesCollection names; - sample::NamesMap map; - for (std::size_t k = 0; k < sample::NamesMap::size(); ++k) { - names.push_back(map.getCursorName()); - ++map; - } - return names; -}; - -sample::NamesCollection getAllSurnames() { - sample::NamesCollection surnames; - sample::NamesMap map; - for (std::size_t k = 0; k < sample::NamesMap::size(); ++k) { - surnames.push_back(map.getCursorSurname()); - ++map; - } - return surnames; -}; - -sample::NamesCollection getResponse(const sample::NamesCollection& request) { - sample::NamesCollection response; - for (auto it = request.begin(); it != request.end(); ++it) { - response.push_back(sample::NamesMap::getSurname(*it)); - } - return response; -}; - -void doRequests(TypedMessanger& messanger, const std::size_t cycles) { - for (std::size_t k = 0; k < cycles; ++k) { - EXPECT_TRUE(messanger.send(getAllNames())); - sample::NamesCollection response; - EXPECT_TRUE(messanger.receive(response, std::chrono::milliseconds(0))); - EXPECT_EQ(response, getAllSurnames()); - } -} - -TEST(Typed, Sync) { - const std::uint16_t port = sample::PortFactory::makePort(); - const std::size_t cycles = 5; - -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // server - auto acceptedClient = sample::accept(port); - TypedMessanger messanger(std::move(acceptedClient), BUFFER_CAPACITY); - // exchange typed messages - for (std::size_t k = 0; k < cycles; ++k) { - sample::NamesCollection request; - EXPECT_TRUE(messanger.receive(request, std::chrono::milliseconds(0))); - EXPECT_TRUE(messanger.send(getResponse(request))); - } -#pragma omp barrier - } - else { - // client - std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); - sample::openTcpClient(*client); - TypedMessanger messanger(std::move(client), BUFFER_CAPACITY); - doRequests(messanger, cycles); -#pragma omp barrier - } - } -} - - -std::string toString(const sample::NamesCollection& names) { - std::stringstream stream; - if (!names.empty()) { - auto it = names.begin(); - stream << *it; - ++it; - for (it; it != names.end(); ++it) { - stream << ';' << *it; - } - } - return stream.str(); -}; - -class TypedAsyncResponder - : public TypedAsynchMessanger - , public TypedMessangerListener - , protected sck::async::ErrorListener - , public sample::Logger { -public: - TypedAsyncResponder(std::unique_ptr messanger) - : TypedAsynchMessanger(std::move(messanger), BUFFER_CAPACITY) - , Logger("TypedAsyncResponder") { - this->resetErrorListener(this); - this->resetListener(this); - }; - -private: - void handle(const sample::NamesCollection& message) final { - this->log("request: ", toString(message)); - sample::NamesCollection response = getResponse(message); - this->log("response: ", toString(response)); - this->send(response); - }; - - void handle(const sck::Error& error) final {}; - - void handle(const std::exception& error) final {}; -}; - -TEST(Typed, Async) { - const std::uint16_t port = sample::PortFactory::makePort(); - const std::size_t cycles = 5; - -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // server - auto acceptedClient = sample::accept(port); - TypedAsyncResponder asynchResponder(std::move(acceptedClient)); - open(asynchResponder); -#pragma omp barrier - close(asynchResponder); - } - else { - // client - std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); - sample::openTcpClient(*client); - TypedMessanger messanger(std::move(client), BUFFER_CAPACITY); - doRequests(messanger, cycles); -#pragma omp barrier - } - } -} - -int main(int argc, char* argv[]) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/Tests/UtilsTest/include/Common.h b/Tests/UtilsTest/include/Common.h deleted file mode 100644 index 05c04508..00000000 --- a/Tests/UtilsTest/include/Common.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef SAMPLE_COMMON_H -#define SAMPLE_COMMON_H - -#include -#include -#include -#include - -namespace sck::sample { - class PortFactory { - public: - static std::uint16_t makePort(); - }; - - template - void open(T& openable) { - openable.open(std::chrono::milliseconds(0)); - EXPECT_TRUE(openable.isOpen()); - } - - template - void close(T& closable) { - closable.close(); - EXPECT_FALSE(closable.isOpen()); - }; -} - -#endif \ No newline at end of file diff --git a/Tests/UtilsTest/src/Common.cpp b/Tests/UtilsTest/src/Common.cpp deleted file mode 100644 index b506d119..00000000 --- a/Tests/UtilsTest/src/Common.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include - -namespace sck::sample { - constexpr std::uint16_t INITIAL_PORT = 9999; - constexpr std::uint16_t DELTA_PORT = 10; - - static std::mutex portMtx; - static std::uint16_t port = INITIAL_PORT; - - std::uint16_t PortFactory::makePort() { - std::lock_guard lk(portMtx); - ++port; - return port; - } -} diff --git a/Tests/CMakeLists.txt b/tests/CMakeLists.txt similarity index 62% rename from Tests/CMakeLists.txt rename to tests/CMakeLists.txt index 31b3a0b1..1a59adc6 100644 --- a/Tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,5 @@ +project(MinimalCppSocket-Tests) + if (WIN32) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) endif (WIN32) @@ -5,7 +7,7 @@ include(FetchContent) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG main + GIT_TAG f079775276a99fd4373569bf5761052a01844270 ) FetchContent_MakeAvailable(googletest) @@ -14,21 +16,11 @@ add_subdirectory(UtilsTest) function(MakeTest NAME) add_executable(${NAME} ${NAME}.cpp) - target_link_libraries(${NAME} - PUBLIC - Utils - UtilsTest - ) + target_link_libraries(${NAME} PUBLIC UtilsTest) install(TARGETS ${NAME}) endfunction() -MakeTest(Test01-tcp-synch) - -MakeTest(Test02-tcp-asynch) - -MakeTest(Test01-udp-synch) - -MakeTest(Test02-udp-asynch) - -MakeTest(Test03-typed) +MakeTest(Test00-address) +MakeTest(Test01-tcp) +MakeTest(Test01-udp) diff --git a/Tests/Test00-ip.cpp b/tests/Test00-address.cpp similarity index 100% rename from Tests/Test00-ip.cpp rename to tests/Test00-address.cpp diff --git a/Tests/Test01-tcp-synch.cpp b/tests/Test01-tcp.cpp similarity index 100% rename from Tests/Test01-tcp-synch.cpp rename to tests/Test01-tcp.cpp diff --git a/Tests/Test01-udp-synch.cpp b/tests/Test01-udp.cpp similarity index 100% rename from Tests/Test01-udp-synch.cpp rename to tests/Test01-udp.cpp diff --git a/Tests/UtilsTest/CMakeLists.txt b/tests/UtilsTest/CMakeLists.txt similarity index 64% rename from Tests/UtilsTest/CMakeLists.txt rename to tests/UtilsTest/CMakeLists.txt index 03e6b099..7984cf60 100644 --- a/Tests/UtilsTest/CMakeLists.txt +++ b/tests/UtilsTest/CMakeLists.txt @@ -4,9 +4,8 @@ MakeLibrary(${PROJECT_SHORTNAME} include) find_package(OpenMP) -target_link_libraries(${PROJECT_SHORTNAME} - PUBLIC - Synch-Cross-Socket +target_link_libraries(${PROJECT_SHORTNAME} PUBLIC + MinimalSocket gtest OpenMP::OpenMP_CXX ) diff --git a/tests/UtilsTest/include/PortFactory.h b/tests/UtilsTest/include/PortFactory.h new file mode 100644 index 00000000..dbef3d57 --- /dev/null +++ b/tests/UtilsTest/include/PortFactory.h @@ -0,0 +1,24 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include + +namespace MinimalSocket::test { +class PortFactory { +public: + PortFactory(); + + Port makePort(); + +private: + std::mutex port_mtx; + Port port; +}; +} // namespace MinimalSocket::test diff --git a/Tests/UtilsTest/include/TcpCommon.h b/tests/UtilsTest/include/TcpCommon.h similarity index 100% rename from Tests/UtilsTest/include/TcpCommon.h rename to tests/UtilsTest/include/TcpCommon.h diff --git a/Tests/UtilsTest/include/UdpCommon.h b/tests/UtilsTest/include/UdpCommon.h similarity index 100% rename from Tests/UtilsTest/include/UdpCommon.h rename to tests/UtilsTest/include/UdpCommon.h diff --git a/tests/UtilsTest/src/PortFactory.cpp b/tests/UtilsTest/src/PortFactory.cpp new file mode 100644 index 00000000..887a4f7e --- /dev/null +++ b/tests/UtilsTest/src/PortFactory.cpp @@ -0,0 +1,24 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +namespace MinimalSocket::test { +namespace { +static constexpr std::uint16_t INITIAL_PORT = 9999; +static constexpr std::uint16_t DELTA_PORT = 10; +} // namespace + +PortFactory::PortFactory() { port = INITIAL_PORT; } + +std::uint16_t PortFactory::makePort() { + std::lock_guard lock(port_mtx); + auto result = port; + port += DELTA_PORT; + return result; +} +} // namespace MinimalSocket::test diff --git a/Tests/UtilsTest/src/TcpCommon.cpp b/tests/UtilsTest/src/TcpCommon.cpp similarity index 100% rename from Tests/UtilsTest/src/TcpCommon.cpp rename to tests/UtilsTest/src/TcpCommon.cpp diff --git a/Tests/UtilsTest/src/UdpCommon.cpp b/tests/UtilsTest/src/UdpCommon.cpp similarity index 100% rename from Tests/UtilsTest/src/UdpCommon.cpp rename to tests/UtilsTest/src/UdpCommon.cpp From 500b08a2060e9d00ed5b75a5ab84f0f14d123eb5 Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Sat, 7 May 2022 20:20:58 +0100 Subject: [PATCH 099/228] fixing win32 compile --- src/header/MinimalSocket/core/Socket.h | 8 ++------ src/src/Commons.cpp | 12 ++++++------ src/src/Commons.h | 3 ++- src/src/core/Socket.cpp | 7 +++++++ 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 37a72cce..71be92d2 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -40,12 +40,8 @@ class Socket { std::unique_ptr socket_id_wrapper; }; -bool operator==(std::nullptr_t, const Socket &subject) { - return subject.isNull(); -} -bool operator==(const Socket &subject, std::nullptr_t) { - return subject.isNull(); -} +bool operator==(std::nullptr_t, const Socket& subject); +bool operator==(const Socket& subject, std::nullptr_t); class Openable : public virtual Socket { public: diff --git a/src/src/Commons.cpp b/src/src/Commons.cpp index f44f3ada..c9c79f6f 100644 --- a/src/src/Commons.cpp +++ b/src/src/Commons.cpp @@ -150,10 +150,10 @@ void address_case(const AddressFamily &family, } #ifdef _WIN32 -std::size_t Channel::SocketHandlerFactory::handlerCounter = 0; -std::mutex Channel::SocketHandlerFactory::handlerCounterMtx; +std::size_t SocketIdWrapper::SocketIDFactory::handlerCounter = 0; +std::mutex SocketIdWrapper::SocketIDFactory::handlerCounterMtx; -void Channel::SocketHandlerFactory::beforeOpen() { +void SocketIdWrapper::SocketIDFactory::beforeOpen() { std::lock_guard hndLck(handlerCounterMtx); ++handlerCounter; if (1 == handlerCounter) { @@ -163,7 +163,7 @@ void Channel::SocketHandlerFactory::beforeOpen() { } } -void Channel::SocketHandlerFactory::afterClose() { +void SocketIdWrapper::SocketIDFactory::afterClose() { std::lock_guard hndLck(handlerCounterMtx); --handlerCounter; if (0 == handlerCounter) { @@ -200,7 +200,7 @@ void SocketIdWrapper::reset(const SocketType &type, } #ifdef _WIN32 - SocketHandlerFactory::beforeOpen(); + SocketIDFactory::beforeOpen(); #endif switch (type) { @@ -236,7 +236,7 @@ void SocketIdWrapper::close() { #endif this->socket_id = SCK_INVALID_SOCKET; #ifdef _WIN32 - SocketHandlerFactory::afterClose(); + SocketIDFactory::afterClose(); #endif } diff --git a/src/src/Commons.h b/src/src/Commons.h index 35681de8..45218016 100644 --- a/src/src/Commons.h +++ b/src/src/Commons.h @@ -12,6 +12,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -137,7 +138,7 @@ class SocketIdWrapper { SocketID socket_id = SCK_INVALID_SOCKET; #ifdef _WIN32 - class SocketHandlerFactory { + class SocketIDFactory { public: /** * @brief If we are about to open the first socket, WSAStartup() is invoked diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index f0e2dac8..7aacec09 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -19,6 +19,13 @@ bool Socket::isNull() const { return socket_id_wrapper->access() == SCK_INVALID_SOCKET; } +bool operator==(std::nullptr_t, const Socket& subject) { + return subject.isNull(); +} +bool operator==(const Socket& subject, std::nullptr_t) { + return subject.isNull(); +} + void Socket::transferIDWrapper(Socket &giver, Socket &recipient) { recipient.socket_id_wrapper = std::move(giver.socket_id_wrapper); giver.socket_id_wrapper = std::make_unique(); From f9698c2f549e7d198ee2a313bc891230eb815358 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 8 May 2022 00:13:42 +0100 Subject: [PATCH 100/228] testing --- src/header/MinimalSocket/core/Address.h | 15 +- src/src/Commons.cpp | 12 +- src/src/core/Address.cpp | 32 ++-- src/src/core/Socket.cpp | 9 +- tests/Test00-address.cpp | 49 ++++++ tests/Test01-tcp.cpp | 209 ++++++++++++++---------- tests/UtilsTest/include/PortFactory.h | 8 +- tests/UtilsTest/include/TcpCommon.h | 39 ++--- tests/UtilsTest/include/UdpCommon.h | 32 ++-- tests/UtilsTest/src/PortFactory.cpp | 5 +- tests/UtilsTest/src/TcpCommon.cpp | 63 +++---- tests/UtilsTest/src/UdpCommon.cpp | 37 +++-- 12 files changed, 312 insertions(+), 198 deletions(-) diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index 46ec4439..2c004e60 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include @@ -25,12 +26,13 @@ using Port = std::uint16_t; class Address { public: /** - * @brief Internally the protocol Family is deduced according to the hostIp - * content. - * @return nullptr if the host is invalid, otherwise a smart pointer storing a - * usable ip + * @brief Internally the protocol Family is deduced according to the + * hostIp content. + * @return nullptr if the host is invalid, otherwise a smart pointer + * storing a usable ip */ - Address(const std::string &hostIp, const Port &port); + static std::unique_ptr
makeAddress(const std::string &hostIp, + const Port &port); /** * @return an ipv4 or ipv6 with localhost as host and the passed port @@ -45,6 +47,9 @@ class Address { bool operator==(const Address &o) const; + Address(const Address &) = default; + Address &operator=(const Address &) = default; + private: Address() = default; diff --git a/src/src/Commons.cpp b/src/src/Commons.cpp index c9c79f6f..fb2ac8d8 100644 --- a/src/src/Commons.cpp +++ b/src/src/Commons.cpp @@ -130,7 +130,11 @@ Address make_address(const SocketIp &address) { ip = std::string(temp, INET6_ADDRSTRLEN); port = ntohs(reinterpret_cast(&address)->sin6_port); } - return Address{ip, port}; + auto result = Address::makeAddress(ip, port); + if (nullptr == result) { + throw Error{"Invalid address"}; + } + return *result; } void address_case(const AddressFamily &family, @@ -184,7 +188,7 @@ void SocketIdWrapper::reset(const SocketID &hndl) { } namespace { -int to_int(const AddressFamily &family) { +int domain_number(const AddressFamily &family) { int result; address_case( family, [&]() { result = static_cast(AF_INET); }, @@ -205,14 +209,14 @@ void SocketIdWrapper::reset(const SocketType &type, switch (type) { case SocketType::TCP: - this->socket_id = ::socket(to_int(family), SOCK_STREAM, 0); + this->socket_id = ::socket(domain_number(family), SOCK_STREAM, 0); if (this->socket_id == SCK_INVALID_SOCKET) { this->close(); throwWithLastErrorCode("Stream socket could not be created"); } break; case SocketType::UDP: - this->socket_id = ::socket(to_int(family), SOCK_DGRAM, 0); + this->socket_id = ::socket(domain_number(family), SOCK_DGRAM, 0); if (this->socket_id == SCK_INVALID_SOCKET) { this->close(); throwWithLastErrorCode("DataGram socket could not be created"); diff --git a/src/src/core/Address.cpp b/src/src/core/Address.cpp index 5987a8a6..556dfc1f 100644 --- a/src/src/core/Address.cpp +++ b/src/src/core/Address.cpp @@ -13,21 +13,24 @@ #include namespace MinimalSocket { -Address::Address(const std::string &hostIp, const Port &port) { - this->host = hostIp; - this->port = port; +std::unique_ptr
Address::makeAddress(const std::string &hostIp, + const Port &port) { + std::unique_ptr
result; + result.reset(new Address{}); + result->host = hostIp; + result->port = port; - if (std::nullopt != makeSocketIp4(host, port)) { - this->family = AddressFamily::IP_V4; - return; + if (std::nullopt != makeSocketIp4(hostIp, port)) { + result->family = AddressFamily::IP_V4; + return result; } - if (std::nullopt != makeSocketIp6(host, port)) { - this->family = AddressFamily::IP_V6; - return; + if (std::nullopt != makeSocketIp6(hostIp, port)) { + result->family = AddressFamily::IP_V6; + return result; } - throw Error{hostIp, " is a not recognized address"}; + return nullptr; } namespace { @@ -59,12 +62,11 @@ std::string to_string(const Address &subject) { std::optional deduceAddressFamily(const std::string &host_address) { - try { - Address temp(host_address, 0); - return temp.getFamily(); - } catch (...) { + auto temp = Address::makeAddress(host_address, 0); + if (nullptr == temp) { + return std::nullopt; } - return std::nullopt; + return temp->getFamily(); } bool isValidAddress(const std::string &host_address) { diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 7aacec09..c14f9ce5 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -19,11 +19,11 @@ bool Socket::isNull() const { return socket_id_wrapper->access() == SCK_INVALID_SOCKET; } -bool operator==(std::nullptr_t, const Socket& subject) { - return subject.isNull(); +bool operator==(std::nullptr_t, const Socket &subject) { + return subject.isNull(); } -bool operator==(const Socket& subject, std::nullptr_t) { - return subject.isNull(); +bool operator==(const Socket &subject, std::nullptr_t) { + return subject.isNull(); } void Socket::transferIDWrapper(Socket &giver, Socket &recipient) { @@ -44,6 +44,7 @@ bool Openable::open() { bool success = true; try { this->open_(); + opened = true; } catch (const Error &) { getIDWrapper().close(); success = false; diff --git a/tests/Test00-address.cpp b/tests/Test00-address.cpp index e69de29b..3e257895 100644 --- a/tests/Test00-address.cpp +++ b/tests/Test00-address.cpp @@ -0,0 +1,49 @@ +#include + +#include + +using namespace MinimalSocket; + +TEST(Address, ParseIpv4) { + { + // valid addresses + std::vector addresses = {"192.168.125.34", "127.0.0.1"}; + for (const auto &address : addresses) { + auto converted = Address::makeAddress(address, 100); + EXPECT_FALSE(nullptr == converted); + } + } + { + // invalid addresses + std::vector addresses = {"192.125.34.34.34", "10000.0.0.1"}; + for (const auto &address : addresses) { + auto converted = Address::makeAddress(address, 100); + EXPECT_TRUE(nullptr == converted); + } + } +} + +TEST(Address, ParseIpv6) { + { + // valid addresses + std::vector addresses = { + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8::1:0"}; + for (const auto &address : addresses) { + auto converted = Address::makeAddress(address, 100); + EXPECT_FALSE(nullptr == converted); + } + } + { + // invalid addresses + std::vector addresses = {"192.125.db8::1:0"}; + for (const auto &address : addresses) { + auto converted = Address::makeAddress(address, 100); + EXPECT_TRUE(nullptr == converted); + } + } +} + +int main(int argc, char *argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/Test01-tcp.cpp b/tests/Test01-tcp.cpp index d6a115d8..e2cd2204 100644 --- a/tests/Test01-tcp.cpp +++ b/tests/Test01-tcp.cpp @@ -1,114 +1,159 @@ #include -#include +#include #include -using namespace sck; -using namespace sck::tcp; -TEST(TcpSynch, OpenClose) { - const std::uint16_t port = sample::PortFactory::makePort(); +#include +#include - #pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // server - auto acceptedClient = sample::accept(port); +#include + +using namespace MinimalSocket; +using namespace MinimalSocket::tcp; +using namespace MinimalSocket::test; + +TEST(Tcp, Establish11Connection) { + const auto port = PortFactory::makePort(); + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // server + TcpServer server(port); + EXPECT_TRUE(server.open()); #pragma omp barrier - sample::close(*acceptedClient); - } - else { - // client - TcpClient client(*sck::Ip::createLocalHost(port)); - sample::openTcpClient(client); + auto client = server.acceptNewClient(); + EXPECT_FALSE(client.isNull()); + } else { + // client + TcpClient client(Address::makeLocalHost(port)); #pragma omp barrier - sample::close(client); - } + EXPECT_TRUE(client.open()); + EXPECT_FALSE(client.isNull()); } + } } -TEST(TcpSynch, OpenCloseManyClients) { - const std::uint16_t port = sample::PortFactory::makePort(); - const std::size_t clientsNumb = 5; +TEST(Tcp, Establish1ManyConnection) { + const auto port = PortFactory::makePort(); + const std::size_t clients = 5; #pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // server - tcp::TcpServer server(port); - sample::open(server); - auto acceptedClients = sample::accept(server, clientsNumb); + { + if (0 == omp_get_thread_num()) { + // server + TcpServer server(port); + EXPECT_TRUE(server.open()); #pragma omp barrier - for (auto it = acceptedClients.begin(); it != acceptedClients.end(); ++it) { - sample::close(**it); - } - } - else { - // client - std::vector> clients; - clients.reserve(clientsNumb); - for (std::size_t k = 0; k < clientsNumb; ++k) { - clients.emplace_back(std::make_unique(*sck::Ip::createLocalHost(port))); - sample::openTcpClient(*clients.back()); - } + for (std::size_t c = 0; c < clients; ++c) { + auto client = server.acceptNewClient(); + EXPECT_FALSE(client.isNull()); #pragma omp barrier - for (auto it = clients.begin(); it != clients.end(); ++it) { - sample::close(**it); - } - } + } + } else { + // clients +#pragma omp barrier + for (std::size_t c = 0; c < clients; ++c) { + TcpClient client(Address::makeLocalHost(port)); + EXPECT_TRUE(client.open()); + EXPECT_FALSE(client.isNull()); +#pragma omp barrier + } } + } + +#pragma omp parallel num_threads(1 + clients) + { + if (0 == omp_get_thread_num()) { + // server + TcpServer server(port); + EXPECT_TRUE(server.open()); + std::list accepted; +#pragma omp barrier + for (std::size_t c = 0; c < clients; ++c) { + auto client = server.acceptNewClient(); + accepted.emplace_back(std::move(client)); + } +#pragma omp barrier + } else { + // clients +#pragma omp barrier + TcpClient client(Address::makeLocalHost(port)); + EXPECT_TRUE(client.open()); + EXPECT_FALSE(client.isNull()); +#pragma omp barrier + } + } } -#include -#include +struct ServerAndClient { + TcpConnection server; + TcpClient client; +}; +ServerAndClient make_server_and_client() { + auto port = PortFactory::makePort(); + TcpServer server(port); + server.open(); + TcpClient client(Address::makeLocalHost(port)); + std::unique_ptr accepted; + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + accepted = std::make_unique(server.acceptNewClient()); + } else { + client.open(); + } + } + + return ServerAndClient{std::move(*accepted), std::move(client)}; +} -TEST(TcpSynch, ClientAsker_ServerResponder) { - const std::uint16_t port = sample::PortFactory::makePort(); - const std::size_t cycles = 5; +TEST(Tcp, SendReceive) { + const std::string message = "Message to send 11! $"; + const std::size_t cycles = 5; + { + auto [server, client] = make_server_and_client(); #pragma omp parallel num_threads(2) { - if (0 == omp_get_thread_num()) { - // server - auto acceptedClient = sample::accept(port); - sample::Responder responder(std::move(acceptedClient)); - responder.respond(cycles); -#pragma omp barrier + if (0 == omp_get_thread_num()) { + for (std::size_t c = 0; c < cycles; ++c) { + std::string buffer; + buffer.resize(message.size() * 2); + server.receive(buffer); + EXPECT_EQ(buffer.size(), message.size()); + EXPECT_EQ(buffer, message); } - else { - // client - std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); - sample::openTcpClient(*client); - sample::Asker asker(std::move(client)); - asker.ask(cycles); -#pragma omp barrier + } else { + for (std::size_t c = 0; c < cycles; ++c) { + client.send(message); } + } } -} - -TEST(TcpSynch, ClientResponder_ServerAsker) { - const std::uint16_t port = sample::PortFactory::makePort(); - const std::size_t cycles = 5; + } + { + auto [server, client] = make_server_and_client(); #pragma omp parallel num_threads(2) { - if (0 == omp_get_thread_num()) { - // server - auto acceptedClient = sample::accept(port); - sample::Asker asker(std::move(acceptedClient)); - asker.ask(cycles); -#pragma omp barrier + if (0 == omp_get_thread_num()) { + for (std::size_t c = 0; c < cycles; ++c) { + std::string buffer; + buffer.resize(message.size() * 2); + client.receive(buffer); + EXPECT_EQ(buffer.size(), message.size()); + EXPECT_EQ(buffer, message); } - else { - // client - std::unique_ptr client = std::make_unique(*sck::Ip::createLocalHost(port)); - sample::openTcpClient(*client); - sample::Responder responder(std::move(client)); - responder.respond(cycles); -#pragma omp barrier + } else { + for (std::size_t c = 0; c < cycles; ++c) { + server.send(message); } + } } + } } -int main(int argc, char* argv[]) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); +int main(int argc, char *argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/tests/UtilsTest/include/PortFactory.h b/tests/UtilsTest/include/PortFactory.h index dbef3d57..3d50d126 100644 --- a/tests/UtilsTest/include/PortFactory.h +++ b/tests/UtilsTest/include/PortFactory.h @@ -13,12 +13,10 @@ namespace MinimalSocket::test { class PortFactory { public: - PortFactory(); - - Port makePort(); + static Port makePort(); private: - std::mutex port_mtx; - Port port; + static std::mutex port_mtx; + static Port port; }; } // namespace MinimalSocket::test diff --git a/tests/UtilsTest/include/TcpCommon.h b/tests/UtilsTest/include/TcpCommon.h index c9bf0081..b72c81c4 100644 --- a/tests/UtilsTest/include/TcpCommon.h +++ b/tests/UtilsTest/include/TcpCommon.h @@ -1,26 +1,27 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ +// /** +// * Author: Andrea Casalino +// * Created: 01.28.2020 +// * +// * report any bug to andrecasa91@gmail.com. +// **/ -#ifndef SAMPLE_TCP_COMMON_H -#define SAMPLE_TCP_COMMON_H +// #ifndef SAMPLE_TCP_COMMON_H +// #define SAMPLE_TCP_COMMON_H -#include -#include -#include -#include +// #include +// #include +// #include +// #include -namespace sck::sample { - typedef std::unique_ptr TcpClientHndlrPtr; +// namespace sck::sample { +// typedef std::unique_ptr TcpClientHndlrPtr; - std::vector accept(tcp::TcpServer& server, const std::size_t clients); +// std::vector accept(tcp::TcpServer& server, const +// std::size_t clients); - TcpClientHndlrPtr accept(const std::uint16_t port); +// TcpClientHndlrPtr accept(const std::uint16_t port); - void openTcpClient(tcp::TcpClient& client); -} +// void openTcpClient(tcp::TcpClient& client); +// } -#endif \ No newline at end of file +// #endif \ No newline at end of file diff --git a/tests/UtilsTest/include/UdpCommon.h b/tests/UtilsTest/include/UdpCommon.h index beefb6a3..86dc384f 100644 --- a/tests/UtilsTest/include/UdpCommon.h +++ b/tests/UtilsTest/include/UdpCommon.h @@ -1,20 +1,22 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ +// /** +// * Author: Andrea Casalino +// * Created: 01.28.2020 +// * +// * report any bug to andrecasa91@gmail.com. +// **/ -#ifndef SAMPLE_UDP_COMMON_H -#define SAMPLE_UDP_COMMON_H +// #ifndef SAMPLE_UDP_COMMON_H +// #define SAMPLE_UDP_COMMON_H -#include -#include +// #include +// #include -namespace sck::sample { - typedef std::unique_ptr UdpConnectionPtr; +// namespace sck::sample { +// typedef std::unique_ptr UdpConnectionPtr; - std::pair makeOpenedUdpConnections(const std::uint16_t portA, const std::uint16_t portB); -} +// std::pair +// makeOpenedUdpConnections(const std::uint16_t portA, const std::uint16_t +// portB); +// } -#endif \ No newline at end of file +// #endif \ No newline at end of file diff --git a/tests/UtilsTest/src/PortFactory.cpp b/tests/UtilsTest/src/PortFactory.cpp index 887a4f7e..a3f3ddc2 100644 --- a/tests/UtilsTest/src/PortFactory.cpp +++ b/tests/UtilsTest/src/PortFactory.cpp @@ -13,9 +13,10 @@ static constexpr std::uint16_t INITIAL_PORT = 9999; static constexpr std::uint16_t DELTA_PORT = 10; } // namespace -PortFactory::PortFactory() { port = INITIAL_PORT; } +std::mutex PortFactory::port_mtx = std::mutex{}; +Port PortFactory::port = INITIAL_PORT; -std::uint16_t PortFactory::makePort() { +Port PortFactory::makePort() { std::lock_guard lock(port_mtx); auto result = port; port += DELTA_PORT; diff --git a/tests/UtilsTest/src/TcpCommon.cpp b/tests/UtilsTest/src/TcpCommon.cpp index 6d046ae4..93acb254 100644 --- a/tests/UtilsTest/src/TcpCommon.cpp +++ b/tests/UtilsTest/src/TcpCommon.cpp @@ -1,35 +1,36 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ +// /** +// * Author: Andrea Casalino +// * Created: 01.28.2020 +// * +// * report any bug to andrecasa91@gmail.com. +// **/ -#include -#include -#include +// #include +// #include +// #include -namespace sck::sample { - std::vector accept(tcp::TcpServer& server, const std::size_t clients) { - std::vector clientPtrs; - clientPtrs.reserve(clients); - for (std::size_t k = 0; k < clients; ++k) { -#pragma omp barrier - clientPtrs.emplace_back(server.acceptClient()); - EXPECT_TRUE(clientPtrs.back()->isOpen()); - } - return clientPtrs; - } +// namespace sck::sample { +// std::vector accept(tcp::TcpServer& server, const +// std::size_t clients) { +// std::vector clientPtrs; +// clientPtrs.reserve(clients); +// for (std::size_t k = 0; k < clients; ++k) { +// #pragma omp barrier +// clientPtrs.emplace_back(server.acceptClient()); +// EXPECT_TRUE(clientPtrs.back()->isOpen()); +// } +// return clientPtrs; +// } - TcpClientHndlrPtr accept(const std::uint16_t port) { - tcp::TcpServer server(port); - open(server); - return std::move(accept(server, 1).front()); - } +// TcpClientHndlrPtr accept(const std::uint16_t port) { +// tcp::TcpServer server(port); +// open(server); +// return std::move(accept(server, 1).front()); +// } - void openTcpClient(tcp::TcpClient& client) { - EXPECT_FALSE(client.isOpen()); -#pragma omp barrier - open(client); - } -} +// void openTcpClient(tcp::TcpClient& client) { +// EXPECT_FALSE(client.isOpen()); +// #pragma omp barrier +// open(client); +// } +// } diff --git a/tests/UtilsTest/src/UdpCommon.cpp b/tests/UtilsTest/src/UdpCommon.cpp index 9fd7a0f0..dcf2b02d 100644 --- a/tests/UtilsTest/src/UdpCommon.cpp +++ b/tests/UtilsTest/src/UdpCommon.cpp @@ -1,20 +1,25 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ +// /** +// * Author: Andrea Casalino +// * Created: 01.28.2020 +// * +// * report any bug to andrecasa91@gmail.com. +// **/ -#include +// #include -namespace sck::sample { - std::pair makeOpenedUdpConnections(const std::uint16_t portA, const std::uint16_t portB) { - UdpConnectionPtr connA = std::make_unique(*Ip::createLocalHost(portB) , portA); - UdpConnectionPtr connB = std::make_unique(*Ip::createLocalHost(portA) , portB); +// namespace sck::sample { +// std::pair +// makeOpenedUdpConnections(const std::uint16_t portA, const std::uint16_t +// portB) { +// UdpConnectionPtr connA = +// std::make_unique(*Ip::createLocalHost(portB) , +// portA); UdpConnectionPtr connB = +// std::make_unique(*Ip::createLocalHost(portA) , +// portB); - open(*connA); - open(*connB); +// open(*connA); +// open(*connB); - return std::make_pair(std::move(connA), std::move(connB)); - }; -} +// return std::make_pair(std::move(connA), std::move(connB)); +// }; +// } From aa64eed1ca85388501b5ceed953b06ab857c4daa Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 8 May 2022 14:57:12 +0100 Subject: [PATCH 101/228] refactoring --- .../SynchSocket/include/udp/UdpConnection.h | 53 +++++----- CrossSocket/SynchSocket/src/tcp/TcpServer.cpp | 96 ++++++++++--------- TODO | 4 +- src/header/MinimalSocket/core/Address.h | 8 +- src/header/MinimalSocket/core/Socket.h | 6 +- src/src/core/Address.cpp | 31 +++--- src/src/core/Socket.cpp | 4 + src/src/core/SocketContext.cpp | 18 ++++ tests/Test01-tcp.cpp | 6 +- 9 files changed, 128 insertions(+), 98 deletions(-) create mode 100644 src/src/core/SocketContext.cpp diff --git a/CrossSocket/SynchSocket/include/udp/UdpConnection.h b/CrossSocket/SynchSocket/include/udp/UdpConnection.h index 6a9fbffe..a4fe04c0 100644 --- a/CrossSocket/SynchSocket/include/udp/UdpConnection.h +++ b/CrossSocket/SynchSocket/include/udp/UdpConnection.h @@ -8,36 +8,35 @@ #ifndef _CROSS_SOCKET_UDPCONNECTION_H_ #define _CROSS_SOCKET_UDPCONNECTION_H_ -#include #include +#include namespace sck::udp { +/** + * @brief refer to + * https://en.wikipedia.org/wiki/User_Datagram_Protocol#:~:text=The%20field%20size%20sets%20a,−%2020%20byte%20IP%20header). + */ +static constexpr std::size_t MAX_UDP_RECV_MESSAGE = 65507; - /** - * @brief refer to https://en.wikipedia.org/wiki/User_Datagram_Protocol#:~:text=The%20field%20size%20sets%20a,−%2020%20byte%20IP%20header). - */ - constexpr std::size_t MAX_UDP_RECV_MESSAGE = 65507; - - /** - * @brief interface for a standard udp connection. - */ - class UdpConnection - : public ConnectionOpenable - , public BindCapable { - public: - /** - @param[in] Address of the remote host to hit - @param[in] port to reserve (passing 0 a random port is reserved) - */ - explicit UdpConnection(const sck::Ip& remoteAddress, const std::uint16_t& localPort = 0); - - protected: - std::uint16_t port; - - void openSteps() override; - - inline sck::Protocol getProtocol() const final { return Protocol::UDP; }; - }; -} +/** + * @brief interface for a standard udp connection. + */ +class UdpConnection : public ConnectionOpenable, public BindCapable { +public: + /** + @param[in] Address of the remote host to hit + @param[in] port to reserve (passing 0 a random port is reserved) + */ + explicit UdpConnection(const sck::Ip &remoteAddress, + const std::uint16_t &localPort = 0); + +protected: + std::uint16_t port; + + void openSteps() override; + + inline sck::Protocol getProtocol() const final { return Protocol::UDP; }; +}; +} // namespace sck::udp #endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp b/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp index 52d31003..b221b4cf 100644 --- a/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp +++ b/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp @@ -5,55 +5,61 @@ * report any bug to andrecasa91@gmail.com. **/ -#include #include "../Channel.h" #include +#include namespace sck::tcp { - constexpr std::size_t LISTEN_BACKLOG = 50; - - TcpClientHandler::TcpClientHandler(std::unique_ptr channel, const sck::Ip& remoteAddress) - : Connection(remoteAddress) { - this->channel = std::move(channel); - } - - TcpServer::TcpServer(const std::uint16_t& port, const Family& family) - : port(port) - , family(family) { - this->channel = std::make_unique(); - } - - std::unique_ptr TcpServer::acceptClient() { - if (!this->isOpen()) { - throw Error("a tcp server should be opened before accepting a new client"); - } - SocketIp acceptedClientAddress; +namespace { +static constexpr std::size_t LISTEN_BACKLOG = 50; +} + +TcpClientHandler::TcpClientHandler(std::unique_ptr channel, + const sck::Ip &remoteAddress) + : Connection(remoteAddress) { + this->channel = std::move(channel); +} + +TcpServer::TcpServer(const std::uint16_t &port, const Family &family) + : port(port), family(family) { + this->channel = std::make_unique(); +} + +std::unique_ptr TcpServer::acceptClient() { + if (!this->isOpen()) { + throw Error("a tcp server should be opened before accepting a new client"); + } + SocketIp acceptedClientAddress; #ifdef _WIN32 - int acceptedAddressLength + int acceptedAddressLength #else - unsigned int acceptedAddressLength + unsigned int acceptedAddressLength #endif - = sizeof(SocketIp); - // accept: wait for a client to call connect and hit this server and get a pointer to this client. - SocketHandler temp = ::accept(**this->channel, &acceptedClientAddress, &acceptedAddressLength); - if (temp == SCK_INVALID_SOCKET) { - throwWithCode("Error: accepting new client"); - } - std::unique_ptr acceptedClientHandler = std::make_unique(temp); - - IpPtr remoteAddress = convert(acceptedClientAddress); - if (nullptr == remoteAddress) { - throw Error("accepted client remote address is not resolvable"); - } - - return std::unique_ptr(new TcpClientHandler(std::move(acceptedClientHandler), *remoteAddress)); - } - - void TcpServer::openSteps() { - this->bindToPort(this->port); - // listen to the port to allow all the following clients acceptance - if (::listen(**this->channel, LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { - throwWithCode("Error: listening on port: " + std::to_string(this->port)); - } - } -} \ No newline at end of file + = sizeof(SocketIp); + // accept: wait for a client to call connect and hit this server and get a + // pointer to this client. + SocketHandler temp = + ::accept(**this->channel, &acceptedClientAddress, &acceptedAddressLength); + if (temp == SCK_INVALID_SOCKET) { + throwWithCode("Error: accepting new client"); + } + std::unique_ptr acceptedClientHandler = + std::make_unique(temp); + + IpPtr remoteAddress = convert(acceptedClientAddress); + if (nullptr == remoteAddress) { + throw Error("accepted client remote address is not resolvable"); + } + + return std::unique_ptr( + new TcpClientHandler(std::move(acceptedClientHandler), *remoteAddress)); +} + +void TcpServer::openSteps() { + this->bindToPort(this->port); + // listen to the port to allow all the following clients acceptance + if (::listen(**this->channel, LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { + throwWithCode("Error: listening on port: " + std::to_string(this->port)); + } +} +} // namespace sck::tcp \ No newline at end of file diff --git a/TODO b/TODO index 8de267ac..8e07df13 100644 --- a/TODO +++ b/TODO @@ -6,8 +6,6 @@ check if there is a way to check if an external passed socket was open or not in support for bluetooth connection -variant per vari stati di udp connection - add timeout per udp connect in receive from, resize buffer a max size udp message @@ -18,4 +16,4 @@ usare noexpect keyword in c'tors? pass number of max clients tcp server should accept -mettere tutti constexpr static +e' thread safety assicurata nelle varie funzioni? diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index 2c004e60..db3aef8c 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include @@ -31,8 +30,7 @@ class Address { * @return nullptr if the host is invalid, otherwise a smart pointer * storing a usable ip */ - static std::unique_ptr
makeAddress(const std::string &hostIp, - const Port &port); + Address(const std::string &hostIp, const Port &port); /** * @return an ipv4 or ipv6 with localhost as host and the passed port @@ -66,6 +64,8 @@ std::string to_string(const Address &subject); std::optional deduceAddressFamily(const std::string &host_address); -bool isValidAddress(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/Socket.h b/src/header/MinimalSocket/core/Socket.h index 71be92d2..be557f2b 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -28,6 +28,8 @@ class Socket { bool isNull() const; + int accessSocketID() const; + protected: Socket(); @@ -40,8 +42,8 @@ class Socket { std::unique_ptr socket_id_wrapper; }; -bool operator==(std::nullptr_t, const Socket& subject); -bool operator==(const Socket& subject, std::nullptr_t); +bool operator==(std::nullptr_t, const Socket &subject); +bool operator==(const Socket &subject, std::nullptr_t); class Openable : public virtual Socket { public: diff --git a/src/src/core/Address.cpp b/src/src/core/Address.cpp index 556dfc1f..19d96880 100644 --- a/src/src/core/Address.cpp +++ b/src/src/core/Address.cpp @@ -13,24 +13,21 @@ #include namespace MinimalSocket { -std::unique_ptr
Address::makeAddress(const std::string &hostIp, - const Port &port) { - std::unique_ptr
result; - result.reset(new Address{}); - result->host = hostIp; - result->port = port; +Address::Address(const std::string &hostIp, const Port &port) { + this->host = hostIp; + this->port = port; if (std::nullopt != makeSocketIp4(hostIp, port)) { - result->family = AddressFamily::IP_V4; - return result; + this->family = AddressFamily::IP_V4; + return; } if (std::nullopt != makeSocketIp6(hostIp, port)) { - result->family = AddressFamily::IP_V6; - return result; + this->family = AddressFamily::IP_V6; + return; } - return nullptr; + this->host.clear(); } namespace { @@ -54,6 +51,14 @@ 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(); @@ -62,11 +67,11 @@ std::string to_string(const Address &subject) { std::optional deduceAddressFamily(const std::string &host_address) { - auto temp = Address::makeAddress(host_address, 0); + Address temp(host_address, 0); if (nullptr == temp) { return std::nullopt; } - return temp->getFamily(); + return temp.getFamily(); } bool isValidAddress(const std::string &host_address) { diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index c14f9ce5..ffac4e53 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -19,6 +19,10 @@ bool Socket::isNull() const { return socket_id_wrapper->access() == SCK_INVALID_SOCKET; } +int Socket::accessSocketID() const { + return static_cast(getIDWrapper().access()); +} + bool operator==(std::nullptr_t, const Socket &subject) { return subject.isNull(); } diff --git a/src/src/core/SocketContext.cpp b/src/src/core/SocketContext.cpp new file mode 100644 index 00000000..45eb2c13 --- /dev/null +++ b/src/src/core/SocketContext.cpp @@ -0,0 +1,18 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +namespace MinimalSocket { +RemoteAddressAware::RemoteAddressAware(const Address &address) + : remote_address(address) { + if (nullptr == getRemoteAddress()) { + throw Error{"Invalid address"}; + } +}; +} // namespace MinimalSocket diff --git a/tests/Test01-tcp.cpp b/tests/Test01-tcp.cpp index e2cd2204..48a28500 100644 --- a/tests/Test01-tcp.cpp +++ b/tests/Test01-tcp.cpp @@ -119,9 +119,8 @@ TEST(Tcp, SendReceive) { if (0 == omp_get_thread_num()) { for (std::size_t c = 0; c < cycles; ++c) { std::string buffer; - buffer.resize(message.size() * 2); + buffer.resize(message.size()); server.receive(buffer); - EXPECT_EQ(buffer.size(), message.size()); EXPECT_EQ(buffer, message); } } else { @@ -139,9 +138,8 @@ TEST(Tcp, SendReceive) { if (0 == omp_get_thread_num()) { for (std::size_t c = 0; c < cycles; ++c) { std::string buffer; - buffer.resize(message.size() * 2); + buffer.resize(message.size()); client.receive(buffer); - EXPECT_EQ(buffer.size(), message.size()); EXPECT_EQ(buffer, message); } } else { From a13ce73e3ea2008b1280fc4e67095dfa0379ee4a Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 8 May 2022 16:14:52 +0100 Subject: [PATCH 102/228] refactoring --- src/header/MinimalSocket/core/Address.h | 1 + src/header/MinimalSocket/core/Socket.h | 5 +- src/header/MinimalSocket/core/SocketContext.h | 2 +- src/src/Commons.cpp | 345 ------------------ src/src/SocketAddress.cpp | 127 +++++++ src/src/SocketAddress.h | 58 +++ src/src/SocketError.cpp | 25 ++ src/src/SocketError.h | 19 + src/src/SocketFunctions.cpp | 114 ++++++ src/src/SocketFunctions.h | 19 + src/src/SocketId.cpp | 105 ++++++ src/src/{Commons.h => SocketId.h} | 92 +---- src/src/Utils.cpp | 28 ++ src/src/Utils.h | 28 ++ src/src/core/Address.cpp | 9 +- src/src/core/Receiver.cpp | 7 +- src/src/core/Sender.cpp | 32 +- src/src/core/Socket.cpp | 19 +- src/src/tcp/TcpClient.cpp | 4 +- src/src/tcp/TcpServer.cpp | 19 +- src/src/udp/UdpSocket.cpp | 15 +- 21 files changed, 600 insertions(+), 473 deletions(-) delete mode 100644 src/src/Commons.cpp create mode 100644 src/src/SocketAddress.cpp create mode 100644 src/src/SocketAddress.h create mode 100644 src/src/SocketError.cpp create mode 100644 src/src/SocketError.h create mode 100644 src/src/SocketFunctions.cpp create mode 100644 src/src/SocketFunctions.h create mode 100644 src/src/SocketId.cpp rename src/src/{Commons.h => SocketId.h} (50%) create mode 100644 src/src/Utils.cpp create mode 100644 src/src/Utils.h diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index db3aef8c..7d2dc185 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -68,4 +68,5 @@ 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/Socket.h b/src/header/MinimalSocket/core/Socket.h index be557f2b..dac8bd76 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -18,6 +18,8 @@ using Buffer = std::string; void setZeros(Buffer &subject); +enum SocketType { UDP, TCP }; + class SocketIdWrapper; class Socket { public: @@ -26,7 +28,7 @@ class Socket { Socket(const Socket &) = delete; Socket &operator=(const Socket &) = delete; - bool isNull() const; + bool empty() const; int accessSocketID() const; @@ -37,6 +39,7 @@ class Socket { const SocketIdWrapper &getIDWrapper() const; SocketIdWrapper &getIDWrapper(); + void destroyIDWrapper(); private: std::unique_ptr socket_id_wrapper; diff --git a/src/header/MinimalSocket/core/SocketContext.h b/src/header/MinimalSocket/core/SocketContext.h index 2023688d..dc658262 100644 --- a/src/header/MinimalSocket/core/SocketContext.h +++ b/src/header/MinimalSocket/core/SocketContext.h @@ -18,7 +18,7 @@ class RemoteAddressAware { RemoteAddressAware &operator=(const RemoteAddressAware &) = default; protected: - RemoteAddressAware(const Address &address) : remote_address(address){}; + RemoteAddressAware(const Address &address); private: Address remote_address; diff --git a/src/src/Commons.cpp b/src/src/Commons.cpp deleted file mode 100644 index fb2ac8d8..00000000 --- a/src/src/Commons.cpp +++ /dev/null @@ -1,345 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifdef _WIN32 -#include -#else -#include -#endif - -#include "Commons.h" -#include - -namespace MinimalSocket { -int getLastErrorCode() { -#ifdef _WIN32 - return WSAGetLastError(); -#else - return static_cast(errno); -#endif -} - -void throwWithLastErrorCode(const std::string &what) { - throw Error(what, " , error code: ", getLastErrorCode()); -} - -std::optional makeSocketIp4(const std::string &raw_address, - const Port &port) { - std::optional result; - auto &result_ref = result.emplace(); - // set everything to 0 first - ::memset(&result_ref, 0, sizeof(SocketIp4)); - result_ref.sin_family = AF_INET; - result_ref.sin_port = htons(port); - - // try address conversion -#if !defined(_WIN32) - in_addr ia; - if (1 == ::inet_pton(AF_INET, raw_address.c_str(), &ia)) { - result_ref.sin_addr.s_addr = ia.s_addr; - return result; - } -#endif - - addrinfo *res, hints = addrinfo{}; - hints.ai_family = AF_INET; - hints.ai_socktype = 0; - hints.ai_protocol = 0; - - int gai_err = ::getaddrinfo(raw_address.c_str(), NULL, &hints, &res); - -#if !defined(_WIN32) - if (gai_err == EAI_SYSTEM) { - return std::nullopt; - } -#endif - if (gai_err != 0) { - return std::nullopt; - } - - auto ipv4 = reinterpret_cast(res->ai_addr); - result_ref.sin_addr.s_addr = ipv4->sin_addr.s_addr; - ::freeaddrinfo(res); - return result; -} - -std::optional makeSocketIp6(const std::string &raw_address, - const Port &port) { - std::optional result; - auto &result_ref = result.emplace(); - // set everything to 0 first - ::memset(&result_ref, 0, sizeof(SocketIp6)); - result_ref.sin6_family = AF_INET6; - result_ref.sin6_flowinfo = 0; - result_ref.sin6_port = htons(port); - - // try address conversion -#if !defined(_WIN32) - in6_addr ia; - if (1 == ::inet_pton(AF_INET6, raw_address.c_str(), &ia)) { - result_ref.sin6_addr = ia; - return result; - } -#endif - - addrinfo *res, hints = addrinfo{}; - hints.ai_family = AF_INET6; - hints.ai_socktype = 0; - hints.ai_protocol = 0; - - int gai_err = ::getaddrinfo(raw_address.c_str(), NULL, &hints, &res); - -#if !defined(_WIN32) - if (gai_err == EAI_SYSTEM) { - return std::nullopt; - } -#endif - if (gai_err != 0) { - return std::nullopt; - } - - auto ipv6 = reinterpret_cast(res->ai_addr); - result_ref.sin6_addr = ipv6->sin6_addr; - ::freeaddrinfo(res); - return result; -} - -Address make_address(const SocketIp &address) { - // refer to - // https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu - std::string ip; - Port port; - if (AF_INET == address.sa_family) { - // ipv4 address - // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to - // an ip that has no sense - ip = std::string( - ::inet_ntoa(reinterpret_cast(&address)->sin_addr)); - port = ntohs(reinterpret_cast(&address)->sin_port); - } else { - // ipv6 address - char temp[INET6_ADDRSTRLEN]; // this is the longest one - // refer to - // https://www.gnu.org/software/libc/manual/html_node/Host-Address-Functions.html - ::memset(temp, 0, INET6_ADDRSTRLEN); - ::inet_ntop(address.sa_family, &address, temp, INET6_ADDRSTRLEN); - ip = std::string(temp, INET6_ADDRSTRLEN); - port = ntohs(reinterpret_cast(&address)->sin6_port); - } - auto result = Address::makeAddress(ip, port); - if (nullptr == result) { - throw Error{"Invalid address"}; - } - return *result; -} - -void address_case(const AddressFamily &family, - const std::function &ipv4_case, - const std::function &ipv6_case) { - switch (family) { - case AddressFamily::IP_V4: - ipv4_case(); - break; - case AddressFamily::IP_V6: - ipv6_case(); - break; - default: - throw Error{"Unrecognized AddressFamily"}; - break; - } -} - -#ifdef _WIN32 -std::size_t SocketIdWrapper::SocketIDFactory::handlerCounter = 0; -std::mutex SocketIdWrapper::SocketIDFactory::handlerCounterMtx; - -void SocketIdWrapper::SocketIDFactory::beforeOpen() { - std::lock_guard hndLck(handlerCounterMtx); - ++handlerCounter; - if (1 == handlerCounter) { - // first socket opened - WSADATA wsa; - WSAStartup(MAKEWORD(2, 0), &wsa); - } -} - -void SocketIdWrapper::SocketIDFactory::afterClose() { - std::lock_guard hndLck(handlerCounterMtx); - --handlerCounter; - if (0 == handlerCounter) { - // last socket closed - WSACleanup(); - } -} -#endif - -SocketIdWrapper::~SocketIdWrapper() { close(); } - -void SocketIdWrapper::reset(const SocketID &hndl) { - if (socket_id != SCK_INVALID_SOCKET) { - close(); - } - - this->socket_id = hndl; -} - -namespace { -int domain_number(const AddressFamily &family) { - int result; - address_case( - family, [&]() { result = static_cast(AF_INET); }, - [&]() { result = static_cast(AF_INET6); }); - return result; -} -} // namespace - -void SocketIdWrapper::reset(const SocketType &type, - const AddressFamily &family) { - if (socket_id != SCK_INVALID_SOCKET) { - close(); - } - -#ifdef _WIN32 - SocketIDFactory::beforeOpen(); -#endif - - switch (type) { - case SocketType::TCP: - this->socket_id = ::socket(domain_number(family), SOCK_STREAM, 0); - if (this->socket_id == SCK_INVALID_SOCKET) { - this->close(); - throwWithLastErrorCode("Stream socket could not be created"); - } - break; - case SocketType::UDP: - this->socket_id = ::socket(domain_number(family), SOCK_DGRAM, 0); - if (this->socket_id == SCK_INVALID_SOCKET) { - this->close(); - throwWithLastErrorCode("DataGram socket could not be created"); - } - break; - default: - throw Error("unknown protocol type"); - } -} - -void SocketIdWrapper::close() { - if (socket_id == SCK_INVALID_SOCKET) { - return; - } -#ifdef _WIN32 - shutdown(this->socket_id, 2); - closesocket(this->socket_id); -#else - ::shutdown(this->socket_id, SHUT_RDWR); - ::close(this->socket_id); -#endif - this->socket_id = SCK_INVALID_SOCKET; -#ifdef _WIN32 - SocketIDFactory::afterClose(); -#endif -} - -namespace { -#ifdef _WIN32 -#define REBIND_OPTION SO_REUSEADDR -#else -#define REBIND_OPTION SO_REUSEPORT -#endif -} // namespace - -void bind(const SocketID &socket_id, const AddressFamily &family, - const Port &port) { - int reusePortOptVal = 1; - ::setsockopt(socket_id, SOL_SOCKET, REBIND_OPTION, - reinterpret_cast(&reusePortOptVal), - sizeof(int)); - - // bind the socket to the port - address_case( - family, - [&]() { - SocketIp4 addr; - ::memset(&addr, 0, sizeof(SocketIp4)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); -#ifdef _WIN32 - addr.sin_addr.s_addr = ADDR_ANY; -#else - addr.sin_addr.s_addr = htonl(INADDR_ANY); -#endif - if (::bind(socket_id, reinterpret_cast(&addr), - sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { - throwWithLastErrorCode("can't bind localhost on port: " + - std::to_string(port)); - } - }, - [&]() { - SocketIp6 addr; - ::memset(&addr, 0, sizeof(SocketIp6)); - addr.sin6_family = AF_INET6; - addr.sin6_flowinfo = 0; - addr.sin6_addr = - IN6ADDR_ANY_INIT; // apparently, there is no such a - // cross-system define for ipv6 addresses - addr.sin6_port = htons(port); - if (::bind(socket_id, reinterpret_cast(&addr), - sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { - throwWithLastErrorCode("can't bind localhost on port: " + - std::to_string(port)); - } - }); -} - -namespace { -static constexpr std::size_t LISTEN_BACKLOG = 50; -} - -void listen(const SocketID &socket_id) { - if (::listen(socket_id, LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { - throwWithLastErrorCode("Error: listening on reserved port"); - } -} - -void connect(const SocketID &socket_id, const Address &remote_address) { - address_case( - remote_address.getFamily(), - [&]() { - // v4 family - auto addr = - makeSocketIp4(remote_address.getHost(), remote_address.getPort()); - if (!addr) { - throw Error(to_string(remote_address), - " is an invalid server address"); - } - if (::connect(socket_id, reinterpret_cast(&(*addr)), - sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { - throwWithLastErrorCode("Connection can't be established"); - } - }, - [&]() { - // v6 family - auto addr = - makeSocketIp6(remote_address.getHost(), remote_address.getPort()); - if (!addr) { - throw Error(to_string(remote_address), - " is an invalid server address"); - } - if (::connect(socket_id, reinterpret_cast(&(*addr)), - sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { - throwWithLastErrorCode("Connection can't be established"); - } - }); -} -} // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/SocketAddress.cpp b/src/src/SocketAddress.cpp new file mode 100644 index 00000000..135f9ac6 --- /dev/null +++ b/src/src/SocketAddress.cpp @@ -0,0 +1,127 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include + +#include "SocketAddress.h" +#include "SocketError.h" + +namespace MinimalSocket { +std::optional toSocketAddressIpv4(const std::string &host, + const Port &port) { + std::optional result; + auto &result_ref = result.emplace(); + // set everything to 0 first + ::memset(&result_ref, 0, sizeof(SocketAddressIpv4)); + result_ref.sin_family = AF_INET; + result_ref.sin_port = htons(port); + + // try address conversion +#if !defined(_WIN32) + in_addr ia; + if (1 == ::inet_pton(AF_INET, host.c_str(), &ia)) { + result_ref.sin_addr.s_addr = ia.s_addr; + return result; + } +#endif + + addrinfo *res, hints = addrinfo{}; + hints.ai_family = AF_INET; + hints.ai_socktype = 0; + hints.ai_protocol = 0; + + int gai_err = ::getaddrinfo(host.c_str(), NULL, &hints, &res); + +#if !defined(_WIN32) + if (gai_err == EAI_SYSTEM) { + return std::nullopt; + } +#endif + if (gai_err != 0) { + return std::nullopt; + } + + auto ipv4 = reinterpret_cast(res->ai_addr); + result_ref.sin_addr.s_addr = ipv4->sin_addr.s_addr; + ::freeaddrinfo(res); + return result; +} + +std::optional toSocketAddressIpv6(const std::string &host, + const Port &port) { + std::optional result; + auto &result_ref = result.emplace(); + // set everything to 0 first + ::memset(&result_ref, 0, sizeof(SocketAddressIpv6)); + result_ref.sin6_family = AF_INET6; + result_ref.sin6_flowinfo = 0; + result_ref.sin6_port = htons(port); + + // try address conversion +#if !defined(_WIN32) + in6_addr ia; + if (1 == ::inet_pton(AF_INET6, host.c_str(), &ia)) { + result_ref.sin6_addr = ia; + return result; + } +#endif + + addrinfo *res, hints = addrinfo{}; + hints.ai_family = AF_INET6; + hints.ai_socktype = 0; + hints.ai_protocol = 0; + + int gai_err = ::getaddrinfo(host.c_str(), NULL, &hints, &res); + +#if !defined(_WIN32) + if (gai_err == EAI_SYSTEM) { + return std::nullopt; + } +#endif + if (gai_err != 0) { + return std::nullopt; + } + + auto ipv6 = reinterpret_cast(res->ai_addr); + result_ref.sin6_addr = ipv6->sin6_addr; + ::freeaddrinfo(res); + return result; +} + +Address toAddress(const SocketAddress &address) { + // refer to + // https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu + std::string ip; + Port port; + if (AF_INET == address.sa_family) { + // ipv4 address + // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to + // an ip that has no sense + ip = std::string(::inet_ntoa( + reinterpret_cast(&address)->sin_addr)); + port = + ntohs(reinterpret_cast(&address)->sin_port); + } else { + // ipv6 address + char temp[INET6_ADDRSTRLEN]; // this is the longest one + // refer to + // https://www.gnu.org/software/libc/manual/html_node/Host-Address-Functions.html + ::memset(temp, 0, INET6_ADDRSTRLEN); + ::inet_ntop(address.sa_family, &address, temp, INET6_ADDRSTRLEN); + ip = std::string(temp, INET6_ADDRSTRLEN); + port = + ntohs(reinterpret_cast(&address)->sin6_port); + } + return Address{ip, port}; +} +} // namespace MinimalSocket diff --git a/src/src/SocketAddress.h b/src/src/SocketAddress.h new file mode 100644 index 00000000..ab22999f --- /dev/null +++ b/src/src/SocketAddress.h @@ -0,0 +1,58 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include "SocketId.h" + +namespace MinimalSocket { +/** + * @brief representation of a generic socket address + */ +#ifdef _WIN32 +using SocketAddress = SOCKADDR; +#else +using SocketAddress = sockaddr; +#endif +/** + * @brief representation of an ipv4 socket address + */ +#ifdef _WIN32 +using SocketAddressIpv4 = SOCKADDR_IN; +#else +using SocketAddressIpv4 = sockaddr_in; +#endif +/** + * @brief representation of an ipv6 socket address + */ +#ifdef _WIN32 +using SocketAddressIpv6 = SOCKADDR_IN6; +#else +using SocketAddressIpv6 = sockaddr_in6; +#endif + +/** + * @brief checks the address syntax and in case + * it's valid as an ipv4, creates the socket API representation + * of the address + */ +std::optional toSocketAddressIpv4(const std::string &host, + const Port &port); +/** + * @brief checks the address syntax and in case + * it's valid as an ipv6, creates the socket API representation + * of the address + */ +std::optional toSocketAddressIpv6(const std::string &host, + const Port &port); + +/** + * @brief Convert a SocketAddress_t into an Address, internally + * deducing the family. + */ +Address toAddress(const SocketAddress &address); +} // namespace MinimalSocket diff --git a/src/src/SocketError.cpp b/src/src/SocketError.cpp new file mode 100644 index 00000000..35a3304e --- /dev/null +++ b/src/src/SocketError.cpp @@ -0,0 +1,25 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include "SocketError.h" +#include "SocketId.h" + +namespace MinimalSocket { +int getLastErrorCode() { +#ifdef _WIN32 + return WSAGetLastError(); +#else + return static_cast(errno); +#endif +} + +void throwWithLastErrorCode(const std::string &what) { + throw Error(what, " , error code: ", getLastErrorCode()); +} +} // namespace MinimalSocket diff --git a/src/src/SocketError.h b/src/src/SocketError.h new file mode 100644 index 00000000..7dcce091 --- /dev/null +++ b/src/src/SocketError.h @@ -0,0 +1,19 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include + +namespace MinimalSocket { +/** + * @brief returns the last error code raised by the socket API + */ +int getLastErrorCode(); + +void throwWithLastErrorCode(const std::string &what); +} // namespace MinimalSocket diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp new file mode 100644 index 00000000..1377f0e6 --- /dev/null +++ b/src/src/SocketFunctions.cpp @@ -0,0 +1,114 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include "SocketAddress.h" +#include "SocketError.h" +#include "SocketFunctions.h" +#include "Utils.h" + +namespace MinimalSocket { +namespace { +#ifdef _WIN32 +#define REBIND_OPTION SO_REUSEADDR +#else +#define REBIND_OPTION SO_REUSEPORT +#endif +} // namespace + +void bind(const SocketID &socket_id, const AddressFamily &family, + const Port &port) { + int reusePortOptVal = 1; + ::setsockopt(socket_id, SOL_SOCKET, REBIND_OPTION, + reinterpret_cast(&reusePortOptVal), + sizeof(int)); + + // bind the socket to the port + visitAddress( + family, + [&]() { + SocketAddressIpv4 addr; + ::memset(&addr, 0, sizeof(SocketAddressIpv4)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); +#ifdef _WIN32 + addr.sin_addr.s_addr = ADDR_ANY; +#else + addr.sin_addr.s_addr = htonl(INADDR_ANY); +#endif + if (::bind(socket_id, reinterpret_cast(&addr), + sizeof(SocketAddressIpv4)) == SCK_SOCKET_ERROR) { + throwWithLastErrorCode("can't bind localhost on port: " + + std::to_string(port)); + } + }, + [&]() { + SocketAddressIpv6 addr; + ::memset(&addr, 0, sizeof(SocketAddressIpv6)); + addr.sin6_family = AF_INET6; + addr.sin6_flowinfo = 0; + addr.sin6_addr = + IN6ADDR_ANY_INIT; // apparently, there is no such a + // cross-system define for ipv6 addresses + addr.sin6_port = htons(port); + if (::bind(socket_id, reinterpret_cast(&addr), + sizeof(SocketAddressIpv6)) == SCK_SOCKET_ERROR) { + throwWithLastErrorCode("can't bind localhost on port: " + + std::to_string(port)); + } + }); +} + +namespace { +static constexpr std::size_t LISTEN_BACKLOG = 50; +} + +void listen(const SocketID &socket_id) { + if (::listen(socket_id, LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { + throwWithLastErrorCode("Error: listening on reserved port"); + } +} + +void connect(const SocketID &socket_id, const Address &remote_address) { + visitAddress( + remote_address.getFamily(), + [&]() { + // v4 family + auto addr = toSocketAddressIpv4(remote_address.getHost(), + remote_address.getPort()); + if (!addr) { + throw Error(to_string(remote_address), + " is an invalid server address"); + } + if (::connect(socket_id, reinterpret_cast(&(*addr)), + sizeof(SocketAddressIpv4)) == SCK_SOCKET_ERROR) { + throwWithLastErrorCode("Connection can't be established"); + } + }, + [&]() { + // v6 family + auto addr = toSocketAddressIpv6(remote_address.getHost(), + remote_address.getPort()); + if (!addr) { + throw Error(to_string(remote_address), + " is an invalid server address"); + } + if (::connect(socket_id, reinterpret_cast(&(*addr)), + sizeof(SocketAddressIpv6)) == SCK_SOCKET_ERROR) { + throwWithLastErrorCode("Connection can't be established"); + } + }); +} +} // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/SocketFunctions.h b/src/src/SocketFunctions.h new file mode 100644 index 00000000..7b847c6f --- /dev/null +++ b/src/src/SocketFunctions.h @@ -0,0 +1,19 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include "SocketId.h" + +namespace MinimalSocket { +void bind(const SocketID &socket_id, const AddressFamily &family, + const Port &port); + +void listen(const SocketID &socket_id); + +void connect(const SocketID &socket_id, const Address &remote_address); +} // namespace MinimalSocket diff --git a/src/src/SocketId.cpp b/src/src/SocketId.cpp new file mode 100644 index 00000000..5f44ac7f --- /dev/null +++ b/src/src/SocketId.cpp @@ -0,0 +1,105 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include "SocketError.h" +#include "Utils.h" + +namespace MinimalSocket { +#ifdef _WIN32 +std::size_t SocketIdWrapper::SocketIDFactory::handlerCounter = 0; +std::mutex SocketIdWrapper::SocketIDFactory::handlerCounterMtx; + +void SocketIdWrapper::SocketIDFactory::beforeOpen() { + std::lock_guard hndLck(handlerCounterMtx); + ++handlerCounter; + if (1 == handlerCounter) { + // first socket opened + WSADATA wsa; + WSAStartup(MAKEWORD(2, 0), &wsa); + } +} + +void SocketIdWrapper::SocketIDFactory::afterClose() { + std::lock_guard hndLck(handlerCounterMtx); + --handlerCounter; + if (0 == handlerCounter) { + // last socket closed + WSACleanup(); + } +} +#endif + +namespace { +void close(SocketID &socket_id) { + if (socket_id == SCK_INVALID_SOCKET) { + return; + } +#ifdef _WIN32 + shutdown(socket_id, 2); + closesocket(socket_id); +#else + ::shutdown(socket_id, SHUT_RDWR); + ::close(socket_id); +#endif + socket_id = SCK_INVALID_SOCKET; +#ifdef _WIN32 + SocketIDFactory::afterClose(); +#endif +} +} // namespace + +SocketIdWrapper::~SocketIdWrapper() { MinimalSocket::close(socket_id); } + +void SocketIdWrapper::reset(const SocketID &hndl) { + if (socket_id != SCK_INVALID_SOCKET) { + MinimalSocket::close(socket_id); + } + this->socket_id = hndl; +} + +namespace { +int domain_number(const AddressFamily &family) { + int result; + visitAddress( + family, [&]() { result = static_cast(AF_INET); }, + [&]() { result = static_cast(AF_INET6); }); + return result; +} +} // namespace + +void SocketIdWrapper::reset(const SocketType &type, + const AddressFamily &family) { + if (socket_id != SCK_INVALID_SOCKET) { + MinimalSocket::close(socket_id); + } + +#ifdef _WIN32 + SocketIDFactory::beforeOpen(); +#endif + + switch (type) { + case SocketType::TCP: + this->socket_id = ::socket(domain_number(family), SOCK_STREAM, 0); + if (this->socket_id == SCK_INVALID_SOCKET) { + MinimalSocket::close(socket_id); + throwWithLastErrorCode("Stream socket could not be created"); + } + break; + case SocketType::UDP: + this->socket_id = ::socket(domain_number(family), SOCK_DGRAM, 0); + if (this->socket_id == SCK_INVALID_SOCKET) { + MinimalSocket::close(socket_id); + throwWithLastErrorCode("DataGram socket could not be created"); + } + break; + default: + throw Error("unknown protocol type"); + } +} +} // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/Commons.h b/src/src/SocketId.h similarity index 50% rename from src/src/Commons.h rename to src/src/SocketId.h index 45218016..cdd6d86f 100644 --- a/src/src/Commons.h +++ b/src/src/SocketId.h @@ -8,89 +8,33 @@ #pragma once #include - -#include -#include -#include -#include +#include #ifdef _WIN32 +#include #include #include #include #include -#define SCK_INVALID_SOCKET INVALID_SOCKET -#define SCK_SOCKET_ERROR SOCKET_ERROR #else #include #include #include //memset #include #include //close -#define SCK_INVALID_SOCKET -1 -#define SCK_SOCKET_ERROR -1 #endif namespace MinimalSocket { -/** - * @brief representation of a generic socket address - */ -#ifdef _WIN32 -using SocketIp = SOCKADDR; -#else -using SocketIp = sockaddr; -#endif -/** - * @brief representation of an ipv4 socket address - */ -#ifdef _WIN32 -using SocketIp4 = SOCKADDR_IN; -#else -using SocketIp4 = sockaddr_in; -#endif -/** - * @brief representation of an ipv6 socket address - */ #ifdef _WIN32 -using SocketIp6 = SOCKADDR_IN6; +#define SCK_INVALID_SOCKET INVALID_SOCKET +#define SCK_SOCKET_ERROR SOCKET_ERROR #else -using SocketIp6 = sockaddr_in6; +#define SCK_INVALID_SOCKET -1 +#define SCK_SOCKET_ERROR -1 #endif /** - * @brief returns the last error code raised by the socket API - */ -int getLastErrorCode(); - -void throwWithLastErrorCode(const std::string &what); - -/** - * @brief checks the address syntax and in case - * it's valid as an ipv4, creates the socket API representation - * of the address - */ -std::optional makeSocketIp4(const std::string &raw_address, - const Port &port); -/** - * @brief checks the address syntax and in case - * it's valid as an ipv6, creates the socket API representation - * of the address - */ -std::optional makeSocketIp6(const std::string &raw_address, - const Port &port); - -/** - * @brief Convert a SocketAddress_t into an Address, internally - * deducing the family. - */ -Address make_address(const SocketIp &address); - -void address_case(const AddressFamily &family, - const std::function &ipv4_case, - const std::function &ipv6_case); - -/** - * socket handle + * socket id */ #ifdef _WIN32 using SocketID = SOCKET; @@ -98,8 +42,6 @@ using SocketID = SOCKET; using SocketID = int; #endif -enum SocketType { UDP, TCP }; - /** * An object storing a socket API handler and containing the minimal * functionalities for interacting with it. @@ -109,11 +51,16 @@ class SocketIdWrapper { SocketIdWrapper(const SocketIdWrapper &) = delete; SocketIdWrapper &operator=(const SocketIdWrapper &) = delete; + const SocketID &accessId() const { return socket_id; }; + /** - * @brief a closed socket is created + * @brief an invalid socket id is created */ SocketIdWrapper() = default; + /** + * @brief close and shutdown the current socket + */ ~SocketIdWrapper(); /** @@ -127,13 +74,6 @@ class SocketIdWrapper { */ void reset(const SocketID &hndl); - /** - * @brief close and shutdown the current socket - */ - void close(); - - const SocketID &access() const { return socket_id; }; - private: SocketID socket_id = SCK_INVALID_SOCKET; @@ -156,10 +96,4 @@ class SocketIdWrapper { #endif }; -void bind(const SocketID &socket_id, const AddressFamily &family, - const Port &port); - -void listen(const SocketID &socket_id); - -void connect(const SocketID &socket_id, const Address &remote_address); } // namespace MinimalSocket diff --git a/src/src/Utils.cpp b/src/src/Utils.cpp new file mode 100644 index 00000000..42da5f4b --- /dev/null +++ b/src/src/Utils.cpp @@ -0,0 +1,28 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include "Utils.h" + +namespace MinimalSocket { +void visitAddress(const AddressFamily &family, + const std::function &ipv4_case, + const std::function &ipv6_case) { + switch (family) { + case AddressFamily::IP_V4: + ipv4_case(); + break; + case AddressFamily::IP_V6: + ipv6_case(); + break; + default: + throw Error{"Unrecognized AddressFamily"}; + break; + } +} +} // namespace MinimalSocket diff --git a/src/src/Utils.h b/src/src/Utils.h new file mode 100644 index 00000000..2a44f6fd --- /dev/null +++ b/src/src/Utils.h @@ -0,0 +1,28 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include "SocketId.h" + +#include + +namespace MinimalSocket { +void visitAddress(const AddressFamily &family, + const std::function &ipv4_case, + const std::function &ipv6_case); + +template +void move_as(Giver &giver, Receiver &receiver) { + static_cast(receiver) = std::move(static_cast(giver)); +} + +template +void copy_as(const Giver &giver, Receiver &receiver) { + static_cast(receiver) = static_cast(giver); +} +} // namespace MinimalSocket diff --git a/src/src/core/Address.cpp b/src/src/core/Address.cpp index 19d96880..f1b12e96 100644 --- a/src/src/core/Address.cpp +++ b/src/src/core/Address.cpp @@ -8,7 +8,8 @@ #include #include -#include "../Commons.h" +#include "../SocketAddress.h" +#include "../Utils.h" #include @@ -17,12 +18,12 @@ Address::Address(const std::string &hostIp, const Port &port) { this->host = hostIp; this->port = port; - if (std::nullopt != makeSocketIp4(hostIp, port)) { + if (std::nullopt != toSocketAddressIpv4(hostIp, port)) { this->family = AddressFamily::IP_V4; return; } - if (std::nullopt != makeSocketIp6(hostIp, port)) { + if (std::nullopt != toSocketAddressIpv6(hostIp, port)) { this->family = AddressFamily::IP_V6; return; } @@ -40,7 +41,7 @@ Address Address::makeLocalHost(const std::uint16_t &port, Address result; result.port = port; result.family = family; - address_case( + visitAddress( family, [&result]() { result.host = LOCALHOST_IPv4; }, [&result]() { result.host = LOCALHOST_IPv6; }); return result; diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 229721ac..6062df56 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -7,7 +7,8 @@ #include -#include "../Commons.h" +#include "../SocketError.h" +#include "../SocketId.h" namespace MinimalSocket { void ReceiveTimeOutAware::lazyUpdateReceiveTimeout( @@ -33,7 +34,7 @@ void ReceiveTimeOutAware::lazyUpdateReceiveTimeout( std::chrono::duration_cast(receive_timeout) .count(); } - if (::setsockopt(getIDWrapper().access(), SOL_SOCKET, SO_RCVTIMEO, + if (::setsockopt(getIDWrapper().accessId(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(struct timeval)) < 0) { #endif @@ -43,7 +44,7 @@ void ReceiveTimeOutAware::lazyUpdateReceiveTimeout( void Receiver::receive(Buffer &message, const ReceiveTimeout &timeout) { std::lock_guard recvLock(receive_mtx); - int recvBytes = ::recv(getIDWrapper().access(), message.data(), + int recvBytes = ::recv(getIDWrapper().accessId(), message.data(), static_cast(message.size()), 0); if (recvBytes == SCK_SOCKET_ERROR) { recvBytes = 0; diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index e776d7b7..dfb8ecc5 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -7,12 +7,14 @@ #include -#include "../Commons.h" +#include "../SocketAddress.h" +#include "../SocketError.h" +#include "../Utils.h" namespace MinimalSocket { bool Sender::send(const Buffer &message) { std::scoped_lock lock(send_mtx); - int sentBytes = ::send(getIDWrapper().access(), message.data(), + int sentBytes = ::send(getIDWrapper().accessId(), message.data(), static_cast(message.size()), 0); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; @@ -24,25 +26,25 @@ bool Sender::send(const Buffer &message) { bool SenderTo::sendTo(const Buffer &message, const Address &recipient) { std::scoped_lock lock(send_mtx); int sentBytes; - address_case( + visitAddress( recipient.getFamily(), [&]() { auto socketIp4 = - makeSocketIp4(recipient.getHost(), recipient.getPort()); - sentBytes = - ::sendto(getIDWrapper().access(), message.data(), - static_cast(message.size()), 0, - reinterpret_cast(&socketIp4.value()), - sizeof(SocketIp4)); + toSocketAddressIpv4(recipient.getHost(), recipient.getPort()); + sentBytes = ::sendto( + getIDWrapper().accessId(), message.data(), + static_cast(message.size()), 0, + reinterpret_cast(&socketIp4.value()), + sizeof(SocketAddressIpv4)); }, [&]() { auto socketIp6 = - makeSocketIp6(recipient.getHost(), recipient.getPort()); - sentBytes = - ::sendto(getIDWrapper().access(), message.data(), - static_cast(message.size()), 0, - reinterpret_cast(&socketIp6.value()), - sizeof(SocketIp6)); + toSocketAddressIpv6(recipient.getHost(), recipient.getPort()); + sentBytes = ::sendto( + getIDWrapper().accessId(), message.data(), + static_cast(message.size()), 0, + reinterpret_cast(&socketIp6.value()), + sizeof(SocketAddressIpv6)); }); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index ffac4e53..1325f8db 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -8,31 +8,31 @@ #include #include -#include "../Commons.h" +#include "../SocketId.h" namespace MinimalSocket { Socket::~Socket() = default; Socket::Socket() { socket_id_wrapper = std::make_unique(); } -bool Socket::isNull() const { - return socket_id_wrapper->access() == SCK_INVALID_SOCKET; +bool Socket::empty() const { + return socket_id_wrapper->accessId() == SCK_INVALID_SOCKET; } int Socket::accessSocketID() const { - return static_cast(getIDWrapper().access()); + return static_cast(getIDWrapper().accessId()); } bool operator==(std::nullptr_t, const Socket &subject) { - return subject.isNull(); + return subject.empty(); } bool operator==(const Socket &subject, std::nullptr_t) { - return subject.isNull(); + return subject.empty(); } void Socket::transferIDWrapper(Socket &giver, Socket &recipient) { recipient.socket_id_wrapper = std::move(giver.socket_id_wrapper); - giver.socket_id_wrapper = std::make_unique(); + // giver.socket_id_wrapper = std::make_unique(); } const SocketIdWrapper &Socket::getIDWrapper() const { @@ -44,13 +44,16 @@ bool Openable::open() { if (opened) { throw Error{"Already opened"}; } + if (nullptr == static_cast(*this)) { + throw Error{"Can't open invalidated socket"}; + } std::scoped_lock lock(open_procedure_mtx); bool success = true; try { this->open_(); opened = true; } catch (const Error &) { - getIDWrapper().close(); + destroyIDWrapper(); success = false; } return success; diff --git a/src/src/tcp/TcpClient.cpp b/src/src/tcp/TcpClient.cpp index acca768b..e3d5a982 100644 --- a/src/src/tcp/TcpClient.cpp +++ b/src/src/tcp/TcpClient.cpp @@ -8,7 +8,7 @@ #include #include -#include "../Commons.h" +#include "../SocketFunctions.h" namespace MinimalSocket::tcp { TcpClient::TcpClient(TcpClient &&o) : RemoteAddressAware(o) { @@ -27,6 +27,6 @@ void TcpClient::open_() { auto &socket = getIDWrapper(); const auto remote_address = getRemoteAddress(); socket.reset(TCP, remote_address.getFamily()); - connect(socket.access(), remote_address); + MinimalSocket::connect(socket.accessId(), remote_address); } } // namespace MinimalSocket::tcp diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 1922af40..85d3d247 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -8,7 +8,9 @@ #include #include -#include "../Commons.h" +#include "../SocketAddress.h" +#include "../SocketError.h" +#include "../SocketFunctions.h" namespace MinimalSocket::tcp { TcpServer::TcpServer(TcpServer &&o) @@ -32,30 +34,31 @@ void TcpServer::open_() { const auto port = getPortToBind(); const auto family = getRemoteAddressFamily(); socket.reset(TCP, family); - bind(socket.access(), family, port); - listen(socket.access()); + MinimalSocket::bind(socket.accessId(), family, port); + MinimalSocket::listen(socket.accessId()); } TcpConnection TcpServer::acceptNewClient() { if (!this->wasOpened()) { throw Error("Tcp server was not opened before starting to accept clients"); } - SocketIp acceptedClientAddress; + SocketAddress acceptedClientAddress; #ifdef _WIN32 int acceptedAddressLength #else unsigned int acceptedAddressLength #endif - = sizeof(SocketIp); + = sizeof(SocketAddress); // accept: wait for a client to call connect and hit this server and get a // pointer to this client. - SocketID accepted_client_socket_id = ::accept( - getIDWrapper().access(), &acceptedClientAddress, &acceptedAddressLength); + SocketID accepted_client_socket_id = + ::accept(getIDWrapper().accessId(), &acceptedClientAddress, + &acceptedAddressLength); if (accepted_client_socket_id == SCK_INVALID_SOCKET) { throwWithLastErrorCode("Error: accepting new client"); } - auto accepted_client_parsed_address = make_address(acceptedClientAddress); + auto accepted_client_parsed_address = toAddress(acceptedClientAddress); TcpConnection result(accepted_client_parsed_address); result.getIDWrapper().reset(accepted_client_socket_id); return std::move(result); diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index d0ba8f0e..f5128b96 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -8,7 +8,7 @@ #include #include -#include "../Commons.h" +#include "../SocketFunctions.h" namespace MinimalSocket::udp { UdpSender::UdpSender(const AddressFamily &accepted_connection_family) @@ -41,7 +41,8 @@ UdpBindable::UdpBindable(const Port port_to_bind, } void UdpBindable::open_() { - bind(getIDWrapper().access(), getRemoteAddressFamily(), getPortToBind()); + MinimalSocket::bind(getIDWrapper().accessId(), getRemoteAddressFamily(), + getPortToBind()); } UdpConnectable UdpBindable::connect(const Address &remote_address) { @@ -50,7 +51,7 @@ UdpConnectable UdpBindable::connect(const Address &remote_address) { } UdpConnectable result(getPortToBind(), remote_address); Socket::transferIDWrapper(*this, result); - MinimalSocket::connect(getIDWrapper().access(), remote_address); + MinimalSocket::connect(getIDWrapper().accessId(), remote_address); return std::move(result); } @@ -65,7 +66,7 @@ UdpConnectable::UdpConnectable(const Port &port, const Address &remote_address) : PortToBindAware(port), RemoteAddressAware(remote_address) {} UdpBindable UdpConnectable::disconnect() { - getIDWrapper().close(); + destroyIDWrapper(); UdpBindable result(getPortToBind(), getRemoteAddress().getFamily()); result.open(); return std::move(result); @@ -83,9 +84,9 @@ UdpConnectable &UdpConnectable::operator=(UdpConnectable &&o) { } void UdpConnectable::open_() { - const auto &socket_id = getIDWrapper().access(); + const auto &socket_id = getIDWrapper().accessId(); const auto &remote_address = getRemoteAddress(); - bind(socket_id, remote_address.getFamily(), getPortToBind()); - connect(socket_id, remote_address); + MinimalSocket::bind(socket_id, remote_address.getFamily(), getPortToBind()); + MinimalSocket::connect(socket_id, remote_address); } } // namespace MinimalSocket::udp From e4d293ce196fabd05ef3bf1eca7fb08005acca60 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 8 May 2022 17:01:42 +0100 Subject: [PATCH 103/228] refactoring --- src/header/MinimalSocket/core/Socket.h | 5 ++++- src/src/Utils.h | 13 +++++------ src/src/core/Socket.cpp | 17 ++++++++++++--- src/src/tcp/TcpClient.cpp | 7 +++--- src/src/tcp/TcpServer.cpp | 15 +++++++------ src/src/udp/UdpSocket.cpp | 30 ++++++++++++++++++-------- 6 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index dac8bd76..b3cf3541 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -35,7 +35,7 @@ class Socket { protected: Socket(); - static void transferIDWrapper(Socket &giver, Socket &recipient); + static void transfer(Socket &receiver, Socket &giver); const SocketIdWrapper &getIDWrapper() const; SocketIdWrapper &getIDWrapper(); @@ -56,6 +56,9 @@ class Openable : public virtual Socket { protected: Openable() = default; + static void transfer(Openable &receiver, + Openable &giver); // Socket::transfer(...) is also called + virtual void open_() = 0; private: diff --git a/src/src/Utils.h b/src/src/Utils.h index 2a44f6fd..f6ac4f6b 100644 --- a/src/src/Utils.h +++ b/src/src/Utils.h @@ -10,19 +10,16 @@ #include "SocketId.h" #include +#include namespace MinimalSocket { void visitAddress(const AddressFamily &family, const std::function &ipv4_case, const std::function &ipv6_case); -template -void move_as(Giver &giver, Receiver &receiver) { - static_cast(receiver) = std::move(static_cast(giver)); -} - -template -void copy_as(const Giver &giver, Receiver &receiver) { - static_cast(receiver) = static_cast(giver); +template void copy_as(U &receiver, const U &giver) { + T &receiver_ref = receiver; + const T &giver_ref = giver; + receiver_ref = giver_ref; } } // namespace MinimalSocket diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 1325f8db..047be502 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -9,6 +9,7 @@ #include #include "../SocketId.h" +#include "../Utils.h" namespace MinimalSocket { Socket::~Socket() = default; @@ -30,9 +31,8 @@ bool operator==(const Socket &subject, std::nullptr_t) { return subject.empty(); } -void Socket::transferIDWrapper(Socket &giver, Socket &recipient) { - recipient.socket_id_wrapper = std::move(giver.socket_id_wrapper); - // giver.socket_id_wrapper = std::make_unique(); +void Socket::transfer(Socket &receiver, Socket &giver) { + receiver.socket_id_wrapper = std::move(giver.socket_id_wrapper); } const SocketIdWrapper &Socket::getIDWrapper() const { @@ -40,6 +40,8 @@ const SocketIdWrapper &Socket::getIDWrapper() const { } SocketIdWrapper &Socket::getIDWrapper() { return *socket_id_wrapper; } +void Socket::destroyIDWrapper() { socket_id_wrapper.reset(); } + bool Openable::open() { if (opened) { throw Error{"Already opened"}; @@ -58,4 +60,13 @@ bool Openable::open() { } return success; } + +void Openable::transfer(Openable &receiver, Openable &giver) { + std::scoped_lock lock(receiver.open_procedure_mtx, giver.open_procedure_mtx); + const bool o_value = giver.opened; + receiver.opened = o_value; + giver.opened = false; + Socket::transfer(receiver, giver); +} + } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/tcp/TcpClient.cpp b/src/src/tcp/TcpClient.cpp index e3d5a982..efa9bce6 100644 --- a/src/src/tcp/TcpClient.cpp +++ b/src/src/tcp/TcpClient.cpp @@ -9,14 +9,15 @@ #include #include "../SocketFunctions.h" +#include "../Utils.h" namespace MinimalSocket::tcp { TcpClient::TcpClient(TcpClient &&o) : RemoteAddressAware(o) { - Socket::transferIDWrapper(o, *this); + Openable::transfer(*this, o); } TcpClient &TcpClient::operator=(TcpClient &&o) { - static_cast(*this) = o; - Socket::transferIDWrapper(o, *this); + Openable::transfer(*this, o); + copy_as(*this, o); return *this; } diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 85d3d247..a5e06f61 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -11,16 +11,17 @@ #include "../SocketAddress.h" #include "../SocketError.h" #include "../SocketFunctions.h" +#include "../Utils.h" namespace MinimalSocket::tcp { TcpServer::TcpServer(TcpServer &&o) : PortToBindAware(o), RemoteAddressFamilyAware(o) { - Socket::transferIDWrapper(o, *this); + Openable::transfer(*this, o); } TcpServer &TcpServer::operator=(TcpServer &&o) { - static_cast(*this) = o; - static_cast(*this) = o; - Socket::transferIDWrapper(o, *this); + Openable::transfer(*this, o); + copy_as(*this, o); + copy_as(*this, o); return *this; } @@ -68,11 +69,11 @@ TcpConnection::TcpConnection(const Address &remote_address) : RemoteAddressAware(remote_address) {} TcpConnection::TcpConnection(TcpConnection &&o) : RemoteAddressAware(o) { - Socket::transferIDWrapper(o, *this); + Socket::transfer(*this, o); } TcpConnection &TcpConnection::operator=(TcpConnection &&o) { - static_cast(*this) = o; - Socket::transferIDWrapper(o, *this); + copy_as(*this, o); + Socket::transfer(*this, o); return *this; } } // namespace MinimalSocket::tcp diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index f5128b96..85902c75 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -9,6 +9,7 @@ #include #include "../SocketFunctions.h" +#include "../Utils.h" namespace MinimalSocket::udp { UdpSender::UdpSender(const AddressFamily &accepted_connection_family) @@ -18,18 +19,18 @@ UdpSender::UdpSender(const AddressFamily &accepted_connection_family) UdpBindable UdpSender::bind(const Port port_to_bind) { UdpBindable result(port_to_bind, getRemoteAddressFamily()); - Socket::transferIDWrapper(*this, result); + Socket::transfer(result, *this); result.open(); return std::move(result); } UdpSender::UdpSender(UdpSender &&o) : RemoteAddressFamilyAware(o.getRemoteAddressFamily()) { - Socket::transferIDWrapper(o, *this); + Socket::transfer(*this, o); } UdpSender &UdpSender::operator=(UdpSender &&o) { - Socket::transferIDWrapper(o, *this); - static_cast(*this) = o; + copy_as(*this, o); + Socket::transfer(*this, o); return *this; } @@ -40,6 +41,17 @@ UdpBindable::UdpBindable(const Port port_to_bind, getIDWrapper().reset(UDP, accepted_connection_family); } +UdpBindable::UdpBindable(UdpBindable &&o) + : PortToBindAware(o), RemoteAddressFamilyAware(o) { + Openable::transfer(*this, o); +} +UdpBindable &UdpBindable::operator=(UdpBindable &&o) { + copy_as(*this, o); + copy_as(*this, o); + Openable::transfer(*this, o); + return *this; +} + void UdpBindable::open_() { MinimalSocket::bind(getIDWrapper().accessId(), getRemoteAddressFamily(), getPortToBind()); @@ -50,7 +62,7 @@ UdpConnectable UdpBindable::connect(const Address &remote_address) { throw Error{"Passed address has invalid family"}; } UdpConnectable result(getPortToBind(), remote_address); - Socket::transferIDWrapper(*this, result); + Openable::transfer(result, *this); MinimalSocket::connect(getIDWrapper().accessId(), remote_address); return std::move(result); } @@ -74,12 +86,12 @@ UdpBindable UdpConnectable::disconnect() { UdpConnectable::UdpConnectable(UdpConnectable &&o) : PortToBindAware(o), RemoteAddressAware(o) { - Socket::transferIDWrapper(o, *this); + Openable::transfer(*this, o); } UdpConnectable &UdpConnectable::operator=(UdpConnectable &&o) { - Socket::transferIDWrapper(o, *this); - static_cast(*this) = o; - static_cast(*this) = o; + copy_as(*this, o); + copy_as(*this, o); + Openable::transfer(*this, o); return *this; } From 6a4fef66c30bdb8ba9b66fb3d6eadfff0c2ededb Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 8 May 2022 18:38:29 +0100 Subject: [PATCH 104/228] refactoring --- src/header/MinimalSocket/core/Socket.h | 2 +- src/src/core/Socket.cpp | 12 +- src/src/udp/UdpSocket.cpp | 37 ++-- tests/CMakeLists.txt | 28 ++- tests/Test00-address.cpp | 64 +++---- tests/Test01-tcp.cpp | 232 ++++++++++++------------- tests/Test01-udp.cpp | 169 +++++++++--------- tests/UtilsTest/CMakeLists.txt | 3 +- tests/UtilsTest/include/Parallel.h | 19 ++ tests/UtilsTest/src/Parallel.cpp | 36 ++++ 10 files changed, 317 insertions(+), 285 deletions(-) create mode 100644 tests/UtilsTest/include/Parallel.h create mode 100644 tests/UtilsTest/src/Parallel.cpp diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index b3cf3541..afb42a99 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -39,7 +39,7 @@ class Socket { const SocketIdWrapper &getIDWrapper() const; SocketIdWrapper &getIDWrapper(); - void destroyIDWrapper(); + void resetIDWrapper(); private: std::unique_ptr socket_id_wrapper; diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 047be502..e82e4ecf 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -14,7 +14,7 @@ namespace MinimalSocket { Socket::~Socket() = default; -Socket::Socket() { socket_id_wrapper = std::make_unique(); } +Socket::Socket() { resetIDWrapper(); } bool Socket::empty() const { return socket_id_wrapper->accessId() == SCK_INVALID_SOCKET; @@ -33,6 +33,7 @@ bool operator==(const Socket &subject, std::nullptr_t) { void Socket::transfer(Socket &receiver, Socket &giver) { receiver.socket_id_wrapper = std::move(giver.socket_id_wrapper); + giver.resetIDWrapper(); } const SocketIdWrapper &Socket::getIDWrapper() const { @@ -40,22 +41,21 @@ const SocketIdWrapper &Socket::getIDWrapper() const { } SocketIdWrapper &Socket::getIDWrapper() { return *socket_id_wrapper; } -void Socket::destroyIDWrapper() { socket_id_wrapper.reset(); } +void Socket::resetIDWrapper() { + socket_id_wrapper = std::make_unique(); +} bool Openable::open() { if (opened) { throw Error{"Already opened"}; } - if (nullptr == static_cast(*this)) { - throw Error{"Can't open invalidated socket"}; - } std::scoped_lock lock(open_procedure_mtx); bool success = true; try { this->open_(); opened = true; } catch (const Error &) { - destroyIDWrapper(); + resetIDWrapper(); success = false; } return success; diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index 85902c75..80022fe3 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -17,13 +17,6 @@ UdpSender::UdpSender(const AddressFamily &accepted_connection_family) getIDWrapper().reset(UDP, accepted_connection_family); } -UdpBindable UdpSender::bind(const Port port_to_bind) { - UdpBindable result(port_to_bind, getRemoteAddressFamily()); - Socket::transfer(result, *this); - result.open(); - return std::move(result); -} - UdpSender::UdpSender(UdpSender &&o) : RemoteAddressFamilyAware(o.getRemoteAddressFamily()) { Socket::transfer(*this, o); @@ -34,12 +27,16 @@ UdpSender &UdpSender::operator=(UdpSender &&o) { return *this; } +UdpBindable UdpSender::bind(const Port port_to_bind) { + UdpBindable result(port_to_bind, getRemoteAddressFamily()); + result.open(); + return std::move(result); +} + UdpBindable::UdpBindable(const Port port_to_bind, const AddressFamily &accepted_connection_family) : PortToBindAware(port_to_bind), - RemoteAddressFamilyAware(accepted_connection_family) { - getIDWrapper().reset(UDP, accepted_connection_family); -} + RemoteAddressFamilyAware(accepted_connection_family) {} UdpBindable::UdpBindable(UdpBindable &&o) : PortToBindAware(o), RemoteAddressFamilyAware(o) { @@ -53,6 +50,7 @@ UdpBindable &UdpBindable::operator=(UdpBindable &&o) { } void UdpBindable::open_() { + getIDWrapper().reset(UDP, getRemoteAddressFamily()); MinimalSocket::bind(getIDWrapper().accessId(), getRemoteAddressFamily(), getPortToBind()); } @@ -62,8 +60,10 @@ UdpConnectable UdpBindable::connect(const Address &remote_address) { throw Error{"Passed address has invalid family"}; } UdpConnectable result(getPortToBind(), remote_address); + if (wasOpened()) { + MinimalSocket::connect(getIDWrapper().accessId(), remote_address); + } Openable::transfer(result, *this); - MinimalSocket::connect(getIDWrapper().accessId(), remote_address); return std::move(result); } @@ -77,13 +77,6 @@ UdpConnectable UdpBindable::connect() { UdpConnectable::UdpConnectable(const Port &port, const Address &remote_address) : PortToBindAware(port), RemoteAddressAware(remote_address) {} -UdpBindable UdpConnectable::disconnect() { - destroyIDWrapper(); - UdpBindable result(getPortToBind(), getRemoteAddress().getFamily()); - result.open(); - return std::move(result); -} - UdpConnectable::UdpConnectable(UdpConnectable &&o) : PortToBindAware(o), RemoteAddressAware(o) { Openable::transfer(*this, o); @@ -98,7 +91,15 @@ UdpConnectable &UdpConnectable::operator=(UdpConnectable &&o) { void UdpConnectable::open_() { const auto &socket_id = getIDWrapper().accessId(); const auto &remote_address = getRemoteAddress(); + getIDWrapper().reset(UDP, remote_address.getFamily()); MinimalSocket::bind(socket_id, remote_address.getFamily(), getPortToBind()); MinimalSocket::connect(socket_id, remote_address); } + +UdpBindable UdpConnectable::disconnect() { + resetIDWrapper(); + UdpBindable result(getPortToBind(), getRemoteAddress().getFamily()); + result.open(); + return std::move(result); +} } // namespace MinimalSocket::udp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1a59adc6..f73b6549 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,26 +1,24 @@ project(MinimalCppSocket-Tests) -if (WIN32) - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -endif (WIN32) include(FetchContent) FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG f079775276a99fd4373569bf5761052a01844270 + catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG 4ff8b27bb6bed4b8b309e56cd269b4f1fbc24e89 ) -FetchContent_MakeAvailable(googletest) +FetchContent_MakeAvailable(catch2) add_subdirectory(UtilsTest) -function(MakeTest NAME) - add_executable(${NAME} ${NAME}.cpp) +function(MakeTest) + set(TEST_NAME "Tests") - target_link_libraries(${NAME} PUBLIC UtilsTest) - - install(TARGETS ${NAME}) + file(GLOB SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) + add_executable(${TEST_NAME} ${SRC_FILES}) + + target_link_libraries(${TEST_NAME} PUBLIC UtilsTest) + + install(TARGETS ${TEST_NAME}) endfunction() -MakeTest(Test00-address) -MakeTest(Test01-tcp) -MakeTest(Test01-udp) +MakeTest() diff --git a/tests/Test00-address.cpp b/tests/Test00-address.cpp index 3e257895..7042c8ae 100644 --- a/tests/Test00-address.cpp +++ b/tests/Test00-address.cpp @@ -1,49 +1,35 @@ -#include +#include +#include #include using namespace MinimalSocket; -TEST(Address, ParseIpv4) { - { - // valid addresses - std::vector addresses = {"192.168.125.34", "127.0.0.1"}; - for (const auto &address : addresses) { - auto converted = Address::makeAddress(address, 100); - EXPECT_FALSE(nullptr == converted); - } - } - { - // invalid addresses - std::vector addresses = {"192.125.34.34.34", "10000.0.0.1"}; - for (const auto &address : addresses) { - auto converted = Address::makeAddress(address, 100); - EXPECT_TRUE(nullptr == converted); - } - } +namespace { +static constexpr Port TEST_PORT = 100; } -TEST(Address, ParseIpv6) { - { - // valid addresses - std::vector addresses = { - "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8::1:0"}; - for (const auto &address : addresses) { - auto converted = Address::makeAddress(address, 100); - EXPECT_FALSE(nullptr == converted); - } - } - { - // invalid addresses - std::vector addresses = {"192.125.db8::1:0"}; - for (const auto &address : addresses) { - auto converted = Address::makeAddress(address, 100); - EXPECT_TRUE(nullptr == converted); - } - } +TEST_CASE("parse valid ipv4 hosts", "[address]") { + auto host = GENERATE("192.168.125.34", "127.0.0.1"); + Address converted(host, TEST_PORT); + CHECK_FALSE(nullptr == converted); + CHECK(converted.getFamily() == AddressFamily::IP_V4); + CHECK(converted.getHost() == host); + CHECK(converted.getPort() == TEST_PORT); } -int main(int argc, char *argv[]) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); +TEST_CASE("parse valid ipv6 hosts", "[address]") { + auto host = + GENERATE("2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8::1:0"); + Address converted(host, TEST_PORT); + CHECK_FALSE(nullptr == converted); + CHECK(converted.getFamily() == AddressFamily::IP_V6); + CHECK(converted.getHost() == host); + CHECK(converted.getPort() == TEST_PORT); +} + +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); } diff --git a/tests/Test01-tcp.cpp b/tests/Test01-tcp.cpp index 48a28500..c228d5a3 100644 --- a/tests/Test01-tcp.cpp +++ b/tests/Test01-tcp.cpp @@ -1,157 +1,143 @@ -#include +#include +#include + #include #include #include #include +#include #include using namespace MinimalSocket; using namespace MinimalSocket::tcp; using namespace MinimalSocket::test; -TEST(Tcp, Establish11Connection) { - const auto port = PortFactory::makePort(); +namespace { +struct SenderReceiver { + Sender &sender; + Receiver &receiver; +}; +template SenderReceiver makeSenderReceiver(T &subject) { + Sender &as_sender = subject; + Receiver &as_receiver = subject; + return SenderReceiver{as_sender, as_receiver}; +} -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // server - TcpServer server(port); - EXPECT_TRUE(server.open()); -#pragma omp barrier - auto client = server.acceptNewClient(); - EXPECT_FALSE(client.isNull()); - } else { - // client - TcpClient client(Address::makeLocalHost(port)); -#pragma omp barrier - EXPECT_TRUE(client.open()); - EXPECT_FALSE(client.isNull()); - } - } +void send_response(const SenderReceiver &requester, + const SenderReceiver &responder) { + std::size_t cycles = 5; + const std::string request = "Hello"; + const std::string response = "Welcome"; + + parallel( + [&]() { + for (std::size_t c = 0; c < cycles; ++c) { + CHECK(requester.sender.send(request)); + Buffer buffer; + buffer.resize(response.size()); + requester.receiver.receive(buffer); + CHECK(buffer == response); + } + }, + [&]() { + for (std::size_t c = 0; c < cycles; ++c) { + Buffer buffer; + buffer.resize(request.size()); + responder.receiver.receive(buffer); + CHECK(buffer == request); + CHECK(responder.sender.send(response)); + } + }); } +} // namespace -TEST(Tcp, Establish1ManyConnection) { +TEST_CASE("Establish tcp connection", "[tcp]") { const auto port = PortFactory::makePort(); - const std::size_t clients = 5; - -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // server - TcpServer server(port); - EXPECT_TRUE(server.open()); -#pragma omp barrier - for (std::size_t c = 0; c < clients; ++c) { - auto client = server.acceptNewClient(); - EXPECT_FALSE(client.isNull()); -#pragma omp barrier - } - } else { - // clients + + std::unique_ptr server_side; + std::unique_ptr client_side; + + parallel( + [&]() { + // server + TcpServer server(port); + server.open(); + REQUIRE(server.open()); #pragma omp barrier - for (std::size_t c = 0; c < clients; ++c) { + auto accepted = server.acceptNewClient(); + REQUIRE_FALSE(nullptr == accepted); + server_side = std::make_unique(std::move(accepted)); + }, + [&]() { + // client TcpClient client(Address::makeLocalHost(port)); - EXPECT_TRUE(client.open()); - EXPECT_FALSE(client.isNull()); #pragma omp barrier - } - } + REQUIRE(client.open()); + REQUIRE_FALSE(nullptr == client); + REQUIRE(client.wasOpened()); + client_side = std::make_unique(std::move(client)); + }); + + REQUIRE_FALSE(nullptr == *client_side); + REQUIRE(client_side->wasOpened()); + REQUIRE_FALSE(nullptr == *server_side); + + 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)); } -#pragma omp parallel num_threads(1 + clients) - { - if (0 == omp_get_thread_num()) { - // server - TcpServer server(port); - EXPECT_TRUE(server.open()); - std::list accepted; -#pragma omp barrier - for (std::size_t c = 0; c < clients; ++c) { - auto client = server.acceptNewClient(); - accepted.emplace_back(std::move(client)); - } -#pragma omp barrier - } else { - // clients -#pragma omp barrier - TcpClient client(Address::makeLocalHost(port)); - EXPECT_TRUE(client.open()); - EXPECT_FALSE(client.isNull()); -#pragma omp barrier - } + SECTION("server send, client respond") { + send_response(makeSenderReceiver(*server_side), + makeSenderReceiver(*client_side)); } } -struct ServerAndClient { - TcpConnection server; - TcpClient client; -}; -ServerAndClient make_server_and_client() { - auto port = PortFactory::makePort(); +TEST_CASE("Establish many tcp connections to same server", "[tcp]") { + const auto port = PortFactory::makePort(); + TcpServer server(port); server.open(); - TcpClient client(Address::makeLocalHost(port)); - std::unique_ptr accepted; - -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - accepted = std::make_unique(server.acceptNewClient()); - } else { - client.open(); - } - } - return ServerAndClient{std::move(*accepted), std::move(client)}; -} - -TEST(Tcp, SendReceive) { - const std::string message = "Message to send 11! $"; - const std::size_t cycles = 5; + const std::size_t clients_numb = 5; - { - auto [server, client] = make_server_and_client(); -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - for (std::size_t c = 0; c < cycles; ++c) { - std::string buffer; - buffer.resize(message.size()); - server.receive(buffer); - EXPECT_EQ(buffer, message); - } - } else { - for (std::size_t c = 0; c < cycles; ++c) { - client.send(message); - } - } - } + SECTION("sequencial connnections") { + std::list accepted_clients; + std::list clients; + parallel( + [&]() { + for (std::size_t c = 0; c < clients_numb; ++c) { + accepted_clients.emplace_back(server.acceptNewClient()); + } + }, + [&]() { + for (std::size_t c = 0; c < clients_numb; ++c) { + auto &client = clients.emplace_back(Address::makeLocalHost(port)); + CHECK(client.open()); + } + }); } - { - auto [server, client] = make_server_and_client(); -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - for (std::size_t c = 0; c < cycles; ++c) { - std::string buffer; - buffer.resize(message.size()); - client.receive(buffer); - EXPECT_EQ(buffer, message); - } - } else { - for (std::size_t c = 0; c < cycles; ++c) { - server.send(message); - } + SECTION("concurrent connnections") { + std::vector tasks; + tasks.emplace_back([&]() { + for (std::size_t c = 0; c < clients_numb; ++c) { + auto accepted = server.acceptNewClient(); } + }); + Task ask_connection = [&]() { + TcpClient client(Address::makeLocalHost(port)); + CHECK(client.open()); + }; + for (std::size_t c = 0; c < clients_numb; ++c) { + tasks.push_back(ask_connection); } + parallel(tasks); } } - -int main(int argc, char *argv[]) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tests/Test01-udp.cpp b/tests/Test01-udp.cpp index 843c20e2..a00c9653 100644 --- a/tests/Test01-udp.cpp +++ b/tests/Test01-udp.cpp @@ -1,91 +1,96 @@ -#include -#include -#include -using namespace sck; -using namespace sck::udp; +// #include +// #include +// #include +// using namespace sck; +// using namespace sck::udp; -TEST(UdpSynch, OpenClose) { - auto connections = sample::makeOpenedUdpConnections(sample::PortFactory::makePort(), sample::PortFactory::makePort()); +// TEST(UdpSynch, OpenClose) { +// auto connections = +// sample::makeOpenedUdpConnections(sample::PortFactory::makePort(), +// sample::PortFactory::makePort()); - #pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // connection A - sample::open(*connections.first); -#pragma omp barrier - sample::close(*connections.first); - } - else { - // connection B - sample::open(*connections.second); -#pragma omp barrier - sample::close(*connections.second); - } - } -} +// #pragma omp parallel num_threads(2) +// { +// if (0 == omp_get_thread_num()) { +// // connection A +// sample::open(*connections.first); +// #pragma omp barrier +// sample::close(*connections.first); +// } +// else { +// // connection B +// sample::open(*connections.second); +// #pragma omp barrier +// sample::close(*connections.second); +// } +// } +// } -#include -#include +// #include +// #include -TEST(UdpSynch, Asker_Responder) { - auto connections = sample::makeOpenedUdpConnections(sample::PortFactory::makePort(), sample::PortFactory::makePort()); - const std::size_t cycles = 5; +// TEST(UdpSynch, Asker_Responder) { +// auto connections = +// sample::makeOpenedUdpConnections(sample::PortFactory::makePort(), +// sample::PortFactory::makePort()); const std::size_t cycles = 5; -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // connection A - sample::Responder responder(std::move(connections.first)); - responder.respond(cycles); -#pragma omp barrier - } - else { - // conenction B - sample::Asker asker(std::move(connections.second)); - asker.ask(cycles); -#pragma omp barrier - } - } -} +// #pragma omp parallel num_threads(2) +// { +// if (0 == omp_get_thread_num()) { +// // connection A +// sample::Responder responder(std::move(connections.first)); +// responder.respond(cycles); +// #pragma omp barrier +// } +// else { +// // conenction B +// sample::Asker asker(std::move(connections.second)); +// asker.ask(cycles); +// #pragma omp barrier +// } +// } +// } -#include +// #include -TEST(UdpSynch, DISABLED_Asker_ServerResponder) { - const std::uint16_t portA = sample::PortFactory::makePort(); - const std::uint16_t portB = sample::PortFactory::makePort(); - const std::size_t cycles = 5; +// TEST(UdpSynch, DISABLED_Asker_ServerResponder) { +// const std::uint16_t portA = sample::PortFactory::makePort(); +// const std::uint16_t portB = sample::PortFactory::makePort(); +// const std::size_t cycles = 5; -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // server - std::unique_ptr server = std::make_unique(portA); - server->open(std::chrono::milliseconds(0)); - EXPECT_TRUE(server->isOpen()); -#pragma omp barrier - sample::Responder responder(std::move(server)); - responder.respond(cycles); -#pragma omp barrier - } - else { - // connection - std::unique_ptr connection = std::make_unique(*sck::Ip::createLocalHost(portA) , portB); - connection->open(std::chrono::milliseconds(0)); - EXPECT_TRUE(connection->isOpen()); - // send some bytes to establish the connection - { - std::string mex = "hello"; - connection->send(std::make_pair(mex.data(), mex.size())); - } -#pragma omp barrier - sample::Asker asker(std::move(connection)); - asker.ask(cycles); -#pragma omp barrier - } - } -} +// #pragma omp parallel num_threads(2) +// { +// if (0 == omp_get_thread_num()) { +// // server +// std::unique_ptr server = +// std::make_unique(portA); +// server->open(std::chrono::milliseconds(0)); +// EXPECT_TRUE(server->isOpen()); +// #pragma omp barrier +// sample::Responder responder(std::move(server)); +// responder.respond(cycles); +// #pragma omp barrier +// } +// else { +// // connection +// std::unique_ptr connection = +// std::make_unique(*sck::Ip::createLocalHost(portA) +// , portB); connection->open(std::chrono::milliseconds(0)); +// EXPECT_TRUE(connection->isOpen()); +// // send some bytes to establish the connection +// { +// std::string mex = "hello"; +// connection->send(std::make_pair(mex.data(), mex.size())); +// } +// #pragma omp barrier +// sample::Asker asker(std::move(connection)); +// asker.ask(cycles); +// #pragma omp barrier +// } +// } +// } -int main(int argc, char* argv[]) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} +// int main(int argc, char* argv[]) { +// ::testing::InitGoogleTest(&argc, argv); +// return RUN_ALL_TESTS(); +// } diff --git a/tests/UtilsTest/CMakeLists.txt b/tests/UtilsTest/CMakeLists.txt index 7984cf60..3117450a 100644 --- a/tests/UtilsTest/CMakeLists.txt +++ b/tests/UtilsTest/CMakeLists.txt @@ -6,6 +6,7 @@ find_package(OpenMP) target_link_libraries(${PROJECT_SHORTNAME} PUBLIC MinimalSocket - gtest OpenMP::OpenMP_CXX + Catch2::Catch2 + Catch2::Catch2WithMain ) diff --git a/tests/UtilsTest/include/Parallel.h b/tests/UtilsTest/include/Parallel.h new file mode 100644 index 00000000..a75ac5b8 --- /dev/null +++ b/tests/UtilsTest/include/Parallel.h @@ -0,0 +1,19 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include + +namespace MinimalSocket::test { +using Task = std::function; + +void parallel(const Task &master, const Task &slave); + +void parallel(const std::vector &tasks); +} // namespace MinimalSocket::test diff --git a/tests/UtilsTest/src/Parallel.cpp b/tests/UtilsTest/src/Parallel.cpp new file mode 100644 index 00000000..cbe0f72b --- /dev/null +++ b/tests/UtilsTest/src/Parallel.cpp @@ -0,0 +1,36 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include +#include + +namespace MinimalSocket::test { +void parallel(const Task &master, const Task &slave) { +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + master(); + } else { + slave(); + } + } +} + +void parallel(const std::vector &tasks) { + int threads = tasks.size(); + if (threads < 2) { + throw std::runtime_error{"invalid number of tasks for parallel region"}; + } + +#pragma omp parallel num_threads(threads) + { + const auto th_id = omp_get_thread_num(); + tasks[th_id](); + } +} +} // namespace MinimalSocket::test From a7ce6436a8d5077642b6a0f092da1b8869c1f5bb Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 8 May 2022 18:44:51 +0100 Subject: [PATCH 105/228] testing --- tests/Test01-tcp.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Test01-tcp.cpp b/tests/Test01-tcp.cpp index c228d5a3..ad84f4d3 100644 --- a/tests/Test01-tcp.cpp +++ b/tests/Test01-tcp.cpp @@ -63,7 +63,6 @@ TEST_CASE("Establish tcp connection", "[tcp]") { [&]() { // server TcpServer server(port); - server.open(); REQUIRE(server.open()); #pragma omp barrier auto accepted = server.acceptNewClient(); From 195918f30ffdae288cc5abb57d46bc09cc875538 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 8 May 2022 19:06:23 +0100 Subject: [PATCH 106/228] testing --- tests/Test01-tcp.cpp | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/tests/Test01-tcp.cpp b/tests/Test01-tcp.cpp index ad84f4d3..1c6cfd6e 100644 --- a/tests/Test01-tcp.cpp +++ b/tests/Test01-tcp.cpp @@ -56,13 +56,16 @@ void send_response(const SenderReceiver &requester, TEST_CASE("Establish tcp connection", "[tcp]") { const auto port = PortFactory::makePort(); + auto v4_or_v6 = true; // GENERATE(true, false); + const auto family = v4_or_v6 ? IP_V4 : IP_V6; + std::unique_ptr server_side; std::unique_ptr client_side; parallel( [&]() { // server - TcpServer server(port); + TcpServer server(port, family); REQUIRE(server.open()); #pragma omp barrier auto accepted = server.acceptNewClient(); @@ -71,7 +74,7 @@ TEST_CASE("Establish tcp connection", "[tcp]") { }, [&]() { // client - TcpClient client(Address::makeLocalHost(port)); + TcpClient client(Address::makeLocalHost(port, family)); #pragma omp barrier REQUIRE(client.open()); REQUIRE_FALSE(nullptr == client); @@ -101,7 +104,10 @@ TEST_CASE("Establish tcp connection", "[tcp]") { TEST_CASE("Establish many tcp connections to same server", "[tcp]") { const auto port = PortFactory::makePort(); - TcpServer server(port); + auto v4_or_v6 = true; // GENERATE(true, false); + const auto family = v4_or_v6 ? IP_V4 : IP_V6; + + TcpServer server(port, family); server.open(); const std::size_t clients_numb = 5; @@ -117,7 +123,8 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { }, [&]() { for (std::size_t c = 0; c < clients_numb; ++c) { - auto &client = clients.emplace_back(Address::makeLocalHost(port)); + auto &client = + clients.emplace_back(Address::makeLocalHost(port, family)); CHECK(client.open()); } }); @@ -131,7 +138,7 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { } }); Task ask_connection = [&]() { - TcpClient client(Address::makeLocalHost(port)); + TcpClient client(Address::makeLocalHost(port, family)); CHECK(client.open()); }; for (std::size_t c = 0; c < clients_numb; ++c) { @@ -140,3 +147,23 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { parallel(tasks); } } + +TEST_CASE("Open multiple times tcp clients", "[tcp]") { + const auto port = PortFactory::makePort(); + + auto v4_or_v6 = true; // GENERATE(true, false); + const auto family = v4_or_v6 ? IP_V4 : IP_V6; + + TcpServer server(port, family); + server.open(); + + std::size_t cycles = 5; + + TcpClient client(Address::makeLocalHost(family)); + + for (std::size_t c = 0; c < cycles; ++c) { + parallel([&]() { server.acceptNewClient(); }, + [&]() { CHECK(client.open()); }); + TcpClient{std::move(client)}; + } +} From 2468f2d3aebbbd66b033b25aff16e965f0153f1c Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 8 May 2022 22:19:57 +0100 Subject: [PATCH 107/228] testing --- TODO | 2 + tests/Test01-udp.cpp | 96 -------- tests/{Test00-address.cpp => TestAddress.cpp} | 0 tests/{Test01-tcp.cpp => TestTCP.cpp} | 12 +- tests/TestUDP.cpp | 223 ++++++++++++++++++ 5 files changed, 228 insertions(+), 105 deletions(-) delete mode 100644 tests/Test01-udp.cpp rename tests/{Test00-address.cpp => TestAddress.cpp} (100%) rename tests/{Test01-tcp.cpp => TestTCP.cpp} (93%) create mode 100644 tests/TestUDP.cpp diff --git a/TODO b/TODO index 8e07df13..945c7295 100644 --- a/TODO +++ b/TODO @@ -17,3 +17,5 @@ usare noexpect keyword in c'tors? pass number of max clients tcp server should accept e' thread safety assicurata nelle varie funzioni? + +check in c'tor passed address is not null (is valid) diff --git a/tests/Test01-udp.cpp b/tests/Test01-udp.cpp deleted file mode 100644 index a00c9653..00000000 --- a/tests/Test01-udp.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// #include -// #include -// #include -// using namespace sck; -// using namespace sck::udp; - -// TEST(UdpSynch, OpenClose) { -// auto connections = -// sample::makeOpenedUdpConnections(sample::PortFactory::makePort(), -// sample::PortFactory::makePort()); - -// #pragma omp parallel num_threads(2) -// { -// if (0 == omp_get_thread_num()) { -// // connection A -// sample::open(*connections.first); -// #pragma omp barrier -// sample::close(*connections.first); -// } -// else { -// // connection B -// sample::open(*connections.second); -// #pragma omp barrier -// sample::close(*connections.second); -// } -// } -// } - -// #include -// #include - -// TEST(UdpSynch, Asker_Responder) { -// auto connections = -// sample::makeOpenedUdpConnections(sample::PortFactory::makePort(), -// sample::PortFactory::makePort()); const std::size_t cycles = 5; - -// #pragma omp parallel num_threads(2) -// { -// if (0 == omp_get_thread_num()) { -// // connection A -// sample::Responder responder(std::move(connections.first)); -// responder.respond(cycles); -// #pragma omp barrier -// } -// else { -// // conenction B -// sample::Asker asker(std::move(connections.second)); -// asker.ask(cycles); -// #pragma omp barrier -// } -// } -// } - -// #include - -// TEST(UdpSynch, DISABLED_Asker_ServerResponder) { -// const std::uint16_t portA = sample::PortFactory::makePort(); -// const std::uint16_t portB = sample::PortFactory::makePort(); -// const std::size_t cycles = 5; - -// #pragma omp parallel num_threads(2) -// { -// if (0 == omp_get_thread_num()) { -// // server -// std::unique_ptr server = -// std::make_unique(portA); -// server->open(std::chrono::milliseconds(0)); -// EXPECT_TRUE(server->isOpen()); -// #pragma omp barrier -// sample::Responder responder(std::move(server)); -// responder.respond(cycles); -// #pragma omp barrier -// } -// else { -// // connection -// std::unique_ptr connection = -// std::make_unique(*sck::Ip::createLocalHost(portA) -// , portB); connection->open(std::chrono::milliseconds(0)); -// EXPECT_TRUE(connection->isOpen()); -// // send some bytes to establish the connection -// { -// std::string mex = "hello"; -// connection->send(std::make_pair(mex.data(), mex.size())); -// } -// #pragma omp barrier -// sample::Asker asker(std::move(connection)); -// asker.ask(cycles); -// #pragma omp barrier -// } -// } -// } - -// int main(int argc, char* argv[]) { -// ::testing::InitGoogleTest(&argc, argv); -// return RUN_ALL_TESTS(); -// } diff --git a/tests/Test00-address.cpp b/tests/TestAddress.cpp similarity index 100% rename from tests/Test00-address.cpp rename to tests/TestAddress.cpp diff --git a/tests/Test01-tcp.cpp b/tests/TestTCP.cpp similarity index 93% rename from tests/Test01-tcp.cpp rename to tests/TestTCP.cpp index 1c6cfd6e..27f926ea 100644 --- a/tests/Test01-tcp.cpp +++ b/tests/TestTCP.cpp @@ -55,9 +55,7 @@ void send_response(const SenderReceiver &requester, TEST_CASE("Establish tcp connection", "[tcp]") { const auto port = PortFactory::makePort(); - - auto v4_or_v6 = true; // GENERATE(true, false); - const auto family = v4_or_v6 ? IP_V4 : IP_V6; + const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); std::unique_ptr server_side; std::unique_ptr client_side; @@ -103,9 +101,7 @@ TEST_CASE("Establish tcp connection", "[tcp]") { TEST_CASE("Establish many tcp connections to same server", "[tcp]") { const auto port = PortFactory::makePort(); - - auto v4_or_v6 = true; // GENERATE(true, false); - const auto family = v4_or_v6 ? IP_V4 : IP_V6; + const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); TcpServer server(port, family); server.open(); @@ -150,9 +146,7 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { TEST_CASE("Open multiple times tcp clients", "[tcp]") { const auto port = PortFactory::makePort(); - - auto v4_or_v6 = true; // GENERATE(true, false); - const auto family = v4_or_v6 ? IP_V4 : IP_V6; + const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); TcpServer server(port, family); server.open(); diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp new file mode 100644 index 00000000..87136592 --- /dev/null +++ b/tests/TestUDP.cpp @@ -0,0 +1,223 @@ +#include +#include + +#include + +#include + +#include +#include + +using namespace MinimalSocket; +using namespace MinimalSocket::udp; +using namespace MinimalSocket::test; + +namespace { +static const std::string request = "Hello"; +static const std::string response = "Welcome"; +} // namespace + +TEST_CASE("exchange messages between UdpSender and UdpBindable", "[udp]") { + const auto receiver_port = PortFactory::makePort(); + const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); + + UdpSender sender(family); + UdpBindable receiver(receiver_port, family); + REQUIRE(receiver.open()); + REQUIRE_FALSE(nullptr == receiver); + + const std::size_t cycles = 5; + + const Address receiver_address = Address::makeLocalHost(receiver_port); + + parallel( + [&]() { + for (std::size_t c = 0; c < cycles; ++c) { + CHECK(sender.sendTo(request, receiver_address)); +#pragma omp barrier + } + }, + [&]() { + for (std::size_t c = 0; c < cycles; ++c) { + Buffer buffer; + buffer.resize(request.size()); +#pragma omp barrier + receiver.receive(buffer); + CHECK(buffer == request); + } + }); +} + +TEST_CASE("exchange messages between UdpBindable and UdpBindable", "[udp]") { + const auto receiver_port = PortFactory::makePort(); + const auto sender_port = PortFactory::makePort(); + const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); + + UdpBindable sender(sender_port, family); + REQUIRE(sender.open()); + UdpBindable receiver(receiver_port, family); + REQUIRE(receiver.open()); + + const std::size_t cycles = 5; + + const Address sender_address = Address::makeLocalHost(sender_port); + const Address receiver_address = Address::makeLocalHost(receiver_port); + + parallel( + [&]() { + for (std::size_t c = 0; c < cycles; ++c) { + CHECK(sender.sendTo(request, receiver_address)); +#pragma omp barrier + Buffer buffer; + buffer.resize(response.size()); +#pragma omp barrier + auto address = receiver.receive(buffer); + CHECK(buffer == response); + CHECK(address == receiver_address); + } + }, + [&]() { + for (std::size_t c = 0; c < cycles; ++c) { + Buffer buffer; + buffer.resize(request.size()); +#pragma omp barrier + auto address = receiver.receive(buffer); + CHECK(buffer == request); + CHECK(address == sender_address); + CHECK(receiver.sendTo(response, sender_address)); +#pragma omp barrier + } + }); +} + +TEST_CASE("exchange messages between UdpConnectable and UdpConnectable", + "[udp]") { + const auto receiver_port = PortFactory::makePort(); + const auto sender_port = PortFactory::makePort(); + const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); + + const Address sender_address = Address::makeLocalHost(sender_port); + const Address receiver_address = Address::makeLocalHost(receiver_port); + + UdpConnectable sender(sender_port, Address::makeLocalHost(family)); + REQUIRE(sender.open()); + UdpConnectable receiver(receiver_port, Address::makeLocalHost(family)); + REQUIRE(receiver.open()); + + const std::size_t cycles = 5; + + parallel( + [&]() { + for (std::size_t c = 0; c < cycles; ++c) { + CHECK(sender.send(request)); +#pragma omp barrier + Buffer buffer; + buffer.resize(response.size()); +#pragma omp barrier + receiver.receive(buffer); + CHECK(buffer == response); + } + }, + [&]() { + for (std::size_t c = 0; c < cycles; ++c) { + Buffer buffer; + buffer.resize(request.size()); +#pragma omp barrier + receiver.receive(buffer); + CHECK(buffer == request); + CHECK(receiver.send(response)); +#pragma omp barrier + } + }); +} + +TEST_CASE("Metamorphosis of udp connections", "[udp]") { + const auto receiver_port = PortFactory::makePort(); + const auto sender_port = PortFactory::makePort(); + const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); + + const Address sender_address = Address::makeLocalHost(sender_port); + const Address receiver_address = Address::makeLocalHost(receiver_port); + + UdpSender sender(family); + UdpBindable receiver(receiver_port, family); + REQUIRE(receiver.open()); + parallel( + [&]() { + CHECK(sender.sendTo(request, receiver_address)); +#pragma omp barrier + }, + [&]() { + Buffer buffer; + buffer.resize(request.size()); +#pragma omp barrier + auto address = receiver.receive(buffer); + CHECK(buffer == request); + CHECK(address == sender_address); + }); + + UdpBindable sender_binded = sender.bind(sender_port); + REQUIRE(sender_binded.wasOpened()); + parallel( + [&]() { + CHECK(sender_binded.sendTo(request, receiver_address)); +#pragma omp barrier + Buffer buffer; + buffer.resize(response.size()); +#pragma omp barrier + auto address = sender_binded.receive(buffer); + CHECK(buffer == response); + CHECK(address == receiver_address); + }, + [&]() { + Buffer buffer; + buffer.resize(request.size()); +#pragma omp barrier + auto address = receiver.receive(buffer); + CHECK(buffer == request); + CHECK(address == sender_address); + CHECK(receiver.sendTo(response, sender_address)); +#pragma omp barrier + }); + + auto deduce_sender = GENERATE(true, false); + + std::unique_ptr sender_connected; + if (deduce_sender) { + parallel( + [&]() { + receiver.sendTo("1", sender_address); +#pragma omp barrier + }, + [&]() { +#pragma omp barrier + sender_connected = + std::make_unique(sender_binded.connect()); + REQUIRE(sender_connected->getRemoteAddress() == receiver_address); + }); + } else { + sender_connected = std::make_unique( + sender_binded.connect(receiver_address)); + } + REQUIRE(sender_connected->wasOpened()); + parallel( + [&]() { + CHECK(sender_connected->send(request)); +#pragma omp barrier + Buffer buffer; + buffer.resize(response.size()); +#pragma omp barrier + sender_connected->receive(buffer); + CHECK(buffer == response); + }, + [&]() { + Buffer buffer; + buffer.resize(request.size()); +#pragma omp barrier + auto address = receiver.receive(buffer); + CHECK(buffer == request); + CHECK(address == sender_address); + CHECK(receiver.sendTo(response, sender_address)); +#pragma omp barrier + }); +} From 083bb573148e316238536ee81c611bed1d4c9ad3 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 8 May 2022 23:33:05 +0100 Subject: [PATCH 108/228] any port bindable --- src/header/MinimalSocket/core/Address.h | 2 ++ src/header/MinimalSocket/core/SocketContext.h | 2 ++ src/header/MinimalSocket/tcp/TcpServer.h | 2 +- src/header/MinimalSocket/udp/UdpSocket.h | 6 +++--- src/src/SocketAddress.h | 7 +++++++ src/src/SocketFunctions.cpp | 20 ++++++++++++++++++- src/src/SocketFunctions.h | 3 ++- src/src/tcp/TcpServer.cpp | 10 +++------- src/src/udp/UdpSocket.cpp | 13 +++++++----- tests/TestUDP.cpp | 4 ++-- 10 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index 7d2dc185..00913e35 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -19,6 +19,8 @@ enum AddressFamily { IP_V4, IP_V6 }; using Port = std::uint16_t; +static constexpr Port ANY_PORT = 0; + /** * @brief representation of a network ip address */ diff --git a/src/header/MinimalSocket/core/SocketContext.h b/src/header/MinimalSocket/core/SocketContext.h index dc658262..8d92f979 100644 --- a/src/header/MinimalSocket/core/SocketContext.h +++ b/src/header/MinimalSocket/core/SocketContext.h @@ -34,6 +34,8 @@ class PortToBindAware { protected: PortToBindAware(const Port &port) : port_to_bind(port){}; + void setPort(const Port &port) { port_to_bind = port; }; + private: Port port_to_bind; }; diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index d049eb1c..d04a3725 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -35,7 +35,7 @@ class TcpServer : public PortToBindAware, TcpServer(TcpServer &&o); TcpServer &operator=(TcpServer &&o); - TcpServer(const Port port_to_bind, + TcpServer(const Port port_to_bind = ANY_PORT, const AddressFamily &accepted_client_family = AddressFamily::IP_V4); TcpConnection acceptNewClient(); diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 4f8b3ffb..ac5f1093 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -31,7 +31,7 @@ class UdpSender : public SenderTo, public RemoteAddressFamilyAware { UdpSender( const AddressFamily &accepted_connection_family = AddressFamily::IP_V4); - UdpBindable bind(const Port port_to_bind); + UdpBindable bind(const Port port_to_bind = ANY_PORT); }; // can send and receive (from anyonw hitting it) as a port was reserved @@ -45,7 +45,7 @@ class UdpBindable : public SenderTo, UdpBindable &operator=(UdpBindable &&o); UdpBindable( - const Port port_to_bind, + const Port port_to_bind = ANY_PORT, const AddressFamily &accepted_connection_family = AddressFamily::IP_V4); // throw in case address family is inconsistent @@ -68,7 +68,7 @@ class UdpConnectable : public Sender, UdpConnectable(UdpConnectable &&o); UdpConnectable &operator=(UdpConnectable &&o); - UdpConnectable(const Port &port, const Address &remote_address); + UdpConnectable(const Address &remote_address, const Port &port = ANY_PORT); UdpBindable disconnect(); diff --git a/src/src/SocketAddress.h b/src/src/SocketAddress.h index ab22999f..d7520d4e 100644 --- a/src/src/SocketAddress.h +++ b/src/src/SocketAddress.h @@ -18,6 +18,13 @@ using SocketAddress = SOCKADDR; #else using SocketAddress = sockaddr; #endif + +#ifdef _WIN32 +using SocketAddressLength = int; +#else +using SocketAddressLength = unsigned int; +#endif + /** * @brief representation of an ipv4 socket address */ diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index 1377f0e6..a7808100 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -21,7 +21,7 @@ namespace { #endif } // namespace -void bind(const SocketID &socket_id, const AddressFamily &family, +Port bind(const SocketID &socket_id, const AddressFamily &family, const Port &port) { int reusePortOptVal = 1; ::setsockopt(socket_id, SOL_SOCKET, REBIND_OPTION, @@ -69,6 +69,24 @@ void bind(const SocketID &socket_id, const AddressFamily &family, std::to_string(port)); } }); + + Port binded_port = port; + if (ANY_PORT == port) { + SocketAddress binded_address; + SocketAddressLength binded_address_length; + if (::getsockname(socket_id, &binded_address, &binded_address_length) == + SCK_SOCKET_ERROR) { + throwWithLastErrorCode("Wasn't able to deduce the binded port"); + } + if (AF_INET == binded_address.sa_family) { + binded_port = + reinterpret_cast(binded_address).sin_port; + } else { + binded_port = + reinterpret_cast(binded_address).sin6_port; + } + } + return binded_port; } namespace { diff --git a/src/src/SocketFunctions.h b/src/src/SocketFunctions.h index 7b847c6f..64da8527 100644 --- a/src/src/SocketFunctions.h +++ b/src/src/SocketFunctions.h @@ -10,7 +10,8 @@ #include "SocketId.h" namespace MinimalSocket { -void bind(const SocketID &socket_id, const AddressFamily &family, +// return port actually binded (you can pass to the function also AnyPort) +Port bind(const SocketID &socket_id, const AddressFamily &family, const Port &port); void listen(const SocketID &socket_id); diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index a5e06f61..c92489af 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -35,7 +35,8 @@ void TcpServer::open_() { const auto port = getPortToBind(); const auto family = getRemoteAddressFamily(); socket.reset(TCP, family); - MinimalSocket::bind(socket.accessId(), family, port); + auto binded_port = MinimalSocket::bind(socket.accessId(), family, port); + setPort(binded_port); MinimalSocket::listen(socket.accessId()); } @@ -44,12 +45,7 @@ TcpConnection TcpServer::acceptNewClient() { throw Error("Tcp server was not opened before starting to accept clients"); } SocketAddress acceptedClientAddress; -#ifdef _WIN32 - int acceptedAddressLength -#else - unsigned int acceptedAddressLength -#endif - = sizeof(SocketAddress); + SocketAddressLength acceptedAddressLength = sizeof(SocketAddress); // accept: wait for a client to call connect and hit this server and get a // pointer to this client. SocketID accepted_client_socket_id = diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index 80022fe3..cb083a65 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -51,15 +51,16 @@ UdpBindable &UdpBindable::operator=(UdpBindable &&o) { void UdpBindable::open_() { getIDWrapper().reset(UDP, getRemoteAddressFamily()); - MinimalSocket::bind(getIDWrapper().accessId(), getRemoteAddressFamily(), - getPortToBind()); + auto binded_port = MinimalSocket::bind( + getIDWrapper().accessId(), getRemoteAddressFamily(), getPortToBind()); + setPort(binded_port); } UdpConnectable UdpBindable::connect(const Address &remote_address) { if (remote_address.getFamily() != getRemoteAddressFamily()) { throw Error{"Passed address has invalid family"}; } - UdpConnectable result(getPortToBind(), remote_address); + UdpConnectable result(remote_address, getPortToBind()); if (wasOpened()) { MinimalSocket::connect(getIDWrapper().accessId(), remote_address); } @@ -74,7 +75,7 @@ UdpConnectable UdpBindable::connect() { return connect(sender_address); } -UdpConnectable::UdpConnectable(const Port &port, const Address &remote_address) +UdpConnectable::UdpConnectable(const Address &remote_address, const Port &port) : PortToBindAware(port), RemoteAddressAware(remote_address) {} UdpConnectable::UdpConnectable(UdpConnectable &&o) @@ -92,7 +93,9 @@ void UdpConnectable::open_() { const auto &socket_id = getIDWrapper().accessId(); const auto &remote_address = getRemoteAddress(); getIDWrapper().reset(UDP, remote_address.getFamily()); - MinimalSocket::bind(socket_id, remote_address.getFamily(), getPortToBind()); + auto binded_port = MinimalSocket::bind(socket_id, remote_address.getFamily(), + getPortToBind()); + setPort(binded_port); MinimalSocket::connect(socket_id, remote_address); } diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 87136592..ff35f01b 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -99,9 +99,9 @@ TEST_CASE("exchange messages between UdpConnectable and UdpConnectable", const Address sender_address = Address::makeLocalHost(sender_port); const Address receiver_address = Address::makeLocalHost(receiver_port); - UdpConnectable sender(sender_port, Address::makeLocalHost(family)); + UdpConnectable sender(Address::makeLocalHost(family), sender_port); REQUIRE(sender.open()); - UdpConnectable receiver(receiver_port, Address::makeLocalHost(family)); + UdpConnectable receiver(Address::makeLocalHost(family), receiver_port); REQUIRE(receiver.open()); const std::size_t cycles = 5; From c3e6070abea5c5d7dc4af23e4ce033d76d7acb66 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 00:14:12 +0100 Subject: [PATCH 109/228] open con timeout --- src/header/MinimalSocket/core/Receiver.h | 9 ++------- src/header/MinimalSocket/core/Socket.h | 8 +++++++- src/src/SocketFunctions.h | 2 +- src/src/core/Socket.cpp | 16 ++++++++++++++-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/header/MinimalSocket/core/Receiver.h b/src/header/MinimalSocket/core/Receiver.h index 519c35e5..a5ab8480 100644 --- a/src/header/MinimalSocket/core/Receiver.h +++ b/src/header/MinimalSocket/core/Receiver.h @@ -10,20 +10,15 @@ #include #include -#include #include namespace MinimalSocket { -using ReceiveTimeout = std::chrono::milliseconds; - -static constexpr ReceiveTimeout NULL_TIMEOUT = ReceiveTimeout{0}; - class ReceiveTimeOutAware : public virtual Socket { protected: - void lazyUpdateReceiveTimeout(const ReceiveTimeout &timeout); + void lazyUpdateReceiveTimeout(const Timeout &timeout); private: - ReceiveTimeout receive_timeout = NULL_TIMEOUT; + Timeout receive_timeout = NULL_TIMEOUT; }; class Receiver : public virtual Socket { diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index afb42a99..d889f77a 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -20,7 +21,12 @@ void setZeros(Buffer &subject); enum SocketType { UDP, TCP }; +using Timeout = std::chrono::milliseconds; + +static constexpr Timeout NULL_TIMEOUT = Timeout{0}; + class SocketIdWrapper; + class Socket { public: virtual ~Socket(); @@ -51,7 +57,7 @@ bool operator==(const Socket &subject, std::nullptr_t); class Openable : public virtual Socket { public: bool wasOpened() const { return opened; } - bool open(); + bool open(const Timeout &timeout = NULL_TIMEOUT); protected: Openable() = default; diff --git a/src/src/SocketFunctions.h b/src/src/SocketFunctions.h index 64da8527..dc3538b7 100644 --- a/src/src/SocketFunctions.h +++ b/src/src/SocketFunctions.h @@ -10,7 +10,7 @@ #include "SocketId.h" namespace MinimalSocket { -// return port actually binded (you can pass to the function also AnyPort) +// return port actually binded (as you could pass to the function also AnyPort) Port bind(const SocketID &socket_id, const AddressFamily &family, const Port &port); diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index e82e4ecf..6d867fc9 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -11,6 +11,8 @@ #include "../SocketId.h" #include "../Utils.h" +#include + namespace MinimalSocket { Socket::~Socket() = default; @@ -45,14 +47,24 @@ void Socket::resetIDWrapper() { socket_id_wrapper = std::make_unique(); } -bool Openable::open() { +bool Openable::open(const Timeout &timeout) { if (opened) { throw Error{"Already opened"}; } std::scoped_lock lock(open_procedure_mtx); bool success = true; try { - this->open_(); + if (NULL_TIMEOUT == timeout) { + this->open_(); + } else { + auto open_task = std::async([&]() { this->open_(); }); + auto open_task_status = open_task.wait_for(timeout); + if (open_task_status != std::future_status::ready) { + resetIDWrapper(); + open_task.get(); // to force the exception throwing + success = false; + } + } opened = true; } catch (const Error &) { resetIDWrapper(); From 25995bd16e5a544f8452c701d40f290bc56621de Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 12:21:37 +0100 Subject: [PATCH 110/228] refactoring --- src/src/core/Socket.cpp | 8 +++----- tests/TestTCP.cpp | 2 ++ tests/TestUDP.cpp | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 6d867fc9..7b0ef330 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -52,7 +52,6 @@ bool Openable::open(const Timeout &timeout) { throw Error{"Already opened"}; } std::scoped_lock lock(open_procedure_mtx); - bool success = true; try { if (NULL_TIMEOUT == timeout) { this->open_(); @@ -61,16 +60,15 @@ bool Openable::open(const Timeout &timeout) { auto open_task_status = open_task.wait_for(timeout); if (open_task_status != std::future_status::ready) { resetIDWrapper(); - open_task.get(); // to force the exception throwing - success = false; + open_task.get(); // should throw already here + throw Error{""}; // jsut to be sure it throws } } opened = true; } catch (const Error &) { resetIDWrapper(); - success = false; } - return success; + return opened; } void Openable::transfer(Openable &receiver, Openable &giver) { diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 27f926ea..669b8d87 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -161,3 +161,5 @@ TEST_CASE("Open multiple times tcp clients", "[tcp]") { TcpClient{std::move(client)}; } } + +// tests receive e open con timeout diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index ff35f01b..602bd376 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -221,3 +221,5 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { #pragma omp barrier }); } + +// tests receive e open con timeout From 88926873d5d81ac56c218df76853ce1c535a47d2 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 12:44:25 +0100 Subject: [PATCH 111/228] refactoring --- src/header/MinimalSocket/core/Receiver.h | 25 ++++++------ src/header/MinimalSocket/core/Socket.h | 8 ++-- src/header/MinimalSocket/udp/UdpSocket.h | 3 +- src/src/core/Receiver.cpp | 48 ++++++++++++++++++------ src/src/core/Sender.cpp | 16 ++++---- src/src/core/Socket.cpp | 4 ++ src/src/udp/UdpSocket.cpp | 13 ++++--- 7 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/header/MinimalSocket/core/Receiver.h b/src/header/MinimalSocket/core/Receiver.h index a5ab8480..5d686c69 100644 --- a/src/header/MinimalSocket/core/Receiver.h +++ b/src/header/MinimalSocket/core/Receiver.h @@ -13,15 +13,17 @@ #include namespace MinimalSocket { -class ReceiveTimeOutAware : public virtual Socket { +class ReceiverBase : public virtual Socket { protected: - void lazyUpdateReceiveTimeout(const Timeout &timeout); + std::unique_ptr> + lazyUpdateReceiveTimeout(const Timeout &timeout); private: + std::mutex receive_mtx; Timeout receive_timeout = NULL_TIMEOUT; }; -class Receiver : public virtual Socket { +class Receiver : public ReceiverBase { public: /** * @param[in] the buffer that will receive the message: @@ -33,13 +35,10 @@ class Receiver : public virtual Socket { * @return the number of received bytes actually received and copied into * message (can be also less than the buffer size) */ - void receive(Buffer &message, const ReceiveTimeout &timeout = NULL_TIMEOUT); - -private: - std::mutex receive_mtx; + std::size_t receive(Buffer &message, const Timeout &timeout = NULL_TIMEOUT); }; -class ReceiverUnkownSender : public virtual Socket { +class ReceiverUnkownSender : public ReceiverBase { public: /** * @param[in] the buffer that will receive the message: @@ -51,10 +50,10 @@ class ReceiverUnkownSender : public virtual Socket { * @return the number of received bytes actually received and copied into * message (can be also less than the buffer size) */ - Address receive(Buffer &message, - const ReceiveTimeout &timeout = NULL_TIMEOUT); - -private: - std::mutex receive_mtx; + struct ReceiveResult { + std::optional
sender; + std::size_t received_bytes; + }; + ReceiveResult receive(Buffer &message, const Timeout &timeout = NULL_TIMEOUT); }; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index d889f77a..100b0fdb 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -15,9 +15,11 @@ #include namespace MinimalSocket { -using Buffer = std::string; - -void setZeros(Buffer &subject); +struct Buffer { + char *buffer; + std::size_t buffer_size; +}; +void clear(Buffer &subject); enum SocketType { UDP, TCP }; diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index ac5f1093..d23ca5ee 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -51,7 +51,8 @@ class UdpBindable : public SenderTo, // throw in case address family is inconsistent UdpConnectable connect(const Address &remote_address); - UdpConnectable connect(); // to first sending 1 byte + std::optional + connect(const Timeout &timeout); // to first sending 1 byte protected: void open_() override; diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 6062df56..3fe67ef1 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -7,16 +7,17 @@ #include +#include "../SocketAddress.h" #include "../SocketError.h" -#include "../SocketId.h" namespace MinimalSocket { -void ReceiveTimeOutAware::lazyUpdateReceiveTimeout( - const ReceiveTimeout &timeout) { +std::unique_ptr> +ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { + std::unique_ptr> lock = + std::make_unique>(receive_mtx); if (timeout == receive_timeout) { - return; + return lock; } - receive_timeout = timeout; // set new timeout #ifdef _WIN32 auto tv = DWORD(this->receive_timeout.count()); @@ -40,20 +41,45 @@ void ReceiveTimeOutAware::lazyUpdateReceiveTimeout( #endif throwWithLastErrorCode("can't set timeout"); } + receive_timeout = timeout; + return lock; } -void Receiver::receive(Buffer &message, const ReceiveTimeout &timeout) { - std::lock_guard recvLock(receive_mtx); - int recvBytes = ::recv(getIDWrapper().accessId(), message.data(), - static_cast(message.size()), 0); +std::size_t Receiver::receive(Buffer &message, const Timeout &timeout) { + auto lock = lazyUpdateReceiveTimeout(timeout); + int recvBytes = ::recv(getIDWrapper().accessId(), message.buffer, + static_cast(message.buffer_size), 0); if (recvBytes == SCK_SOCKET_ERROR) { recvBytes = 0; throwWithLastErrorCode("receive failed"); } - if (recvBytes > message.size()) { + if (recvBytes > message.buffer_size) { // if here, the message received is probably corrupted recvBytes = 0; } - message.resize(recvBytes); + return static_cast(recvBytes); +} + +ReceiverUnkownSender::ReceiveResult +ReceiverUnkownSender::receive(Buffer &message, const Timeout &timeout) { + auto lock = lazyUpdateReceiveTimeout(timeout); + SocketAddress sender_address; + SocketAddressLength sender_address_length; + int recvBytes = ::recvfrom(getIDWrapper().accessId(), message.buffer, + static_cast(message.buffer_size), 0, + &sender_address, &sender_address_length); + if (recvBytes == SCK_SOCKET_ERROR) { + recvBytes = 0; + throwWithLastErrorCode("receive failed"); + } + ReceiveResult result; + if (recvBytes > message.buffer_size) { + // if here, the message received is probably corrupted + result.received_bytes = 0; + } else { + result.received_bytes = static_cast(recvBytes); + result.sender = toAddress(sender_address); + } + return result; } } // namespace MinimalSocket diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index dfb8ecc5..18596c32 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -14,13 +14,13 @@ namespace MinimalSocket { bool Sender::send(const Buffer &message) { std::scoped_lock lock(send_mtx); - int sentBytes = ::send(getIDWrapper().accessId(), message.data(), - static_cast(message.size()), 0); + int sentBytes = ::send(getIDWrapper().accessId(), message.buffer, + static_cast(message.buffer_size), 0); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; throwWithLastErrorCode("send failed"); } - return (sentBytes == static_cast(message.size())); + return (sentBytes == static_cast(message.buffer_size)); } bool SenderTo::sendTo(const Buffer &message, const Address &recipient) { @@ -32,8 +32,8 @@ bool SenderTo::sendTo(const Buffer &message, const Address &recipient) { auto socketIp4 = toSocketAddressIpv4(recipient.getHost(), recipient.getPort()); sentBytes = ::sendto( - getIDWrapper().accessId(), message.data(), - static_cast(message.size()), 0, + getIDWrapper().accessId(), message.buffer, + static_cast(message.buffer_size), 0, reinterpret_cast(&socketIp4.value()), sizeof(SocketAddressIpv4)); }, @@ -41,8 +41,8 @@ bool SenderTo::sendTo(const Buffer &message, const Address &recipient) { auto socketIp6 = toSocketAddressIpv6(recipient.getHost(), recipient.getPort()); sentBytes = ::sendto( - getIDWrapper().accessId(), message.data(), - static_cast(message.size()), 0, + getIDWrapper().accessId(), message.buffer, + static_cast(message.buffer_size), 0, reinterpret_cast(&socketIp6.value()), sizeof(SocketAddressIpv6)); }); @@ -50,6 +50,6 @@ bool SenderTo::sendTo(const Buffer &message, const Address &recipient) { sentBytes = 0; throwWithLastErrorCode("send to failed"); } - return (sentBytes == static_cast(message.size())); + return (sentBytes == static_cast(message.buffer_size)); } } // namespace MinimalSocket diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 7b0ef330..7cfe9b84 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -14,6 +14,10 @@ #include namespace MinimalSocket { +void clear(Buffer &subject) { + ::memset(subject.buffer, 0, subject.buffer_size); +} + Socket::~Socket() = default; Socket::Socket() { resetIDWrapper(); } diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index cb083a65..e781828b 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -68,11 +68,14 @@ UdpConnectable UdpBindable::connect(const Address &remote_address) { return std::move(result); } -UdpConnectable UdpBindable::connect() { - std::string buffer; - buffer.resize(MAX_UDP_RECV_MESSAGE); - auto sender_address = this->receive(buffer); - return connect(sender_address); +std::optional UdpBindable::connect(const Timeout &timeout) { + char buffer[MAX_UDP_RECV_MESSAGE]; + Buffer buffer_temp = Buffer{&buffer[0], MAX_UDP_RECV_MESSAGE}; + auto received_result = this->receive(buffer_temp, timeout); + if (received_result.sender) { + return connect(*received_result.sender); + } + return std::nullopt; } UdpConnectable::UdpConnectable(const Address &remote_address, const Port &port) From c9ed06bd75f24a8d8b87b3c2173f8237a7c882af Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 13:02:51 +0100 Subject: [PATCH 112/228] refactoring --- src/header/MinimalSocket/core/Receiver.h | 16 +++++++++-- src/header/MinimalSocket/core/Sender.h | 8 ++++-- src/header/MinimalSocket/core/Socket.h | 9 +++++- src/src/core/Receiver.cpp | 35 +++++++++++++++++++----- src/src/core/Sender.cpp | 12 ++++++-- src/src/core/Socket.cpp | 8 ++++++ src/src/udp/UdpSocket.cpp | 10 +++---- 7 files changed, 78 insertions(+), 20 deletions(-) diff --git a/src/header/MinimalSocket/core/Receiver.h b/src/header/MinimalSocket/core/Receiver.h index 5d686c69..75ccdc54 100644 --- a/src/header/MinimalSocket/core/Receiver.h +++ b/src/header/MinimalSocket/core/Receiver.h @@ -36,6 +36,9 @@ class Receiver : public ReceiverBase { * message (can be also less than the buffer size) */ std::size_t receive(Buffer &message, const Timeout &timeout = NULL_TIMEOUT); + + std::string receive(std::size_t expected_max_bytes, + const Timeout &timeout = NULL_TIMEOUT); }; class ReceiverUnkownSender : public ReceiverBase { @@ -51,9 +54,18 @@ class ReceiverUnkownSender : public ReceiverBase { * message (can be also less than the buffer size) */ struct ReceiveResult { - std::optional
sender; + Address sender; std::size_t received_bytes; }; - ReceiveResult receive(Buffer &message, const Timeout &timeout = NULL_TIMEOUT); + std::optional receive(Buffer &message, + const Timeout &timeout = NULL_TIMEOUT); + + struct ReceiveStringResult { + Address sender; + std::string received_message; + }; + std::optional + receive(std::size_t expected_max_bytes, + const Timeout &timeout = NULL_TIMEOUT); }; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Sender.h b/src/header/MinimalSocket/core/Sender.h index 2d6a02a8..09f95f70 100644 --- a/src/header/MinimalSocket/core/Sender.h +++ b/src/header/MinimalSocket/core/Sender.h @@ -19,7 +19,9 @@ class Sender : public virtual Socket { * @return true if the message was completely sent * @param[in] the message to send */ - bool send(const Buffer &message); + bool send(const ConstBuffer &message); + + bool send(const std::string &message); private: std::mutex send_mtx; @@ -31,7 +33,9 @@ class SenderTo : public virtual Socket { * @return true if the message was completely sent * @param[in] the message to send */ - bool sendTo(const Buffer &message, const Address &recipient); + bool sendTo(const ConstBuffer &message, const Address &recipient); + + bool sendTo(const std::string &message, const Address &recipient); private: std::mutex send_mtx; diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 100b0fdb..b761d11a 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -17,9 +17,16 @@ namespace MinimalSocket { struct Buffer { char *buffer; - std::size_t buffer_size; + const std::size_t buffer_size; }; void clear(Buffer &subject); +Buffer makeStringBuffer(std::string &subject); + +struct ConstBuffer { + const char *buffer; + const std::size_t buffer_size; +}; +ConstBuffer makeStringConstBuffer(const std::string &subject); enum SocketType { UDP, TCP }; diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 3fe67ef1..940ee49c 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -60,7 +60,17 @@ std::size_t Receiver::receive(Buffer &message, const Timeout &timeout) { return static_cast(recvBytes); } -ReceiverUnkownSender::ReceiveResult +std::string Receiver::receive(std::size_t expected_max_bytes, + const Timeout &timeout) { + std::string buffer; + buffer.resize(expected_max_bytes); + auto buffer_temp = makeStringBuffer(buffer); + auto recvBytes = receive(buffer_temp, timeout); + buffer.resize(recvBytes); + return buffer; +} + +std::optional ReceiverUnkownSender::receive(Buffer &message, const Timeout &timeout) { auto lock = lazyUpdateReceiveTimeout(timeout); SocketAddress sender_address; @@ -72,14 +82,25 @@ ReceiverUnkownSender::receive(Buffer &message, const Timeout &timeout) { recvBytes = 0; throwWithLastErrorCode("receive failed"); } - ReceiveResult result; if (recvBytes > message.buffer_size) { // if here, the message received is probably corrupted - result.received_bytes = 0; - } else { - result.received_bytes = static_cast(recvBytes); - result.sender = toAddress(sender_address); + return std::nullopt; + } + return ReceiveResult{toAddress(sender_address), + static_cast(recvBytes)}; +} + +std::optional +ReceiverUnkownSender::receive(std::size_t expected_max_bytes, + const Timeout &timeout) { + std::string buffer; + buffer.resize(expected_max_bytes); + auto buffer_temp = makeStringBuffer(buffer); + auto result = receive(buffer_temp, timeout); + if (!result) { + return std::nullopt; } - return result; + buffer.resize(buffer_temp.buffer_size); + return ReceiveStringResult{result->sender, std::move(buffer)}; } } // namespace MinimalSocket diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index 18596c32..a1225bf5 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -12,7 +12,7 @@ #include "../Utils.h" namespace MinimalSocket { -bool Sender::send(const Buffer &message) { +bool Sender::send(const ConstBuffer &message) { std::scoped_lock lock(send_mtx); int sentBytes = ::send(getIDWrapper().accessId(), message.buffer, static_cast(message.buffer_size), 0); @@ -23,7 +23,11 @@ bool Sender::send(const Buffer &message) { return (sentBytes == static_cast(message.buffer_size)); } -bool SenderTo::sendTo(const Buffer &message, const Address &recipient) { +bool Sender::send(const std::string &message) { + return send(makeStringConstBuffer(message)); +} + +bool SenderTo::sendTo(const ConstBuffer &message, const Address &recipient) { std::scoped_lock lock(send_mtx); int sentBytes; visitAddress( @@ -52,4 +56,8 @@ bool SenderTo::sendTo(const Buffer &message, const Address &recipient) { } return (sentBytes == static_cast(message.buffer_size)); } + +bool SenderTo::sendTo(const std::string &message, const Address &recipient) { + return sendTo(makeStringConstBuffer(message), recipient); +} } // namespace MinimalSocket diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 7cfe9b84..db0c8ced 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -18,6 +18,14 @@ void clear(Buffer &subject) { ::memset(subject.buffer, 0, subject.buffer_size); } +Buffer makeStringBuffer(std::string &subject) { + return Buffer{subject.data(), subject.size()}; +} + +ConstBuffer makeStringConstBuffer(const std::string &subject) { + return ConstBuffer{subject.data(), subject.size()}; +} + Socket::~Socket() = default; Socket::Socket() { resetIDWrapper(); } diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index e781828b..d8c472a7 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -69,13 +69,11 @@ UdpConnectable UdpBindable::connect(const Address &remote_address) { } std::optional UdpBindable::connect(const Timeout &timeout) { - char buffer[MAX_UDP_RECV_MESSAGE]; - Buffer buffer_temp = Buffer{&buffer[0], MAX_UDP_RECV_MESSAGE}; - auto received_result = this->receive(buffer_temp, timeout); - if (received_result.sender) { - return connect(*received_result.sender); + auto maybe_received = this->receive(MAX_UDP_RECV_MESSAGE, timeout); + if (!maybe_received) { + return std::nullopt; } - return std::nullopt; + return connect(maybe_received->sender); } UdpConnectable::UdpConnectable(const Address &remote_address, const Port &port) From 4de9b80625c55e36746dd258375a3bc4590a1789 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 13:04:11 +0100 Subject: [PATCH 113/228] clear buffer before receive --- src/src/core/Receiver.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 940ee49c..87640111 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -47,6 +47,7 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { std::size_t Receiver::receive(Buffer &message, const Timeout &timeout) { auto lock = lazyUpdateReceiveTimeout(timeout); + clear(message); int recvBytes = ::recv(getIDWrapper().accessId(), message.buffer, static_cast(message.buffer_size), 0); if (recvBytes == SCK_SOCKET_ERROR) { @@ -73,6 +74,7 @@ std::string Receiver::receive(std::size_t expected_max_bytes, std::optional ReceiverUnkownSender::receive(Buffer &message, const Timeout &timeout) { auto lock = lazyUpdateReceiveTimeout(timeout); + clear(message); SocketAddress sender_address; SocketAddressLength sender_address_length; int recvBytes = ::recvfrom(getIDWrapper().accessId(), message.buffer, From 52c39731b28244d44f5ba484b4a0ddf358dbb2d7 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 13:10:03 +0100 Subject: [PATCH 114/228] udp socket only send removed --- TODO | 4 +- src/header/MinimalSocket/udp/UdpSocket.h | 60 ++++++++++-------------- src/src/udp/UdpSocket.cpp | 49 ++++++------------- 3 files changed, 40 insertions(+), 73 deletions(-) diff --git a/TODO b/TODO index 945c7295..d8a7cc9b 100644 --- a/TODO +++ b/TODO @@ -10,8 +10,6 @@ add timeout per udp connect in receive from, resize buffer a max size udp message -open connection with timeout - usare noexpect keyword in c'tors? pass number of max clients tcp server should accept @@ -19,3 +17,5 @@ pass number of max clients tcp server should accept e' thread safety assicurata nelle varie funzioni? check in c'tor passed address is not null (is valid) + +nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index d23ca5ee..8a3534ef 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -18,40 +18,27 @@ namespace MinimalSocket::udp { */ static constexpr std::size_t MAX_UDP_RECV_MESSAGE = 65507; -class UdpSender; -class UdpBindable; -class UdpConnectable; - -// can only send as no port was reserved -class UdpSender : public SenderTo, public RemoteAddressFamilyAware { -public: - UdpSender(UdpSender &&o); - UdpSender &operator=(UdpSender &&o); - - UdpSender( - const AddressFamily &accepted_connection_family = AddressFamily::IP_V4); - - UdpBindable bind(const Port port_to_bind = ANY_PORT); -}; - -// can send and receive (from anyonw hitting it) as a port was reserved -class UdpBindable : public SenderTo, - public ReceiverUnkownSender, - public PortToBindAware, - public RemoteAddressFamilyAware, - public Openable { +class UdpConnected; + +// can send and receive (from anyone hitting this socket) as a port was reserved +class UdpBinded : public SenderTo, + public ReceiverUnkownSender, + public PortToBindAware, + public RemoteAddressFamilyAware, + public Openable { public: - UdpBindable(UdpBindable &&o); - UdpBindable &operator=(UdpBindable &&o); + UdpBinded(UdpBinded &&o); + UdpBinded &operator=(UdpBinded &&o); - UdpBindable( + UdpBinded( const Port port_to_bind = ANY_PORT, const AddressFamily &accepted_connection_family = AddressFamily::IP_V4); // throw in case address family is inconsistent - UdpConnectable connect(const Address &remote_address); + // leave this socket empty after success + UdpConnected connect(const Address &remote_address); - std::optional + std::optional connect(const Timeout &timeout); // to first sending 1 byte protected: @@ -60,18 +47,19 @@ class UdpBindable : public SenderTo, // can send and receive only from the specific remote address the socket was // connected to -class UdpConnectable : public Sender, - public Receiver, - public PortToBindAware, - public RemoteAddressAware, - public Openable { +class UdpConnected : public Sender, + public Receiver, + public PortToBindAware, + public RemoteAddressAware, + public Openable { public: - UdpConnectable(UdpConnectable &&o); - UdpConnectable &operator=(UdpConnectable &&o); + UdpConnected(UdpConnected &&o); + UdpConnected &operator=(UdpConnected &&o); - UdpConnectable(const Address &remote_address, const Port &port = ANY_PORT); + UdpConnected(const Address &remote_address, const Port &port = ANY_PORT); - UdpBindable disconnect(); + // leave this socket empty after success + UdpBinded disconnect(); protected: void open_() override; diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index d8c472a7..d7fe5e49 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -12,55 +12,34 @@ #include "../Utils.h" namespace MinimalSocket::udp { -UdpSender::UdpSender(const AddressFamily &accepted_connection_family) - : RemoteAddressFamilyAware(accepted_connection_family) { - getIDWrapper().reset(UDP, accepted_connection_family); -} - -UdpSender::UdpSender(UdpSender &&o) - : RemoteAddressFamilyAware(o.getRemoteAddressFamily()) { - Socket::transfer(*this, o); -} -UdpSender &UdpSender::operator=(UdpSender &&o) { - copy_as(*this, o); - Socket::transfer(*this, o); - return *this; -} - -UdpBindable UdpSender::bind(const Port port_to_bind) { - UdpBindable result(port_to_bind, getRemoteAddressFamily()); - result.open(); - return std::move(result); -} - -UdpBindable::UdpBindable(const Port port_to_bind, - const AddressFamily &accepted_connection_family) +UdpBinded::UdpBinded(const Port port_to_bind, + const AddressFamily &accepted_connection_family) : PortToBindAware(port_to_bind), RemoteAddressFamilyAware(accepted_connection_family) {} -UdpBindable::UdpBindable(UdpBindable &&o) +UdpBinded::UdpBinded(UdpBinded &&o) : PortToBindAware(o), RemoteAddressFamilyAware(o) { Openable::transfer(*this, o); } -UdpBindable &UdpBindable::operator=(UdpBindable &&o) { +UdpBinded &UdpBinded::operator=(UdpBinded &&o) { copy_as(*this, o); copy_as(*this, o); Openable::transfer(*this, o); return *this; } -void UdpBindable::open_() { +void UdpBinded::open_() { getIDWrapper().reset(UDP, getRemoteAddressFamily()); auto binded_port = MinimalSocket::bind( getIDWrapper().accessId(), getRemoteAddressFamily(), getPortToBind()); setPort(binded_port); } -UdpConnectable UdpBindable::connect(const Address &remote_address) { +UdpConnected UdpBinded::connect(const Address &remote_address) { if (remote_address.getFamily() != getRemoteAddressFamily()) { throw Error{"Passed address has invalid family"}; } - UdpConnectable result(remote_address, getPortToBind()); + UdpConnected result(remote_address, getPortToBind()); if (wasOpened()) { MinimalSocket::connect(getIDWrapper().accessId(), remote_address); } @@ -68,7 +47,7 @@ UdpConnectable UdpBindable::connect(const Address &remote_address) { return std::move(result); } -std::optional UdpBindable::connect(const Timeout &timeout) { +std::optional UdpBinded::connect(const Timeout &timeout) { auto maybe_received = this->receive(MAX_UDP_RECV_MESSAGE, timeout); if (!maybe_received) { return std::nullopt; @@ -76,21 +55,21 @@ std::optional UdpBindable::connect(const Timeout &timeout) { return connect(maybe_received->sender); } -UdpConnectable::UdpConnectable(const Address &remote_address, const Port &port) +UdpConnected::UdpConnected(const Address &remote_address, const Port &port) : PortToBindAware(port), RemoteAddressAware(remote_address) {} -UdpConnectable::UdpConnectable(UdpConnectable &&o) +UdpConnected::UdpConnected(UdpConnected &&o) : PortToBindAware(o), RemoteAddressAware(o) { Openable::transfer(*this, o); } -UdpConnectable &UdpConnectable::operator=(UdpConnectable &&o) { +UdpConnected &UdpConnected::operator=(UdpConnected &&o) { copy_as(*this, o); copy_as(*this, o); Openable::transfer(*this, o); return *this; } -void UdpConnectable::open_() { +void UdpConnected::open_() { const auto &socket_id = getIDWrapper().accessId(); const auto &remote_address = getRemoteAddress(); getIDWrapper().reset(UDP, remote_address.getFamily()); @@ -100,9 +79,9 @@ void UdpConnectable::open_() { MinimalSocket::connect(socket_id, remote_address); } -UdpBindable UdpConnectable::disconnect() { +UdpBinded UdpConnected::disconnect() { resetIDWrapper(); - UdpBindable result(getPortToBind(), getRemoteAddress().getFamily()); + UdpBinded result(getPortToBind(), getRemoteAddress().getFamily()); result.open(); return std::move(result); } From a46071e0c1b1dec2e93e9f8b3f7cd70255f5ed78 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 13:45:49 +0100 Subject: [PATCH 115/228] refactoring --- TODO | 2 + src/header/MinimalSocket/udp/UdpSocket.h | 2 +- tests/CMakeLists.txt | 16 +- tests/{UtilsTest/src => }/Parallel.cpp | 2 +- tests/{UtilsTest/include => }/Parallel.h | 0 tests/{UtilsTest/src => }/PortFactory.cpp | 2 +- tests/{UtilsTest/include => }/PortFactory.h | 0 tests/TestTCP.cpp | 16 +- tests/TestUDP.cpp | 239 +++++++++----------- tests/UtilsTest/CMakeLists.txt | 12 - tests/UtilsTest/include/TcpCommon.h | 27 --- tests/UtilsTest/include/UdpCommon.h | 22 -- tests/UtilsTest/src/TcpCommon.cpp | 36 --- tests/UtilsTest/src/UdpCommon.cpp | 25 -- 14 files changed, 122 insertions(+), 279 deletions(-) rename tests/{UtilsTest/src => }/Parallel.cpp (96%) rename tests/{UtilsTest/include => }/Parallel.h (100%) rename tests/{UtilsTest/src => }/PortFactory.cpp (95%) rename tests/{UtilsTest/include => }/PortFactory.h (100%) delete mode 100644 tests/UtilsTest/CMakeLists.txt delete mode 100644 tests/UtilsTest/include/TcpCommon.h delete mode 100644 tests/UtilsTest/include/UdpCommon.h delete mode 100644 tests/UtilsTest/src/TcpCommon.cpp delete mode 100644 tests/UtilsTest/src/UdpCommon.cpp diff --git a/TODO b/TODO index d8a7cc9b..a434cb3f 100644 --- a/TODO +++ b/TODO @@ -19,3 +19,5 @@ e' thread safety assicurata nelle varie funzioni? check in c'tor passed address is not null (is valid) nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema + +riabilita in test ipv6 diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 8a3534ef..47f4afdf 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -39,7 +39,7 @@ class UdpBinded : public SenderTo, UdpConnected connect(const Address &remote_address); std::optional - connect(const Timeout &timeout); // to first sending 1 byte + connect(const Timeout &timeout = NULL_TIMEOUT); // to first sending 1 byte protected: void open_() override; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f73b6549..631322ff 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,17 +8,21 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(catch2) -add_subdirectory(UtilsTest) - -function(MakeTest) +function(MakeTests) set(TEST_NAME "Tests") - file(GLOB SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) + file(GLOB SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h) add_executable(${TEST_NAME} ${SRC_FILES}) - target_link_libraries(${TEST_NAME} PUBLIC UtilsTest) + find_package(OpenMP) + target_link_libraries(${TEST_NAME} PUBLIC + MinimalSocket + OpenMP::OpenMP_CXX + Catch2::Catch2 + Catch2::Catch2WithMain + ) install(TARGETS ${TEST_NAME}) endfunction() -MakeTest() +MakeTests() diff --git a/tests/UtilsTest/src/Parallel.cpp b/tests/Parallel.cpp similarity index 96% rename from tests/UtilsTest/src/Parallel.cpp rename to tests/Parallel.cpp index cbe0f72b..93376ea3 100644 --- a/tests/UtilsTest/src/Parallel.cpp +++ b/tests/Parallel.cpp @@ -5,7 +5,7 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include "Parallel.h" #include #include diff --git a/tests/UtilsTest/include/Parallel.h b/tests/Parallel.h similarity index 100% rename from tests/UtilsTest/include/Parallel.h rename to tests/Parallel.h diff --git a/tests/UtilsTest/src/PortFactory.cpp b/tests/PortFactory.cpp similarity index 95% rename from tests/UtilsTest/src/PortFactory.cpp rename to tests/PortFactory.cpp index a3f3ddc2..ad8eead1 100644 --- a/tests/UtilsTest/src/PortFactory.cpp +++ b/tests/PortFactory.cpp @@ -5,7 +5,7 @@ * report any bug to andrecasa91@gmail.com. **/ -#include +#include "PortFactory.h" namespace MinimalSocket::test { namespace { diff --git a/tests/UtilsTest/include/PortFactory.h b/tests/PortFactory.h similarity index 100% rename from tests/UtilsTest/include/PortFactory.h rename to tests/PortFactory.h diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 669b8d87..5be68fc5 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -7,8 +7,8 @@ #include #include -#include -#include +#include "Parallel.h" +#include "PortFactory.h" using namespace MinimalSocket; using namespace MinimalSocket::tcp; @@ -35,18 +35,14 @@ void send_response(const SenderReceiver &requester, [&]() { for (std::size_t c = 0; c < cycles; ++c) { CHECK(requester.sender.send(request)); - Buffer buffer; - buffer.resize(response.size()); - requester.receiver.receive(buffer); - CHECK(buffer == response); + auto received_response = requester.receiver.receive(response.size()); + CHECK(received_response == response); } }, [&]() { for (std::size_t c = 0; c < cycles; ++c) { - Buffer buffer; - buffer.resize(request.size()); - responder.receiver.receive(buffer); - CHECK(buffer == request); + auto received_request = responder.receiver.receive(request.size()); + CHECK(received_request == request); CHECK(responder.sender.send(response)); } }); diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 602bd376..9e596490 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -5,8 +5,8 @@ #include -#include -#include +#include "Parallel.h" +#include "PortFactory.h" using namespace MinimalSocket; using namespace MinimalSocket::udp; @@ -17,208 +17,171 @@ static const std::string request = "Hello"; static const std::string response = "Welcome"; } // namespace -TEST_CASE("exchange messages between UdpSender and UdpBindable", "[udp]") { - const auto receiver_port = PortFactory::makePort(); +TEST_CASE("exchange messages between UdpBinded and UdpBinded", "[udp]") { const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); - - UdpSender sender(family); - UdpBindable receiver(receiver_port, family); - REQUIRE(receiver.open()); - REQUIRE_FALSE(nullptr == receiver); - const std::size_t cycles = 5; - const Address receiver_address = Address::makeLocalHost(receiver_port); + const auto requester_port = PortFactory::makePort(); + const Address requester_address = Address::makeLocalHost(requester_port); + UdpBinded requester(requester_port, family); + REQUIRE(requester.open()); - parallel( - [&]() { - for (std::size_t c = 0; c < cycles; ++c) { - CHECK(sender.sendTo(request, receiver_address)); -#pragma omp barrier - } - }, - [&]() { - for (std::size_t c = 0; c < cycles; ++c) { - Buffer buffer; - buffer.resize(request.size()); -#pragma omp barrier - receiver.receive(buffer); - CHECK(buffer == request); - } - }); -} - -TEST_CASE("exchange messages between UdpBindable and UdpBindable", "[udp]") { - const auto receiver_port = PortFactory::makePort(); - const auto sender_port = PortFactory::makePort(); - const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); - - UdpBindable sender(sender_port, family); - REQUIRE(sender.open()); - UdpBindable receiver(receiver_port, family); - REQUIRE(receiver.open()); - - const std::size_t cycles = 5; - - const Address sender_address = Address::makeLocalHost(sender_port); - const Address receiver_address = Address::makeLocalHost(receiver_port); + const auto responder_port = PortFactory::makePort(); + const Address responder_address = Address::makeLocalHost(responder_port); + UdpBinded responder(responder_port, family); + REQUIRE(responder.open()); parallel( [&]() { for (std::size_t c = 0; c < cycles; ++c) { - CHECK(sender.sendTo(request, receiver_address)); + CHECK(requester.sendTo(request, responder_address)); #pragma omp barrier - Buffer buffer; - buffer.resize(response.size()); #pragma omp barrier - auto address = receiver.receive(buffer); - CHECK(buffer == response); - CHECK(address == receiver_address); + auto received_response = requester.receive(response.size()); + REQUIRE(received_response); + CHECK(received_response->received_message == response); + CHECK(received_response->sender == responder_address); } }, [&]() { for (std::size_t c = 0; c < cycles; ++c) { - Buffer buffer; - buffer.resize(request.size()); #pragma omp barrier - auto address = receiver.receive(buffer); - CHECK(buffer == request); - CHECK(address == sender_address); - CHECK(receiver.sendTo(response, sender_address)); + auto received_request = responder.receive(request.size()); + REQUIRE(received_request); + CHECK(received_request->received_message == request); + CHECK(received_request->sender == requester_address); + responder.sendTo(response, requester_address); #pragma omp barrier } }); } -TEST_CASE("exchange messages between UdpConnectable and UdpConnectable", - "[udp]") { - const auto receiver_port = PortFactory::makePort(); - const auto sender_port = PortFactory::makePort(); +TEST_CASE("exchange messages between UdpConnected and UdpConnected", "[udp]") { const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); + const std::size_t cycles = 5; - const Address sender_address = Address::makeLocalHost(sender_port); - const Address receiver_address = Address::makeLocalHost(receiver_port); + const auto requester_port = PortFactory::makePort(); + const Address requester_address = Address::makeLocalHost(requester_port); - UdpConnectable sender(Address::makeLocalHost(family), sender_port); - REQUIRE(sender.open()); - UdpConnectable receiver(Address::makeLocalHost(family), receiver_port); - REQUIRE(receiver.open()); + const auto responder_port = PortFactory::makePort(); + const Address responder_address = Address::makeLocalHost(responder_port); - const std::size_t cycles = 5; + UdpConnected requester(responder_address, requester_port); + REQUIRE(requester.open()); + UdpConnected responder(requester_address, responder_port); + REQUIRE(responder.open()); parallel( [&]() { for (std::size_t c = 0; c < cycles; ++c) { - CHECK(sender.send(request)); + CHECK(requester.send(request)); #pragma omp barrier - Buffer buffer; - buffer.resize(response.size()); #pragma omp barrier - receiver.receive(buffer); - CHECK(buffer == response); + auto received_response = requester.receive(response.size()); + CHECK(received_response == response); } }, [&]() { for (std::size_t c = 0; c < cycles; ++c) { - Buffer buffer; - buffer.resize(request.size()); #pragma omp barrier - receiver.receive(buffer); - CHECK(buffer == request); - CHECK(receiver.send(response)); + auto received_request = responder.receive(request.size()); + CHECK(received_request == request); + responder.send(response); #pragma omp barrier } }); } TEST_CASE("Metamorphosis of udp connections", "[udp]") { - const auto receiver_port = PortFactory::makePort(); - const auto sender_port = PortFactory::makePort(); const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); + const std::size_t cycles = 5; - const Address sender_address = Address::makeLocalHost(sender_port); - const Address receiver_address = Address::makeLocalHost(receiver_port); + const auto requester_port = PortFactory::makePort(); + const Address requester_address = Address::makeLocalHost(requester_port); + const auto responder_port = PortFactory::makePort(); + const Address responder_address = Address::makeLocalHost(responder_port); - UdpSender sender(family); - UdpBindable receiver(receiver_port, family); - REQUIRE(receiver.open()); - parallel( - [&]() { - CHECK(sender.sendTo(request, receiver_address)); + 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()); + + // connect requester to responder + auto deduce_sender = GENERATE(true, false); + std::unique_ptr requester_connected; + if (deduce_sender) { + parallel( + [&]() { + responder.sendTo("1", requester_address); #pragma omp barrier - }, - [&]() { - Buffer buffer; - buffer.resize(request.size()); + }, + [&]() { #pragma omp barrier - auto address = receiver.receive(buffer); - CHECK(buffer == request); - CHECK(address == sender_address); - }); + std::optional socket_connected = + requester_only_bind->connect(); + REQUIRE(socket_connected); + REQUIRE(socket_connected->getRemoteAddress() == responder_address); + requester_connected = std::make_unique( + std::move(socket_connected.value())); + }); + } else { + requester_connected = std::make_unique( + requester_only_bind->connect(responder_address)); + } + REQUIRE(requester_connected->wasOpened()); - UdpBindable sender_binded = sender.bind(sender_port); - REQUIRE(sender_binded.wasOpened()); + // try message exchange parallel( [&]() { - CHECK(sender_binded.sendTo(request, receiver_address)); + for (std::size_t c = 0; c < cycles; ++c) { + CHECK(requester_connected->send(request)); #pragma omp barrier - Buffer buffer; - buffer.resize(response.size()); #pragma omp barrier - auto address = sender_binded.receive(buffer); - CHECK(buffer == response); - CHECK(address == receiver_address); + auto received_response = + requester_connected->receive(response.size()); + CHECK(received_response == response); + } }, [&]() { - Buffer buffer; - buffer.resize(request.size()); + for (std::size_t c = 0; c < cycles; ++c) { #pragma omp barrier - auto address = receiver.receive(buffer); - CHECK(buffer == request); - CHECK(address == sender_address); - CHECK(receiver.sendTo(response, sender_address)); + auto received_request = responder.receive(request.size()); + REQUIRE(received_request); + CHECK(received_request->received_message == request); + responder.sendTo(response, requester_address); #pragma omp barrier + } }); - auto deduce_sender = GENERATE(true, false); + // try to disconnect requester + requester_only_bind = + std::make_unique(requester_connected->disconnect()); + REQUIRE(requester_only_bind->wasOpened()); - std::unique_ptr sender_connected; - if (deduce_sender) { - parallel( - [&]() { - receiver.sendTo("1", sender_address); -#pragma omp barrier - }, - [&]() { -#pragma omp barrier - sender_connected = - std::make_unique(sender_binded.connect()); - REQUIRE(sender_connected->getRemoteAddress() == receiver_address); - }); - } else { - sender_connected = std::make_unique( - sender_binded.connect(receiver_address)); - } - REQUIRE(sender_connected->wasOpened()); + // try message exchange parallel( [&]() { - CHECK(sender_connected->send(request)); + for (std::size_t c = 0; c < cycles; ++c) { + CHECK(requester_only_bind->sendTo(request, responder_address)); #pragma omp barrier - Buffer buffer; - buffer.resize(response.size()); #pragma omp barrier - sender_connected->receive(buffer); - CHECK(buffer == response); + auto received_response = + requester_only_bind->receive(response.size()); + REQUIRE(received_response); + CHECK(received_response->received_message == response); + } }, [&]() { - Buffer buffer; - buffer.resize(request.size()); + for (std::size_t c = 0; c < cycles; ++c) { #pragma omp barrier - auto address = receiver.receive(buffer); - CHECK(buffer == request); - CHECK(address == sender_address); - CHECK(receiver.sendTo(response, sender_address)); + auto received_request = responder.receive(request.size()); + REQUIRE(received_request); + CHECK(received_request->received_message == request); + responder.sendTo(response, requester_address); #pragma omp barrier + } }); } diff --git a/tests/UtilsTest/CMakeLists.txt b/tests/UtilsTest/CMakeLists.txt deleted file mode 100644 index 3117450a..00000000 --- a/tests/UtilsTest/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -set(PROJECT_SHORTNAME "UtilsTest") - -MakeLibrary(${PROJECT_SHORTNAME} include) - -find_package(OpenMP) - -target_link_libraries(${PROJECT_SHORTNAME} PUBLIC - MinimalSocket - OpenMP::OpenMP_CXX - Catch2::Catch2 - Catch2::Catch2WithMain -) diff --git a/tests/UtilsTest/include/TcpCommon.h b/tests/UtilsTest/include/TcpCommon.h deleted file mode 100644 index b72c81c4..00000000 --- a/tests/UtilsTest/include/TcpCommon.h +++ /dev/null @@ -1,27 +0,0 @@ -// /** -// * Author: Andrea Casalino -// * Created: 01.28.2020 -// * -// * report any bug to andrecasa91@gmail.com. -// **/ - -// #ifndef SAMPLE_TCP_COMMON_H -// #define SAMPLE_TCP_COMMON_H - -// #include -// #include -// #include -// #include - -// namespace sck::sample { -// typedef std::unique_ptr TcpClientHndlrPtr; - -// std::vector accept(tcp::TcpServer& server, const -// std::size_t clients); - -// TcpClientHndlrPtr accept(const std::uint16_t port); - -// void openTcpClient(tcp::TcpClient& client); -// } - -// #endif \ No newline at end of file diff --git a/tests/UtilsTest/include/UdpCommon.h b/tests/UtilsTest/include/UdpCommon.h deleted file mode 100644 index 86dc384f..00000000 --- a/tests/UtilsTest/include/UdpCommon.h +++ /dev/null @@ -1,22 +0,0 @@ -// /** -// * Author: Andrea Casalino -// * Created: 01.28.2020 -// * -// * report any bug to andrecasa91@gmail.com. -// **/ - -// #ifndef SAMPLE_UDP_COMMON_H -// #define SAMPLE_UDP_COMMON_H - -// #include -// #include - -// namespace sck::sample { -// typedef std::unique_ptr UdpConnectionPtr; - -// std::pair -// makeOpenedUdpConnections(const std::uint16_t portA, const std::uint16_t -// portB); -// } - -// #endif \ No newline at end of file diff --git a/tests/UtilsTest/src/TcpCommon.cpp b/tests/UtilsTest/src/TcpCommon.cpp deleted file mode 100644 index 93acb254..00000000 --- a/tests/UtilsTest/src/TcpCommon.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// /** -// * Author: Andrea Casalino -// * Created: 01.28.2020 -// * -// * report any bug to andrecasa91@gmail.com. -// **/ - -// #include -// #include -// #include - -// namespace sck::sample { -// std::vector accept(tcp::TcpServer& server, const -// std::size_t clients) { -// std::vector clientPtrs; -// clientPtrs.reserve(clients); -// for (std::size_t k = 0; k < clients; ++k) { -// #pragma omp barrier -// clientPtrs.emplace_back(server.acceptClient()); -// EXPECT_TRUE(clientPtrs.back()->isOpen()); -// } -// return clientPtrs; -// } - -// TcpClientHndlrPtr accept(const std::uint16_t port) { -// tcp::TcpServer server(port); -// open(server); -// return std::move(accept(server, 1).front()); -// } - -// void openTcpClient(tcp::TcpClient& client) { -// EXPECT_FALSE(client.isOpen()); -// #pragma omp barrier -// open(client); -// } -// } diff --git a/tests/UtilsTest/src/UdpCommon.cpp b/tests/UtilsTest/src/UdpCommon.cpp deleted file mode 100644 index dcf2b02d..00000000 --- a/tests/UtilsTest/src/UdpCommon.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// /** -// * Author: Andrea Casalino -// * Created: 01.28.2020 -// * -// * report any bug to andrecasa91@gmail.com. -// **/ - -// #include - -// namespace sck::sample { -// std::pair -// makeOpenedUdpConnections(const std::uint16_t portA, const std::uint16_t -// portB) { -// UdpConnectionPtr connA = -// std::make_unique(*Ip::createLocalHost(portB) , -// portA); UdpConnectionPtr connB = -// std::make_unique(*Ip::createLocalHost(portA) , -// portB); - -// open(*connA); -// open(*connB); - -// return std::make_pair(std::move(connA), std::move(connB)); -// }; -// } From a0f4966eb442528f400e625415c0066c80038b76 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 14:01:39 +0100 Subject: [PATCH 116/228] testing --- tests/TestTCP.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 5be68fc5..e3cb59d8 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -149,12 +149,15 @@ TEST_CASE("Open multiple times tcp clients", "[tcp]") { std::size_t cycles = 5; - TcpClient client(Address::makeLocalHost(family)); + TcpClient client(Address::makeLocalHost(port, family)); for (std::size_t c = 0; c < cycles; ++c) { parallel([&]() { server.acceptNewClient(); }, - [&]() { CHECK(client.open()); }); - TcpClient{std::move(client)}; + [&]() { + CHECK(client.open()); + TcpClient{std::move(client)}; + CHECK_FALSE(client.wasOpened()); + }); } } From 3cf960b692d71f716b2881d92462b5105dd3b4a8 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 14:26:02 +0100 Subject: [PATCH 117/228] testing --- src/src/SocketAddress.cpp | 13 ++++++++++--- src/src/SocketAddress.h | 3 +++ src/src/core/Receiver.cpp | 19 ++++++++++++------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/src/SocketAddress.cpp b/src/src/SocketAddress.cpp index 135f9ac6..449c0dc2 100644 --- a/src/src/SocketAddress.cpp +++ b/src/src/SocketAddress.cpp @@ -102,8 +102,9 @@ Address toAddress(const SocketAddress &address) { // refer to // https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu std::string ip; - Port port; - if (AF_INET == address.sa_family) { + Port port = ANY_PORT; + switch (address.sa_family) { + case AF_INET: { // ipv4 address // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to // an ip that has no sense @@ -111,7 +112,9 @@ Address toAddress(const SocketAddress &address) { reinterpret_cast(&address)->sin_addr)); port = ntohs(reinterpret_cast(&address)->sin_port); - } else { + + } break; + case AF_INET6: { // ipv6 address char temp[INET6_ADDRSTRLEN]; // this is the longest one // refer to @@ -121,6 +124,10 @@ Address toAddress(const SocketAddress &address) { ip = std::string(temp, INET6_ADDRSTRLEN); port = ntohs(reinterpret_cast(&address)->sin6_port); + + } break; + default: + break; } return Address{ip, port}; } diff --git a/src/src/SocketAddress.h b/src/src/SocketAddress.h index d7520d4e..8049f77a 100644 --- a/src/src/SocketAddress.h +++ b/src/src/SocketAddress.h @@ -42,6 +42,9 @@ using SocketAddressIpv6 = SOCKADDR_IN6; using SocketAddressIpv6 = sockaddr_in6; #endif +static constexpr std::size_t MAX_POSSIBLE_ADDRESS_SIZE = + std::max(sizeof(SocketAddressIpv4), sizeof(SocketAddressIpv6)); + /** * @brief checks the address syntax and in case * it's valid as an ipv4, creates the socket API representation diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 87640111..537c825d 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -75,11 +75,15 @@ std::optional ReceiverUnkownSender::receive(Buffer &message, const Timeout &timeout) { auto lock = lazyUpdateReceiveTimeout(timeout); clear(message); - SocketAddress sender_address; - SocketAddressLength sender_address_length; - int recvBytes = ::recvfrom(getIDWrapper().accessId(), message.buffer, - static_cast(message.buffer_size), 0, - &sender_address, &sender_address_length); + + char sender_address[MAX_POSSIBLE_ADDRESS_SIZE]; + SocketAddressLength sender_address_length = MAX_POSSIBLE_ADDRESS_SIZE; + + int recvBytes = + ::recvfrom(getIDWrapper().accessId(), message.buffer, + static_cast(message.buffer_size), 0, + reinterpret_cast(&sender_address[0]), + &sender_address_length); if (recvBytes == SCK_SOCKET_ERROR) { recvBytes = 0; throwWithLastErrorCode("receive failed"); @@ -88,8 +92,9 @@ ReceiverUnkownSender::receive(Buffer &message, const Timeout &timeout) { // if here, the message received is probably corrupted return std::nullopt; } - return ReceiveResult{toAddress(sender_address), - static_cast(recvBytes)}; + return ReceiveResult{ + toAddress(reinterpret_cast(sender_address)), + static_cast(recvBytes)}; } std::optional From 2038e9da76144033c7a9fb473e29ab4b295a81ce Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 14:41:21 +0100 Subject: [PATCH 118/228] testing --- src/src/SocketAddress.cpp | 4 ++-- src/src/SocketFunctions.cpp | 19 +++++++++++++------ src/src/tcp/TcpServer.cpp | 14 +++++++++----- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/src/SocketAddress.cpp b/src/src/SocketAddress.cpp index 449c0dc2..cf3a0765 100644 --- a/src/src/SocketAddress.cpp +++ b/src/src/SocketAddress.cpp @@ -51,7 +51,7 @@ std::optional toSocketAddressIpv4(const std::string &host, return std::nullopt; } - auto ipv4 = reinterpret_cast(res->ai_addr); + auto ipv4 = reinterpret_cast(res->ai_addr); result_ref.sin_addr.s_addr = ipv4->sin_addr.s_addr; ::freeaddrinfo(res); return result; @@ -92,7 +92,7 @@ std::optional toSocketAddressIpv6(const std::string &host, return std::nullopt; } - auto ipv6 = reinterpret_cast(res->ai_addr); + auto ipv6 = reinterpret_cast(res->ai_addr); result_ref.sin6_addr = ipv6->sin6_addr; ::freeaddrinfo(res); return result; diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index a7808100..35f0a716 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -72,18 +72,25 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, Port binded_port = port; if (ANY_PORT == port) { - SocketAddress binded_address; - SocketAddressLength binded_address_length; - if (::getsockname(socket_id, &binded_address, &binded_address_length) == - SCK_SOCKET_ERROR) { + char binded_address[MAX_POSSIBLE_ADDRESS_SIZE]; + SocketAddressLength binded_address_length = MAX_POSSIBLE_ADDRESS_SIZE; + if (::getsockname(socket_id, + reinterpret_cast(&binded_address[0]), + &binded_address_length) == SCK_SOCKET_ERROR) { throwWithLastErrorCode("Wasn't able to deduce the binded port"); } - if (AF_INET == binded_address.sa_family) { + switch (reinterpret_cast(binded_address).sa_family) { + case AF_INET: binded_port = reinterpret_cast(binded_address).sin_port; - } else { + break; + case AF_INET6: binded_port = reinterpret_cast(binded_address).sin6_port; + break; + default: + throw Error{"Wasn't able to deduce the binded port"}; + break; } } return binded_port; diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index c92489af..25feb54f 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -44,18 +44,22 @@ TcpConnection TcpServer::acceptNewClient() { if (!this->wasOpened()) { throw Error("Tcp server was not opened before starting to accept clients"); } - SocketAddress acceptedClientAddress; - SocketAddressLength acceptedAddressLength = sizeof(SocketAddress); + + char acceptedClientAddress[MAX_POSSIBLE_ADDRESS_SIZE]; + SocketAddressLength acceptedClientAddress_length = MAX_POSSIBLE_ADDRESS_SIZE; + // accept: wait for a client to call connect and hit this server and get a // pointer to this client. SocketID accepted_client_socket_id = - ::accept(getIDWrapper().accessId(), &acceptedClientAddress, - &acceptedAddressLength); + ::accept(getIDWrapper().accessId(), + reinterpret_cast(&acceptedClientAddress[0]), + &acceptedClientAddress_length); if (accepted_client_socket_id == SCK_INVALID_SOCKET) { throwWithLastErrorCode("Error: accepting new client"); } - auto accepted_client_parsed_address = toAddress(acceptedClientAddress); + auto accepted_client_parsed_address = + toAddress(reinterpret_cast(acceptedClientAddress)); TcpConnection result(accepted_client_parsed_address); result.getIDWrapper().reset(accepted_client_socket_id); return std::move(result); From 6ef909da40eb6cfd03173c02982a20eb41422a20 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 14:49:50 +0100 Subject: [PATCH 119/228] testing --- tests/TestTCP.cpp | 2 +- tests/TestUDP.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index e3cb59d8..866e2e26 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -51,7 +51,7 @@ void send_response(const SenderReceiver &requester, TEST_CASE("Establish tcp connection", "[tcp]") { const auto port = PortFactory::makePort(); - const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); + const auto family = IP_V6; // GENERATE(IP_V4, IP_V6); std::unique_ptr server_side; std::unique_ptr client_side; diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 9e596490..0a0371ee 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -17,8 +17,8 @@ static const std::string request = "Hello"; static const std::string response = "Welcome"; } // namespace -TEST_CASE("exchange messages between UdpBinded and UdpBinded", "[udp]") { - const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); +TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { + const auto family = IP_V6; // GENERATE(IP_V4, IP_V6); const std::size_t cycles = 5; const auto requester_port = PortFactory::makePort(); @@ -56,7 +56,7 @@ TEST_CASE("exchange messages between UdpBinded and UdpBinded", "[udp]") { }); } -TEST_CASE("exchange messages between UdpConnected and UdpConnected", "[udp]") { +TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); const std::size_t cycles = 5; From 3c46f8e29deae62a598e7ce022e36efe11987282 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 16:30:17 +0100 Subject: [PATCH 120/228] testing --- TODO | 6 ++++++ tests/TestTCP.cpp | 6 +++--- tests/TestUDP.cpp | 36 ++++++++++++++++++++++++------------ 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/TODO b/TODO index a434cb3f..f7763227 100644 --- a/TODO +++ b/TODO @@ -21,3 +21,9 @@ check in c'tor passed address is not null (is valid) nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema riabilita in test ipv6 + +aggiungere test con ANY_PORT + +aggiugnere test in udp connected dove si vede che terzo socket che manda non connesso non viene ricevuto + +accept tcp client con timeout diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 866e2e26..8d748f8f 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -51,7 +51,7 @@ void send_response(const SenderReceiver &requester, TEST_CASE("Establish tcp connection", "[tcp]") { const auto port = PortFactory::makePort(); - const auto family = IP_V6; // GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(IP_V4, IP_V6); std::unique_ptr server_side; std::unique_ptr client_side; @@ -97,7 +97,7 @@ TEST_CASE("Establish tcp connection", "[tcp]") { TEST_CASE("Establish many tcp connections to same server", "[tcp]") { const auto port = PortFactory::makePort(); - const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(IP_V4, IP_V6); TcpServer server(port, family); server.open(); @@ -142,7 +142,7 @@ 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 family = IP_V4; // GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(IP_V4, IP_V6); TcpServer server(port, family); server.open(); diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 0a0371ee..a6364524 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -15,19 +15,26 @@ using namespace MinimalSocket::test; namespace { static const std::string request = "Hello"; static const std::string response = "Welcome"; + +bool are_same(const Address &a, const Address &b, const AddressFamily &family) { + return (family == AddressFamily::IP_V4) ? (a == b) + : (a.getPort() == b.getPort()); +} } // namespace TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { - const auto family = IP_V6; // GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(IP_V4, IP_V6); const std::size_t cycles = 5; const auto requester_port = PortFactory::makePort(); - const Address requester_address = Address::makeLocalHost(requester_port); + const Address requester_address = + Address::makeLocalHost(requester_port, family); UdpBinded requester(requester_port, family); REQUIRE(requester.open()); const auto responder_port = PortFactory::makePort(); - const Address responder_address = Address::makeLocalHost(responder_port); + const Address responder_address = + Address::makeLocalHost(responder_port, family); UdpBinded responder(responder_port, family); REQUIRE(responder.open()); @@ -40,7 +47,7 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { auto received_response = requester.receive(response.size()); REQUIRE(received_response); CHECK(received_response->received_message == response); - CHECK(received_response->sender == responder_address); + CHECK(are_same(received_response->sender, responder_address, family)); } }, [&]() { @@ -49,7 +56,7 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { auto received_request = responder.receive(request.size()); REQUIRE(received_request); CHECK(received_request->received_message == request); - CHECK(received_request->sender == requester_address); + CHECK(are_same(received_request->sender, requester_address, family)); responder.sendTo(response, requester_address); #pragma omp barrier } @@ -57,14 +64,16 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { } TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { - const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(IP_V4, IP_V6); const std::size_t cycles = 5; const auto requester_port = PortFactory::makePort(); - const Address requester_address = Address::makeLocalHost(requester_port); + const Address requester_address = + Address::makeLocalHost(requester_port, family); const auto responder_port = PortFactory::makePort(); - const Address responder_address = Address::makeLocalHost(responder_port); + const Address responder_address = + Address::makeLocalHost(responder_port, family); UdpConnected requester(responder_address, requester_port); REQUIRE(requester.open()); @@ -93,13 +102,15 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { } TEST_CASE("Metamorphosis of udp connections", "[udp]") { - const auto family = IP_V4; // GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(IP_V4, IP_V6); const std::size_t cycles = 5; const auto requester_port = PortFactory::makePort(); - const Address requester_address = Address::makeLocalHost(requester_port); + const Address requester_address = + Address::makeLocalHost(requester_port, family); const auto responder_port = PortFactory::makePort(); - const Address responder_address = Address::makeLocalHost(responder_port); + const Address responder_address = + Address::makeLocalHost(responder_port, family); UdpBinded responder(responder_port, family); REQUIRE(responder.open()); @@ -122,7 +133,8 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { std::optional socket_connected = requester_only_bind->connect(); REQUIRE(socket_connected); - REQUIRE(socket_connected->getRemoteAddress() == responder_address); + CHECK(are_same(socket_connected->getRemoteAddress(), + responder_address, family)); requester_connected = std::make_unique( std::move(socket_connected.value())); }); From 6225612a392534c118754e704e696b162d753f81 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 16:36:17 +0100 Subject: [PATCH 121/228] refactoring --- src/header/MinimalSocket/udp/UdpSocket.h | 5 +++++ src/src/udp/UdpSocket.cpp | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 47f4afdf..28270017 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -64,4 +64,9 @@ class UdpConnected : public Sender, protected: void open_() override; }; + +UdpConnected makeUdpConnected( + const Port &port = ANY_PORT, + const AddressFamily &accepted_connection_family = AddressFamily::IP_V4, + const Timeout &timeout = NULL_TIMEOUT); } // namespace MinimalSocket::udp diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index d7fe5e49..5519c595 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -85,4 +85,16 @@ UdpBinded UdpConnected::disconnect() { result.open(); return std::move(result); } + +UdpConnected makeUdpConnected(const Port &port, + const AddressFamily &accepted_connection_family, + const Timeout &timeout) { + UdpBinded primal_socket(port, accepted_connection_family); + primal_socket.open(); + auto maybe_result = primal_socket.connect(timeout); + if (!maybe_result) { + throw Error{"Something went wrong creating a UdpConnected socket"}; + } + return std::move(maybe_result.value()); +} } // namespace MinimalSocket::udp From 5f2c1b57276d61c2d4ee6120df586975fc74a214 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 17:22:48 +0100 Subject: [PATCH 122/228] testing --- src/header/MinimalSocket/udp/UdpSocket.h | 2 +- src/src/core/Socket.cpp | 2 +- src/src/udp/UdpSocket.cpp | 7 +- tests/TestOpenTimeout.cpp | 36 ++++++ tests/TestTCP.cpp | 156 +++++++++++++++++------ 5 files changed, 162 insertions(+), 41 deletions(-) create mode 100644 tests/TestOpenTimeout.cpp diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 28270017..31b0c58e 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -65,7 +65,7 @@ class UdpConnected : public Sender, void open_() override; }; -UdpConnected makeUdpConnected( +UdpConnected makeUdpConnectedToUnknown( const Port &port = ANY_PORT, const AddressFamily &accepted_connection_family = AddressFamily::IP_V4, const Timeout &timeout = NULL_TIMEOUT); diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index db0c8ced..a0a64704 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -73,7 +73,7 @@ bool Openable::open(const Timeout &timeout) { if (open_task_status != std::future_status::ready) { resetIDWrapper(); open_task.get(); // should throw already here - throw Error{""}; // jsut to be sure it throws + throw Error{""}; // just to be sure it throws } } opened = true; diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index 5519c595..3609b5c2 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -86,9 +86,10 @@ UdpBinded UdpConnected::disconnect() { return std::move(result); } -UdpConnected makeUdpConnected(const Port &port, - const AddressFamily &accepted_connection_family, - const Timeout &timeout) { +UdpConnected +makeUdpConnectedToUnknown(const Port &port, + const AddressFamily &accepted_connection_family, + const Timeout &timeout) { UdpBinded primal_socket(port, accepted_connection_family); primal_socket.open(); auto maybe_result = primal_socket.connect(timeout); diff --git a/tests/TestOpenTimeout.cpp b/tests/TestOpenTimeout.cpp new file mode 100644 index 00000000..01227dad --- /dev/null +++ b/tests/TestOpenTimeout.cpp @@ -0,0 +1,36 @@ +#include +#include + +#include + +#include + +using namespace MinimalSocket; + +namespace { +class OpenableTest : public Openable { +public: + OpenableTest(const Timeout &duration) : open_duration(duration) {} + +protected: + void open_() final { std::this_thread::sleep_for(open_duration); }; + +private: + const Timeout open_duration; +}; +} // namespace + +TEST_CASE("Simulate open with timeout", "[open]") { + auto open_time = Timeout{500}; + OpenableTest test(open_time); + + SECTION("expected success") { + CHECK(test.open(Timeout{1000})); + CHECK(test.wasOpened()); + } + + SECTION("expected failure") { + CHECK_FALSE(test.open(Timeout{250})); + CHECK_FALSE(test.wasOpened()); + } +} diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 8d748f8f..803a18c0 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -15,6 +15,40 @@ using namespace MinimalSocket::tcp; using namespace MinimalSocket::test; namespace { +struct Peers { + std::unique_ptr server_side; + std::unique_ptr client_side; +}; +Peers make_peers(const Port &port, const AddressFamily &family) { + std::unique_ptr server_side; + std::unique_ptr client_side; + + parallel( + [&]() { + // server + TcpServer server(port, family); + REQUIRE(server.open()); +#pragma omp barrier + auto accepted = server.acceptNewClient(); + REQUIRE_FALSE(nullptr == accepted); + server_side = std::make_unique(std::move(accepted)); + }, + [&]() { + // client + TcpClient client(Address::makeLocalHost(port, family)); +#pragma omp barrier + REQUIRE(client.open()); + REQUIRE_FALSE(nullptr == client); + REQUIRE(client.wasOpened()); + client_side = std::make_unique(std::move(client)); + }); + + return Peers{std::move(server_side), std::move(client_side)}; +} + +static const std::string request = "Hello"; +static const std::string response = "Welcome"; + struct SenderReceiver { Sender &sender; Receiver &receiver; @@ -28,8 +62,6 @@ template SenderReceiver makeSenderReceiver(T &subject) { void send_response(const SenderReceiver &requester, const SenderReceiver &responder) { std::size_t cycles = 5; - const std::string request = "Hello"; - const std::string response = "Welcome"; parallel( [&]() { @@ -53,45 +85,33 @@ TEST_CASE("Establish tcp connection", "[tcp]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(IP_V4, IP_V6); - std::unique_ptr server_side; - std::unique_ptr client_side; + SECTION("expected success") { + auto peers = make_peers(port, family); + auto &[server_side, client_side] = peers; - parallel( - [&]() { - // server - TcpServer server(port, family); - REQUIRE(server.open()); -#pragma omp barrier - auto accepted = server.acceptNewClient(); - REQUIRE_FALSE(nullptr == accepted); - server_side = std::make_unique(std::move(accepted)); - }, - [&]() { - // client - TcpClient client(Address::makeLocalHost(port, family)); -#pragma omp barrier - REQUIRE(client.open()); - REQUIRE_FALSE(nullptr == client); - REQUIRE(client.wasOpened()); - client_side = std::make_unique(std::move(client)); - }); + REQUIRE_FALSE(nullptr == *client_side); + REQUIRE(client_side->wasOpened()); + REQUIRE_FALSE(nullptr == *server_side); - REQUIRE_FALSE(nullptr == *client_side); - REQUIRE(client_side->wasOpened()); - REQUIRE_FALSE(nullptr == *server_side); + const std::size_t cycles = 5; + const std::string request = "Hello"; + const std::string response = "Welcome"; - 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)); + } - SECTION("client send, server respond") { - send_response(makeSenderReceiver(*client_side), - makeSenderReceiver(*server_side)); + SECTION("server send, client respond") { + send_response(makeSenderReceiver(*server_side), + makeSenderReceiver(*client_side)); + } } - SECTION("server send, client respond") { - send_response(makeSenderReceiver(*server_side), - makeSenderReceiver(*client_side)); + SECTION("expected failure") { + TcpClient client(Address::makeLocalHost(port, family)); + CHECK_FALSE(client.open()); + CHECK_FALSE(client.wasOpened()); } } @@ -161,4 +181,68 @@ TEST_CASE("Open multiple times tcp clients", "[tcp]") { } } -// tests receive e open con timeout +#include + +TEST_CASE("Open tcp client with timeout", "[tcp]") { + const auto port = PortFactory::makePort(); + const auto family = GENERATE(IP_V4, IP_V6); + + const auto timeout = Timeout{500}; + + TcpClient client(Address::makeLocalHost(port, family)); + + SECTION("expect fail within timeout") { + CHECK_FALSE(client.open(timeout)); + CHECK_FALSE(client.wasOpened()); + } + + SECTION("expect success within timeout") { + const auto wait = Timeout{250}; + TcpServer server(port, family); + REQUIRE(server.open()); + parallel( + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(wait); + server.acceptNewClient(); + }, + [&]() { +#pragma omp barrier + CHECK(client.open(timeout)); + }); + } +} + +TEST_CASE("Receive with timeout", "[tcp]") { + const auto port = PortFactory::makePort(); + const auto family = GENERATE(IP_V4, IP_V6); + + auto peers = make_peers(port, family); + auto &[server_side, client_side] = peers; + + REQUIRE_FALSE(nullptr == *client_side); + REQUIRE(client_side->wasOpened()); + REQUIRE_FALSE(nullptr == *server_side); + + const auto timeout = Timeout{500}; + + SECTION("expect fail within timeout") { + auto received = server_side->receive(request.size(), timeout); + CHECK(received.empty()); + } + + SECTION("expect success within timeout") { + const auto wait = Timeout{250}; + parallel( + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(wait); + client_side->send(request); + }, + [&]() { +#pragma omp barrier + auto received_request = server_side->receive(request.size(), timeout); + CHECK(received_request == request); + }); + } +} From dcd80bc8e56969d118481d9dbeb9eef5680d67fc Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 17:36:03 +0100 Subject: [PATCH 123/228] testing for timeout functions --- tests/TestTCP.cpp | 70 ++++++++++++++++------------------- tests/TestUDP.cpp | 93 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 40 deletions(-) diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 803a18c0..1a97e1dc 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -85,6 +86,12 @@ TEST_CASE("Establish tcp connection", "[tcp]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(IP_V4, IP_V6); + SECTION("expected failure") { + TcpClient client(Address::makeLocalHost(port, family)); + CHECK_FALSE(client.open()); + CHECK_FALSE(client.wasOpened()); + } + SECTION("expected success") { auto peers = make_peers(port, family); auto &[server_side, client_side] = peers; @@ -106,12 +113,31 @@ TEST_CASE("Establish tcp connection", "[tcp]") { send_response(makeSenderReceiver(*server_side), makeSenderReceiver(*client_side)); } - } - SECTION("expected failure") { - TcpClient client(Address::makeLocalHost(port, family)); - CHECK_FALSE(client.open()); - CHECK_FALSE(client.wasOpened()); + SECTION("receive with timeout") { + const auto timeout = Timeout{500}; + + SECTION("expect fail within timeout") { + auto received_request = server_side->receive(request.size(), timeout); + CHECK(received_request.empty()); + } + + SECTION("expect success within timeout") { + const auto wait = Timeout{250}; + parallel( + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(wait); + client_side->send(request); + }, + [&]() { +#pragma omp barrier + auto received_request = + server_side->receive(request.size(), timeout); + CHECK(received_request == request); + }); + } + } } } @@ -212,37 +238,3 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { }); } } - -TEST_CASE("Receive with timeout", "[tcp]") { - const auto port = PortFactory::makePort(); - const auto family = GENERATE(IP_V4, IP_V6); - - auto peers = make_peers(port, family); - auto &[server_side, client_side] = peers; - - REQUIRE_FALSE(nullptr == *client_side); - REQUIRE(client_side->wasOpened()); - REQUIRE_FALSE(nullptr == *server_side); - - const auto timeout = Timeout{500}; - - SECTION("expect fail within timeout") { - auto received = server_side->receive(request.size(), timeout); - CHECK(received.empty()); - } - - SECTION("expect success within timeout") { - const auto wait = Timeout{250}; - parallel( - [&]() { -#pragma omp barrier - std::this_thread::sleep_for(wait); - client_side->send(request); - }, - [&]() { -#pragma omp barrier - auto received_request = server_side->receive(request.size(), timeout); - CHECK(received_request == request); - }); - } -} diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index a6364524..d3f7c84c 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -61,6 +62,33 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { #pragma omp barrier } }); + + SECTION("receive with timeout") { + const auto timeout = Timeout{500}; + + SECTION("expect fail within timeout") { + auto received_request = responder.receive(request.size(), timeout); + CHECK_FALSE(received_request); + } + + SECTION("expect success within timeout") { + const auto wait = Timeout{250}; + parallel( + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(wait); + requester.sendTo(request, responder_address); + }, + [&]() { +#pragma omp barrier + auto received_request = responder.receive(request.size(), timeout); + REQUIRE(received_request); + CHECK(received_request->received_message == request); + CHECK( + are_same(received_request->sender, requester_address, family)); + }); + } + } } TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { @@ -99,6 +127,30 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { #pragma omp barrier } }); + + SECTION("receive with timeout") { + const auto timeout = Timeout{500}; + + SECTION("expect fail within timeout") { + auto received_request = responder.receive(request.size(), timeout); + CHECK(received_request.empty()); + } + + SECTION("expect success within timeout") { + const auto wait = Timeout{250}; + parallel( + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(wait); + requester.send(request); + }, + [&]() { +#pragma omp barrier + auto received_request = responder.receive(request.size(), timeout); + CHECK(received_request == request); + }); + } + } } TEST_CASE("Metamorphosis of udp connections", "[udp]") { @@ -197,4 +249,43 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { }); } -// tests receive e open con timeout +#include + +TEST_CASE("Open connection with timeout", "[udp]") { + const auto family = GENERATE(IP_V4, IP_V6); + + const auto requester_port = PortFactory::makePort(); + const Address requester_address = + Address::makeLocalHost(requester_port, family); + UdpBinded requester(requester_port, family); + REQUIRE(requester.open()); + + const auto responder_port = PortFactory::makePort(); + const Address responder_address = + Address::makeLocalHost(responder_port, family); + UdpBinded responder(responder_port, family); + REQUIRE(responder.open()); + + const auto timeout = Timeout{500}; + + SECTION("expect fail within timeout") { + CHECK_FALSE(requester.connect(timeout)); + } + + SECTION("expect success within timeout") { + const auto wait = Timeout{250}; + parallel( + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(wait); + responder.sendTo("1", requester_address); + }, + [&]() { +#pragma omp barrier + auto connected_result = requester.connect(timeout); + REQUIRE(connected_result); + CHECK(are_same(connected_result->getRemoteAddress(), + responder_address, family)); + }); + } +} From 0af20ec2e0513ca948e85b042f00a73226d8b550 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 17:47:18 +0100 Subject: [PATCH 124/228] testing --- TODO | 2 -- tests/TestTCP.cpp | 2 -- tests/TestUDP.cpp | 20 ++++++++++++++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/TODO b/TODO index f7763227..fb6fbc4d 100644 --- a/TODO +++ b/TODO @@ -24,6 +24,4 @@ riabilita in test ipv6 aggiungere test con ANY_PORT -aggiugnere test in udp connected dove si vede che terzo socket che manda non connesso non viene ricevuto - accept tcp client con timeout diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 1a97e1dc..a71e7fb1 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -207,8 +207,6 @@ TEST_CASE("Open multiple times tcp clients", "[tcp]") { } } -#include - TEST_CASE("Open tcp client with timeout", "[tcp]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(IP_V4, IP_V6); diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index d3f7c84c..9ec8d713 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -151,6 +151,24 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { }); } } + + SECTION( + "receive from third peer expected to fail since socket was connected") { + UdpConnected second_requester(responder_address, PortFactory::makePort()); + const auto timeout = Timeout{500}; + const auto wait = Timeout{250}; + parallel( + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(wait); + second_requester.send(request); + }, + [&]() { +#pragma omp barrier + auto received_request = responder.receive(request.size(), timeout); + CHECK(received_request.empty()); + }); + } } TEST_CASE("Metamorphosis of udp connections", "[udp]") { @@ -249,8 +267,6 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { }); } -#include - TEST_CASE("Open connection with timeout", "[udp]") { const auto family = GENERATE(IP_V4, IP_V6); From b131f0c58ce9143d852085bef46383905743d3d9 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 17:51:43 +0100 Subject: [PATCH 125/228] enabling ci tests --- .github/workflows/runTests.yml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index c48060b5..eaa4b0b4 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -31,21 +31,19 @@ jobs: - name: Checkout submodules run: git submodule update --init --recursive - name: CMake configure - run: cmake -B./build -DCMAKE_INSTALL_PREFIX:STRING=./artifacts/ -DBUILD_SAMPLES=OFF -DBUILD_TESTS=ON -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_BUILD_TYPE:STRING=Release ${{ matrix.compiler_opt }} + run: cmake -B./build -DCMAKE_INSTALL_PREFIX:STRING=./artifacts/ -DBUILD_MinimalCppSocket_SAMPLES=OFF -DBUILD_MinimalCppSocket_TESTS=ON -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_BUILD_TYPE:STRING=Release ${{ matrix.compiler_opt }} - name: Build run: cmake --build ./build --config Release - name: Install run: cmake --install ./build --config Release - - name: Test01-tcp-synch - run: ./artifacts/bin/Test01-tcp-synch - - name: Test01-udp-synch - run: ./artifacts/bin/Test01-udp-synch - - name: Test02-tcp-asynch - run: ./artifacts/bin/Test02-tcp-asynch - - name: Test02-udp-asynch - run: ./artifacts/bin/Test02-udp-asynch - - name: Test03-typed - run: ./artifacts/bin/Test03-typed + - name: Test-open + run: ./artifacts/bin/Tests "[open]" + - name: Test-address + run: ./artifacts/bin/Tests "[address]" + - name: Test-tcp + run: ./artifacts/bin/Tests "[tcp]" + - name: Test-udp + run: ./artifacts/bin/Tests "[udp]" - uses: actions/upload-artifact@v2 with: path: artifacts From 09212753715ec1cca8b041c3729da759f8591807 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 9 May 2022 17:52:16 +0100 Subject: [PATCH 126/228] enabling artifats ci --- .github/workflows/installArtifacts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/installArtifacts.yml b/.github/workflows/installArtifacts.yml index e53dee49..7bc96846 100644 --- a/.github/workflows/installArtifacts.yml +++ b/.github/workflows/installArtifacts.yml @@ -47,7 +47,7 @@ jobs: - name: Checkout submodules run: git submodule update --init --recursive - name: CMake configure - run: cmake -B./build -DCMAKE_INSTALL_PREFIX:STRING=./artifacts/ -DBUILD_SAMPLES=OFF -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_BUILD_TYPE:STRING=Release ${{ matrix.compiler_opt }} ${{ matrix.lib_opt }} + run: cmake -B./build -DCMAKE_INSTALL_PREFIX:STRING=./artifacts/ -DBUILD_MinimalCppSocket_SAMPLES=OFF -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_BUILD_TYPE:STRING=Release ${{ matrix.compiler_opt }} ${{ matrix.lib_opt }} - name: build release run: cmake --build ./build --config Release - name: Install artifacts From ab697843d575f509a826803de198b2e9ca7a5b73 Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Mon, 9 May 2022 17:07:12 +0100 Subject: [PATCH 127/228] fixing windows compile errors --- src/src/SocketAddress.h | 2 +- src/src/SocketId.cpp | 2 +- src/src/SocketId.h | 26 +++++++++++++------------- src/src/core/Receiver.cpp | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/src/SocketAddress.h b/src/src/SocketAddress.h index 8049f77a..6a3bcc25 100644 --- a/src/src/SocketAddress.h +++ b/src/src/SocketAddress.h @@ -43,7 +43,7 @@ using SocketAddressIpv6 = sockaddr_in6; #endif static constexpr std::size_t MAX_POSSIBLE_ADDRESS_SIZE = - std::max(sizeof(SocketAddressIpv4), sizeof(SocketAddressIpv6)); + std::max(sizeof(SocketAddressIpv4), sizeof(SocketAddressIpv6)); /** * @brief checks the address syntax and in case diff --git a/src/src/SocketId.cpp b/src/src/SocketId.cpp index 5f44ac7f..f60f9a39 100644 --- a/src/src/SocketId.cpp +++ b/src/src/SocketId.cpp @@ -49,7 +49,7 @@ void close(SocketID &socket_id) { #endif socket_id = SCK_INVALID_SOCKET; #ifdef _WIN32 - SocketIDFactory::afterClose(); + SocketIdWrapper::SocketIDFactory::afterClose(); #endif } } // namespace diff --git a/src/src/SocketId.h b/src/src/SocketId.h index cdd6d86f..e1776741 100644 --- a/src/src/SocketId.h +++ b/src/src/SocketId.h @@ -74,26 +74,26 @@ class SocketIdWrapper { */ void reset(const SocketID &hndl); -private: - SocketID socket_id = SCK_INVALID_SOCKET; - #ifdef _WIN32 class SocketIDFactory { public: - /** - * @brief If we are about to open the first socket, WSAStartup() is invoked - */ - static void beforeOpen(); - /** - * @brief If we are closing the last socket, WSACleanup() is invoked - */ - static void afterClose(); + /** + * @brief If we are about to open the first socket, WSAStartup() is invoked + */ + static void beforeOpen(); + /** + * @brief If we are closing the last socket, WSACleanup() is invoked + */ + static void afterClose(); private: - static std::mutex handlerCounterMtx; - static std::size_t handlerCounter; + static std::mutex handlerCounterMtx; + static std::size_t handlerCounter; }; #endif + +private: + SocketID socket_id = SCK_INVALID_SOCKET; }; } // namespace MinimalSocket diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 537c825d..3cbf2141 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -21,7 +21,7 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { // set new timeout #ifdef _WIN32 auto tv = DWORD(this->receive_timeout.count()); - if (setsockopt(getIDWrapper().access(), SOL_SOCKET, SO_RCVTIMEO, + if (setsockopt(getIDWrapper().accessId(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { #else From d6238a6b9daea90135aa692117af3da06680294a Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Mon, 9 May 2022 20:43:56 +0100 Subject: [PATCH 128/228] better WSData handling --- TODO | 4 ---- src/src/SocketAddress.cpp | 20 ++++++++++++++++---- src/src/SocketId.cpp | 32 +++++++++++++------------------- src/src/SocketId.h | 37 ++++++++++++++++++------------------- 4 files changed, 47 insertions(+), 46 deletions(-) diff --git a/TODO b/TODO index fb6fbc4d..51e95c92 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,5 @@ use inet_pton also in windows -replace all std::uint16_t with Port - check if there is a way to check if an external passed socket was open or not in SocketHandler::open(const SocketHandlerType &) support for bluetooth connection @@ -20,8 +18,6 @@ check in c'tor passed address is not null (is valid) nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema -riabilita in test ipv6 - aggiungere test con ANY_PORT accept tcp client con timeout diff --git a/src/src/SocketAddress.cpp b/src/src/SocketAddress.cpp index cf3a0765..90a9625c 100644 --- a/src/src/SocketAddress.cpp +++ b/src/src/SocketAddress.cpp @@ -19,6 +19,10 @@ namespace MinimalSocket { std::optional toSocketAddressIpv4(const std::string &host, const Port &port) { +#ifdef _WIN32 + WSALazyInitializer::lazyInit(); +#endif + std::optional result; auto &result_ref = result.emplace(); // set everything to 0 first @@ -51,14 +55,18 @@ std::optional toSocketAddressIpv4(const std::string &host, return std::nullopt; } - auto ipv4 = reinterpret_cast(res->ai_addr); - result_ref.sin_addr.s_addr = ipv4->sin_addr.s_addr; + const auto& ipv4 = reinterpret_cast(res->ai_addr); + result_ref.sin_addr.s_addr = ipv4.sin_addr.s_addr; ::freeaddrinfo(res); return result; } std::optional toSocketAddressIpv6(const std::string &host, const Port &port) { +#ifdef _WIN32 + WSALazyInitializer::lazyInit(); +#endif + std::optional result; auto &result_ref = result.emplace(); // set everything to 0 first @@ -92,13 +100,17 @@ std::optional toSocketAddressIpv6(const std::string &host, return std::nullopt; } - auto ipv6 = reinterpret_cast(res->ai_addr); - result_ref.sin6_addr = ipv6->sin6_addr; + const auto& ipv6 = reinterpret_cast(res->ai_addr); + result_ref.sin6_addr = ipv6.sin6_addr; ::freeaddrinfo(res); return result; } Address toAddress(const SocketAddress &address) { +#ifdef _WIN32 + WSALazyInitializer::lazyInit(); +#endif + // refer to // https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu std::string ip; diff --git a/src/src/SocketId.cpp b/src/src/SocketId.cpp index f60f9a39..191d10ca 100644 --- a/src/src/SocketId.cpp +++ b/src/src/SocketId.cpp @@ -12,26 +12,23 @@ namespace MinimalSocket { #ifdef _WIN32 -std::size_t SocketIdWrapper::SocketIDFactory::handlerCounter = 0; -std::mutex SocketIdWrapper::SocketIDFactory::handlerCounterMtx; - -void SocketIdWrapper::SocketIDFactory::beforeOpen() { - std::lock_guard hndLck(handlerCounterMtx); - ++handlerCounter; - if (1 == handlerCounter) { - // first socket opened +WSALazyInitializer::WSALazyInitializer() { WSADATA wsa; WSAStartup(MAKEWORD(2, 0), &wsa); - } } -void SocketIdWrapper::SocketIDFactory::afterClose() { - std::lock_guard hndLck(handlerCounterMtx); - --handlerCounter; - if (0 == handlerCounter) { - // last socket closed +WSALazyInitializer::~WSALazyInitializer() { WSACleanup(); - } +} + +std::mutex WSALazyInitializer::lazy_proxy_mtx = std::mutex{}; +std::unique_ptr WSALazyInitializer::lazy_proxy = nullptr; + +void WSALazyInitializer::lazyInit() { + std::scoped_lock lock(WSALazyInitializer::lazy_proxy_mtx); + if (nullptr == WSALazyInitializer::lazy_proxy) { + WSALazyInitializer::lazy_proxy.reset(new WSALazyInitializer{}); + } } #endif @@ -48,9 +45,6 @@ void close(SocketID &socket_id) { ::close(socket_id); #endif socket_id = SCK_INVALID_SOCKET; -#ifdef _WIN32 - SocketIdWrapper::SocketIDFactory::afterClose(); -#endif } } // namespace @@ -80,7 +74,7 @@ void SocketIdWrapper::reset(const SocketType &type, } #ifdef _WIN32 - SocketIDFactory::beforeOpen(); + WSALazyInitializer::lazyInit(); #endif switch (type) { diff --git a/src/src/SocketId.h b/src/src/SocketId.h index e1776741..40fcfab8 100644 --- a/src/src/SocketId.h +++ b/src/src/SocketId.h @@ -11,11 +11,13 @@ #include #ifdef _WIN32 -#include #include #include #include #include + +#include +#include #else #include #include @@ -74,26 +76,23 @@ class SocketIdWrapper { */ void reset(const SocketID &hndl); -#ifdef _WIN32 - class SocketIDFactory { - public: - /** - * @brief If we are about to open the first socket, WSAStartup() is invoked - */ - static void beforeOpen(); - /** - * @brief If we are closing the last socket, WSACleanup() is invoked - */ - static void afterClose(); - - private: - static std::mutex handlerCounterMtx; - static std::size_t handlerCounter; - }; -#endif - private: SocketID socket_id = SCK_INVALID_SOCKET; }; + +#ifdef _WIN32 +class WSALazyInitializer { +public: + static void lazyInit(); + + ~WSALazyInitializer(); + +private: + WSALazyInitializer(); + + static std::mutex lazy_proxy_mtx; + static std::unique_ptr lazy_proxy; +}; +#endif } // namespace MinimalSocket From 032f926eddeab2d09cfb1253841b7cee5b77f0f6 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Wed, 11 May 2022 14:05:09 +0100 Subject: [PATCH 129/228] control over listen backlog size --- TODO | 4 ---- src/header/MinimalSocket/tcp/TcpServer.h | 7 +++++++ src/src/SocketFunctions.cpp | 8 ++------ src/src/SocketFunctions.h | 2 +- src/src/tcp/TcpServer.cpp | 9 ++++++++- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/TODO b/TODO index 51e95c92..e101c0dd 100644 --- a/TODO +++ b/TODO @@ -4,8 +4,6 @@ check if there is a way to check if an external passed socket was open or not in support for bluetooth connection -add timeout per udp connect - in receive from, resize buffer a max size udp message usare noexpect keyword in c'tors? @@ -14,8 +12,6 @@ pass number of max clients tcp server should accept e' thread safety assicurata nelle varie funzioni? -check in c'tor passed address is not null (is valid) - nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema aggiungere test con ANY_PORT diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index d04a3725..b30814d4 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -40,7 +40,14 @@ class TcpServer : public PortToBindAware, TcpConnection acceptNewClient(); + void setClientQueueSize(const std::size_t queue_size); + protected: void open_() override; + +private: + std::size_t client_queue_size = + 50; // maximum number of clients put in the queue wiating for connection + // to be accepted }; } // namespace MinimalSocket::tcp diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index 35f0a716..2600a86d 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -96,12 +96,8 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, return binded_port; } -namespace { -static constexpr std::size_t LISTEN_BACKLOG = 50; -} - -void listen(const SocketID &socket_id) { - if (::listen(socket_id, LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { +void listen(const SocketID &socket_id, const std::size_t backlog_size) { + if (::listen(socket_id, backlog_size) == SCK_SOCKET_ERROR) { throwWithLastErrorCode("Error: listening on reserved port"); } } diff --git a/src/src/SocketFunctions.h b/src/src/SocketFunctions.h index dc3538b7..8c93a2f2 100644 --- a/src/src/SocketFunctions.h +++ b/src/src/SocketFunctions.h @@ -14,7 +14,7 @@ namespace MinimalSocket { Port bind(const SocketID &socket_id, const AddressFamily &family, const Port &port); -void listen(const SocketID &socket_id); +void listen(const SocketID &socket_id, const std::size_t backlog_size); void connect(const SocketID &socket_id, const Address &remote_address); } // namespace MinimalSocket diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 25feb54f..9ab4e34c 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -37,7 +37,14 @@ void TcpServer::open_() { socket.reset(TCP, family); auto binded_port = MinimalSocket::bind(socket.accessId(), family, port); setPort(binded_port); - MinimalSocket::listen(socket.accessId()); + MinimalSocket::listen(socket.accessId(), client_queue_size); +} + +void TcpServer::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() { From a0c8bc22f30d994cbff5b90995b02043dbece3eb Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Wed, 11 May 2022 19:37:11 +0100 Subject: [PATCH 130/228] better socket error with code --- TODO | 4 ---- src/header/MinimalSocket/Error.h | 3 ++- src/src/SocketError.cpp | 9 ++++----- src/src/SocketError.h | 16 +++++++++++----- src/src/SocketFunctions.cpp | 16 ++++++++-------- src/src/SocketId.cpp | 20 +++++++++----------- src/src/core/Receiver.cpp | 8 ++++---- src/src/core/Sender.cpp | 4 ++-- src/src/tcp/TcpServer.cpp | 2 +- tests/TestAddress.cpp | 5 +++-- 10 files changed, 44 insertions(+), 43 deletions(-) diff --git a/TODO b/TODO index e101c0dd..3853437c 100644 --- a/TODO +++ b/TODO @@ -1,15 +1,11 @@ use inet_pton also in windows -check if there is a way to check if an external passed socket was open or not in SocketHandler::open(const SocketHandlerType &) - support for bluetooth connection in receive from, resize buffer a max size udp message usare noexpect keyword in c'tors? -pass number of max clients tcp server should accept - e' thread safety assicurata nelle varie funzioni? nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema diff --git a/src/header/MinimalSocket/Error.h b/src/header/MinimalSocket/Error.h index a1eb0637..99f89427 100644 --- a/src/header/MinimalSocket/Error.h +++ b/src/header/MinimalSocket/Error.h @@ -11,13 +11,14 @@ #include namespace MinimalSocket { + class Error : public std::runtime_error { public: Error(const std::string &what) : std::runtime_error(what){}; template Error(Args... args) : Error(merge(args...)) {} -private: +protected: template static std::string merge(Args... args) { std::stringstream stream; merge(stream, args...); diff --git a/src/src/SocketError.cpp b/src/src/SocketError.cpp index 35a3304e..029dc5b2 100644 --- a/src/src/SocketError.cpp +++ b/src/src/SocketError.cpp @@ -5,12 +5,11 @@ * report any bug to andrecasa91@gmail.com. **/ -#include - #include "SocketError.h" #include "SocketId.h" namespace MinimalSocket { +namespace { int getLastErrorCode() { #ifdef _WIN32 return WSAGetLastError(); @@ -18,8 +17,8 @@ int getLastErrorCode() { return static_cast(errno); #endif } +} // namespace -void throwWithLastErrorCode(const std::string &what) { - throw Error(what, " , error code: ", getLastErrorCode()); -} +SocketError::SocketError(const std::string &what) + : Error(what, " , error code: ", getLastErrorCode()) {} } // namespace MinimalSocket diff --git a/src/src/SocketError.h b/src/src/SocketError.h index 7dcce091..b56b0f6b 100644 --- a/src/src/SocketError.h +++ b/src/src/SocketError.h @@ -7,13 +7,19 @@ #pragma once +#include + #include namespace MinimalSocket { -/** - * @brief returns the last error code raised by the socket API - */ -int getLastErrorCode(); +class SocketError : public Error { +public: + /** + * @brief last error code raised by the socket API is automatically retrieved + */ + SocketError(const std::string &what); -void throwWithLastErrorCode(const std::string &what); + template + SocketError(const Args &...args) : SocketError{merge(args...)} {}; +}; } // namespace MinimalSocket diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index 2600a86d..d29565da 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -50,8 +50,8 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, #endif if (::bind(socket_id, reinterpret_cast(&addr), sizeof(SocketAddressIpv4)) == SCK_SOCKET_ERROR) { - throwWithLastErrorCode("can't bind localhost on port: " + - std::to_string(port)); + throw SocketError{"Can't bind localhost on port: ", + std::to_string(port)}; } }, [&]() { @@ -65,8 +65,8 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, addr.sin6_port = htons(port); if (::bind(socket_id, reinterpret_cast(&addr), sizeof(SocketAddressIpv6)) == SCK_SOCKET_ERROR) { - throwWithLastErrorCode("can't bind localhost on port: " + - std::to_string(port)); + throw SocketError{"Can't bind localhost on port: ", + std::to_string(port)}; } }); @@ -77,7 +77,7 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, if (::getsockname(socket_id, reinterpret_cast(&binded_address[0]), &binded_address_length) == SCK_SOCKET_ERROR) { - throwWithLastErrorCode("Wasn't able to deduce the binded port"); + throw SocketError{"Wasn't able to deduce the binded port"}; } switch (reinterpret_cast(binded_address).sa_family) { case AF_INET: @@ -98,7 +98,7 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, void listen(const SocketID &socket_id, const std::size_t backlog_size) { if (::listen(socket_id, backlog_size) == SCK_SOCKET_ERROR) { - throwWithLastErrorCode("Error: listening on reserved port"); + throw SocketError{"Error: listening on reserved port"}; } } @@ -115,7 +115,7 @@ void connect(const SocketID &socket_id, const Address &remote_address) { } if (::connect(socket_id, reinterpret_cast(&(*addr)), sizeof(SocketAddressIpv4)) == SCK_SOCKET_ERROR) { - throwWithLastErrorCode("Connection can't be established"); + throw SocketError{"Connection can't be established"}; } }, [&]() { @@ -128,7 +128,7 @@ void connect(const SocketID &socket_id, const Address &remote_address) { } if (::connect(socket_id, reinterpret_cast(&(*addr)), sizeof(SocketAddressIpv6)) == SCK_SOCKET_ERROR) { - throwWithLastErrorCode("Connection can't be established"); + throw SocketError{"Connection can't be established"}; } }); } diff --git a/src/src/SocketId.cpp b/src/src/SocketId.cpp index 191d10ca..ecf29717 100644 --- a/src/src/SocketId.cpp +++ b/src/src/SocketId.cpp @@ -13,22 +13,20 @@ namespace MinimalSocket { #ifdef _WIN32 WSALazyInitializer::WSALazyInitializer() { - WSADATA wsa; - WSAStartup(MAKEWORD(2, 0), &wsa); + WSADATA wsa; + WSAStartup(MAKEWORD(2, 0), &wsa); } -WSALazyInitializer::~WSALazyInitializer() { - WSACleanup(); -} +WSALazyInitializer::~WSALazyInitializer() { WSACleanup(); } std::mutex WSALazyInitializer::lazy_proxy_mtx = std::mutex{}; std::unique_ptr WSALazyInitializer::lazy_proxy = nullptr; void WSALazyInitializer::lazyInit() { - std::scoped_lock lock(WSALazyInitializer::lazy_proxy_mtx); - if (nullptr == WSALazyInitializer::lazy_proxy) { - WSALazyInitializer::lazy_proxy.reset(new WSALazyInitializer{}); - } + std::scoped_lock lock(WSALazyInitializer::lazy_proxy_mtx); + if (nullptr == WSALazyInitializer::lazy_proxy) { + WSALazyInitializer::lazy_proxy.reset(new WSALazyInitializer{}); + } } #endif @@ -82,14 +80,14 @@ void SocketIdWrapper::reset(const SocketType &type, this->socket_id = ::socket(domain_number(family), SOCK_STREAM, 0); if (this->socket_id == SCK_INVALID_SOCKET) { MinimalSocket::close(socket_id); - throwWithLastErrorCode("Stream socket could not be created"); + throw SocketError{"Stream socket could not be created"}; } break; case SocketType::UDP: this->socket_id = ::socket(domain_number(family), SOCK_DGRAM, 0); if (this->socket_id == SCK_INVALID_SOCKET) { MinimalSocket::close(socket_id); - throwWithLastErrorCode("DataGram socket could not be created"); + throw SocketError{"DataGram socket could not be created"}; } break; default: diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 3cbf2141..fdb6c969 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -18,6 +18,7 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { if (timeout == receive_timeout) { return lock; } + receive_timeout = timeout; // set new timeout #ifdef _WIN32 auto tv = DWORD(this->receive_timeout.count()); @@ -39,9 +40,8 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { reinterpret_cast(&tv), sizeof(struct timeval)) < 0) { #endif - throwWithLastErrorCode("can't set timeout"); + throw SocketError{"can't set timeout"}; } - receive_timeout = timeout; return lock; } @@ -52,7 +52,7 @@ std::size_t Receiver::receive(Buffer &message, const Timeout &timeout) { static_cast(message.buffer_size), 0); if (recvBytes == SCK_SOCKET_ERROR) { recvBytes = 0; - throwWithLastErrorCode("receive failed"); + throw SocketError{"receive failed"}; } if (recvBytes > message.buffer_size) { // if here, the message received is probably corrupted @@ -86,7 +86,7 @@ ReceiverUnkownSender::receive(Buffer &message, const Timeout &timeout) { &sender_address_length); if (recvBytes == SCK_SOCKET_ERROR) { recvBytes = 0; - throwWithLastErrorCode("receive failed"); + throw SocketError{"receive failed"}; } if (recvBytes > message.buffer_size) { // if here, the message received is probably corrupted diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index a1225bf5..436b8fe3 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -18,7 +18,7 @@ bool Sender::send(const ConstBuffer &message) { static_cast(message.buffer_size), 0); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; - throwWithLastErrorCode("send failed"); + throw SocketError{"send failed"}; } return (sentBytes == static_cast(message.buffer_size)); } @@ -52,7 +52,7 @@ bool SenderTo::sendTo(const ConstBuffer &message, const Address &recipient) { }); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; - throwWithLastErrorCode("send to failed"); + throw SocketError{"sendto failed"}; } return (sentBytes == static_cast(message.buffer_size)); } diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 9ab4e34c..2d537a3c 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -62,7 +62,7 @@ TcpConnection TcpServer::acceptNewClient() { reinterpret_cast(&acceptedClientAddress[0]), &acceptedClientAddress_length); if (accepted_client_socket_id == SCK_INVALID_SOCKET) { - throwWithLastErrorCode("Error: accepting new client"); + throw SocketError{"accepting a new client"}; } auto accepted_client_parsed_address = diff --git a/tests/TestAddress.cpp b/tests/TestAddress.cpp index 7042c8ae..adafa5c1 100644 --- a/tests/TestAddress.cpp +++ b/tests/TestAddress.cpp @@ -10,7 +10,7 @@ static constexpr Port TEST_PORT = 100; } TEST_CASE("parse valid ipv4 hosts", "[address]") { - auto host = GENERATE("192.168.125.34", "127.0.0.1"); + 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); @@ -20,7 +20,8 @@ TEST_CASE("parse valid ipv4 hosts", "[address]") { TEST_CASE("parse valid ipv6 hosts", "[address]") { auto host = - GENERATE("2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8::1:0"); + 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); From 322c500d4c062f29b3b789725917db1d5684839c Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Wed, 11 May 2022 20:20:34 +0100 Subject: [PATCH 131/228] testing --- TODO | 4 ++++ src/src/SocketError.cpp | 11 +++++------ src/src/SocketError.h | 13 ++++++++++++- src/src/core/Receiver.cpp | 28 ++++++++++++++++++---------- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/TODO b/TODO index 3853437c..c43b69fe 100644 --- a/TODO +++ b/TODO @@ -13,3 +13,7 @@ nella descrizione stressare il fatto che si usa c++ moderno per addomesticare so aggiungere test con ANY_PORT accept tcp client con timeout + +add negative test per capire se vegono thrownati giusti SocketError + +add test dove request e spezzettata in piu frammenti (piu send diverse) diff --git a/src/src/SocketError.cpp b/src/src/SocketError.cpp index 029dc5b2..3b6e6cb7 100644 --- a/src/src/SocketError.cpp +++ b/src/src/SocketError.cpp @@ -9,16 +9,15 @@ #include "SocketId.h" namespace MinimalSocket { -namespace { -int getLastErrorCode() { +ErrorCodeAware::ErrorCodeAware() { + error_code = #ifdef _WIN32 - return WSAGetLastError(); + WSAGetLastError(); #else - return static_cast(errno); + static_cast(errno); #endif } -} // namespace SocketError::SocketError(const std::string &what) - : Error(what, " , error code: ", getLastErrorCode()) {} + : ErrorCodeAware(), Error(what, " , error code: ", getErrorCode()) {} } // namespace MinimalSocket diff --git a/src/src/SocketError.h b/src/src/SocketError.h index b56b0f6b..35bdead3 100644 --- a/src/src/SocketError.h +++ b/src/src/SocketError.h @@ -12,7 +12,18 @@ #include namespace MinimalSocket { -class SocketError : public Error { +class ErrorCodeAware { +public: + int getErrorCode() const { return error_code; } + +protected: + ErrorCodeAware(); + +private: + int error_code; +}; + +class SocketError : public ErrorCodeAware, public Error { public: /** * @brief last error code raised by the socket API is automatically retrieved diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index fdb6c969..6c98dae3 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -23,7 +23,7 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { #ifdef _WIN32 auto tv = DWORD(this->receive_timeout.count()); if (setsockopt(getIDWrapper().accessId(), SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&tv), + static_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { #else struct timeval tv = {0, 0}; @@ -37,7 +37,7 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { .count(); } if (::setsockopt(getIDWrapper().accessId(), SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&tv), + static_cast(&tv), sizeof(struct timeval)) < 0) { #endif throw SocketError{"can't set timeout"}; @@ -45,15 +45,26 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { return lock; } +namespace { +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() == EAGAIN) && (timeout != NULL_TIMEOUT)) { + return; + } + throw error_with_code; +} +} // namespace + std::size_t Receiver::receive(Buffer &message, const Timeout &timeout) { auto lock = lazyUpdateReceiveTimeout(timeout); clear(message); int recvBytes = ::recv(getIDWrapper().accessId(), message.buffer, static_cast(message.buffer_size), 0); - if (recvBytes == SCK_SOCKET_ERROR) { - recvBytes = 0; - throw SocketError{"receive failed"}; - } + check_received_bytes(recvBytes, timeout); if (recvBytes > message.buffer_size) { // if here, the message received is probably corrupted recvBytes = 0; @@ -84,10 +95,7 @@ ReceiverUnkownSender::receive(Buffer &message, const Timeout &timeout) { static_cast(message.buffer_size), 0, reinterpret_cast(&sender_address[0]), &sender_address_length); - if (recvBytes == SCK_SOCKET_ERROR) { - recvBytes = 0; - throw SocketError{"receive failed"}; - } + check_received_bytes(recvBytes, timeout); if (recvBytes > message.buffer_size) { // if here, the message received is probably corrupted return std::nullopt; From 4aa48627945da27d0c9979e563c29f6e7dcad26e Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Wed, 11 May 2022 20:49:36 +0100 Subject: [PATCH 132/228] testing --- src/src/core/Socket.cpp | 8 ++++++-- tests/TestTCP.cpp | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index a0a64704..2ba082d6 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -70,15 +70,19 @@ bool Openable::open(const Timeout &timeout) { } else { auto open_task = std::async([&]() { this->open_(); }); auto open_task_status = open_task.wait_for(timeout); - if (open_task_status != std::future_status::ready) { + if (open_task_status == std::future_status::ready) { + open_task.get(); // will throw if ready because an exception throwned + // immediately + } else { resetIDWrapper(); - open_task.get(); // should throw already here throw Error{""}; // just to be sure it throws } } opened = true; } catch (const Error &) { resetIDWrapper(); + } catch (...) { + throw Error{"Not opened for unkown reason"}; } return opened; } diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index a71e7fb1..825eeebc 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -228,11 +228,14 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { [&]() { #pragma omp barrier std::this_thread::sleep_for(wait); - server.acceptNewClient(); + TcpConnection conn = server.acceptNewClient(); + auto received_request = conn.receive(request.size()); + CHECK(received_request == request); }, [&]() { #pragma omp barrier CHECK(client.open(timeout)); + client.send(request); }); } } From c7159393e81f8b6a3c309d2612fbabb5ae625078 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Wed, 11 May 2022 20:59:39 +0100 Subject: [PATCH 133/228] testing --- tests/TestTCP.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 825eeebc..39012a88 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -239,3 +239,30 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { }); } } + +TEST_CASE("Reserve random port for tcp server", "[tcp]") { + const auto family = GENERATE(IP_V4, IP_V6); + + TcpServer server(ANY_PORT, family); + REQUIRE(server.open()); + const auto port = server.getPortToBind(); + REQUIRE(port != 0); + + parallel( + [&]() { +#pragma omp barrier + auto accepted = server.acceptNewClient(); + REQUIRE_FALSE(nullptr == accepted); + auto received_request = accepted.receive(request.size()); + CHECK(received_request == request); + }, + [&]() { + // client + TcpClient client(Address::makeLocalHost(port, family)); +#pragma omp barrier + REQUIRE(client.open()); + REQUIRE_FALSE(nullptr == client); + REQUIRE(client.wasOpened()); + client.send(request); + }); +} \ No newline at end of file From 38839949228ea077cf32ee6846aad61ae478b7b0 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Wed, 11 May 2022 21:27:56 +0100 Subject: [PATCH 134/228] fixing compile issue --- src/src/core/Receiver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 6c98dae3..a037e473 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -23,7 +23,7 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { #ifdef _WIN32 auto tv = DWORD(this->receive_timeout.count()); if (setsockopt(getIDWrapper().accessId(), SOL_SOCKET, SO_RCVTIMEO, - static_cast(&tv), + static_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { #else struct timeval tv = {0, 0}; From c22ba4a8f50312a8d81e2d442bb58fc5541af8a0 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Wed, 11 May 2022 21:33:52 +0100 Subject: [PATCH 135/228] fixing compile error --- src/src/core/Receiver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index a037e473..3c3d6eb4 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -23,7 +23,7 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { #ifdef _WIN32 auto tv = DWORD(this->receive_timeout.count()); if (setsockopt(getIDWrapper().accessId(), SOL_SOCKET, SO_RCVTIMEO, - static_cast(&tv), + reinterpret_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { #else struct timeval tv = {0, 0}; From d660c549d6757233772312804ef667c7cc435c9e Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Wed, 11 May 2022 23:56:05 +0100 Subject: [PATCH 136/228] testing --- src/header/MinimalSocket/core/Socket.h | 5 ++- src/src/SocketAddress.cpp | 62 ++++++++++++++++++-------- src/src/SocketAddress.h | 4 ++ src/src/SocketFunctions.cpp | 16 +++---- src/src/core/Socket.cpp | 7 +-- tests/TestTCP.cpp | 24 +++++----- 6 files changed, 72 insertions(+), 46 deletions(-) diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index b761d11a..f0892bdd 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -7,10 +7,13 @@ #pragma once +#include + #include #include #include #include +#include #include #include @@ -66,7 +69,7 @@ bool operator==(const Socket &subject, std::nullptr_t); class Openable : public virtual Socket { public: bool wasOpened() const { return opened; } - bool open(const Timeout &timeout = NULL_TIMEOUT); + std::optional open(const Timeout &timeout = NULL_TIMEOUT); protected: Openable() = default; diff --git a/src/src/SocketAddress.cpp b/src/src/SocketAddress.cpp index 90a9625c..8960b780 100644 --- a/src/src/SocketAddress.cpp +++ b/src/src/SocketAddress.cpp @@ -20,7 +20,7 @@ namespace MinimalSocket { std::optional toSocketAddressIpv4(const std::string &host, const Port &port) { #ifdef _WIN32 - WSALazyInitializer::lazyInit(); + WSALazyInitializer::lazyInit(); #endif std::optional result; @@ -55,7 +55,7 @@ std::optional toSocketAddressIpv4(const std::string &host, return std::nullopt; } - const auto& ipv4 = reinterpret_cast(res->ai_addr); + const auto &ipv4 = reinterpret_cast(res->ai_addr); result_ref.sin_addr.s_addr = ipv4.sin_addr.s_addr; ::freeaddrinfo(res); return result; @@ -64,7 +64,7 @@ std::optional toSocketAddressIpv4(const std::string &host, std::optional toSocketAddressIpv6(const std::string &host, const Port &port) { #ifdef _WIN32 - WSALazyInitializer::lazyInit(); + WSALazyInitializer::lazyInit(); #endif std::optional result; @@ -100,30 +100,45 @@ std::optional toSocketAddressIpv6(const std::string &host, return std::nullopt; } - const auto& ipv6 = reinterpret_cast(res->ai_addr); + const auto &ipv6 = reinterpret_cast(res->ai_addr); result_ref.sin6_addr = ipv6.sin6_addr; ::freeaddrinfo(res); return result; } -Address toAddress(const SocketAddress &address) { +std::optional toPort(const SocketAddress &address) { #ifdef _WIN32 - WSALazyInitializer::lazyInit(); + WSALazyInitializer::lazyInit(); +#endif + + switch (address.sa_family) { + case AF_INET: { + return ntohs(reinterpret_cast(address).sin_port); + + } break; + case AF_INET6: { + return ntohs( + reinterpret_cast(address).sin6_port); + + } break; + } + return std::nullopt; +} + +std::optional toHost(const SocketAddress &address) { +#ifdef _WIN32 + WSALazyInitializer::lazyInit(); #endif // refer to // https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu - std::string ip; - Port port = ANY_PORT; switch (address.sa_family) { case AF_INET: { // ipv4 address // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to // an ip that has no sense - ip = std::string(::inet_ntoa( - reinterpret_cast(&address)->sin_addr)); - port = - ntohs(reinterpret_cast(&address)->sin_port); + return std::string(::inet_ntoa( + reinterpret_cast(address).sin_addr)); } break; case AF_INET6: { @@ -133,14 +148,23 @@ Address toAddress(const SocketAddress &address) { // https://www.gnu.org/software/libc/manual/html_node/Host-Address-Functions.html ::memset(temp, 0, INET6_ADDRSTRLEN); ::inet_ntop(address.sa_family, &address, temp, INET6_ADDRSTRLEN); - ip = std::string(temp, INET6_ADDRSTRLEN); - port = - ntohs(reinterpret_cast(&address)->sin6_port); - + return std::string(temp, INET6_ADDRSTRLEN); } break; - default: - break; } - return Address{ip, port}; + return std::nullopt; +} + +Address toAddress(const SocketAddress &address) { +#ifdef _WIN32 + WSALazyInitializer::lazyInit(); +#endif + + auto host = toHost(address); + auto port = toPort(address); + if (host && port) { + return Address{*host, *port}; + } + + return Address{"", 0}; } } // namespace MinimalSocket diff --git a/src/src/SocketAddress.h b/src/src/SocketAddress.h index 6a3bcc25..240f112d 100644 --- a/src/src/SocketAddress.h +++ b/src/src/SocketAddress.h @@ -60,6 +60,10 @@ std::optional toSocketAddressIpv4(const std::string &host, std::optional toSocketAddressIpv6(const std::string &host, const Port &port); +std::optional toPort(const SocketAddress &address); + +std::optional toHost(const SocketAddress &address); + /** * @brief Convert a SocketAddress_t into an Address, internally * deducing the family. diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index d29565da..17f1c986 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -79,18 +79,12 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, &binded_address_length) == SCK_SOCKET_ERROR) { throw SocketError{"Wasn't able to deduce the binded port"}; } - switch (reinterpret_cast(binded_address).sa_family) { - case AF_INET: - binded_port = - reinterpret_cast(binded_address).sin_port; - break; - case AF_INET6: - binded_port = - reinterpret_cast(binded_address).sin6_port; - break; - default: + auto maybe_port = + toPort(reinterpret_cast(binded_address)); + if (maybe_port) { + binded_port = *maybe_port; + } else { throw Error{"Wasn't able to deduce the binded port"}; - break; } } return binded_port; diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 2ba082d6..4a899a6a 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -59,7 +59,7 @@ void Socket::resetIDWrapper() { socket_id_wrapper = std::make_unique(); } -bool Openable::open(const Timeout &timeout) { +std::optional Openable::open(const Timeout &timeout) { if (opened) { throw Error{"Already opened"}; } @@ -79,12 +79,13 @@ bool Openable::open(const Timeout &timeout) { } } opened = true; - } catch (const Error &) { + } catch (const Error &e) { resetIDWrapper(); + return e; } catch (...) { throw Error{"Not opened for unkown reason"}; } - return opened; + return std::nullopt; } void Openable::transfer(Openable &receiver, Openable &giver) { diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 39012a88..9e9116db 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -28,7 +28,7 @@ Peers make_peers(const Port &port, const AddressFamily &family) { [&]() { // server TcpServer server(port, family); - REQUIRE(server.open()); + REQUIRE_FALSE(server.open()); #pragma omp barrier auto accepted = server.acceptNewClient(); REQUIRE_FALSE(nullptr == accepted); @@ -38,7 +38,7 @@ Peers make_peers(const Port &port, const AddressFamily &family) { // client TcpClient client(Address::makeLocalHost(port, family)); #pragma omp barrier - REQUIRE(client.open()); + REQUIRE_FALSE(client.open()); REQUIRE_FALSE(nullptr == client); REQUIRE(client.wasOpened()); client_side = std::make_unique(std::move(client)); @@ -88,7 +88,7 @@ TEST_CASE("Establish tcp connection", "[tcp]") { SECTION("expected failure") { TcpClient client(Address::makeLocalHost(port, family)); - CHECK_FALSE(client.open()); + CHECK(client.open()); CHECK_FALSE(client.wasOpened()); } @@ -163,7 +163,7 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { for (std::size_t c = 0; c < clients_numb; ++c) { auto &client = clients.emplace_back(Address::makeLocalHost(port, family)); - CHECK(client.open()); + CHECK_FALSE(client.open()); } }); } @@ -177,7 +177,7 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { }); Task ask_connection = [&]() { TcpClient client(Address::makeLocalHost(port, family)); - CHECK(client.open()); + CHECK_FALSE(client.open()); }; for (std::size_t c = 0; c < clients_numb; ++c) { tasks.push_back(ask_connection); @@ -200,7 +200,7 @@ TEST_CASE("Open multiple times tcp clients", "[tcp]") { for (std::size_t c = 0; c < cycles; ++c) { parallel([&]() { server.acceptNewClient(); }, [&]() { - CHECK(client.open()); + CHECK_FALSE(client.open()); TcpClient{std::move(client)}; CHECK_FALSE(client.wasOpened()); }); @@ -216,14 +216,14 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { TcpClient client(Address::makeLocalHost(port, family)); SECTION("expect fail within timeout") { - CHECK_FALSE(client.open(timeout)); + CHECK(client.open(timeout)); CHECK_FALSE(client.wasOpened()); } SECTION("expect success within timeout") { const auto wait = Timeout{250}; TcpServer server(port, family); - REQUIRE(server.open()); + REQUIRE_FALSE(server.open()); parallel( [&]() { #pragma omp barrier @@ -234,7 +234,7 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { }, [&]() { #pragma omp barrier - CHECK(client.open(timeout)); + CHECK_FALSE(client.open(timeout)); client.send(request); }); } @@ -244,7 +244,7 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { const auto family = GENERATE(IP_V4, IP_V6); TcpServer server(ANY_PORT, family); - REQUIRE(server.open()); + REQUIRE_FALSE(server.open()); const auto port = server.getPortToBind(); REQUIRE(port != 0); @@ -260,9 +260,9 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { // client TcpClient client(Address::makeLocalHost(port, family)); #pragma omp barrier - REQUIRE(client.open()); + REQUIRE_FALSE(client.open()); REQUIRE_FALSE(nullptr == client); REQUIRE(client.wasOpened()); client.send(request); }); -} \ No newline at end of file +} From a4e2a8612a35897571c70411592bfc2f6a6bb311 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 12 May 2022 00:17:12 +0100 Subject: [PATCH 137/228] testing --- tests/TestOpenTimeout.cpp | 4 ++-- tests/TestUDP.cpp | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/TestOpenTimeout.cpp b/tests/TestOpenTimeout.cpp index 01227dad..90774987 100644 --- a/tests/TestOpenTimeout.cpp +++ b/tests/TestOpenTimeout.cpp @@ -25,12 +25,12 @@ TEST_CASE("Simulate open with timeout", "[open]") { OpenableTest test(open_time); SECTION("expected success") { - CHECK(test.open(Timeout{1000})); + CHECK_FALSE(test.open(Timeout{1000})); CHECK(test.wasOpened()); } SECTION("expected failure") { - CHECK_FALSE(test.open(Timeout{250})); + CHECK(test.open(Timeout{250})); CHECK_FALSE(test.wasOpened()); } } diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 9ec8d713..319905d4 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -31,13 +31,13 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { const Address requester_address = Address::makeLocalHost(requester_port, family); UdpBinded requester(requester_port, family); - REQUIRE(requester.open()); + REQUIRE_FALSE(requester.open()); const auto responder_port = PortFactory::makePort(); const Address responder_address = Address::makeLocalHost(responder_port, family); UdpBinded responder(responder_port, family); - REQUIRE(responder.open()); + REQUIRE_FALSE(responder.open()); parallel( [&]() { @@ -68,7 +68,7 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { SECTION("expect fail within timeout") { auto received_request = responder.receive(request.size(), timeout); - CHECK_FALSE(received_request); + CHECK(received_request); } SECTION("expect success within timeout") { @@ -104,9 +104,9 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { Address::makeLocalHost(responder_port, family); UdpConnected requester(responder_address, requester_port); - REQUIRE(requester.open()); + REQUIRE_FALSE(requester.open()); UdpConnected responder(requester_address, responder_port); - REQUIRE(responder.open()); + REQUIRE_FALSE(responder.open()); parallel( [&]() { @@ -183,7 +183,7 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { Address::makeLocalHost(responder_port, family); UdpBinded responder(responder_port, family); - REQUIRE(responder.open()); + REQUIRE_FALSE(responder.open()); std::unique_ptr requester_only_bind = std::make_unique(requester_port, family); @@ -274,13 +274,13 @@ TEST_CASE("Open connection with timeout", "[udp]") { const Address requester_address = Address::makeLocalHost(requester_port, family); UdpBinded requester(requester_port, family); - REQUIRE(requester.open()); + REQUIRE_FALSE(requester.open()); const auto responder_port = PortFactory::makePort(); const Address responder_address = Address::makeLocalHost(responder_port, family); UdpBinded responder(responder_port, family); - REQUIRE(responder.open()); + REQUIRE_FALSE(responder.open()); const auto timeout = Timeout{500}; From f337301093bdcc5919560d50c0d0170cbef140fc Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 12 May 2022 11:47:45 +0100 Subject: [PATCH 138/228] testing --- tests/TestUDP.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 319905d4..fd708863 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -305,3 +305,41 @@ TEST_CASE("Open connection with timeout", "[udp]") { }); } } + +TEST_CASE("Reserve random port for udp connection", "[udp]") { + const auto family = GENERATE(IP_V4, IP_V6); + + auto requester_port = ANY_PORT; + UdpBinded requester(requester_port, family); + REQUIRE_FALSE(requester.open()); + requester_port = requester.getPortToBind(); + const Address requester_address = + Address::makeLocalHost(requester_port, family); + + auto responder_port = GENERATE(PortFactory::makePort(), ANY_PORT); + UdpBinded responder(responder_port, family); + REQUIRE_FALSE(responder.open()); + responder_port = responder.getPortToBind(); + const Address responder_address = + Address::makeLocalHost(responder_port, family); + + parallel( + [&]() { + CHECK(requester.sendTo(request, responder_address)); +#pragma omp barrier +#pragma omp barrier + 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)); + }, + [&]() { +#pragma omp barrier + 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); +#pragma omp barrier + }); +} From 7122f814dfb602bab316d6dceaf5a644047388be Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 12 May 2022 12:20:42 +0100 Subject: [PATCH 139/228] testing --- TODO | 2 -- src/src/SocketId.cpp | 14 +++++--------- src/src/core/Receiver.cpp | 2 +- src/src/udp/UdpSocket.cpp | 10 ++++++++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/TODO b/TODO index c43b69fe..39b87f27 100644 --- a/TODO +++ b/TODO @@ -10,8 +10,6 @@ e' thread safety assicurata nelle varie funzioni? nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema -aggiungere test con ANY_PORT - accept tcp client con timeout add negative test per capire se vegono thrownati giusti SocketError diff --git a/src/src/SocketId.cpp b/src/src/SocketId.cpp index ecf29717..b69a8ba3 100644 --- a/src/src/SocketId.cpp +++ b/src/src/SocketId.cpp @@ -78,20 +78,16 @@ void SocketIdWrapper::reset(const SocketType &type, switch (type) { case SocketType::TCP: this->socket_id = ::socket(domain_number(family), SOCK_STREAM, 0); - if (this->socket_id == SCK_INVALID_SOCKET) { - MinimalSocket::close(socket_id); - throw SocketError{"Stream socket could not be created"}; - } break; case SocketType::UDP: this->socket_id = ::socket(domain_number(family), SOCK_DGRAM, 0); - if (this->socket_id == SCK_INVALID_SOCKET) { - MinimalSocket::close(socket_id); - throw SocketError{"DataGram socket could not be created"}; - } break; default: - throw Error("unknown protocol type"); + throw Error("Unknown socket type"); + } + if (this->socket_id == SCK_INVALID_SOCKET) { + MinimalSocket::close(socket_id); + throw SocketError{"Stream socket could not be created"}; } } } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 3c3d6eb4..b37a3d00 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -38,7 +38,7 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { } if (::setsockopt(getIDWrapper().accessId(), SOL_SOCKET, SO_RCVTIMEO, static_cast(&tv), - sizeof(struct timeval)) < 0) { + sizeof(struct timeval)) != 0) { #endif throw SocketError{"can't set timeout"}; } diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index 3609b5c2..0c15b9e3 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -82,7 +82,10 @@ void UdpConnected::open_() { UdpBinded UdpConnected::disconnect() { resetIDWrapper(); UdpBinded result(getPortToBind(), getRemoteAddress().getFamily()); - result.open(); + auto maybe_open_error = result.open(); + if (maybe_open_error) { + throw *maybe_open_error; + } return std::move(result); } @@ -91,7 +94,10 @@ makeUdpConnectedToUnknown(const Port &port, const AddressFamily &accepted_connection_family, const Timeout &timeout) { UdpBinded primal_socket(port, accepted_connection_family); - primal_socket.open(); + auto maybe_open_error = primal_socket.open(); + if (maybe_open_error) { + throw *maybe_open_error; + } auto maybe_result = primal_socket.connect(timeout); if (!maybe_result) { throw Error{"Something went wrong creating a UdpConnected socket"}; From d8985201876a117125a725b105411b332020d054 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 12 May 2022 12:53:04 +0100 Subject: [PATCH 140/228] testing --- src/src/SocketFunctions.cpp | 22 ++++++++++++++-------- src/src/SocketId.cpp | 3 ++- src/src/core/Receiver.cpp | 3 ++- src/src/core/Sender.cpp | 6 ++++-- src/src/tcp/TcpServer.cpp | 3 ++- tests/TestUDP.cpp | 12 ++++++------ 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index 17f1c986..2e2d1537 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -50,8 +50,9 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, #endif if (::bind(socket_id, reinterpret_cast(&addr), sizeof(SocketAddressIpv4)) == SCK_SOCKET_ERROR) { - throw SocketError{"Can't bind localhost on port: ", - std::to_string(port)}; + auto err = SocketError{"Can't bind localhost on port: ", + std::to_string(port)}; + throw err; } }, [&]() { @@ -65,8 +66,9 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, addr.sin6_port = htons(port); if (::bind(socket_id, reinterpret_cast(&addr), sizeof(SocketAddressIpv6)) == SCK_SOCKET_ERROR) { - throw SocketError{"Can't bind localhost on port: ", - std::to_string(port)}; + auto err = SocketError{"Can't bind localhost on port: ", + std::to_string(port)}; + throw err; } }); @@ -77,7 +79,8 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, if (::getsockname(socket_id, reinterpret_cast(&binded_address[0]), &binded_address_length) == SCK_SOCKET_ERROR) { - throw SocketError{"Wasn't able to deduce the binded port"}; + auto err = SocketError{"Wasn't able to deduce the binded port"}; + throw err; } auto maybe_port = toPort(reinterpret_cast(binded_address)); @@ -92,7 +95,8 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, void listen(const SocketID &socket_id, const std::size_t backlog_size) { if (::listen(socket_id, backlog_size) == SCK_SOCKET_ERROR) { - throw SocketError{"Error: listening on reserved port"}; + auto err = SocketError{"Error: listening on reserved port"}; + throw err; } } @@ -109,7 +113,8 @@ void connect(const SocketID &socket_id, const Address &remote_address) { } if (::connect(socket_id, reinterpret_cast(&(*addr)), sizeof(SocketAddressIpv4)) == SCK_SOCKET_ERROR) { - throw SocketError{"Connection can't be established"}; + auto err = SocketError{"Connection can't be established"}; + throw err; } }, [&]() { @@ -122,7 +127,8 @@ void connect(const SocketID &socket_id, const Address &remote_address) { } if (::connect(socket_id, reinterpret_cast(&(*addr)), sizeof(SocketAddressIpv6)) == SCK_SOCKET_ERROR) { - throw SocketError{"Connection can't be established"}; + auto err = SocketError{"Connection can't be established"}; + throw err; } }); } diff --git a/src/src/SocketId.cpp b/src/src/SocketId.cpp index b69a8ba3..edabbdb4 100644 --- a/src/src/SocketId.cpp +++ b/src/src/SocketId.cpp @@ -87,7 +87,8 @@ void SocketIdWrapper::reset(const SocketType &type, } if (this->socket_id == SCK_INVALID_SOCKET) { MinimalSocket::close(socket_id); - throw SocketError{"Stream socket could not be created"}; + auto err = SocketError{"Stream socket could not be created"}; + throw err; } } } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index b37a3d00..6e1f570f 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -40,7 +40,8 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { static_cast(&tv), sizeof(struct timeval)) != 0) { #endif - throw SocketError{"can't set timeout"}; + auto err = SocketError{"can't set timeout"}; + throw err; } return lock; } diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index 436b8fe3..f464f87b 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -18,7 +18,8 @@ bool Sender::send(const ConstBuffer &message) { static_cast(message.buffer_size), 0); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; - throw SocketError{"send failed"}; + auto err = SocketError{"send failed"}; + throw err; } return (sentBytes == static_cast(message.buffer_size)); } @@ -52,7 +53,8 @@ bool SenderTo::sendTo(const ConstBuffer &message, const Address &recipient) { }); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; - throw SocketError{"sendto failed"}; + auto err = SocketError{"sendto failed"}; + throw err; } return (sentBytes == static_cast(message.buffer_size)); } diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 2d537a3c..4578c541 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -62,7 +62,8 @@ TcpConnection TcpServer::acceptNewClient() { reinterpret_cast(&acceptedClientAddress[0]), &acceptedClientAddress_length); if (accepted_client_socket_id == SCK_INVALID_SOCKET) { - throw SocketError{"accepting a new client"}; + auto err = SocketError{"accepting a new client"}; + throw err; } auto accepted_client_parsed_address = diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index fd708863..b19704f5 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -154,14 +154,16 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { SECTION( "receive from third peer expected to fail since socket was connected") { - UdpConnected second_requester(responder_address, PortFactory::makePort()); + UdpBinded second_requester(PortFactory::makePort(), family); + REQUIRE_FALSE(second_requester.open()); const auto timeout = Timeout{500}; const auto wait = Timeout{250}; parallel( [&]() { #pragma omp barrier std::this_thread::sleep_for(wait); - second_requester.send(request); + second_requester.sendTo(request, + Address::makeLocalHost(requester_port)); }, [&]() { #pragma omp barrier @@ -187,7 +189,7 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { std::unique_ptr requester_only_bind = std::make_unique(requester_port, family); - REQUIRE(requester_only_bind->open()); + REQUIRE_FALSE(requester_only_bind->open()); // connect requester to responder auto deduce_sender = GENERATE(true, false); @@ -284,9 +286,7 @@ TEST_CASE("Open connection with timeout", "[udp]") { const auto timeout = Timeout{500}; - SECTION("expect fail within timeout") { - CHECK_FALSE(requester.connect(timeout)); - } + SECTION("expect fail within timeout") { CHECK(requester.connect(timeout)); } SECTION("expect success within timeout") { const auto wait = Timeout{250}; From 8d7c630587d208c0346e42949daaf02de3279096 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 12 May 2022 12:59:24 +0100 Subject: [PATCH 141/228] testing --- TODO | 2 ++ src/header/MinimalSocket/core/Address.h | 5 ++-- src/src/core/Address.cpp | 13 ++++------ tests/TestTCP.cpp | 15 ++++++----- tests/TestUDP.cpp | 33 +++++++++---------------- 5 files changed, 27 insertions(+), 41 deletions(-) diff --git a/TODO b/TODO index 39b87f27..38931aee 100644 --- a/TODO +++ b/TODO @@ -15,3 +15,5 @@ accept tcp client con timeout add negative test per capire se vegono thrownati giusti SocketError add test dove request e spezzettata in piu frammenti (piu send diverse) + +negative test provando a bindare piu' volte la stessa porta per tcp e udp diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index 00913e35..a749ce21 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -37,9 +37,8 @@ class Address { /** * @return an ipv4 or ipv6 with localhost as host and the passed port */ - static Address - makeLocalHost(const Port &port, - const AddressFamily &family = AddressFamily::IP_V4); + Address(const Port &port, const AddressFamily &family = + AddressFamily::IP_V4); // local host assumed const std::string &getHost() const { return this->host; }; const Port &getPort() const { return this->port; }; diff --git a/src/src/core/Address.cpp b/src/src/core/Address.cpp index f1b12e96..731df6cf 100644 --- a/src/src/core/Address.cpp +++ b/src/src/core/Address.cpp @@ -36,15 +36,12 @@ static const std::string LOCALHOST_IPv4 = "127.0.0.1"; static const std::string LOCALHOST_IPv6 = "::1"; } // namespace -Address Address::makeLocalHost(const std::uint16_t &port, - const AddressFamily &family) { - Address result; - result.port = port; - result.family = family; +Address::Address(const std::uint16_t &port, const AddressFamily &family) { + this->port = port; + this->family = family; visitAddress( - family, [&result]() { result.host = LOCALHOST_IPv4; }, - [&result]() { result.host = LOCALHOST_IPv6; }); - return result; + family, [this]() { this->host = LOCALHOST_IPv4; }, + [this]() { this->host = LOCALHOST_IPv6; }); } bool Address::operator==(const Address &o) const { diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 9e9116db..40d37f73 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -36,7 +36,7 @@ Peers make_peers(const Port &port, const AddressFamily &family) { }, [&]() { // client - TcpClient client(Address::makeLocalHost(port, family)); + TcpClient client(Address(port, family)); #pragma omp barrier REQUIRE_FALSE(client.open()); REQUIRE_FALSE(nullptr == client); @@ -87,7 +87,7 @@ TEST_CASE("Establish tcp connection", "[tcp]") { const auto family = GENERATE(IP_V4, IP_V6); SECTION("expected failure") { - TcpClient client(Address::makeLocalHost(port, family)); + TcpClient client(Address(port, family)); CHECK(client.open()); CHECK_FALSE(client.wasOpened()); } @@ -161,8 +161,7 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { }, [&]() { for (std::size_t c = 0; c < clients_numb; ++c) { - auto &client = - clients.emplace_back(Address::makeLocalHost(port, family)); + auto &client = clients.emplace_back(Address(port, family)); CHECK_FALSE(client.open()); } }); @@ -176,7 +175,7 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { } }); Task ask_connection = [&]() { - TcpClient client(Address::makeLocalHost(port, family)); + TcpClient client(Address(port, family)); CHECK_FALSE(client.open()); }; for (std::size_t c = 0; c < clients_numb; ++c) { @@ -195,7 +194,7 @@ TEST_CASE("Open multiple times tcp clients", "[tcp]") { std::size_t cycles = 5; - TcpClient client(Address::makeLocalHost(port, family)); + TcpClient client(Address(port, family)); for (std::size_t c = 0; c < cycles; ++c) { parallel([&]() { server.acceptNewClient(); }, @@ -213,7 +212,7 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { const auto timeout = Timeout{500}; - TcpClient client(Address::makeLocalHost(port, family)); + TcpClient client(Address(port, family)); SECTION("expect fail within timeout") { CHECK(client.open(timeout)); @@ -258,7 +257,7 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { }, [&]() { // client - TcpClient client(Address::makeLocalHost(port, family)); + TcpClient client(Address(port, family)); #pragma omp barrier REQUIRE_FALSE(client.open()); REQUIRE_FALSE(nullptr == client); diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index b19704f5..606faa7e 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -28,14 +28,12 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { const std::size_t cycles = 5; const auto requester_port = PortFactory::makePort(); - const Address requester_address = - Address::makeLocalHost(requester_port, family); + const Address requester_address = Address(requester_port, family); UdpBinded requester(requester_port, family); REQUIRE_FALSE(requester.open()); const auto responder_port = PortFactory::makePort(); - const Address responder_address = - Address::makeLocalHost(responder_port, family); + const Address responder_address = Address(responder_port, family); UdpBinded responder(responder_port, family); REQUIRE_FALSE(responder.open()); @@ -96,12 +94,10 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { const std::size_t cycles = 5; const auto requester_port = PortFactory::makePort(); - const Address requester_address = - Address::makeLocalHost(requester_port, family); + const Address requester_address = Address(requester_port, family); const auto responder_port = PortFactory::makePort(); - const Address responder_address = - Address::makeLocalHost(responder_port, family); + const Address responder_address = Address(responder_port, family); UdpConnected requester(responder_address, requester_port); REQUIRE_FALSE(requester.open()); @@ -162,8 +158,7 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { [&]() { #pragma omp barrier std::this_thread::sleep_for(wait); - second_requester.sendTo(request, - Address::makeLocalHost(requester_port)); + second_requester.sendTo(request, Address(requester_port)); }, [&]() { #pragma omp barrier @@ -178,11 +173,9 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { const std::size_t cycles = 5; const auto requester_port = PortFactory::makePort(); - const Address requester_address = - Address::makeLocalHost(requester_port, family); + const Address requester_address = Address(requester_port, family); const auto responder_port = PortFactory::makePort(); - const Address responder_address = - Address::makeLocalHost(responder_port, family); + const Address responder_address = Address(responder_port, family); UdpBinded responder(responder_port, family); REQUIRE_FALSE(responder.open()); @@ -273,14 +266,12 @@ TEST_CASE("Open connection with timeout", "[udp]") { const auto family = GENERATE(IP_V4, IP_V6); const auto requester_port = PortFactory::makePort(); - const Address requester_address = - Address::makeLocalHost(requester_port, family); + const Address requester_address = Address(requester_port, family); UdpBinded requester(requester_port, family); REQUIRE_FALSE(requester.open()); const auto responder_port = PortFactory::makePort(); - const Address responder_address = - Address::makeLocalHost(responder_port, family); + const Address responder_address = Address(responder_port, family); UdpBinded responder(responder_port, family); REQUIRE_FALSE(responder.open()); @@ -313,15 +304,13 @@ TEST_CASE("Reserve random port for udp connection", "[udp]") { UdpBinded requester(requester_port, family); REQUIRE_FALSE(requester.open()); requester_port = requester.getPortToBind(); - const Address requester_address = - Address::makeLocalHost(requester_port, family); + const Address requester_address = Address(requester_port, family); auto responder_port = GENERATE(PortFactory::makePort(), ANY_PORT); UdpBinded responder(responder_port, family); REQUIRE_FALSE(responder.open()); responder_port = responder.getPortToBind(); - const Address responder_address = - Address::makeLocalHost(responder_port, family); + const Address responder_address = Address(responder_port, family); parallel( [&]() { From 828b177acc53a156006d37dd4870a9ae13bcf4bf Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 12 May 2022 14:33:43 +0100 Subject: [PATCH 142/228] testing --- TODO | 2 ++ src/src/core/Receiver.cpp | 4 ++++ tests/TestUDP.cpp | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index 38931aee..d6e5dd41 100644 --- a/TODO +++ b/TODO @@ -17,3 +17,5 @@ add negative test per capire se vegono thrownati giusti SocketError add test dove request e spezzettata in piu frammenti (piu send diverse) negative test provando a bindare piu' volte la stessa porta per tcp e udp + +ricontrollare se vari buffer char sono da ripulire a zeros prima di uso diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 6e1f570f..d66f8b6e 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -101,6 +101,10 @@ ReceiverUnkownSender::receive(Buffer &message, const Timeout &timeout) { // if here, the message received is probably corrupted return std::nullopt; } + if (0 == recvBytes) { + // if here, timeout was reached + return std::nullopt; + } return ReceiveResult{ toAddress(reinterpret_cast(sender_address)), static_cast(recvBytes)}; diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 606faa7e..e81ce100 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -277,7 +277,9 @@ TEST_CASE("Open connection with timeout", "[udp]") { const auto timeout = Timeout{500}; - SECTION("expect fail within timeout") { CHECK(requester.connect(timeout)); } + SECTION("expect fail within timeout") { + CHECK_FALSE(requester.connect(timeout)); + } SECTION("expect success within timeout") { const auto wait = Timeout{250}; From 337ff446f903d0702e8b379813df00cf68f3f4d4 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 12 May 2022 16:16:42 +0100 Subject: [PATCH 143/228] testing --- tests/TestUDP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index e81ce100..04ab4901 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -66,7 +66,7 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { SECTION("expect fail within timeout") { auto received_request = responder.receive(request.size(), timeout); - CHECK(received_request); + CHECK_FALSE(received_request); } SECTION("expect success within timeout") { From 1ed52d9d885b5f79dcab3ebb2fd0a6c65be63ab1 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 12 May 2022 17:14:47 +0100 Subject: [PATCH 144/228] testing --- src/src/SocketAddress.cpp | 47 +++++++++++++++++++++----------- tests/TestUDP.cpp | 56 ++++++++++++++++++++++++++++++--------- 2 files changed, 75 insertions(+), 28 deletions(-) diff --git a/src/src/SocketAddress.cpp b/src/src/SocketAddress.cpp index 8960b780..e337c36a 100644 --- a/src/src/SocketAddress.cpp +++ b/src/src/SocketAddress.cpp @@ -130,28 +130,43 @@ std::optional toHost(const SocketAddress &address) { WSALazyInitializer::lazyInit(); #endif - // refer to - // https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu + std::optional result; switch (address.sa_family) { case AF_INET: { - // ipv4 address - // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to - // an ip that has no sense - return std::string(::inet_ntoa( - reinterpret_cast(address).sin_addr)); - + { + const auto &as_addr_in = + reinterpret_cast(address).sin_addr; + char host_converted[INET_ADDRSTRLEN]; + ::memset(&host_converted[0], 0, INET_ADDRSTRLEN); + const char *result_raw = ::inet_ntop(AF_INET, &as_addr_in, + &host_converted[0], INET_ADDRSTRLEN); + if (NULL != result_raw) { + result.emplace(result_raw); + break; + } + } + { + // try also inet_ntoa + result.emplace(::inet_ntoa( + reinterpret_cast(address).sin_addr)); + if ("0" == result.value()) { + result.reset(); + } + } } break; case AF_INET6: { - // ipv6 address - char temp[INET6_ADDRSTRLEN]; // this is the longest one - // refer to - // https://www.gnu.org/software/libc/manual/html_node/Host-Address-Functions.html - ::memset(temp, 0, INET6_ADDRSTRLEN); - ::inet_ntop(address.sa_family, &address, temp, INET6_ADDRSTRLEN); - return std::string(temp, INET6_ADDRSTRLEN); + const auto &as_addr_in_6 = + reinterpret_cast(address).sin6_addr; + char host_converted[INET6_ADDRSTRLEN]; + ::memset(&host_converted[0], 0, INET6_ADDRSTRLEN); + const char *result_raw = ::inet_ntop(AF_INET6, &as_addr_in_6, + &host_converted[0], INET6_ADDRSTRLEN); + if (NULL != result_raw) { + result.emplace(result_raw); + } } break; } - return std::nullopt; + return result; } Address toAddress(const SocketAddress &address) { diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 04ab4901..e11184a8 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -147,25 +147,58 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { }); } } +} - SECTION( - "receive from third peer expected to fail since socket was connected") { - UdpBinded second_requester(PortFactory::makePort(), family); - REQUIRE_FALSE(second_requester.open()); - const auto timeout = Timeout{500}; - const auto wait = Timeout{250}; +TEST_CASE( + "Receive from thirdy peer expected to fail after udp socket connected", + "[udp]") { + const auto family = GENERATE(IP_V4, 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_FALSE(requester.open()); + UdpConnected responder(requester_address, responder_port); + REQUIRE_FALSE(responder.open()); + + auto exchange_messages_before = GENERATE(true, false); + if (exchange_messages_before) { parallel( [&]() { + CHECK(requester.send(request)); #pragma omp barrier - std::this_thread::sleep_for(wait); - second_requester.sendTo(request, Address(requester_port)); +#pragma omp barrier + auto received_response = requester.receive(response.size()); + CHECK(received_response == response); }, [&]() { #pragma omp barrier - auto received_request = responder.receive(request.size(), timeout); - CHECK(received_request.empty()); + auto received_request = responder.receive(request.size()); + CHECK(received_request == request); + responder.send(response); +#pragma omp barrier }); } + + UdpBinded second_requester(PortFactory::makePort(), family); + REQUIRE_FALSE(second_requester.open()); + const auto timeout = Timeout{500}; + const auto wait = Timeout{250}; + parallel( + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(wait); + second_requester.sendTo(request, Address(responder_port, family)); + }, + [&]() { +#pragma omp barrier + auto received_request = responder.receive(request.size(), timeout); + CHECK(received_request.empty()); + }); } TEST_CASE("Metamorphosis of udp connections", "[udp]") { @@ -195,8 +228,7 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { }, [&]() { #pragma omp barrier - std::optional socket_connected = - requester_only_bind->connect(); + auto socket_connected = requester_only_bind->connect(); REQUIRE(socket_connected); CHECK(are_same(socket_connected->getRemoteAddress(), responder_address, family)); From 3509c2605225f43cf67c43d9ecdca29de1e1e0bc Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Thu, 12 May 2022 17:00:01 +0100 Subject: [PATCH 145/228] finxing compile error --- src/src/SocketFunctions.cpp | 2 +- tests/TestTCP.cpp | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index 2e2d1537..3556efed 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -94,7 +94,7 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, } void listen(const SocketID &socket_id, const std::size_t backlog_size) { - if (::listen(socket_id, backlog_size) == SCK_SOCKET_ERROR) { + if (::listen(socket_id, static_cast(backlog_size)) == SCK_SOCKET_ERROR) { auto err = SocketError{"Error: listening on reserved port"}; throw err; } diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 40d37f73..e08c7e90 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -86,15 +86,16 @@ TEST_CASE("Establish tcp connection", "[tcp]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(IP_V4, IP_V6); - SECTION("expected failure") { - TcpClient client(Address(port, family)); - CHECK(client.open()); - CHECK_FALSE(client.wasOpened()); - } + //SECTION("expected failure") { + // TcpClient client(Address(port, family)); + // CHECK(client.open()); + // CHECK_FALSE(client.wasOpened()); + //} SECTION("expected success") { auto peers = make_peers(port, family); - auto &[server_side, client_side] = peers; + auto& server_side = peers.server_side; + auto& client_side = peers.client_side; REQUIRE_FALSE(nullptr == *client_side); REQUIRE(client_side->wasOpened()); From 0c15e9efc623a3a13952d7ff5a1c855103925ab4 Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Thu, 12 May 2022 17:05:03 +0100 Subject: [PATCH 146/228] minor changes --- tests/TestTCP.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index e08c7e90..11d7ccb0 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -94,32 +94,32 @@ TEST_CASE("Establish tcp connection", "[tcp]") { SECTION("expected success") { auto peers = make_peers(port, family); - auto& server_side = peers.server_side; - auto& client_side = peers.client_side; + auto& server_side = *peers.server_side.get(); + auto& client_side = *peers.client_side.get(); - REQUIRE_FALSE(nullptr == *client_side); - REQUIRE(client_side->wasOpened()); - REQUIRE_FALSE(nullptr == *server_side); + REQUIRE_FALSE(nullptr == client_side); + REQUIRE(client_side.wasOpened()); + REQUIRE_FALSE(nullptr == server_side); 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()); } @@ -129,12 +129,12 @@ TEST_CASE("Establish tcp connection", "[tcp]") { [&]() { #pragma omp barrier std::this_thread::sleep_for(wait); - client_side->send(request); + client_side.send(request); }, [&]() { #pragma omp barrier auto received_request = - server_side->receive(request.size(), timeout); + server_side.receive(request.size(), timeout); CHECK(received_request == request); }); } From 9626fd283a94fbea935af2f97f483425c0d71ae4 Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Thu, 12 May 2022 19:51:38 +0100 Subject: [PATCH 147/228] set WSA version possible --- src/header/MinimalSocket/core/Socket.h | 15 +++++++++ src/src/SocketId.cpp | 42 ++++++++++++++++++++++++-- src/src/SocketId.h | 1 - src/src/core/Socket.cpp | 16 ++++++++++ tests/TestAddress.cpp | 11 ++++++- tests/TestTCP.cpp | 13 +++++--- 6 files changed, 88 insertions(+), 10 deletions(-) diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index f0892bdd..3854845d 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -16,6 +16,7 @@ #include #include #include +#include namespace MinimalSocket { struct Buffer { @@ -37,6 +38,20 @@ using Timeout = std::chrono::milliseconds; static constexpr Timeout NULL_TIMEOUT = Timeout{0}; +#ifdef _WIN32 +using WSAVersion = std::array; + +class WSAManager { +public: + static void setWsaVersion(const WSAVersion& version); + static WSAVersion getWsaVersion(); + +private: + static std::mutex wsa_version_mtx; + static WSAVersion wsa_version; +}; +#endif + class SocketIdWrapper; class Socket { diff --git a/src/src/SocketId.cpp b/src/src/SocketId.cpp index edabbdb4..8020ce89 100644 --- a/src/src/SocketId.cpp +++ b/src/src/SocketId.cpp @@ -14,7 +14,36 @@ namespace MinimalSocket { #ifdef _WIN32 WSALazyInitializer::WSALazyInitializer() { WSADATA wsa; - WSAStartup(MAKEWORD(2, 0), &wsa); + const auto version = WSAManager::getWsaVersion(); + const BYTE version_major = static_cast(version[0]); + const BYTE version_minor = static_cast(version[1]); + auto result = WSAStartup(MAKEWORD( version_major, version_minor), &wsa); + if (0 == result) { + return; + } + std::string message; + switch (result) { + case WSASYSNOTREADY: + message = " , system not ready"; + break; + case WSAVERNOTSUPPORTED: + message = " , version not supported"; + break; + case WSAEINPROGRESS: + message = " , blocking operation in progress"; + break; + case WSAEPROCLIM: + message = " , maximum supported tasks reached"; + break; + case WSAEFAULT: + message = " , invalid WSADATA"; + break; + default: + throw Error{"Not able to initialize WSA, reason unknown"}; + break; + } + auto err = Error{"Not able to initialize WSA, error code: ", std::to_string(result), message}; + throw err; } WSALazyInitializer::~WSALazyInitializer() { WSACleanup(); } @@ -24,8 +53,15 @@ std::unique_ptr WSALazyInitializer::lazy_proxy = nullptr; void WSALazyInitializer::lazyInit() { std::scoped_lock lock(WSALazyInitializer::lazy_proxy_mtx); - if (nullptr == WSALazyInitializer::lazy_proxy) { - WSALazyInitializer::lazy_proxy.reset(new WSALazyInitializer{}); + if (nullptr != WSALazyInitializer::lazy_proxy) { + return; + } + try { + WSALazyInitializer::lazy_proxy.reset(new WSALazyInitializer{}); + } + catch (const Error& e) { + WSALazyInitializer::lazy_proxy = nullptr; + throw e; } } #endif diff --git a/src/src/SocketId.h b/src/src/SocketId.h index 40fcfab8..31c18f25 100644 --- a/src/src/SocketId.h +++ b/src/src/SocketId.h @@ -80,7 +80,6 @@ class SocketIdWrapper { SocketID socket_id = SCK_INVALID_SOCKET; }; - #ifdef _WIN32 class WSALazyInitializer { public: diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 4a899a6a..f59f6dea 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -26,6 +26,22 @@ ConstBuffer makeStringConstBuffer(const std::string &subject) { return ConstBuffer{subject.data(), subject.size()}; } + +#ifdef _WIN32 +std::mutex WSAManager::wsa_version_mtx = std::mutex{}; +WSAVersion WSAManager::wsa_version = WSAVersion{2,2}; + +void WSAManager::setWsaVersion(const WSAVersion& version) { + std::scoped_lock lock(wsa_version_mtx); + wsa_version = version; +} + +WSAVersion WSAManager::getWsaVersion() { + std::scoped_lock lock(wsa_version_mtx); + return wsa_version; +} +#endif + Socket::~Socket() = default; Socket::Socket() { resetIDWrapper(); } diff --git a/tests/TestAddress.cpp b/tests/TestAddress.cpp index adafa5c1..057721fe 100644 --- a/tests/TestAddress.cpp +++ b/tests/TestAddress.cpp @@ -1,14 +1,23 @@ #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); +} +#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); diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 11d7ccb0..38b2508d 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -86,11 +86,14 @@ TEST_CASE("Establish tcp connection", "[tcp]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(IP_V4, IP_V6); - //SECTION("expected failure") { - // TcpClient client(Address(port, family)); - // CHECK(client.open()); - // CHECK_FALSE(client.wasOpened()); - //} + +#if !defined(_WIN32) + SECTION("expected failure") { + TcpClient client(Address(port, family)); + CHECK(client.open()); + CHECK_FALSE(client.wasOpened()); + } +#endif SECTION("expected success") { auto peers = make_peers(port, family); From 73856d8dce326cd593a4ea8202bf407ec21478eb Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Thu, 12 May 2022 19:58:30 +0100 Subject: [PATCH 148/228] trying windows 2019 --- .github/workflows/runTests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index eaa4b0b4..bab21dcd 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -21,7 +21,7 @@ jobs: os: ubuntu-latest compiler_opt: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -G \"Unix Makefiles\"" - name: windows-VS - os: windows-latest + os: windows-2019 compiler_opt: "" runs-on: ${{ matrix.os }} From 31fd514cf0735fef9e95e8926c58fe6d1f4ee93c Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 00:05:39 +0100 Subject: [PATCH 149/228] timeout error --- src/header/MinimalSocket/Error.h | 27 +++++++++++++++ src/src/{SocketError.cpp => Error.cpp} | 3 +- src/src/SocketAddress.cpp | 1 - src/src/SocketError.h | 36 -------------------- src/src/SocketFunctions.cpp | 1 - src/src/SocketId.cpp | 46 +++++++++++++------------- src/src/core/Receiver.cpp | 2 +- src/src/core/Sender.cpp | 2 +- src/src/tcp/TcpServer.cpp | 1 - 9 files changed, 54 insertions(+), 65 deletions(-) rename src/src/{SocketError.cpp => Error.cpp} (92%) delete mode 100644 src/src/SocketError.h diff --git a/src/header/MinimalSocket/Error.h b/src/header/MinimalSocket/Error.h index 99f89427..a7c15a8e 100644 --- a/src/header/MinimalSocket/Error.h +++ b/src/header/MinimalSocket/Error.h @@ -9,6 +9,7 @@ #include #include +#include namespace MinimalSocket { @@ -37,4 +38,30 @@ class Error : public std::runtime_error { stream << back; }; }; + +class ErrorCodeAware { +public: + int getErrorCode() const { return error_code; } + +protected: + ErrorCodeAware(); + +private: + int error_code; +}; +class SocketError : public ErrorCodeAware, public Error { +public: + /** + * @brief last error code raised by the socket API is automatically retrieved + */ + SocketError(const std::string &what); + + template + SocketError(const Args &...args) : SocketError{merge(args...)} {}; +}; + +class TimeoutError : public Error { +public: + TimeoutError() : Error("Timeout error"){}; +}; } // namespace MinimalSocket diff --git a/src/src/SocketError.cpp b/src/src/Error.cpp similarity index 92% rename from src/src/SocketError.cpp rename to src/src/Error.cpp index 3b6e6cb7..2143b12a 100644 --- a/src/src/SocketError.cpp +++ b/src/src/Error.cpp @@ -5,7 +5,8 @@ * report any bug to andrecasa91@gmail.com. **/ -#include "SocketError.h" +#include + #include "SocketId.h" namespace MinimalSocket { diff --git a/src/src/SocketAddress.cpp b/src/src/SocketAddress.cpp index e337c36a..ca844c9e 100644 --- a/src/src/SocketAddress.cpp +++ b/src/src/SocketAddress.cpp @@ -14,7 +14,6 @@ #include #include "SocketAddress.h" -#include "SocketError.h" namespace MinimalSocket { std::optional toSocketAddressIpv4(const std::string &host, diff --git a/src/src/SocketError.h b/src/src/SocketError.h deleted file mode 100644 index 35bdead3..00000000 --- a/src/src/SocketError.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#pragma once - -#include - -#include - -namespace MinimalSocket { -class ErrorCodeAware { -public: - int getErrorCode() const { return error_code; } - -protected: - ErrorCodeAware(); - -private: - int error_code; -}; - -class SocketError : public ErrorCodeAware, public Error { -public: - /** - * @brief last error code raised by the socket API is automatically retrieved - */ - SocketError(const std::string &what); - - template - SocketError(const Args &...args) : SocketError{merge(args...)} {}; -}; -} // namespace MinimalSocket diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index 3556efed..ece64ead 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -8,7 +8,6 @@ #include #include "SocketAddress.h" -#include "SocketError.h" #include "SocketFunctions.h" #include "Utils.h" diff --git a/src/src/SocketId.cpp b/src/src/SocketId.cpp index 8020ce89..0b52d651 100644 --- a/src/src/SocketId.cpp +++ b/src/src/SocketId.cpp @@ -7,7 +7,6 @@ #include -#include "SocketError.h" #include "Utils.h" namespace MinimalSocket { @@ -17,32 +16,34 @@ WSALazyInitializer::WSALazyInitializer() { const auto version = WSAManager::getWsaVersion(); const BYTE version_major = static_cast(version[0]); const BYTE version_minor = static_cast(version[1]); - auto result = WSAStartup(MAKEWORD( version_major, version_minor), &wsa); + auto result = WSAStartup(MAKEWORD(version_major, version_minor), &wsa); if (0 == result) { - return; + return; } std::string message; switch (result) { case WSASYSNOTREADY: - message = " , system not ready"; - break; + message = " , system not ready"; + break; case WSAVERNOTSUPPORTED: - message = " , version not supported"; - break; + message = " , version not supported"; + break; case WSAEINPROGRESS: - message = " , blocking operation in progress"; - break; + message = " , blocking operation in progress"; + break; case WSAEPROCLIM: - message = " , maximum supported tasks reached"; - break; + message = " , maximum supported tasks reached"; + break; case WSAEFAULT: - message = " , invalid WSADATA"; - break; - default: - throw Error{"Not able to initialize WSA, reason unknown"}; - break; + message = " , invalid WSADATA"; + break; + default: + throw Error{"Not able to initialize WSA, reason unknown"}; + break; } - auto err = Error{"Not able to initialize WSA, error code: ", std::to_string(result), message}; + auto err = + Error{"Not able to initialize WSA, error code: ", std::to_string(result), + message}; throw err; } @@ -54,14 +55,13 @@ std::unique_ptr WSALazyInitializer::lazy_proxy = nullptr; void WSALazyInitializer::lazyInit() { std::scoped_lock lock(WSALazyInitializer::lazy_proxy_mtx); if (nullptr != WSALazyInitializer::lazy_proxy) { - return; + return; } try { - WSALazyInitializer::lazy_proxy.reset(new WSALazyInitializer{}); - } - catch (const Error& e) { - WSALazyInitializer::lazy_proxy = nullptr; - throw e; + WSALazyInitializer::lazy_proxy.reset(new WSALazyInitializer{}); + } catch (const Error &e) { + WSALazyInitializer::lazy_proxy = nullptr; + throw e; } } #endif diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index d66f8b6e..c6ed2198 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -5,10 +5,10 @@ * report any bug to andrecasa91@gmail.com. **/ +#include #include #include "../SocketAddress.h" -#include "../SocketError.h" namespace MinimalSocket { std::unique_ptr> diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index f464f87b..6881716f 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -5,10 +5,10 @@ * report any bug to andrecasa91@gmail.com. **/ +#include #include #include "../SocketAddress.h" -#include "../SocketError.h" #include "../Utils.h" namespace MinimalSocket { diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 4578c541..6816d330 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -9,7 +9,6 @@ #include #include "../SocketAddress.h" -#include "../SocketError.h" #include "../SocketFunctions.h" #include "../Utils.h" From 0d22e9d37bfd8addc059bba5c95ae3f927a32907 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 00:26:29 +0100 Subject: [PATCH 150/228] timeout error --- src/header/MinimalSocket/core/Socket.h | 12 +++++------ src/src/core/Socket.cpp | 29 +++++++++++++++----------- tests/IsTimeout.cpp | 14 +++++++++++++ tests/IsTimeout.h | 14 +++++++++++++ tests/TestOpenTimeout.cpp | 6 +++++- tests/TestTCP.cpp | 18 +++++++++------- 6 files changed, 67 insertions(+), 26 deletions(-) create mode 100644 tests/IsTimeout.cpp create mode 100644 tests/IsTimeout.h diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 3854845d..dc00f410 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -16,7 +17,6 @@ #include #include #include -#include namespace MinimalSocket { struct Buffer { @@ -43,12 +43,12 @@ using WSAVersion = std::array; class WSAManager { public: - static void setWsaVersion(const WSAVersion& version); - static WSAVersion getWsaVersion(); + static void setWsaVersion(const WSAVersion &version); + static WSAVersion getWsaVersion(); private: - static std::mutex wsa_version_mtx; - static WSAVersion wsa_version; + static std::mutex wsa_version_mtx; + static WSAVersion wsa_version; }; #endif @@ -84,7 +84,7 @@ bool operator==(const Socket &subject, std::nullptr_t); class Openable : public virtual Socket { public: bool wasOpened() const { return opened; } - std::optional open(const Timeout &timeout = NULL_TIMEOUT); + std::unique_ptr open(const Timeout &timeout = NULL_TIMEOUT); protected: Openable() = default; diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index f59f6dea..b29180bd 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -26,19 +26,18 @@ ConstBuffer makeStringConstBuffer(const std::string &subject) { return ConstBuffer{subject.data(), subject.size()}; } - #ifdef _WIN32 std::mutex WSAManager::wsa_version_mtx = std::mutex{}; -WSAVersion WSAManager::wsa_version = WSAVersion{2,2}; +WSAVersion WSAManager::wsa_version = WSAVersion{2, 2}; -void WSAManager::setWsaVersion(const WSAVersion& version) { - std::scoped_lock lock(wsa_version_mtx); - wsa_version = version; +void WSAManager::setWsaVersion(const WSAVersion &version) { + std::scoped_lock lock(wsa_version_mtx); + wsa_version = version; } WSAVersion WSAManager::getWsaVersion() { - std::scoped_lock lock(wsa_version_mtx); - return wsa_version; + std::scoped_lock lock(wsa_version_mtx); + return wsa_version; } #endif @@ -75,7 +74,7 @@ void Socket::resetIDWrapper() { socket_id_wrapper = std::make_unique(); } -std::optional Openable::open(const Timeout &timeout) { +std::unique_ptr Openable::open(const Timeout &timeout) { if (opened) { throw Error{"Already opened"}; } @@ -91,17 +90,23 @@ std::optional Openable::open(const Timeout &timeout) { // immediately } else { resetIDWrapper(); - throw Error{""}; // just to be sure it throws + std::unique_ptr result; + result.reset(new TimeoutError{}); + return result; } } opened = true; + } catch (const SocketError &e) { + resetIDWrapper(); + return std::make_unique(e); } catch (const Error &e) { resetIDWrapper(); - return e; + return std::make_unique(e); } catch (...) { - throw Error{"Not opened for unkown reason"}; + resetIDWrapper(); + return std::make_unique("Not opened for an unkown reason"); } - return std::nullopt; + return nullptr; } void Openable::transfer(Openable &receiver, Openable &giver) { diff --git a/tests/IsTimeout.cpp b/tests/IsTimeout.cpp new file mode 100644 index 00000000..3e793f1d --- /dev/null +++ b/tests/IsTimeout.cpp @@ -0,0 +1,14 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include "IsTimeout.h" + +namespace MinimalSocket::test { +bool is_timeout(const Error *subject) { + return dynamic_cast(subject) != nullptr; +} +} // namespace MinimalSocket::test diff --git a/tests/IsTimeout.h b/tests/IsTimeout.h new file mode 100644 index 00000000..8ede39b5 --- /dev/null +++ b/tests/IsTimeout.h @@ -0,0 +1,14 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include + +namespace MinimalSocket::test { +bool is_timeout(const Error *subject); +} // namespace MinimalSocket::test diff --git a/tests/TestOpenTimeout.cpp b/tests/TestOpenTimeout.cpp index 90774987..d4c9f0fb 100644 --- a/tests/TestOpenTimeout.cpp +++ b/tests/TestOpenTimeout.cpp @@ -3,6 +3,8 @@ #include +#include "IsTimeout.h" + #include using namespace MinimalSocket; @@ -30,7 +32,9 @@ TEST_CASE("Simulate open with timeout", "[open]") { } SECTION("expected failure") { - CHECK(test.open(Timeout{250})); + auto err = test.open(Timeout{250}); + CHECK(err); + CHECK(test::is_timeout(err.get())); CHECK_FALSE(test.wasOpened()); } } diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 38b2508d..b5d9f406 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -8,6 +8,7 @@ #include #include +#include "IsTimeout.h" #include "Parallel.h" #include "PortFactory.h" @@ -86,19 +87,20 @@ TEST_CASE("Establish tcp connection", "[tcp]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(IP_V4, IP_V6); - #if !defined(_WIN32) SECTION("expected failure") { - TcpClient client(Address(port, family)); - CHECK(client.open()); - CHECK_FALSE(client.wasOpened()); + TcpClient client(Address(port, family)); + auto err = client.open(); + CHECK(err); + CHECK(test::is_timeout(err.get())); + CHECK_FALSE(client.wasOpened()); } #endif SECTION("expected success") { auto peers = make_peers(port, family); - auto& server_side = *peers.server_side.get(); - auto& client_side = *peers.client_side.get(); + auto &server_side = *peers.server_side.get(); + auto &client_side = *peers.client_side.get(); REQUIRE_FALSE(nullptr == client_side); REQUIRE(client_side.wasOpened()); @@ -219,7 +221,9 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { TcpClient client(Address(port, family)); SECTION("expect fail within timeout") { - CHECK(client.open(timeout)); + auto err = client.open(timeout); + CHECK(err); + CHECK(test::is_timeout(err.get())); CHECK_FALSE(client.wasOpened()); } From 2b15b642a0cc9031030dcc575085dc9286ad30d2 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 11:24:52 +0100 Subject: [PATCH 151/228] try within timeout generalized --- TODO | 2 ++ src/src/Utils.cpp | 20 ++++++++++++++++++++ src/src/Utils.h | 9 ++++++++- src/src/core/Socket.cpp | 30 +++++++++++++----------------- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/TODO b/TODO index d6e5dd41..864edea3 100644 --- a/TODO +++ b/TODO @@ -19,3 +19,5 @@ add test dove request e spezzettata in piu frammenti (piu send diverse) negative test provando a bindare piu' volte la stessa porta per tcp e udp ricontrollare se vari buffer char sono da ripulire a zeros prima di uso + +valutare dove si puo' sostituire optional con expected diff --git a/src/src/Utils.cpp b/src/src/Utils.cpp index 42da5f4b..bda5c766 100644 --- a/src/src/Utils.cpp +++ b/src/src/Utils.cpp @@ -9,6 +9,8 @@ #include "Utils.h" +#include + namespace MinimalSocket { void visitAddress(const AddressFamily &family, const std::function &ipv4_case, @@ -25,4 +27,22 @@ void visitAddress(const AddressFamily &family, break; } } + +bool try_within_timeout(const std::function &action_to_try, + const std::function &action_to_abort, + const Timeout &timeout) { + if (NULL_TIMEOUT == timeout) { + throw Error{"Invalid timeout"}; + } + auto open_task = std::async([&]() { action_to_try(); }); + auto open_task_status = open_task.wait_for(timeout); + if (open_task_status == std::future_status::ready) { + open_task.get(); // will throw if ready because an exception throwned + // before timeout + } else { + action_to_abort(); + return false; + } + return true; +} } // namespace MinimalSocket diff --git a/src/src/Utils.h b/src/src/Utils.h index f6ac4f6b..c757b17d 100644 --- a/src/src/Utils.h +++ b/src/src/Utils.h @@ -7,10 +7,12 @@ #pragma once +#include + #include "SocketId.h" #include -#include +#include namespace MinimalSocket { void visitAddress(const AddressFamily &family, @@ -22,4 +24,9 @@ template void copy_as(U &receiver, const U &giver) { const T &giver_ref = giver; receiver_ref = giver_ref; } + +// rethrow exception if happens +bool try_within_timeout(const std::function &action_to_try, + const std::function &action_to_abort, + const Timeout &timeout); } // namespace MinimalSocket diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index b29180bd..5a83125d 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -79,34 +79,30 @@ std::unique_ptr Openable::open(const Timeout &timeout) { throw Error{"Already opened"}; } std::scoped_lock lock(open_procedure_mtx); + std::unique_ptr result; try { if (NULL_TIMEOUT == timeout) { this->open_(); } else { - auto open_task = std::async([&]() { this->open_(); }); - auto open_task_status = open_task.wait_for(timeout); - if (open_task_status == std::future_status::ready) { - open_task.get(); // will throw if ready because an exception throwned - // immediately - } else { - resetIDWrapper(); - std::unique_ptr result; - result.reset(new TimeoutError{}); - return result; + bool success = + try_within_timeout([this]() { this->open_(); }, + [this]() { this->resetIDWrapper(); }, timeout); + if (!success) { + return std::make_unique(); } } opened = true; } catch (const SocketError &e) { - resetIDWrapper(); - return std::make_unique(e); + result = std::make_unique(e); } catch (const Error &e) { - resetIDWrapper(); - return std::make_unique(e); + result = std::make_unique(e); } catch (...) { - resetIDWrapper(); - return std::make_unique("Not opened for an unkown reason"); + result = std::make_unique("Not opened for an unkown reason"); } - return nullptr; + if (nullptr != result) { + this->resetIDWrapper(); + } + return result; } void Openable::transfer(Openable &receiver, Openable &giver) { From b6ce90d96bd52e099df7a1eaa5a552325fb5ef2c Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 13:03:10 +0100 Subject: [PATCH 152/228] refactoring --- TODO | 2 + src/header/MinimalSocket/core/Definitions.h | 32 ++++++++++++++ src/header/MinimalSocket/core/Expected.h | 47 +++++++++++++++++++++ src/header/MinimalSocket/core/Socket.h | 22 +--------- src/header/MinimalSocket/tcp/TcpServer.h | 3 +- src/src/Utils.cpp | 5 +-- src/src/Utils.h | 3 +- src/src/core/Definitions.cpp | 24 +++++++++++ src/src/core/Socket.cpp | 22 +--------- src/src/tcp/TcpServer.cpp | 38 ++++++++++++----- 10 files changed, 142 insertions(+), 56 deletions(-) create mode 100644 src/header/MinimalSocket/core/Definitions.h create mode 100644 src/header/MinimalSocket/core/Expected.h create mode 100644 src/src/core/Definitions.cpp diff --git a/TODO b/TODO index 864edea3..75f10707 100644 --- a/TODO +++ b/TODO @@ -21,3 +21,5 @@ negative test provando a bindare piu' volte la stessa porta per tcp e udp ricontrollare se vari buffer char sono da ripulire a zeros prima di uso valutare dove si puo' sostituire optional con expected + +ripulire include ridondanti diff --git a/src/header/MinimalSocket/core/Definitions.h b/src/header/MinimalSocket/core/Definitions.h new file mode 100644 index 00000000..53a184ba --- /dev/null +++ b/src/header/MinimalSocket/core/Definitions.h @@ -0,0 +1,32 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include + +namespace MinimalSocket { +struct Buffer { + char *buffer; + const std::size_t buffer_size; +}; +void clear(Buffer &subject); +Buffer makeStringBuffer(std::string &subject); + +struct ConstBuffer { + const char *buffer; + const std::size_t buffer_size; +}; +ConstBuffer makeStringConstBuffer(const std::string &subject); + +enum SocketType { UDP, TCP }; + +using Timeout = std::chrono::milliseconds; + +static constexpr Timeout NULL_TIMEOUT = Timeout{0}; +} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Expected.h b/src/header/MinimalSocket/core/Expected.h new file mode 100644 index 00000000..5cb310cc --- /dev/null +++ b/src/header/MinimalSocket/core/Expected.h @@ -0,0 +1,47 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include + +#include +#include + +namespace MinimalSocket { +template class Expected { +public: + using T_Ptr = std::unique_ptr; + using Error_Ptr = std::unique_ptr; + + Expected(const Expected &) = delete; + Expected &operator=(const Expected &) = delete; + + Expected(T_Ptr value) : value(std::move(value)){}; + Expected(Error_Ptr exception) : exception(std::move(exception)){}; + + const T *value() const { return value.get(); }; + T *value() { return value.get(); }; + + const Error *error() const { return exception.get(); }; + + void visit( + const std::function &value_case = [](const T &) {}, + const std::function &exception_case = + [](const Error &) {}) const { + if (nullptr == value) { + exception_case(*exception.get()); + } else { + value_case(*value.get()); + } + } + +private: + T_Ptr value; + Error_Ptr exception; +}; +} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index dc00f410..7b667398 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -8,36 +8,16 @@ #pragma once #include +#include #include #include -#include #include #include #include -#include #include namespace MinimalSocket { -struct Buffer { - char *buffer; - const std::size_t buffer_size; -}; -void clear(Buffer &subject); -Buffer makeStringBuffer(std::string &subject); - -struct ConstBuffer { - const char *buffer; - const std::size_t buffer_size; -}; -ConstBuffer makeStringConstBuffer(const std::string &subject); - -enum SocketType { UDP, TCP }; - -using Timeout = std::chrono::milliseconds; - -static constexpr Timeout NULL_TIMEOUT = Timeout{0}; - #ifdef _WIN32 using WSAVersion = std::array; diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index b30814d4..02e5b25d 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -38,7 +38,8 @@ class TcpServer : public PortToBindAware, TcpServer(const Port port_to_bind = ANY_PORT, const AddressFamily &accepted_client_family = AddressFamily::IP_V4); - TcpConnection acceptNewClient(); + // TODO replace with expected and add TcpConnection acceptNewClient() + TcpConnection acceptNewClient(const Timeout &timeout = NULL_TIMEOUT); void setClientQueueSize(const std::size_t queue_size); diff --git a/src/src/Utils.cpp b/src/src/Utils.cpp index bda5c766..035dd98d 100644 --- a/src/src/Utils.cpp +++ b/src/src/Utils.cpp @@ -28,7 +28,7 @@ void visitAddress(const AddressFamily &family, } } -bool try_within_timeout(const std::function &action_to_try, +void try_within_timeout(const std::function &action_to_try, const std::function &action_to_abort, const Timeout &timeout) { if (NULL_TIMEOUT == timeout) { @@ -41,8 +41,7 @@ bool try_within_timeout(const std::function &action_to_try, // before timeout } else { action_to_abort(); - return false; + throw TimeoutError{}; } - return true; } } // namespace MinimalSocket diff --git a/src/src/Utils.h b/src/src/Utils.h index c757b17d..a56698e3 100644 --- a/src/src/Utils.h +++ b/src/src/Utils.h @@ -26,7 +26,8 @@ template void copy_as(U &receiver, const U &giver) { } // rethrow exception if happens -bool try_within_timeout(const std::function &action_to_try, +// throw timeout excpetion if timeout reached +void try_within_timeout(const std::function &action_to_try, const std::function &action_to_abort, const Timeout &timeout); } // namespace MinimalSocket diff --git a/src/src/core/Definitions.cpp b/src/src/core/Definitions.cpp new file mode 100644 index 00000000..1dbbfc1f --- /dev/null +++ b/src/src/core/Definitions.cpp @@ -0,0 +1,24 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include "../SocketId.h" + +namespace MinimalSocket { +void clear(Buffer &subject) { + ::memset(subject.buffer, 0, subject.buffer_size); +} + +Buffer makeStringBuffer(std::string &subject) { + return Buffer{subject.data(), subject.size()}; +} + +ConstBuffer makeStringConstBuffer(const std::string &subject) { + return ConstBuffer{subject.data(), subject.size()}; +} +} // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 5a83125d..464de04c 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -11,21 +11,7 @@ #include "../SocketId.h" #include "../Utils.h" -#include - namespace MinimalSocket { -void clear(Buffer &subject) { - ::memset(subject.buffer, 0, subject.buffer_size); -} - -Buffer makeStringBuffer(std::string &subject) { - return Buffer{subject.data(), subject.size()}; -} - -ConstBuffer makeStringConstBuffer(const std::string &subject) { - return ConstBuffer{subject.data(), subject.size()}; -} - #ifdef _WIN32 std::mutex WSAManager::wsa_version_mtx = std::mutex{}; WSAVersion WSAManager::wsa_version = WSAVersion{2, 2}; @@ -84,12 +70,8 @@ std::unique_ptr Openable::open(const Timeout &timeout) { if (NULL_TIMEOUT == timeout) { this->open_(); } else { - bool success = - try_within_timeout([this]() { this->open_(); }, - [this]() { this->resetIDWrapper(); }, timeout); - if (!success) { - return std::make_unique(); - } + try_within_timeout([this]() { this->open_(); }, + [this]() { this->resetIDWrapper(); }, timeout); } opened = true; } catch (const SocketError &e) { diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 6816d330..03af6e69 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -46,23 +46,41 @@ void TcpServer::setClientQueueSize(const std::size_t queue_size) { client_queue_size = queue_size; } -TcpConnection TcpServer::acceptNewClient() { +TcpConnection TcpServer::acceptNewClient(const Timeout &timeout) { 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; - // accept: wait for a client to call connect and hit this server and get a - // pointer to this client. - SocketID accepted_client_socket_id = - ::accept(getIDWrapper().accessId(), - reinterpret_cast(&acceptedClientAddress[0]), - &acceptedClientAddress_length); - if (accepted_client_socket_id == SCK_INVALID_SOCKET) { - auto err = SocketError{"accepting a new client"}; - throw err; + 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(getIDWrapper().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->resetIDWrapper(); + this->open(); + }, + timeout); + } + } catch (...) { + std::rethrow_exception(std::current_exception()); } auto accepted_client_parsed_address = From 12eb10bd74f2b8dded6287f6f19106d08e7b01ad Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 14:54:18 +0100 Subject: [PATCH 153/228] removing expected --- src/header/MinimalSocket/Error.h | 5 --- src/header/MinimalSocket/core/Expected.h | 47 ------------------------ src/header/MinimalSocket/core/Socket.h | 2 +- src/header/MinimalSocket/tcp/TcpServer.h | 5 ++- src/header/MinimalSocket/udp/UdpSocket.h | 16 +++++--- src/src/Utils.cpp | 2 +- src/src/Utils.h | 5 +++ src/src/core/Receiver.cpp | 1 + src/src/core/Socket.cpp | 17 +++++---- src/src/tcp/TcpServer.cpp | 18 +++++++-- src/src/udp/UdpSocket.cpp | 30 +++++++++------ 11 files changed, 64 insertions(+), 84 deletions(-) delete mode 100644 src/header/MinimalSocket/core/Expected.h diff --git a/src/header/MinimalSocket/Error.h b/src/header/MinimalSocket/Error.h index a7c15a8e..48fc1b7b 100644 --- a/src/header/MinimalSocket/Error.h +++ b/src/header/MinimalSocket/Error.h @@ -59,9 +59,4 @@ class SocketError : public ErrorCodeAware, public Error { template SocketError(const Args &...args) : SocketError{merge(args...)} {}; }; - -class TimeoutError : public Error { -public: - TimeoutError() : Error("Timeout error"){}; -}; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Expected.h b/src/header/MinimalSocket/core/Expected.h deleted file mode 100644 index 5cb310cc..00000000 --- a/src/header/MinimalSocket/core/Expected.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#pragma once - -#include - -#include -#include - -namespace MinimalSocket { -template class Expected { -public: - using T_Ptr = std::unique_ptr; - using Error_Ptr = std::unique_ptr; - - Expected(const Expected &) = delete; - Expected &operator=(const Expected &) = delete; - - Expected(T_Ptr value) : value(std::move(value)){}; - Expected(Error_Ptr exception) : exception(std::move(exception)){}; - - const T *value() const { return value.get(); }; - T *value() { return value.get(); }; - - const Error *error() const { return exception.get(); }; - - void visit( - const std::function &value_case = [](const T &) {}, - const std::function &exception_case = - [](const Error &) {}) const { - if (nullptr == value) { - exception_case(*exception.get()); - } else { - value_case(*value.get()); - } - } - -private: - T_Ptr value; - Error_Ptr exception; -}; -} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 7b667398..88e9ec5b 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -64,7 +64,7 @@ bool operator==(const Socket &subject, std::nullptr_t); class Openable : public virtual Socket { public: bool wasOpened() const { return opened; } - std::unique_ptr open(const Timeout &timeout = NULL_TIMEOUT); + bool open(const Timeout &timeout = NULL_TIMEOUT); protected: Openable() = default; diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index 02e5b25d..0281decc 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -38,8 +38,9 @@ class TcpServer : public PortToBindAware, TcpServer(const Port port_to_bind = ANY_PORT, const AddressFamily &accepted_client_family = AddressFamily::IP_V4); - // TODO replace with expected and add TcpConnection acceptNewClient() - TcpConnection acceptNewClient(const Timeout &timeout = NULL_TIMEOUT); + TcpConnection acceptNewClient(); // blocking + + std::optional acceptNewClient(const Timeout &timeout); void setClientQueueSize(const std::size_t queue_size); diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 31b0c58e..47e0514e 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -38,8 +38,10 @@ class UdpBinded : public SenderTo, // leave this socket empty after success UdpConnected connect(const Address &remote_address); + UdpConnected connect(); // to first sending 1 byte, inf timeout + std::optional - connect(const Timeout &timeout = NULL_TIMEOUT); // to first sending 1 byte + connect(const Timeout &timeout); // to first sending 1 byte protected: void open_() override; @@ -65,8 +67,12 @@ class UdpConnected : public Sender, void open_() override; }; -UdpConnected makeUdpConnectedToUnknown( - const Port &port = ANY_PORT, - const AddressFamily &accepted_connection_family = AddressFamily::IP_V4, - const Timeout &timeout = NULL_TIMEOUT); +UdpConnected +makeUdpConnectedToUnknown(const Port &port, + const AddressFamily &accepted_connection_family); + +std::optional +makeUdpConnectedToUnknown(const Port &port, + const AddressFamily &accepted_connection_family, + const Timeout &timeout); } // namespace MinimalSocket::udp diff --git a/src/src/Utils.cpp b/src/src/Utils.cpp index 035dd98d..35fef86a 100644 --- a/src/src/Utils.cpp +++ b/src/src/Utils.cpp @@ -41,7 +41,7 @@ void try_within_timeout(const std::function &action_to_try, // before timeout } else { action_to_abort(); - throw TimeoutError{}; + throw TimeOutError{}; } } } // namespace MinimalSocket diff --git a/src/src/Utils.h b/src/src/Utils.h index a56698e3..72befa3d 100644 --- a/src/src/Utils.h +++ b/src/src/Utils.h @@ -25,6 +25,11 @@ template void copy_as(U &receiver, const U &giver) { receiver_ref = giver_ref; } +class TimeOutError : public Error { +public: + TimeOutError() : Error("Timeout"){}; +}; + // rethrow exception if happens // throw timeout excpetion if timeout reached void try_within_timeout(const std::function &action_to_try, diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index c6ed2198..0f342e54 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -54,6 +54,7 @@ void check_received_bytes(int &recvBytes, const Timeout &timeout) { SocketError error_with_code("receive failed"); recvBytes = 0; if ((error_with_code.getErrorCode() == EAGAIN) && (timeout != NULL_TIMEOUT)) { + // just out of time: tolerate return; } throw error_with_code; diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 464de04c..c73b058e 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -60,12 +60,12 @@ void Socket::resetIDWrapper() { socket_id_wrapper = std::make_unique(); } -std::unique_ptr Openable::open(const Timeout &timeout) { +bool Openable::open(const Timeout &timeout) { if (opened) { throw Error{"Already opened"}; } std::scoped_lock lock(open_procedure_mtx); - std::unique_ptr result; + std::unique_ptr exception; try { if (NULL_TIMEOUT == timeout) { this->open_(); @@ -75,16 +75,19 @@ std::unique_ptr Openable::open(const Timeout &timeout) { } opened = true; } catch (const SocketError &e) { - result = std::make_unique(e); + exception = std::make_unique(e); + } catch (const TimeOutError &) { + // assert and do nothing } catch (const Error &e) { - result = std::make_unique(e); + exception = std::make_unique(e); } catch (...) { - result = std::make_unique("Not opened for an unkown reason"); + exception = std::make_unique("Not opened for an unkown reason"); } - if (nullptr != result) { + if (nullptr != exception) { this->resetIDWrapper(); + throw *exception; } - return result; + return opened; } void Openable::transfer(Openable &receiver, Openable &giver) { diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 03af6e69..6bdb1e7b 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -46,7 +46,13 @@ void TcpServer::setClientQueueSize(const std::size_t queue_size) { client_queue_size = queue_size; } -TcpConnection TcpServer::acceptNewClient(const Timeout &timeout) { +TcpConnection TcpServer::acceptNewClient() { + auto temp = acceptNewClient(NULL_TIMEOUT); + return std::move(temp.value()); +} + +std::optional +TcpServer::acceptNewClient(const Timeout &timeout) { if (!this->wasOpened()) { throw Error("Tcp server was not opened before starting to accept clients"); } @@ -79,15 +85,19 @@ TcpConnection TcpServer::acceptNewClient(const Timeout &timeout) { }, timeout); } + } catch (const TimeOutError &) { + return std::nullopt; } catch (...) { std::rethrow_exception(std::current_exception()); } auto accepted_client_parsed_address = toAddress(reinterpret_cast(acceptedClientAddress)); - TcpConnection result(accepted_client_parsed_address); - result.getIDWrapper().reset(accepted_client_socket_id); - return std::move(result); + std::optional result; + auto &accepted = + result.emplace(TcpConnection{accepted_client_parsed_address}); + accepted.getIDWrapper().reset(accepted_client_socket_id); + return result; } TcpConnection::TcpConnection(const Address &remote_address) diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index 0c15b9e3..94ef7dc2 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -47,6 +47,11 @@ UdpConnected UdpBinded::connect(const Address &remote_address) { return std::move(result); } +UdpConnected UdpBinded::connect() { + auto result = this->connect(NULL_TIMEOUT); + return std::move(result.value()); +} + std::optional UdpBinded::connect(const Timeout &timeout) { auto maybe_received = this->receive(MAX_UDP_RECV_MESSAGE, timeout); if (!maybe_received) { @@ -82,26 +87,27 @@ void UdpConnected::open_() { UdpBinded UdpConnected::disconnect() { resetIDWrapper(); UdpBinded result(getPortToBind(), getRemoteAddress().getFamily()); - auto maybe_open_error = result.open(); - if (maybe_open_error) { - throw *maybe_open_error; - } + result.open(); return std::move(result); } UdpConnected +makeUdpConnectedToUnknown(const Port &port, + const AddressFamily &accepted_connection_family) { + auto result = + makeUdpConnectedToUnknown(port, accepted_connection_family, NULL_TIMEOUT); + return std::move(result.value()); +} + +std::optional makeUdpConnectedToUnknown(const Port &port, const AddressFamily &accepted_connection_family, const Timeout &timeout) { UdpBinded primal_socket(port, accepted_connection_family); - auto maybe_open_error = primal_socket.open(); - if (maybe_open_error) { - throw *maybe_open_error; - } - auto maybe_result = primal_socket.connect(timeout); - if (!maybe_result) { - throw Error{"Something went wrong creating a UdpConnected socket"}; + auto success = primal_socket.open(); + if (!success) { + return std::nullopt; } - return std::move(maybe_result.value()); + return primal_socket.connect(timeout); } } // namespace MinimalSocket::udp From 34ae0cceb0a7cbfc9150509983ba50d1e5761bdd Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 15:02:16 +0100 Subject: [PATCH 154/228] testing --- tests/IsTimeout.cpp | 14 -------------- tests/IsTimeout.h | 14 -------------- tests/TestOpenTimeout.cpp | 6 +----- tests/TestTCP.cpp | 27 +++++++++++---------------- tests/TestUDP.cpp | 33 ++++++++++++++++----------------- 5 files changed, 28 insertions(+), 66 deletions(-) delete mode 100644 tests/IsTimeout.cpp delete mode 100644 tests/IsTimeout.h diff --git a/tests/IsTimeout.cpp b/tests/IsTimeout.cpp deleted file mode 100644 index 3e793f1d..00000000 --- a/tests/IsTimeout.cpp +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include "IsTimeout.h" - -namespace MinimalSocket::test { -bool is_timeout(const Error *subject) { - return dynamic_cast(subject) != nullptr; -} -} // namespace MinimalSocket::test diff --git a/tests/IsTimeout.h b/tests/IsTimeout.h deleted file mode 100644 index 8ede39b5..00000000 --- a/tests/IsTimeout.h +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#pragma once - -#include - -namespace MinimalSocket::test { -bool is_timeout(const Error *subject); -} // namespace MinimalSocket::test diff --git a/tests/TestOpenTimeout.cpp b/tests/TestOpenTimeout.cpp index d4c9f0fb..77ea9914 100644 --- a/tests/TestOpenTimeout.cpp +++ b/tests/TestOpenTimeout.cpp @@ -3,8 +3,6 @@ #include -#include "IsTimeout.h" - #include using namespace MinimalSocket; @@ -32,9 +30,7 @@ TEST_CASE("Simulate open with timeout", "[open]") { } SECTION("expected failure") { - auto err = test.open(Timeout{250}); - CHECK(err); - CHECK(test::is_timeout(err.get())); + CHECK_FALSE(test.open(Timeout{250})); CHECK_FALSE(test.wasOpened()); } } diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index b5d9f406..5e8132e1 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -8,7 +8,6 @@ #include #include -#include "IsTimeout.h" #include "Parallel.h" #include "PortFactory.h" @@ -29,7 +28,7 @@ Peers make_peers(const Port &port, const AddressFamily &family) { [&]() { // server TcpServer server(port, family); - REQUIRE_FALSE(server.open()); + REQUIRE(server.open()); #pragma omp barrier auto accepted = server.acceptNewClient(); REQUIRE_FALSE(nullptr == accepted); @@ -39,7 +38,7 @@ Peers make_peers(const Port &port, const AddressFamily &family) { // client TcpClient client(Address(port, family)); #pragma omp barrier - REQUIRE_FALSE(client.open()); + REQUIRE(client.open()); REQUIRE_FALSE(nullptr == client); REQUIRE(client.wasOpened()); client_side = std::make_unique(std::move(client)); @@ -90,9 +89,7 @@ TEST_CASE("Establish tcp connection", "[tcp]") { #if !defined(_WIN32) SECTION("expected failure") { TcpClient client(Address(port, family)); - auto err = client.open(); - CHECK(err); - CHECK(test::is_timeout(err.get())); + CHECK_FALSE(client.open()); CHECK_FALSE(client.wasOpened()); } #endif @@ -168,7 +165,7 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { [&]() { for (std::size_t c = 0; c < clients_numb; ++c) { auto &client = clients.emplace_back(Address(port, family)); - CHECK_FALSE(client.open()); + CHECK(client.open()); } }); } @@ -182,7 +179,7 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { }); Task ask_connection = [&]() { TcpClient client(Address(port, family)); - CHECK_FALSE(client.open()); + CHECK(client.open()); }; for (std::size_t c = 0; c < clients_numb; ++c) { tasks.push_back(ask_connection); @@ -205,7 +202,7 @@ TEST_CASE("Open multiple times tcp clients", "[tcp]") { for (std::size_t c = 0; c < cycles; ++c) { parallel([&]() { server.acceptNewClient(); }, [&]() { - CHECK_FALSE(client.open()); + CHECK(client.open()); TcpClient{std::move(client)}; CHECK_FALSE(client.wasOpened()); }); @@ -221,16 +218,14 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { TcpClient client(Address(port, family)); SECTION("expect fail within timeout") { - auto err = client.open(timeout); - CHECK(err); - CHECK(test::is_timeout(err.get())); + CHECK_FALSE(client.open(timeout)); CHECK_FALSE(client.wasOpened()); } SECTION("expect success within timeout") { const auto wait = Timeout{250}; TcpServer server(port, family); - REQUIRE_FALSE(server.open()); + REQUIRE(server.open()); parallel( [&]() { #pragma omp barrier @@ -241,7 +236,7 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { }, [&]() { #pragma omp barrier - CHECK_FALSE(client.open(timeout)); + CHECK(client.open(timeout)); client.send(request); }); } @@ -251,7 +246,7 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { const auto family = GENERATE(IP_V4, IP_V6); TcpServer server(ANY_PORT, family); - REQUIRE_FALSE(server.open()); + REQUIRE(server.open()); const auto port = server.getPortToBind(); REQUIRE(port != 0); @@ -267,7 +262,7 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { // client TcpClient client(Address(port, family)); #pragma omp barrier - REQUIRE_FALSE(client.open()); + REQUIRE(client.open()); REQUIRE_FALSE(nullptr == client); REQUIRE(client.wasOpened()); client.send(request); diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index e11184a8..72abd8c2 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -30,12 +30,12 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { const auto requester_port = PortFactory::makePort(); const Address requester_address = Address(requester_port, family); UdpBinded requester(requester_port, family); - REQUIRE_FALSE(requester.open()); + REQUIRE(requester.open()); const auto responder_port = PortFactory::makePort(); const Address responder_address = Address(responder_port, family); UdpBinded responder(responder_port, family); - REQUIRE_FALSE(responder.open()); + REQUIRE(responder.open()); parallel( [&]() { @@ -100,9 +100,9 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { const Address responder_address = Address(responder_port, family); UdpConnected requester(responder_address, requester_port); - REQUIRE_FALSE(requester.open()); + REQUIRE(requester.open()); UdpConnected responder(requester_address, responder_port); - REQUIRE_FALSE(responder.open()); + REQUIRE(responder.open()); parallel( [&]() { @@ -161,9 +161,9 @@ TEST_CASE( const Address responder_address = Address(responder_port, family); UdpConnected requester(responder_address, requester_port); - REQUIRE_FALSE(requester.open()); + REQUIRE(requester.open()); UdpConnected responder(requester_address, responder_port); - REQUIRE_FALSE(responder.open()); + REQUIRE(responder.open()); auto exchange_messages_before = GENERATE(true, false); if (exchange_messages_before) { @@ -185,7 +185,7 @@ TEST_CASE( } UdpBinded second_requester(PortFactory::makePort(), family); - REQUIRE_FALSE(second_requester.open()); + REQUIRE(second_requester.open()); const auto timeout = Timeout{500}; const auto wait = Timeout{250}; parallel( @@ -211,7 +211,7 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { const Address responder_address = Address(responder_port, family); UdpBinded responder(responder_port, family); - REQUIRE_FALSE(responder.open()); + REQUIRE(responder.open()); std::unique_ptr requester_only_bind = std::make_unique(requester_port, family); @@ -229,11 +229,10 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { [&]() { #pragma omp barrier auto socket_connected = requester_only_bind->connect(); - REQUIRE(socket_connected); - CHECK(are_same(socket_connected->getRemoteAddress(), - responder_address, family)); - requester_connected = std::make_unique( - std::move(socket_connected.value())); + CHECK(are_same(socket_connected.getRemoteAddress(), responder_address, + family)); + requester_connected = + std::make_unique(std::move(socket_connected)); }); } else { requester_connected = std::make_unique( @@ -300,12 +299,12 @@ TEST_CASE("Open connection with timeout", "[udp]") { const auto requester_port = PortFactory::makePort(); const Address requester_address = Address(requester_port, family); UdpBinded requester(requester_port, family); - REQUIRE_FALSE(requester.open()); + REQUIRE(requester.open()); const auto responder_port = PortFactory::makePort(); const Address responder_address = Address(responder_port, family); UdpBinded responder(responder_port, family); - REQUIRE_FALSE(responder.open()); + REQUIRE(responder.open()); const auto timeout = Timeout{500}; @@ -336,13 +335,13 @@ TEST_CASE("Reserve random port for udp connection", "[udp]") { auto requester_port = ANY_PORT; UdpBinded requester(requester_port, family); - REQUIRE_FALSE(requester.open()); + 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); - REQUIRE_FALSE(responder.open()); + REQUIRE(responder.open()); responder_port = responder.getPortToBind(); const Address responder_address = Address(responder_port, family); From 0083a158056a2d96e400ca507b85046eb6e99f2d Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 15:15:04 +0100 Subject: [PATCH 155/228] testing --- TODO | 2 ++ tests/TestOpenTimeout.cpp | 2 +- tests/TestTCP.cpp | 9 ++++++++- tests/TestUDP.cpp | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index 75f10707..ecf17aed 100644 --- a/TODO +++ b/TODO @@ -23,3 +23,5 @@ ricontrollare se vari buffer char sono da ripulire a zeros prima di uso valutare dove si puo' sostituire optional con expected ripulire include ridondanti + +test accept tcp client with timeout diff --git a/tests/TestOpenTimeout.cpp b/tests/TestOpenTimeout.cpp index 77ea9914..01227dad 100644 --- a/tests/TestOpenTimeout.cpp +++ b/tests/TestOpenTimeout.cpp @@ -25,7 +25,7 @@ TEST_CASE("Simulate open with timeout", "[open]") { OpenableTest test(open_time); SECTION("expected success") { - CHECK_FALSE(test.open(Timeout{1000})); + CHECK(test.open(Timeout{1000})); CHECK(test.wasOpened()); } diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 5e8132e1..d4c0b1ed 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -89,7 +89,7 @@ TEST_CASE("Establish tcp connection", "[tcp]") { #if !defined(_WIN32) SECTION("expected failure") { TcpClient client(Address(port, family)); - CHECK_FALSE(client.open()); + CHECK_THROWS_AS(client.open(), Error); CHECK_FALSE(client.wasOpened()); } #endif @@ -218,7 +218,14 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { 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 +#endif CHECK_FALSE(client.wasOpened()); } diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 72abd8c2..3e1807e2 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -215,7 +215,7 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { std::unique_ptr requester_only_bind = std::make_unique(requester_port, family); - REQUIRE_FALSE(requester_only_bind->open()); + REQUIRE(requester_only_bind->open()); // connect requester to responder auto deduce_sender = GENERATE(true, false); From 92e92f971c72245d77673350ff03c6a7d0468bff Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 16:06:34 +0100 Subject: [PATCH 156/228] testing accept with timeout --- src/header/MinimalSocket/tcp/TcpServer.h | 6 +- src/src/Utils.cpp | 6 +- src/src/tcp/TcpServer.cpp | 10 +- tests/TestTCP.cpp | 125 +++++++++++++++++++++++ 4 files changed, 140 insertions(+), 7 deletions(-) diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index 0281decc..dbf353dd 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -11,6 +11,8 @@ #include #include +#include + namespace MinimalSocket::tcp { class TcpServer; @@ -50,6 +52,8 @@ class TcpServer : public PortToBindAware, private: std::size_t client_queue_size = 50; // maximum number of clients put in the queue wiating for connection - // to be accepted + // to be accepted + + std::mutex accept_mtx; }; } // namespace MinimalSocket::tcp diff --git a/src/src/Utils.cpp b/src/src/Utils.cpp index 35fef86a..399c01b4 100644 --- a/src/src/Utils.cpp +++ b/src/src/Utils.cpp @@ -40,7 +40,11 @@ void try_within_timeout(const std::function &action_to_try, open_task.get(); // will throw if ready because an exception throwned // before timeout } else { - action_to_abort(); + try { + action_to_abort(); + open_task.get(); + } catch (...) { + } throw TimeOutError{}; } } diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 6bdb1e7b..6cc573d1 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -53,6 +53,7 @@ TcpConnection TcpServer::acceptNewClient() { std::optional TcpServer::acceptNewClient(const Timeout &timeout) { + std::scoped_lock lock(accept_mtx); if (!this->wasOpened()) { throw Error("Tcp server was not opened before starting to accept clients"); } @@ -79,13 +80,12 @@ TcpServer::acceptNewClient(const Timeout &timeout) { accept_client(); } else { try_within_timeout([&]() { accept_client(); }, - [this]() { - this->resetIDWrapper(); - this->open(); - }, - timeout); + [this]() { this->resetIDWrapper(); }, 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()); diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index d4c0b1ed..b18b0cb8 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -275,3 +275,128 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { client.send(request); }); } + +TEST_CASE("Accept client with timeout", "[tcp]") { + const auto family = GENERATE(IP_V4, 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; + parallel( + [&]() { +#pragma omp barrier + CHECK(client_first.open()); + }, + [&]() { +#pragma omp barrier + auto accepted = server.acceptNewClient(); + server_side_first = + std::make_unique(std::move(accepted)); + }); + + // expect second accept to fail + CHECK_FALSE(server.acceptNewClient(timeout)); + CHECK(server.wasOpened()); + + // check first accepted connection is still valid + parallel( + [&]() { +#pragma omp barrier + auto received_request = server_side_first->receive(request.size()); + CHECK(received_request == request); + }, + [&]() { + // client +#pragma omp barrier + client_first.send(request); + }); + + // connect second client after accept unsuccess and check they can exchange + // messages + parallel( + [&]() { + TcpClient client_second = TcpClient{server_address}; +#pragma omp barrier + CHECK(client_second.open()); + client_second.send(request); + }, + [&]() { +#pragma omp barrier + auto server_side_second = server.acceptNewClient(); + auto received_request = server_side_second.receive(request.size()); + CHECK(received_request == request); + }); + } + + SECTION("expect success within timeout") { + const auto wait = Timeout{250}; + parallel( + [&]() { + TcpClient client = TcpClient{server_address}; +#pragma omp barrier + std::this_thread::sleep_for(wait); + CHECK(client.open()); + }, + [&]() { +#pragma omp barrier + CHECK(server.acceptNewClient(timeout)); + }); + } +} + +// TEST_CASE("temppp", "[tcp]") { +// const auto family = GENERATE(IP_V4, IP_V6); +// const auto port = PortFactory::makePort(); + +// std::unique_ptr server = std::make_unique(port, +// family); REQUIRE(server->open()); + +// TcpClient client(Address(port, family)); +// std::unique_ptr server_side; +// parallel( +// [&]() { +// #pragma omp barrier +// CHECK(client.open()); +// }, +// [&]() { +// #pragma omp barrier +// auto connected = server->acceptNewClient(); +// server_side = std::make_unique(std::move(connected)); +// }); + +// parallel( +// [&]() { +// #pragma omp barrier +// auto received_request = server_side->receive(request.size()); +// CHECK(received_request == request); +// }, +// [&]() { +// // client +// #pragma omp barrier +// client.send(request); +// }); + +// server.reset(); +// server = std::make_unique(port, family); +// REQUIRE(server->open()); + +// parallel( +// [&]() { +// #pragma omp barrier +// auto received_request = server_side->receive(request.size()); +// CHECK(received_request == request); +// }, +// [&]() { +// // client +// #pragma omp barrier +// client.send(request); +// }); +// } From d3711a7d0142aba44ed663d159016b43fce6d5c3 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 16:07:52 +0100 Subject: [PATCH 157/228] minor change --- tests/TestTCP.cpp | 49 ----------------------------------------------- 1 file changed, 49 deletions(-) diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index b18b0cb8..af7d5077 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -351,52 +351,3 @@ TEST_CASE("Accept client with timeout", "[tcp]") { }); } } - -// TEST_CASE("temppp", "[tcp]") { -// const auto family = GENERATE(IP_V4, IP_V6); -// const auto port = PortFactory::makePort(); - -// std::unique_ptr server = std::make_unique(port, -// family); REQUIRE(server->open()); - -// TcpClient client(Address(port, family)); -// std::unique_ptr server_side; -// parallel( -// [&]() { -// #pragma omp barrier -// CHECK(client.open()); -// }, -// [&]() { -// #pragma omp barrier -// auto connected = server->acceptNewClient(); -// server_side = std::make_unique(std::move(connected)); -// }); - -// parallel( -// [&]() { -// #pragma omp barrier -// auto received_request = server_side->receive(request.size()); -// CHECK(received_request == request); -// }, -// [&]() { -// // client -// #pragma omp barrier -// client.send(request); -// }); - -// server.reset(); -// server = std::make_unique(port, family); -// REQUIRE(server->open()); - -// parallel( -// [&]() { -// #pragma omp barrier -// auto received_request = server_side->receive(request.size()); -// CHECK(received_request == request); -// }, -// [&]() { -// // client -// #pragma omp barrier -// client.send(request); -// }); -// } From a8e6fae88e24b2c5e2d0d4b4ef1050e85f21d9d7 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 16:20:47 +0100 Subject: [PATCH 158/228] removing redundant headers --- TODO | 4 +--- src/header/MinimalSocket/core/Socket.h | 4 ++-- tests/TestTCP.cpp | 1 + 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/TODO b/TODO index ecf17aed..bcf4744d 100644 --- a/TODO +++ b/TODO @@ -20,8 +20,6 @@ negative test provando a bindare piu' volte la stessa porta per tcp e udp ricontrollare se vari buffer char sono da ripulire a zeros prima di uso -valutare dove si puo' sostituire optional con expected - ripulire include ridondanti -test accept tcp client with timeout +stressare bene il fatto che c'e' possibilita' di usare con timeout varie operazioni diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 88e9ec5b..22c8ff5f 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -7,15 +7,15 @@ #pragma once -#include #include +#ifdef _WIN32 #include +#endif #include #include #include #include -#include namespace MinimalSocket { #ifdef _WIN32 diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index af7d5077..bff1433c 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include From 82cc1482487a30acb8e5d42dd7ab2d6911988136 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 16:24:01 +0100 Subject: [PATCH 159/228] removing redundnat include --- src/src/SocketId.h | 13 +++++-------- src/src/Utils.h | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/src/SocketId.h b/src/src/SocketId.h index 31c18f25..d859ae46 100644 --- a/src/src/SocketId.h +++ b/src/src/SocketId.h @@ -15,9 +15,6 @@ #include #include #include - -#include -#include #else #include #include @@ -83,15 +80,15 @@ class SocketIdWrapper { #ifdef _WIN32 class WSALazyInitializer { public: - static void lazyInit(); + static void lazyInit(); - ~WSALazyInitializer(); + ~WSALazyInitializer(); private: - WSALazyInitializer(); + WSALazyInitializer(); - static std::mutex lazy_proxy_mtx; - static std::unique_ptr lazy_proxy; + static std::mutex lazy_proxy_mtx; + static std::unique_ptr lazy_proxy; }; #endif } // namespace MinimalSocket diff --git a/src/src/Utils.h b/src/src/Utils.h index 72befa3d..5befe359 100644 --- a/src/src/Utils.h +++ b/src/src/Utils.h @@ -12,7 +12,6 @@ #include "SocketId.h" #include -#include namespace MinimalSocket { void visitAddress(const AddressFamily &family, From 712ab6f4357c069a62e8f4ec2332da3c53e10b8f Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 23:19:27 +0100 Subject: [PATCH 160/228] refactoring --- CMakeLists.txt | 11 ++----- TODO | 6 ++-- samples/CMakeLists.txt | 8 +++++ {cmake => samples/cmake}/MakeSample.cmake | 12 ++----- {Utils/src => samples/utils}/Asker.cpp | 0 {Utils/include => samples/utils}/Asker.h | 0 samples/utils/CMakeLists.txt | 7 +++++ {Utils/src => samples/utils}/Names.cpp | 0 {Utils/include => samples/utils}/Names.h | 0 {Utils/src => samples/utils}/Responder.cpp | 0 {Utils/include => samples/utils}/Responder.h | 0 src/header/MinimalSocket/core/Sender.h | 18 ++++++++++- src/src/core/Sender.cpp | 33 +++++++++++++++++++- 13 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 samples/CMakeLists.txt rename {cmake => samples/cmake}/MakeSample.cmake (55%) rename {Utils/src => samples/utils}/Asker.cpp (100%) rename {Utils/include => samples/utils}/Asker.h (100%) create mode 100644 samples/utils/CMakeLists.txt rename {Utils/src => samples/utils}/Names.cpp (100%) rename {Utils/include => samples/utils}/Names.h (100%) rename {Utils/src => samples/utils}/Responder.cpp (100%) rename {Utils/include => samples/utils}/Responder.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d15c69fd..4b71b7e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,20 +16,15 @@ set(WITH_SOURCE_TREE ON) include(GroupSources) include(AutoCollect) include(MakeLibrary) -include(MakeSample) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") project(MinimalCppSocket) add_subdirectory(src) -# if(BUILD_SAMPLES OR BUILD_TESTS) -# add_subdirectory(Utils) -# endif() - -# if(BUILD_SAMPLES) -# add_subdirectory(Samples) -# endif() +if(BUILD_MinimalCppSocket_SAMPLES) + add_subdirectory(samples) +endif() if(BUILD_MinimalCppSocket_TESTS) add_subdirectory(tests) diff --git a/TODO b/TODO index bcf4744d..d73e3f41 100644 --- a/TODO +++ b/TODO @@ -20,6 +20,8 @@ negative test provando a bindare piu' volte la stessa porta per tcp e udp ricontrollare se vari buffer char sono da ripulire a zeros prima di uso -ripulire include ridondanti - stressare bene il fatto che c'e' possibilita' di usare con timeout varie operazioni + +stressare che si possono fare in maniera safe send concorrenti per udp + +add test for udp concurrent send to diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt new file mode 100644 index 00000000..8e5c518c --- /dev/null +++ b/samples/CMakeLists.txt @@ -0,0 +1,8 @@ +project(MinimalCppSocket-Samples) + +include(cmake/MakeSample.cmake) + +add_subdirectory(utils) + +# add_subdirectory(tcp) +# add_subdirectory(udp) diff --git a/cmake/MakeSample.cmake b/samples/cmake/MakeSample.cmake similarity index 55% rename from cmake/MakeSample.cmake rename to samples/cmake/MakeSample.cmake index f3be14b9..d10422f3 100644 --- a/cmake/MakeSample.cmake +++ b/samples/cmake/MakeSample.cmake @@ -1,20 +1,12 @@ function(MakeSample NAME) add_executable(${NAME} ${NAME}.cpp) - target_link_libraries(${NAME} - PUBLIC + target_link_libraries(${NAME} PUBLIC Utils + MinimalSocket ) set_target_properties(${NAME} PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=%PATH%;${CMAKE_INSTALL_PREFIX}/bin/") install(TARGETS ${NAME}) endfunction() - -function(MakeLaunch NAME) - add_executable(${NAME} ${NAME}.cpp) - - add_dependencies(${NAME} ${ARGV}) - - install(TARGETS ${NAME}) -endfunction() diff --git a/Utils/src/Asker.cpp b/samples/utils/Asker.cpp similarity index 100% rename from Utils/src/Asker.cpp rename to samples/utils/Asker.cpp diff --git a/Utils/include/Asker.h b/samples/utils/Asker.h similarity index 100% rename from Utils/include/Asker.h rename to samples/utils/Asker.h diff --git a/samples/utils/CMakeLists.txt b/samples/utils/CMakeLists.txt new file mode 100644 index 00000000..7d261ae6 --- /dev/null +++ b/samples/utils/CMakeLists.txt @@ -0,0 +1,7 @@ +set(NAME Utils) + +file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.h ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) + +add_library(${NAME} ${SOURCES}) +target_link_libraries(${NAME} PUBLIC MinimalSocket) +target_include_directories(${NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/Utils/src/Names.cpp b/samples/utils/Names.cpp similarity index 100% rename from Utils/src/Names.cpp rename to samples/utils/Names.cpp diff --git a/Utils/include/Names.h b/samples/utils/Names.h similarity index 100% rename from Utils/include/Names.h rename to samples/utils/Names.h diff --git a/Utils/src/Responder.cpp b/samples/utils/Responder.cpp similarity index 100% rename from Utils/src/Responder.cpp rename to samples/utils/Responder.cpp diff --git a/Utils/include/Responder.h b/samples/utils/Responder.h similarity index 100% rename from Utils/include/Responder.h rename to samples/utils/Responder.h diff --git a/src/header/MinimalSocket/core/Sender.h b/src/header/MinimalSocket/core/Sender.h index 09f95f70..d32e16bd 100644 --- a/src/header/MinimalSocket/core/Sender.h +++ b/src/header/MinimalSocket/core/Sender.h @@ -10,7 +10,10 @@ #include #include +#include +#include #include +#include namespace MinimalSocket { class Sender : public virtual Socket { @@ -38,6 +41,19 @@ class SenderTo : public virtual Socket { bool sendTo(const std::string &message, const Address &recipient); private: - std::mutex send_mtx; + std::future reserveAddress(const Address &to_reserve); + void freeAddress(const Address &to_reserve); + + std::mutex recipients_register_mtx; + + struct AddressHasher { + std::hash string_hasher; + + std::size_t operator()(const Address &subject) const { + return string_hasher(to_string(subject)); + } + }; + std::unordered_map>, AddressHasher> + recipients_register; }; } // namespace MinimalSocket diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index 6881716f..54f3b8c1 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -28,8 +28,38 @@ bool Sender::send(const std::string &message) { return send(makeStringConstBuffer(message)); } +std::future SenderTo::reserveAddress(const Address &to_reserve) { + std::lock_guard lock(recipients_register_mtx); + auto it = recipients_register.find(to_reserve); + if (it == recipients_register.end()) { + auto &promises = recipients_register[to_reserve]; + promises.emplace_back(); + auto &promise = promises.back(); + auto result = promise.get_future(); + promise.set_value(); + return result; + } + auto &promises = it->second; + promises.emplace_back(); + auto &promise = promises.back(); + return promise.get_future(); +} + +void SenderTo::freeAddress(const Address &to_reserve) { + std::lock_guard lock(recipients_register_mtx); + auto it = recipients_register.find(to_reserve); + auto &promises = it->second; + if (1 == promises.size()) { + recipients_register.erase(it); + } else { + promises.pop_front(); + promises.front().set_value(); + } +} + bool SenderTo::sendTo(const ConstBuffer &message, const Address &recipient) { - std::scoped_lock lock(send_mtx); + auto send_allowed = reserveAddress(recipient); + send_allowed.wait(); int sentBytes; visitAddress( recipient.getFamily(), @@ -51,6 +81,7 @@ bool SenderTo::sendTo(const ConstBuffer &message, const Address &recipient) { reinterpret_cast(&socketIp6.value()), sizeof(SocketAddressIpv6)); }); + freeAddress(recipient); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; auto err = SocketError{"sendto failed"}; From b4a6cfa6b6ba79b59aee3aa25d4bb5d16a95e67f Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Fri, 13 May 2022 23:36:46 +0100 Subject: [PATCH 161/228] minor changes --- src/header/MinimalSocket/core/Sender.h | 4 +++- src/src/core/Sender.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/header/MinimalSocket/core/Sender.h b/src/header/MinimalSocket/core/Sender.h index d32e16bd..750c1b84 100644 --- a/src/header/MinimalSocket/core/Sender.h +++ b/src/header/MinimalSocket/core/Sender.h @@ -36,6 +36,7 @@ class SenderTo : public virtual Socket { * @return true if the message was completely sent * @param[in] the message to send */ + // TODO spiegare che possono essere concorrenti e come funziona bool sendTo(const ConstBuffer &message, const Address &recipient); bool sendTo(const std::string &message, const Address &recipient); @@ -53,7 +54,8 @@ class SenderTo : public virtual Socket { return string_hasher(to_string(subject)); } }; - std::unordered_map>, AddressHasher> + using WaitingToSendQueue = std::list>; + std::unordered_map recipients_register; }; } // namespace MinimalSocket diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index 54f3b8c1..77f5945b 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -29,7 +29,7 @@ bool Sender::send(const std::string &message) { } std::future SenderTo::reserveAddress(const Address &to_reserve) { - std::lock_guard lock(recipients_register_mtx); + std::scoped_lock lock(recipients_register_mtx); auto it = recipients_register.find(to_reserve); if (it == recipients_register.end()) { auto &promises = recipients_register[to_reserve]; @@ -46,7 +46,7 @@ std::future SenderTo::reserveAddress(const Address &to_reserve) { } void SenderTo::freeAddress(const Address &to_reserve) { - std::lock_guard lock(recipients_register_mtx); + std::scoped_lock lock(recipients_register_mtx); auto it = recipients_register.find(to_reserve); auto &promises = it->second; if (1 == promises.size()) { From 75d5ff1ec8309e5944d42ef3ff675e7156a8eff1 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 14 May 2022 15:20:52 +0100 Subject: [PATCH 162/228] samples --- CrossSocket/AsynchSocket/CMakeLists.txt | 13 -- .../AsynchSocket/include/core/AsyncSocket.h | 59 -------- .../AsynchSocket/include/core/ErrorListener.h | 23 --- .../AsynchSocket/include/core/Talker.h | 53 ------- .../include/messanger/AsyncMessanger.h | 46 ------ .../include/messanger/MessangerListener.h | 20 --- .../include/tcpServer/AsyncTcpServer.h | 33 ---- .../include/tcpServer/TcpServerListener.h | 20 --- .../AsynchSocket/src/core/AsyncSocket.cpp | 64 -------- .../src/messanger/AsyncMessanger.cpp | 26 ---- .../src/tcpServer/AsyncTcpServer.cpp | 19 --- CrossSocket/CMakeLists.txt | 11 -- CrossSocket/SynchSocket/CMakeLists.txt | 15 -- CrossSocket/SynchSocket/include/Error.h | 49 ------ CrossSocket/SynchSocket/include/Ip.h | 62 -------- .../SynchSocket/include/core/BindCapable.h | 23 --- .../SynchSocket/include/core/Connection.h | 48 ------ .../SynchSocket/include/core/Messanger.h | 41 ----- .../SynchSocket/include/core/SocketClosable.h | 35 ----- .../include/core/SocketDecorator.h | 37 ----- .../SynchSocket/include/core/SocketOpenable.h | 41 ----- .../include/core/components/Buffer.h | 35 ----- .../include/core/components/ChannelAware.h | 27 ---- .../include/core/components/Closable.h | 21 --- .../include/core/components/FamilyAware.h | 23 --- .../include/core/components/Openable.h | 24 --- .../include/core/components/ProtocolAware.h | 20 --- .../include/core/components/ReceiveCapable.h | 29 ---- .../core/components/RemoteAddressAware.h | 30 ---- .../include/core/components/SendCapable.h | 25 ---- .../include/core/components/StateAware.h | 21 --- .../SynchSocket/include/tcp/TcpClient.h | 32 ---- .../SynchSocket/include/tcp/TcpServer.h | 60 -------- .../SynchSocket/include/udp/UdpConnection.h | 42 ------ .../SynchSocket/include/udp/UdpServer.h | 34 ----- CrossSocket/SynchSocket/src/Channel.cpp | 109 -------------- CrossSocket/SynchSocket/src/Channel.h | 75 ---------- CrossSocket/SynchSocket/src/Commons.cpp | 141 ------------------ CrossSocket/SynchSocket/src/Commons.h | 91 ----------- CrossSocket/SynchSocket/src/Ip.cpp | 54 ------- .../SynchSocket/src/core/BindCapable.cpp | 60 -------- .../SynchSocket/src/core/Connection.cpp | 44 ------ .../SynchSocket/src/core/Messanger.cpp | 55 ------- .../SynchSocket/src/core/SocketClosable.cpp | 36 ----- .../SynchSocket/src/core/SocketDecorator.cpp | 26 ---- .../SynchSocket/src/core/SocketOpenable.cpp | 49 ------ .../src/core/components/Buffer.cpp | 33 ---- .../src/core/components/ChannelAware.cpp | 15 -- CrossSocket/SynchSocket/src/tcp/TcpClient.cpp | 15 -- CrossSocket/SynchSocket/src/tcp/TcpServer.cpp | 65 -------- .../SynchSocket/src/udp/UdpConnection.cpp | 22 --- CrossSocket/SynchSocket/src/udp/UdpServer.cpp | 50 ------- CrossSocket/TypedSocket/CMakeLists.txt | 20 --- .../include/core/TypedAsyncMessanger.h | 57 ------- .../TypedSocket/include/core/TypedMessanger.h | 35 ----- .../include/core/TypedMessangerListener.h | 19 --- .../TypedSocket/include/core/TypedReceiver.h | 51 ------- .../TypedSocket/include/core/TypedSender.h | 36 ----- .../include/core/components/Decoder.h | 21 --- .../include/core/components/Encoder.h | 21 --- .../core/components/TypedReceiveCapable.h | 21 --- .../core/components/TypedSendCapable.h | 19 --- .../TypedSocket/src/core/TypedMessanger.cpp | 15 -- samples/utils/Asker.h | 36 ++--- samples/utils/Names.cpp | 85 ++--------- samples/utils/Names.h | 55 ++----- 66 files changed, 44 insertions(+), 2548 deletions(-) delete mode 100644 CrossSocket/AsynchSocket/CMakeLists.txt delete mode 100644 CrossSocket/AsynchSocket/include/core/AsyncSocket.h delete mode 100644 CrossSocket/AsynchSocket/include/core/ErrorListener.h delete mode 100644 CrossSocket/AsynchSocket/include/core/Talker.h delete mode 100644 CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h delete mode 100644 CrossSocket/AsynchSocket/include/messanger/MessangerListener.h delete mode 100644 CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h delete mode 100644 CrossSocket/AsynchSocket/include/tcpServer/TcpServerListener.h delete mode 100644 CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp delete mode 100644 CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp delete mode 100644 CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp delete mode 100644 CrossSocket/CMakeLists.txt delete mode 100644 CrossSocket/SynchSocket/CMakeLists.txt delete mode 100644 CrossSocket/SynchSocket/include/Error.h delete mode 100644 CrossSocket/SynchSocket/include/Ip.h delete mode 100644 CrossSocket/SynchSocket/include/core/BindCapable.h delete mode 100644 CrossSocket/SynchSocket/include/core/Connection.h delete mode 100644 CrossSocket/SynchSocket/include/core/Messanger.h delete mode 100644 CrossSocket/SynchSocket/include/core/SocketClosable.h delete mode 100644 CrossSocket/SynchSocket/include/core/SocketDecorator.h delete mode 100644 CrossSocket/SynchSocket/include/core/SocketOpenable.h delete mode 100644 CrossSocket/SynchSocket/include/core/components/Buffer.h delete mode 100644 CrossSocket/SynchSocket/include/core/components/ChannelAware.h delete mode 100644 CrossSocket/SynchSocket/include/core/components/Closable.h delete mode 100644 CrossSocket/SynchSocket/include/core/components/FamilyAware.h delete mode 100644 CrossSocket/SynchSocket/include/core/components/Openable.h delete mode 100644 CrossSocket/SynchSocket/include/core/components/ProtocolAware.h delete mode 100644 CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h delete mode 100644 CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h delete mode 100644 CrossSocket/SynchSocket/include/core/components/SendCapable.h delete mode 100644 CrossSocket/SynchSocket/include/core/components/StateAware.h delete mode 100644 CrossSocket/SynchSocket/include/tcp/TcpClient.h delete mode 100644 CrossSocket/SynchSocket/include/tcp/TcpServer.h delete mode 100644 CrossSocket/SynchSocket/include/udp/UdpConnection.h delete mode 100644 CrossSocket/SynchSocket/include/udp/UdpServer.h delete mode 100644 CrossSocket/SynchSocket/src/Channel.cpp delete mode 100644 CrossSocket/SynchSocket/src/Channel.h delete mode 100644 CrossSocket/SynchSocket/src/Commons.cpp delete mode 100644 CrossSocket/SynchSocket/src/Commons.h delete mode 100644 CrossSocket/SynchSocket/src/Ip.cpp delete mode 100644 CrossSocket/SynchSocket/src/core/BindCapable.cpp delete mode 100644 CrossSocket/SynchSocket/src/core/Connection.cpp delete mode 100644 CrossSocket/SynchSocket/src/core/Messanger.cpp delete mode 100644 CrossSocket/SynchSocket/src/core/SocketClosable.cpp delete mode 100644 CrossSocket/SynchSocket/src/core/SocketDecorator.cpp delete mode 100644 CrossSocket/SynchSocket/src/core/SocketOpenable.cpp delete mode 100644 CrossSocket/SynchSocket/src/core/components/Buffer.cpp delete mode 100644 CrossSocket/SynchSocket/src/core/components/ChannelAware.cpp delete mode 100644 CrossSocket/SynchSocket/src/tcp/TcpClient.cpp delete mode 100644 CrossSocket/SynchSocket/src/tcp/TcpServer.cpp delete mode 100644 CrossSocket/SynchSocket/src/udp/UdpConnection.cpp delete mode 100644 CrossSocket/SynchSocket/src/udp/UdpServer.cpp delete mode 100644 CrossSocket/TypedSocket/CMakeLists.txt delete mode 100644 CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h delete mode 100644 CrossSocket/TypedSocket/include/core/TypedMessanger.h delete mode 100644 CrossSocket/TypedSocket/include/core/TypedMessangerListener.h delete mode 100644 CrossSocket/TypedSocket/include/core/TypedReceiver.h delete mode 100644 CrossSocket/TypedSocket/include/core/TypedSender.h delete mode 100644 CrossSocket/TypedSocket/include/core/components/Decoder.h delete mode 100644 CrossSocket/TypedSocket/include/core/components/Encoder.h delete mode 100644 CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h delete mode 100644 CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h delete mode 100644 CrossSocket/TypedSocket/src/core/TypedMessanger.cpp diff --git a/CrossSocket/AsynchSocket/CMakeLists.txt b/CrossSocket/AsynchSocket/CMakeLists.txt deleted file mode 100644 index 5b8c3728..00000000 --- a/CrossSocket/AsynchSocket/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -set(PROJECT_SHORTNAME "Asynch-Cross-Socket") - -MakeLibrary(${PROJECT_SHORTNAME} include) - -target_link_libraries(${PROJECT_SHORTNAME} -PUBLIC - Synch-Cross-Socket -) - -target_compile_definitions(${PROJECT_SHORTNAME} -PUBLIC - ASYNCH_ENABLED -) diff --git a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h b/CrossSocket/AsynchSocket/include/core/AsyncSocket.h deleted file mode 100644 index 3b0f60c9..00000000 --- a/CrossSocket/AsynchSocket/include/core/AsyncSocket.h +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ASYNCSOCKET_H -#define _CROSS_SOCKET_ASYNCSOCKET_H - -#include -#include -#include -#include -#include - -namespace sck::async { - typedef Talker ErrorTalker; - - /** - * @brief An asynchronous socket internally stores a service, which is a private thread using in some way the wrapped socket. - * The internal service is created and spawned everytime the object is opened and stop and destroyed everytime is closed. - */ - class AsyncSocket - : public SocketDecorator - , public ErrorTalker { - public: - virtual ~AsyncSocket() { this->close(); }; - - /** - * @return true only when the internal service was already spawned. - * IMPORTANT!! the object might be built passing an already opened socket. However the - * service is not spawned inside the c'tor and calling this function after construction will return false, - * even if the passed socket was opened. It is however possible to call open to actually start the service. - */ - inline bool isOpen() const override { return this->serviceLife; }; - - /** - * @brief tries to open the wrapped socket if not already opened and then spawn the internal service. - * @throw if the service was already spawned. - */ - void open(const std::chrono::milliseconds& timeout) final; - - void close() final; - - protected: - AsyncSocket(std::unique_ptr socket); - - virtual void serviceIteration() = 0; - - private: - void spawnService(); - - std::atomic_bool serviceLife = false; - std::unique_ptr service; - }; -} - -#endif diff --git a/CrossSocket/AsynchSocket/include/core/ErrorListener.h b/CrossSocket/AsynchSocket/include/core/ErrorListener.h deleted file mode 100644 index 940e3a70..00000000 --- a/CrossSocket/AsynchSocket/include/core/ErrorListener.h +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ERRORLISTENER_H_ -#define _CROSS_SOCKET_ERRORLISTENER_H_ - -#include -#include - -namespace sck::async { - class ErrorListener { - public: - virtual void handle(const Error& error) = 0; - - virtual void handle(const std::exception& error) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/include/core/Talker.h b/CrossSocket/AsynchSocket/include/core/Talker.h deleted file mode 100644 index 5aab4c79..00000000 --- a/CrossSocket/AsynchSocket/include/core/Talker.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TALKER_H_ -#define _CROSS_SOCKET_TALKER_H_ - -#include -#include - -namespace sck::async { - /** - * @brief An object able to notify to an external listener. - * After construction, no listener is assumed, meaning that calling notify will - * actually have no effect. - */ - template - class Talker { - public: - /** - * @brief Sets the listener that should receive the notifications - * @param the new listener to set. Only 1 listener at a time can be supported - */ - void resetListener(Listener* listener) { - if (nullptr == listener) { - throw Error("The passed listener is empty"); - } - std::lock_guard lk(this->listenerMtx); - this->listener = listener; - }; - - protected: - Talker() = default; - - template - void notify(T content) { - std::lock_guard lk(this->listenerMtx); - if (nullptr == this->listener) { - return; - } - this->listener->handle(std::forward(content)); - }; - - private: - std::mutex listenerMtx; - Listener* listener = nullptr; - }; -} - -#endif diff --git a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h b/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h deleted file mode 100644 index 638d69e7..00000000 --- a/CrossSocket/AsynchSocket/include/messanger/AsyncMessanger.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ASYNCMESSANGER_H -#define _CROSS_SOCKET_ASYNCMESSANGER_H - -#include -#include -#include -#include -#include -#include -#include - -namespace sck::async { - typedef Talker MessageTalker; - - /** - * @brief An asynchronous messanger can be any kind of socket able to send and receive messages. - * This object stores such a messanger and keeps receive messages inside the private service stored by this class. - * From the outside it is only possible to send messages or subscribe to the received messages by setting the proper - * MessageListener. - */ - class AsyncMessanger - : public AsyncSocket - , public MessageTalker - , public SendCapable - , public Buffer { - public: - AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity); - - inline bool send(const std::pair& message) final { return this->messPtr->send(message); }; - - protected: - Messanger* messPtr; - - private: - void serviceIteration() final; - }; -} - -#endif diff --git a/CrossSocket/AsynchSocket/include/messanger/MessangerListener.h b/CrossSocket/AsynchSocket/include/messanger/MessangerListener.h deleted file mode 100644 index 5a955b79..00000000 --- a/CrossSocket/AsynchSocket/include/messanger/MessangerListener.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_MESSANGERLISTENER_H_ -#define _CROSS_SOCKET_MESSANGERLISTENER_H_ - -#include - -namespace sck::async { - class MessangerListener { - public: - virtual void handle(const std::pair& message) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h b/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h deleted file mode 100644 index d81aab10..00000000 --- a/CrossSocket/AsynchSocket/include/tcpServer/AsyncTcpServer.h +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ASYNCTCPSERVER_H -#define _CROSS_SOCKET_ASYNCTCPSERVER_H - -#include -#include - -namespace sck::async { - typedef Talker TcpClientHandlerTalker; - - /** - * @brief An asynchronous tcp server keep accept new clients - * inside the private service stored by this class. From the outside it is possible to subscribe to the - * accepted clients by setting the proper TcpServerListener - */ - class AsyncTcpServer - : public AsyncSocket - , public TcpClientHandlerTalker { - public: - explicit AsyncTcpServer(std::unique_ptr server); - - protected: - void serviceIteration() override; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/include/tcpServer/TcpServerListener.h b/CrossSocket/AsynchSocket/include/tcpServer/TcpServerListener.h deleted file mode 100644 index 475c734b..00000000 --- a/CrossSocket/AsynchSocket/include/tcpServer/TcpServerListener.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TCPSERVERLISTENER_H_ -#define _CROSS_SOCKET_TCPSERVERLISTENER_H_ - -#include - -namespace sck::async { - class TcpServerListener { - public: - virtual void handle(std::unique_ptr clientHandler) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp b/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp deleted file mode 100644 index 61e058f2..00000000 --- a/CrossSocket/AsynchSocket/src/core/AsyncSocket.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -namespace sck::async { - AsyncSocket::AsyncSocket(std::unique_ptr socket) - : SocketDecorator(std::move(socket)) { - }; - - void AsyncSocket::spawnService() { - this->serviceLife = false; - this->service = std::make_unique([this]() { - this->serviceLife = true; - while (this->serviceLife) { - try { - this->serviceIteration(); - } - catch (...) { - try { - std::rethrow_exception(std::current_exception()); - } - catch (const Error & e) { - this->Talker::notify(e); - } - catch (const std::exception & e) { - this->Talker::notify(e); - } - } - } - }); - // wait till the thread is actually spawned - while (!this->serviceLife) { - std::this_thread::sleep_for(std::chrono::milliseconds(3)); - } - } - - void AsyncSocket::open(const std::chrono::milliseconds& timeout) { - if (nullptr != this->service) { - throw Error("The socket was already opened"); - } - // open the wrapped socket when necessary - if (!this->wrapped->isOpen()) { - this->SocketDecorator::open(timeout); - } - this->spawnService(); - }; - - void AsyncSocket::close() { - this->wrapped->close(); - if (nullptr == this->service) { - return; - } - this->serviceLife = false; - if (this->service->joinable()) { - this->service->join(); - } - this->service.reset(); - }; -} diff --git a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp b/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp deleted file mode 100644 index f69178b4..00000000 --- a/CrossSocket/AsynchSocket/src/messanger/AsyncMessanger.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include - -namespace sck::async { - AsyncMessanger::AsyncMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) - : AsyncSocket(std::move(messanger)) - , Buffer(bufferCapacity) { - this->messPtr = dynamic_cast (this->wrapped.get()); - }; - - void AsyncMessanger::serviceIteration() { - this->resetBufferSize(); - auto buffer = this->getBuffer(); - auto recvBytes = this->messPtr->receive(buffer, std::chrono::milliseconds(0)); - buffer.second = recvBytes; - this->Talker::notify(buffer); - } -} \ No newline at end of file diff --git a/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp b/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp deleted file mode 100644 index 6bee799e..00000000 --- a/CrossSocket/AsynchSocket/src/tcpServer/AsyncTcpServer.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -namespace sck::async { - AsyncTcpServer::AsyncTcpServer(std::unique_ptr server) - : AsyncSocket(std::move(server)) { - }; - - void AsyncTcpServer::serviceIteration() { - auto client = dynamic_cast(this->wrapped.get())->acceptClient(); - this->Talker::notify(std::move(client)); - } -} diff --git a/CrossSocket/CMakeLists.txt b/CrossSocket/CMakeLists.txt deleted file mode 100644 index 4212ae8c..00000000 --- a/CrossSocket/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_subdirectory(SynchSocket) - -option(COMPILE_ASYNCH "Compile the asynchronous cross socket package" ON) -if(COMPILE_ASYNCH) -add_subdirectory(AsynchSocket) -endif() - -option(COMPILE_TYPED "Compile the typed cross socket package" ON) -if(COMPILE_TYPED) -add_subdirectory(TypedSocket) -endif() diff --git a/CrossSocket/SynchSocket/CMakeLists.txt b/CrossSocket/SynchSocket/CMakeLists.txt deleted file mode 100644 index 05ef2418..00000000 --- a/CrossSocket/SynchSocket/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -set(PROJECT_SHORTNAME "Synch-Cross-Socket") - -MakeLibrary(${PROJECT_SHORTNAME} include) - -if(WIN32) - target_link_libraries(${PROJECT_SHORTNAME} - PRIVATE - wsock32 ws2_32 - ) -endif() -find_package(Threads) -target_link_libraries(${PROJECT_SHORTNAME} - PUBLIC - ${CMAKE_THREAD_LIBS_INIT} -) diff --git a/CrossSocket/SynchSocket/include/Error.h b/CrossSocket/SynchSocket/include/Error.h deleted file mode 100644 index 80f66be8..00000000 --- a/CrossSocket/SynchSocket/include/Error.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ERROR_H -#define _CROSS_SOCKET_ERROR_H - -#include -#include - -namespace sck { - /** - * @brief A runtime error that can be raised when using any object in sck:: - */ - class Error : public std::runtime_error { - public: - explicit Error(const std::string& what) : std::runtime_error(what) { - }; - - template - Error(Args ... args) - : Error(merge(args...)) { - } - - private: - template - static std::string merge(Args ... args) { - std::stringstream stream; - merge(stream, args...); - return stream.str(); - }; - - template - static void merge(std::stringstream& stream, const T& current, Args ... remaining) { - stream << current; - merge(stream, remaining...); - }; - - template - static void merge(std::stringstream& stream, const T& back) { - stream << back; - }; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/Ip.h b/CrossSocket/SynchSocket/include/Ip.h deleted file mode 100644 index 68016b57..00000000 --- a/CrossSocket/SynchSocket/include/Ip.h +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_IP_H_ -#define _CROSS_SOCKET_IP_H_ - -#include -#include - -namespace sck { - /** - * @brief The address family. Refer to https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzab6/address.htm - */ - enum Family {IP_V4, IP_V6}; - - class Ip; - typedef std::unique_ptr IpPtr; - - /** - * @brief representation of a network ip address - */ - class Ip { - public: - Ip(const Ip&) = default; - Ip& operator=(const Ip&) = default; - Ip(Ip&&) = delete; - Ip& operator=(Ip&&) = delete; - - /** - * @brief Internally the protocol Family is deduced according to the hostIp content. - * @return nullptr if the host is invalid, otherwise a smart pointer storing a usable ip - */ - static IpPtr create(const std::string& hostIp, const std::uint16_t& port); - - /** - * @return an ipv4 or ipv6 with localhost as host and the passed port - */ - static IpPtr createLocalHost(const std::uint16_t& port = 0, const Family& protocolType = Family::IP_V4); - - inline const std::string& getHost() const { return this->host; }; - - inline const std::uint16_t& getPort() const { return this->port; }; - - inline Family getFamily() const { return this->family; }; - - private: - Ip(const std::string& host, const std::uint16_t& port, const Family& family); - - std::string host; - std::uint16_t port; - Family family; - }; - - bool operator==(const Ip& lhs, const Ip& rhs); - -} - -#endif diff --git a/CrossSocket/SynchSocket/include/core/BindCapable.h b/CrossSocket/SynchSocket/include/core/BindCapable.h deleted file mode 100644 index 06e0dcb8..00000000 --- a/CrossSocket/SynchSocket/include/core/BindCapable.h +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_BINDCAPABLE_H_ -#define _CROSS_SOCKET_BINDCAPABLE_H_ - -#include -#include - -namespace sck { - class BindCapable - : virtual public ChannelAware - , virtual public FamilyAware { - protected: - void bindToPort(const std::uint16_t& port); - }; -} - -#endif diff --git a/CrossSocket/SynchSocket/include/core/Connection.h b/CrossSocket/SynchSocket/include/core/Connection.h deleted file mode 100644 index b008b624..00000000 --- a/CrossSocket/SynchSocket/include/core/Connection.h +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_CONNECTION_H_ -#define _CROSS_SOCKET_CONNECTION_H_ - -#include -#include -#include -#include - -namespace sck { - class Connection - : public SocketClosable - , public Messanger - , public RemoteAddressAware { - protected: - /** - * @param[in] the address of the connection to reach - */ - explicit Connection(const sck::Ip& remoteAddress); - }; - - - - class ConnectionOpenable - : public Connection - , public SocketOpenable { - protected: - /** - * @param[in] the address of the connection to reach - */ - explicit ConnectionOpenable(const sck::Ip& remoteAddress); - - /** - * @brief connect is internally called - */ - void openSteps() override; - - inline sck::Family getFamily() const final { return this->remoteAddress->getFamily(); }; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/Messanger.h b/CrossSocket/SynchSocket/include/core/Messanger.h deleted file mode 100644 index 90ffae4b..00000000 --- a/CrossSocket/SynchSocket/include/core/Messanger.h +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_MESSANGER_H_ -#define _CROSS_SOCKET_MESSANGER_H_ - -#include -#include -#include -#include - -namespace sck { - class Messanger - : virtual public ChannelAware - , virtual public ReceiveCapable - , virtual public SendCapable { - public: - std::size_t receive(const std::pair& message, const std::chrono::milliseconds& timeout) final; - - bool send(const std::pair& message) final; - - private: - std::chrono::milliseconds actualTimeOut = std::chrono::milliseconds(0); - - /** - * @brief enforces to call receive from a single thread - */ - std::mutex receiveMtx; - - /** - * @brief enforces to call send from a single thread - */ - std::mutex sendMtx; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/SocketClosable.h b/CrossSocket/SynchSocket/include/core/SocketClosable.h deleted file mode 100644 index 07f0b53a..00000000 --- a/CrossSocket/SynchSocket/include/core/SocketClosable.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SOCKETCLOSABLE_H_ -#define _CROSS_SOCKET_SOCKETCLOSABLE_H_ - -#include -#include -#include - -namespace sck { - class SocketClosable - : virtual public ChannelAware - , virtual public StateAware - , virtual public Closable { - public: - virtual ~SocketClosable() override; - - bool isOpen() const final; - - void close() final; - - protected: - /** - * @brief The methods containing the specific steps to perform in order to fully close a concrete socket - */ - virtual void closeSteps(); - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/SocketDecorator.h b/CrossSocket/SynchSocket/include/core/SocketDecorator.h deleted file mode 100644 index 6d5396d7..00000000 --- a/CrossSocket/SynchSocket/include/core/SocketDecorator.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SOCKETDECORATOR_H_ -#define _CROSS_SOCKET_SOCKETDECORATOR_H_ - -#include -#include - -namespace sck { - class SocketDecorator - : public StateAware - , public Openable - , public Closable { - public: - inline bool isOpen() const override { return this->wrapped->isOpen(); }; - - inline void close() override { this->wrapped->close(); }; - - /** - * @brief Tries to open the wrapped socket - * @throw when the wrapped socket is not openable - */ - void open(const std::chrono::milliseconds& timeout) override; - - protected: - SocketDecorator(std::unique_ptr channel); - - std::unique_ptr wrapped; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/SocketOpenable.h b/CrossSocket/SynchSocket/include/core/SocketOpenable.h deleted file mode 100644 index 45c28013..00000000 --- a/CrossSocket/SynchSocket/include/core/SocketOpenable.h +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SOCKETOPENABLE_H_ -#define _CROSS_SOCKET_SOCKETOPENABLE_H_ - -#include -#include -#include -#include -#include -#include - -namespace sck { - class SocketOpenable - : virtual public ChannelAware - , virtual public StateAware - , public Openable - , virtual protected Closable - , virtual public FamilyAware - , virtual public ProtocolAware { - public: - /** - * @brief When something goes wrong inside the method, close is - * internally called, leaving the object in a closed status. - */ - void open(const std::chrono::milliseconds& timeout) final; - - protected: - /** - * @brief The methods containing the specific steps to perform to fully open a concrete socket - */ - virtual void openSteps() = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/Buffer.h b/CrossSocket/SynchSocket/include/core/components/Buffer.h deleted file mode 100644 index 1ad57fc7..00000000 --- a/CrossSocket/SynchSocket/include/core/components/Buffer.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_BUFFER_H_ -#define _CROSS_SOCKET_BUFFER_H_ - -#include -#include - -namespace sck { - class Buffer { - protected: - Buffer(const std::size_t capacity); - - inline std::pair getBuffer() { return std::make_pair(this->buffer.data(), this->buffer.size()); }; - - inline const std::string& getStringBuffer() const { return this->buffer; }; - - // with 0 reset to size equal to capacity - // throw if the new size is higher than the max capacity - void resetBufferSize(const std::size_t newSize = 0); - - void resetBufferCapacity(const std::size_t newCapacity); - - private: - std::string buffer; - std::size_t bufferCapacity; - }; -} - -#endif diff --git a/CrossSocket/SynchSocket/include/core/components/ChannelAware.h b/CrossSocket/SynchSocket/include/core/components/ChannelAware.h deleted file mode 100644 index 08d13b17..00000000 --- a/CrossSocket/SynchSocket/include/core/components/ChannelAware.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_CHANNELAWARE_H_ -#define _CROSS_SOCKET_CHANNELAWARE_H_ - -#include - -namespace sck { - class Channel; - - class ChannelAware { - public: - virtual ~ChannelAware(); - - protected: - ChannelAware() = default; - - std::unique_ptr channel; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/Closable.h b/CrossSocket/SynchSocket/include/core/components/Closable.h deleted file mode 100644 index 98e72055..00000000 --- a/CrossSocket/SynchSocket/include/core/components/Closable.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_CLOSABLE_H_ -#define _CROSS_SOCKET_CLOSABLE_H_ - -namespace sck { - class Closable { - public: - /** - * @brief close the object - */ - virtual void close() = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/FamilyAware.h b/CrossSocket/SynchSocket/include/core/components/FamilyAware.h deleted file mode 100644 index c4452f2d..00000000 --- a/CrossSocket/SynchSocket/include/core/components/FamilyAware.h +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_FAMILYAWARE_H_ -#define _CROSS_SOCKET_FAMILYAWARE_H_ - -#include - -namespace sck { - class FamilyAware { - protected: - /** - * @brief These values should be internally deduced from object to object - */ - virtual sck::Family getFamily() const = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/Openable.h b/CrossSocket/SynchSocket/include/core/components/Openable.h deleted file mode 100644 index f7ef08da..00000000 --- a/CrossSocket/SynchSocket/include/core/components/Openable.h +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_OPENABLE_H_ -#define _CROSS_SOCKET_OPENABLE_H_ - -#include - -namespace sck { - class Openable { - public: - /** - * @brief Tries to open the object, until the passed timeout, after which the object spontaneously close itself. - * @param timeout to assume for the open operation. When passing 0, an infinite timeout is assumed - */ - virtual void open(const std::chrono::milliseconds& timeout) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/ProtocolAware.h b/CrossSocket/SynchSocket/include/core/components/ProtocolAware.h deleted file mode 100644 index 008263f7..00000000 --- a/CrossSocket/SynchSocket/include/core/components/ProtocolAware.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_PROTOCOLAWARE_H_ -#define _CROSS_SOCKET_PROTOCOLAWARE_H_ - -namespace sck { - enum Protocol { UDP, TCP }; - - class ProtocolAware { - protected: - virtual sck::Protocol getProtocol() const = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h b/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h deleted file mode 100644 index 4620d254..00000000 --- a/CrossSocket/SynchSocket/include/core/components/ReceiveCapable.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_RECEIVECAPABLE_H_ -#define _CROSS_SOCKET_RECEIVECAPABLE_H_ - -#include -#include - -namespace sck { - class ReceiveCapable { - public: - /** - * @param[in] the buffer that will receive the message: - * first element of the pair is the data pointer of the buffer - * second element of the pair is the buffer size - * A request to receive a message of maximal size equal to message.second will be forwarded to the socket api. - * @param[in] the timeout to consider - * @return the number of received bytes actually received and copied into message (can be also less than the buffer size) - */ - virtual std::size_t receive(const std::pair& message, const std::chrono::milliseconds& timeout) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h b/CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h deleted file mode 100644 index c2c620d8..00000000 --- a/CrossSocket/SynchSocket/include/core/components/RemoteAddressAware.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_REMOTEADDRESSAWARE_H_ -#define _CROSS_SOCKET_REMOTEADDRESSAWARE_H_ - -#include -#include - -namespace sck { - class RemoteAddressAware { - public: - /** - * @brief returns the address of the remote entity connected with this socket - */ - inline const sck::Ip& getRemoteAddress() const { return *this->remoteAddress; }; - - protected: - /** - * @brief address of the entity connected to this socket - */ - std::unique_ptr remoteAddress; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/SendCapable.h b/CrossSocket/SynchSocket/include/core/components/SendCapable.h deleted file mode 100644 index dd79e4ce..00000000 --- a/CrossSocket/SynchSocket/include/core/components/SendCapable.h +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_SENDCAPABLE_H_ -#define _CROSS_SOCKET_SENDCAPABLE_H_ - -#include -#include - -namespace sck { - class SendCapable { - public: - /** - * @return true if the message was completely sent - * @param[in] the message to send - */ - virtual bool send(const std::pair& message) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/core/components/StateAware.h b/CrossSocket/SynchSocket/include/core/components/StateAware.h deleted file mode 100644 index d3347501..00000000 --- a/CrossSocket/SynchSocket/include/core/components/StateAware.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_STATEAWARE_H_ -#define _CROSS_SOCKET_STATEAWARE_H_ - -namespace sck { - class StateAware { - public: - /** - * @return true only if this object was previously successfully opened - */ - virtual bool isOpen() const = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/tcp/TcpClient.h b/CrossSocket/SynchSocket/include/tcp/TcpClient.h deleted file mode 100644 index 2a0f0597..00000000 --- a/CrossSocket/SynchSocket/include/tcp/TcpClient.h +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TCPCLIENT_H_ -#define _CROSS_SOCKET_TCPCLIENT_H_ - -#include - -namespace sck::tcp { - /** - * @brief interface for a tcp client. - * When calling open, the client ask the connection to a tcp server, which must be previously activated, i.e. - * it should be ready to listen and accept this client - */ - class TcpClient - : public ConnectionOpenable { - public: - /** - * @param[in] the address of the server to reach - */ - explicit TcpClient(const sck::Ip& remoteAddress); - - protected: - inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/tcp/TcpServer.h b/CrossSocket/SynchSocket/include/tcp/TcpServer.h deleted file mode 100644 index 8c13901c..00000000 --- a/CrossSocket/SynchSocket/include/tcp/TcpServer.h +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TCPSERVER_H_ -#define _CROSS_SOCKET_TCPSERVER_H_ - -#include -#include -#include -#include - -namespace sck::tcp { - class TcpClientHandler; - - /** - * @brief interface for a tcp server. - * When calling open, the server binds and listen to the port, in order to be later ready to accept clients - */ - class TcpServer - : public SocketOpenable - , public SocketClosable - , public BindCapable { - public: - /** - * @param[in] the port to reserve - * @param[in] the expected protocol family of the client to accept - */ - explicit TcpServer(const std::uint16_t& port, const Family& family = Family::IP_V4); - - /** - * @brief Wait for a new client to ask the connection and after accepting it - * returns an interface to use for exchanging data to and from the accepted clients. - * This is a blocking operation. - */ - std::unique_ptr acceptClient(); - - protected: - inline sck::Family getFamily() const final { return this->family; }; - inline sck::Protocol getProtocol() const final { return Protocol::TCP; }; - - private: - void openSteps() override; - - std::uint16_t port; - sck::Family family; - }; - - class TcpClientHandler - : public Connection { - friend class TcpServer; - protected: - TcpClientHandler(std::unique_ptr channel, const sck::Ip& remoteAddress); - }; -} - -#endif diff --git a/CrossSocket/SynchSocket/include/udp/UdpConnection.h b/CrossSocket/SynchSocket/include/udp/UdpConnection.h deleted file mode 100644 index a4fe04c0..00000000 --- a/CrossSocket/SynchSocket/include/udp/UdpConnection.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_UDPCONNECTION_H_ -#define _CROSS_SOCKET_UDPCONNECTION_H_ - -#include -#include - -namespace sck::udp { -/** - * @brief refer to - * https://en.wikipedia.org/wiki/User_Datagram_Protocol#:~:text=The%20field%20size%20sets%20a,−%2020%20byte%20IP%20header). - */ -static constexpr std::size_t MAX_UDP_RECV_MESSAGE = 65507; - -/** - * @brief interface for a standard udp connection. - */ -class UdpConnection : public ConnectionOpenable, public BindCapable { -public: - /** - @param[in] Address of the remote host to hit - @param[in] port to reserve (passing 0 a random port is reserved) - */ - explicit UdpConnection(const sck::Ip &remoteAddress, - const std::uint16_t &localPort = 0); - -protected: - std::uint16_t port; - - void openSteps() override; - - inline sck::Protocol getProtocol() const final { return Protocol::UDP; }; -}; -} // namespace sck::udp - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/include/udp/UdpServer.h b/CrossSocket/SynchSocket/include/udp/UdpServer.h deleted file mode 100644 index 045ea49f..00000000 --- a/CrossSocket/SynchSocket/include/udp/UdpServer.h +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_UDPSERVER_H_ -#define _CROSS_SOCKET_UDPSERVER_H_ - -#include - -namespace sck::udp { - /** - * @brief A UdpServer extends the UdpConnection class, since it has the possibility to deduce the remoteAddress when calling open, - * by setting as target the first UdpConnection that hits this socket, sending - * at least a 1 byte (or more) message. - * IMPORTANT!!! The first message sent to the server will be lost. - */ - class UdpServer - : public UdpConnection { - public: - /** - * @param[in] the port to reserve - * @param[in] the expected protocol family of the client to accept - */ - explicit UdpServer(const std::uint16_t& localPort, const sck::Family& protocol = sck::Family::IP_V4); - - private: - void openSteps() final; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/Channel.cpp b/CrossSocket/SynchSocket/src/Channel.cpp deleted file mode 100644 index 1516aa80..00000000 --- a/CrossSocket/SynchSocket/src/Channel.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include "Channel.h" -#include - -namespace sck { -#ifdef _WIN32 - std::size_t Channel::SocketHandlerFactory::handlerCounter = 0; - std::mutex Channel::SocketHandlerFactory::handlerCounterMtx; - - void Channel::SocketHandlerFactory::beforeOpen() { - std::lock_guard hndLck(handlerCounterMtx); - ++handlerCounter; - if (1 == handlerCounter) { - // first socket opened - WSADATA wsa; - WSAStartup(MAKEWORD(2, 0), &wsa); - } - } - - void Channel::SocketHandlerFactory::afterClose() { - std::lock_guard hndLck(handlerCounterMtx); - --handlerCounter; - if (0 == handlerCounter) { - // last socket closed - WSACleanup(); - } - } -#endif - - Channel::~Channel() { - if (this->opened) { - this->close(); - } - } - - Channel::Channel() - : opened(false) - , hndl(SCK_INVALID_SOCKET) { - } - - Channel::Channel(const SocketHandler& hndl) - : opened(true) - , hndl(hndl) { -#ifdef _WIN32 - SocketHandlerFactory::beforeOpen(); -#endif - } - - void Channel::open(const Protocol& type, const Family& family) { - if (this->opened) return; -#ifdef _WIN32 - SocketHandlerFactory::beforeOpen(); -#endif - auto toIntFamily = [](const Family& family) -> int { - switch (family) { - case sck::Family::IP_V4: - return AF_INET; - case sck::Family::IP_V6: - return AF_INET6; - default: - throw Error("unknown address family type"); - } - throw Error("unknown address family type"); - }; - - switch (type) { - case Protocol::TCP: - this->hndl = ::socket(toIntFamily(family), SOCK_STREAM, 0); - if (this->hndl == SCK_INVALID_SOCKET) { - this->close(); - throwWithCode("Stream socket could not be created"); - } - break; - case Protocol::UDP: - this->hndl = ::socket(toIntFamily(family), SOCK_DGRAM, 0); - if (this->hndl == SCK_INVALID_SOCKET) { - this->close(); - throwWithCode("DataGram socket could not be created"); - } - break; - default: - throw Error("unknown protocol type"); - } - - this->opened = true; - } - - void Channel::close() { - if (!this->opened) return; -#ifdef _WIN32 - shutdown(this->hndl, 2); - closesocket(this->hndl); -#else - ::shutdown(this->hndl, SHUT_RDWR); - ::close(this->hndl); -#endif - this->opened = false; - this->hndl = SCK_INVALID_SOCKET; -#ifdef _WIN32 - SocketHandlerFactory::afterClose(); -#endif - } -} \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/Channel.h b/CrossSocket/SynchSocket/src/Channel.h deleted file mode 100644 index 4a14d6b9..00000000 --- a/CrossSocket/SynchSocket/src/Channel.h +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_CHANNEL_H_ -#define _CROSS_SOCKET_CHANNEL_H_ - -#include "Commons.h" -#include - -namespace sck { - /** - * An object storing a socket API handler and containing the minimal - * functionalities for interacting with it. - */ - class Channel { - public: - Channel(const Channel&) = delete; - Channel& operator=(const Channel&) = delete; - - /** - * @brief a closed socket is created - */ - Channel(); - - /** - * @brief the passed handler should be already created externally - * by the socket api - */ - explicit Channel(const SocketHandler& hndl); - - ~Channel(); - - /** - * @brief internally creates a new socket - */ - void open(const Protocol& type, const Family& family); - /** - * @brief close and shutdown the current socket - */ - void close(); - - inline bool isOpen() const { return this->opened; }; - - inline const SocketHandler& operator*() const { return this->hndl; }; - - private: - std::atomic_bool opened; - SocketHandler hndl; - -#ifdef _WIN32 - class SocketHandlerFactory { - public: - /** - * @brief If we are about to open the first socket, WSAStartup() is invoked - */ - static void beforeOpen(); - /** - * @brief If we are closing the last socket, WSACleanup() is invoked - */ - static void afterClose(); - - private: - static std::mutex handlerCounterMtx; - static std::size_t handlerCounter; - }; -#endif - }; - -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/Commons.cpp b/CrossSocket/SynchSocket/src/Commons.cpp deleted file mode 100644 index ed77af35..00000000 --- a/CrossSocket/SynchSocket/src/Commons.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifdef _WIN32 -#include -#else -#include -#endif -#include "Commons.h" -#include - -namespace sck { - int getLastErrorCode() { -#ifdef _WIN32 - return WSAGetLastError(); -#else - return static_cast(errno); -#endif - } - - void throwWithCode(const std::string& what){ - throw Error(what, " , error code: ", getLastErrorCode()); - } - - std::unique_ptr convertIpv4(const sck::Ip& address) { - auto tryConversion = [](SocketIp4& recipient, const sck::Ip& address) -> bool { - #if !defined(_WIN32) - in_addr ia; - if (1 == ::inet_pton(AF_INET, address.getHost().c_str(), &ia)) { - recipient.sin_addr.s_addr = ia.s_addr; - return true; - } - #endif - - addrinfo* res, hints = addrinfo{}; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - - int gai_err = ::getaddrinfo(address.getHost().c_str(), NULL, &hints, &res); - - #if !defined(_WIN32) - if (gai_err == EAI_SYSTEM) { - return false; - } - #endif - if (gai_err != 0) { - return false; - } - - auto ipv4 = reinterpret_cast(res->ai_addr); - ::freeaddrinfo(res); - recipient.sin_addr.s_addr = ipv4->sin_addr.s_addr; - return true; - }; - - if (sck::Family::IP_V4 == address.getFamily()) { - std::unique_ptr resolved = std::make_unique(); - // set everything to 0 first - ::memset(&(*resolved), 0, sizeof(SocketIp4)); - resolved->sin_family = AF_INET; - if (!tryConversion(*resolved, address)) { - return nullptr; - } - resolved->sin_port = htons(address.getPort()); - return resolved; - } - return nullptr; - } - - std::unique_ptr convertIpv6(const sck::Ip& address) { - auto tryConversion = [](SocketIp6& recipient, const sck::Ip& address) -> bool { - #if !defined(_WIN32) - in6_addr ia; - if (1 == ::inet_pton(AF_INET6, address.getHost().c_str(), &ia)) { - recipient.sin6_addr = ia; - return true; - } - #endif - - addrinfo* res, hints = addrinfo{}; - hints.ai_family = AF_INET6; - hints.ai_socktype = SOCK_STREAM; - - int gai_err = ::getaddrinfo(address.getHost().c_str(), NULL, &hints, &res); - - #if !defined(_WIN32) - if (gai_err == EAI_SYSTEM) { - return false; - } - #endif - if (gai_err != 0) { - return false; - } - - auto ipv6 = reinterpret_cast(res->ai_addr); - ::freeaddrinfo(res); - recipient.sin6_addr = ipv6->sin6_addr; - return true; - }; - - if (sck::Family::IP_V6 == address.getFamily()) { - std::unique_ptr resolved = std::make_unique(); - // set everything to 0 first - ::memset(&(*resolved), 0, sizeof(SocketIp6)); - resolved->sin6_family = AF_INET6; - resolved->sin6_flowinfo = 0; - if (!tryConversion(*resolved, address)) { - return nullptr; - } - resolved->sin6_port = htons(address.getPort()); - return resolved; - } - return nullptr; - } - - IpPtr convert(const SocketIp& address) { - // refer to https://stackoverflow.com/questions/11684008/how-do-you-cast-sockaddr-structure-to-a-sockaddr-in-c-networking-sockets-ubu - std::string ip; - std::uint16_t port; - if (AF_INET == address.sa_family) { - // ipv4 address - // inet_ntoa is deprecated, but using inet_ntop for ipv4 address, leads to an ip that has no sense - ip = std::string(::inet_ntoa(reinterpret_cast(&address)->sin_addr)); - port = ntohs(reinterpret_cast(&address)->sin_port); - } - else { - // ipv6 address - char temp[INET6_ADDRSTRLEN]; //this is the longest one - // refer to https://www.gnu.org/software/libc/manual/html_node/Host-Address-Functions.html - ::memset(temp, 0, INET6_ADDRSTRLEN); - ::inet_ntop(address.sa_family, &address, temp, INET6_ADDRSTRLEN); - ip = std::string(temp, INET6_ADDRSTRLEN); - port = ntohs(reinterpret_cast(&address)->sin6_port); - } - return sck::Ip::create(ip, port); - } -} \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/Commons.h b/CrossSocket/SynchSocket/src/Commons.h deleted file mode 100644 index 078d1d06..00000000 --- a/CrossSocket/SynchSocket/src/Commons.h +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_COMMONS_H_ -#define _CROSS_SOCKET_COMMONS_H_ - -#ifdef _WIN32 -#include -#include -#include -#include -#define SCK_INVALID_SOCKET INVALID_SOCKET -#define SCK_SOCKET_ERROR SOCKET_ERROR -#else -#include -#include -#include //memset -#include //close -#include -#define SCK_INVALID_SOCKET -1 -#define SCK_SOCKET_ERROR -1 -#endif -#include -#include -#include - -namespace sck { - /** - * socket handle - */ -#ifdef _WIN32 - using SocketHandler = SOCKET; -#else - using SocketHandler = int; -#endif - /** - * @brief representation of a generic socket address - */ -#ifdef _WIN32 - using SocketIp = SOCKADDR; -#else - using SocketIp = sockaddr; -#endif - /** - * @brief representation of an ipv4 socket address - */ -#ifdef _WIN32 - using SocketIp4 = SOCKADDR_IN; -#else - using SocketIp4 = sockaddr_in; -#endif - /** - * @brief representation of an ipv6 socket address - */ -#ifdef _WIN32 - using SocketIp6 = SOCKADDR_IN6; -#else - using SocketIp6 = sockaddr_in6; -#endif - - /** - * @brief returns the last error code raised by the socket API - */ - int getLastErrorCode(); - - void throwWithCode(const std::string& what); - - /** - * @brief checks the address syntax and in case - * it's valid as an ipv4, creates the socket API representation - * of the address - */ - std::unique_ptr convertIpv4(const sck::Ip& address); - /** - * @brief checks the address syntax and in case - * it's valid as an ipv6, creates the socket API representation - * of the address - */ - std::unique_ptr convertIpv6(const sck::Ip& address); - /** - * @brief Convert a SocketAddress_t into an Address, internally - * deducing the family. - */ - IpPtr convert(const SocketIp& address); -} - -#endif \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/Ip.cpp b/CrossSocket/SynchSocket/src/Ip.cpp deleted file mode 100644 index 9b19977b..00000000 --- a/CrossSocket/SynchSocket/src/Ip.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "Commons.h" - -namespace sck { - static const std::string LOCALHOST_IPv4 = "127.0.0.1"; - static const std::string LOCALHOST_IPv6 = "::1"; - - Ip::Ip(const std::string& host, const std::uint16_t& port, const Family& family) - : host(host) - , port(port) - , family(family) { - } - - IpPtr Ip::create(const std::string& host, const std::uint16_t& port) { - //try to resolve the Ip as an ipv4 - IpPtr addr; - addr.reset(new Ip(host, port, sck::Family::IP_V4)); - if (nullptr != convertIpv4(*addr)) { - return addr; - } - //try to resolve the Ip as an ipv6 - addr.reset(new Ip(host, port, sck::Family::IP_V6)); - if (nullptr != convertIpv6(*addr)) { - return addr; - } - return nullptr; - } - - IpPtr Ip::createLocalHost(const std::uint16_t& port, const Family& protocolType) { - switch (protocolType) { - case Family::IP_V4: - return std::unique_ptr(new Ip(LOCALHOST_IPv4, port, sck::Family::IP_V4)); - case Family::IP_V6: - return std::unique_ptr(new Ip(LOCALHOST_IPv6, port, sck::Family::IP_V6)); - default: - return nullptr; - } - return nullptr; - } - - bool operator==(const Ip& lhs, const Ip& rhs) { - return ( (0 == lhs.getHost().compare(rhs.getHost())) && - (lhs.getPort() == rhs.getPort()) && - (lhs.getFamily() == rhs.getFamily()) ); - }; - -} diff --git a/CrossSocket/SynchSocket/src/core/BindCapable.cpp b/CrossSocket/SynchSocket/src/core/BindCapable.cpp deleted file mode 100644 index bb177025..00000000 --- a/CrossSocket/SynchSocket/src/core/BindCapable.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "../Channel.h" -#include - -#ifdef _WIN32 -#define REBIND_OPTION SO_REUSEADDR -#else -#define REBIND_OPTION SO_REUSEPORT -#endif - -namespace sck { - void BindCapable::bindToPort(const std::uint16_t& port) { - int reusePortOptVal = 1; - ::setsockopt(**this->channel, SOL_SOCKET, REBIND_OPTION, reinterpret_cast(&reusePortOptVal), sizeof(int)); - - // bind the socket to the port - auto fam = this->getFamily(); - if (sck::Family::IP_V4 == fam) { - SocketIp4 addr; - ::memset(&addr, 0, sizeof(SocketIp4)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); -#ifdef _WIN32 - addr.sin_addr.s_addr = ADDR_ANY; -#else - addr.sin_addr.s_addr = htonl(INADDR_ANY); -#endif - if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { - throwWithCode("can't bind localhost on port: " + std::to_string(port)); - } - } - else if (sck::Family::IP_V6 == fam) { - SocketIp6 addr; - ::memset(&addr, 0, sizeof(SocketIp6)); - addr.sin6_family = AF_INET6; - addr.sin6_flowinfo = 0; - addr.sin6_addr = IN6ADDR_ANY_INIT; // apparently, there is no such a cross-system define for ipv4 addresses - addr.sin6_port = htons(port); - if (::bind(**this->channel, reinterpret_cast(&addr), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { - throwWithCode("can't bind localhost on port: " + std::to_string(port)); - } - } - else { - throw Error("found an unrecognized family type when binding the socket"); - } - }; -} diff --git a/CrossSocket/SynchSocket/src/core/Connection.cpp b/CrossSocket/SynchSocket/src/core/Connection.cpp deleted file mode 100644 index 0354c740..00000000 --- a/CrossSocket/SynchSocket/src/core/Connection.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "../Channel.h" -#include - -namespace sck { - Connection::Connection(const sck::Ip& remoteAddress) { - this->remoteAddress = std::make_unique(remoteAddress); - this->channel = std::make_unique(); - } - - ConnectionOpenable::ConnectionOpenable(const sck::Ip& remoteAddress) - : Connection(remoteAddress) { - } - - void ConnectionOpenable::openSteps() { - if (sck::Family::IP_V4 == this->getFamily()) { - //v4 family - auto addr = convertIpv4(*this->remoteAddress); - if (!addr) { - throw Error(this->remoteAddress->getHost(), ":", std::to_string(this->remoteAddress->getPort()), " is an invalid server address"); - } - if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp4)) == SCK_SOCKET_ERROR) { - throwWithCode("Connection can't be established"); - } - } - else { - //v6 family - auto addr = convertIpv6(*this->remoteAddress); - if (!addr) { - throw Error(this->remoteAddress->getHost(), ":", std::to_string(this->remoteAddress->getPort()), " is an invalid server address"); - } - if (::connect(**this->channel, reinterpret_cast(&(*addr)), sizeof(SocketIp6)) == SCK_SOCKET_ERROR) { - throwWithCode("Connection can't be established"); - } - } - } -} \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/core/Messanger.cpp b/CrossSocket/SynchSocket/src/core/Messanger.cpp deleted file mode 100644 index eb1682c5..00000000 --- a/CrossSocket/SynchSocket/src/core/Messanger.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "../Channel.h" - -namespace sck { - std::size_t Messanger::receive(const std::pair& message, const std::chrono::milliseconds& timeout) { - std::lock_guard recvLock(this->receiveMtx); - if (timeout.count() != this->actualTimeOut.count()) { - //set new timeout - this->actualTimeOut = timeout; -#ifdef _WIN32 - auto tv = DWORD(this->actualTimeOut.count()); - if (setsockopt(**this->channel, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { -#else - struct timeval tv = { 0,0 }; - if (this->actualTimeOut.count() >= 1000) { - tv.tv_sec = std::chrono::duration_cast(this->actualTimeOut).count(); - } - else { - tv.tv_usec = std::chrono::duration_cast(this->actualTimeOut).count(); - } - if (::setsockopt(**this->channel, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(struct timeval)) < 0) { -#endif - throwWithCode("can't set timeout"); - } - } - - int recvBytes = ::recv(**this->channel, message.first, static_cast(message.second), 0); - if (recvBytes == SCK_SOCKET_ERROR) { - recvBytes = 0; - throwWithCode("receive failed"); - } - if (recvBytes > message.second) { - // if here, the message received is probably corrupted - recvBytes = 0; - } - return static_cast(recvBytes); - } - - bool Messanger::send(const std::pair& message) { - std::lock_guard sendLock(this->sendMtx); - int sentBytes = ::send(**this->channel, message.first, static_cast(message.second), 0); - if (sentBytes == SCK_SOCKET_ERROR) { - sentBytes = 0; - throwWithCode("send failed"); - } - return (sentBytes == static_cast(message.second)); - } -} diff --git a/CrossSocket/SynchSocket/src/core/SocketClosable.cpp b/CrossSocket/SynchSocket/src/core/SocketClosable.cpp deleted file mode 100644 index 57c20c3a..00000000 --- a/CrossSocket/SynchSocket/src/core/SocketClosable.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "../Channel.h" - -namespace sck { - bool SocketClosable::isOpen() const { - return this->channel->isOpen(); - }; - - SocketClosable::~SocketClosable() { - if (this->isOpen()) { - this->close(); - } - } - - void SocketClosable::close() { - if (!this->isOpen()) { - return; - } - try { - this->closeSteps(); - } - catch (...) { - } - } - - void SocketClosable::closeSteps() { - this->channel->close(); - } -} diff --git a/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp b/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp deleted file mode 100644 index f395d6c1..00000000 --- a/CrossSocket/SynchSocket/src/core/SocketDecorator.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include - -namespace sck { - SocketDecorator::SocketDecorator(std::unique_ptr channel) - : wrapped(std::move(channel)) { - if (nullptr == this->wrapped) { - throw Error("The decorator can't wrap a nullptr"); - } - } - - void SocketDecorator::open(const std::chrono::milliseconds& timeout) { - Openable* openable = dynamic_cast(this->wrapped.get()); - if (nullptr == openable) { - throw Error("The wrapped object is not openable"); - } - openable->open(timeout); - } -} diff --git a/CrossSocket/SynchSocket/src/core/SocketOpenable.cpp b/CrossSocket/SynchSocket/src/core/SocketOpenable.cpp deleted file mode 100644 index 67a42413..00000000 --- a/CrossSocket/SynchSocket/src/core/SocketOpenable.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "../Channel.h" -#include -#include -#include - -namespace sck { - void SocketOpenable::open(const std::chrono::milliseconds& timeout) { - if (this->isOpen()) { - return; - } - - std::atomic_bool stopWait(false); - auto openSteps = [this, &stopWait]() { - try { - this->channel->open(this->getProtocol(), this->getFamily()); - this->openSteps(); - } - catch (...) { - this->close(); - stopWait = true; - return; - } - stopWait = true; - }; - - if (0 == timeout.count()) { - openSteps(); - } - else { - std::condition_variable notification; - std::mutex notificationMtx; - std::thread opener(openSteps); - - std::unique_lock notificationLck(notificationMtx); - notification.wait_for(notificationLck, timeout, [&stopWait]() { return static_cast(stopWait); }); - if (!this->isOpen()) { - this->close(); - } - } - } -} diff --git a/CrossSocket/SynchSocket/src/core/components/Buffer.cpp b/CrossSocket/SynchSocket/src/core/components/Buffer.cpp deleted file mode 100644 index 0551bdea..00000000 --- a/CrossSocket/SynchSocket/src/core/components/Buffer.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include - -namespace sck { - Buffer::Buffer(const std::size_t capacity) { - this->resetBufferCapacity(capacity); - } - - void Buffer::resetBufferSize(const std::size_t newSize) { - if (0 == newSize) { - this->buffer.resize(this->bufferCapacity); - return; - } - else if(newSize > this->bufferCapacity) { - throw Error("New buffer size can't exceed buffer capacity"); - } - this->buffer.resize(newSize); - } - - void Buffer::resetBufferCapacity(const std::size_t newCapacity) { - if (0 == newCapacity) { - throw Error("null capacity is invalid"); - } - this->bufferCapacity = newCapacity; - } -} diff --git a/CrossSocket/SynchSocket/src/core/components/ChannelAware.cpp b/CrossSocket/SynchSocket/src/core/components/ChannelAware.cpp deleted file mode 100644 index 07474439..00000000 --- a/CrossSocket/SynchSocket/src/core/components/ChannelAware.cpp +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "../../Channel.h" - -namespace sck { - ChannelAware::~ChannelAware() { - this->channel.reset(); - } -} diff --git a/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp b/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp deleted file mode 100644 index ec7afe3e..00000000 --- a/CrossSocket/SynchSocket/src/tcp/TcpClient.cpp +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "../Channel.h" - -namespace sck::tcp { - TcpClient::TcpClient(const sck::Ip& remoteAddress) - : ConnectionOpenable(remoteAddress) { - } -} diff --git a/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp b/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp deleted file mode 100644 index b221b4cf..00000000 --- a/CrossSocket/SynchSocket/src/tcp/TcpServer.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include "../Channel.h" -#include -#include - -namespace sck::tcp { -namespace { -static constexpr std::size_t LISTEN_BACKLOG = 50; -} - -TcpClientHandler::TcpClientHandler(std::unique_ptr channel, - const sck::Ip &remoteAddress) - : Connection(remoteAddress) { - this->channel = std::move(channel); -} - -TcpServer::TcpServer(const std::uint16_t &port, const Family &family) - : port(port), family(family) { - this->channel = std::make_unique(); -} - -std::unique_ptr TcpServer::acceptClient() { - if (!this->isOpen()) { - throw Error("a tcp server should be opened before accepting a new client"); - } - SocketIp acceptedClientAddress; -#ifdef _WIN32 - int acceptedAddressLength -#else - unsigned int acceptedAddressLength -#endif - = sizeof(SocketIp); - // accept: wait for a client to call connect and hit this server and get a - // pointer to this client. - SocketHandler temp = - ::accept(**this->channel, &acceptedClientAddress, &acceptedAddressLength); - if (temp == SCK_INVALID_SOCKET) { - throwWithCode("Error: accepting new client"); - } - std::unique_ptr acceptedClientHandler = - std::make_unique(temp); - - IpPtr remoteAddress = convert(acceptedClientAddress); - if (nullptr == remoteAddress) { - throw Error("accepted client remote address is not resolvable"); - } - - return std::unique_ptr( - new TcpClientHandler(std::move(acceptedClientHandler), *remoteAddress)); -} - -void TcpServer::openSteps() { - this->bindToPort(this->port); - // listen to the port to allow all the following clients acceptance - if (::listen(**this->channel, LISTEN_BACKLOG) == SCK_SOCKET_ERROR) { - throwWithCode("Error: listening on port: " + std::to_string(this->port)); - } -} -} // namespace sck::tcp \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp b/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp deleted file mode 100644 index bd03ceda..00000000 --- a/CrossSocket/SynchSocket/src/udp/UdpConnection.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "../Channel.h" - -namespace sck::udp { - - UdpConnection::UdpConnection(const sck::Ip& remoteAddress, const std::uint16_t& localPort) - : ConnectionOpenable(remoteAddress) - , port(localPort) { - } - - void UdpConnection::openSteps() { - this->bindToPort(this->port); - this->ConnectionOpenable::openSteps(); - } -} \ No newline at end of file diff --git a/CrossSocket/SynchSocket/src/udp/UdpServer.cpp b/CrossSocket/SynchSocket/src/udp/UdpServer.cpp deleted file mode 100644 index 48dbd197..00000000 --- a/CrossSocket/SynchSocket/src/udp/UdpServer.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include "../Channel.h" -#include - -namespace sck::udp { - - sck::Ip getInitialAddress(const sck::Family& family) { - if (sck::Family::IP_V4 == family) { - return *sck::Ip::createLocalHost(0, sck::Family::IP_V4); - } - if (sck::Family::IP_V6 == family) { - return *sck::Ip::createLocalHost(0, sck::Family::IP_V6); - } - throw Error("unrecognized family"); - } - - UdpServer::UdpServer(const std::uint16_t& localPort, const sck::Family& protocol) - : UdpConnection(getInitialAddress(protocol), localPort) { - } - - void UdpServer::openSteps() { - this->bindToPort(this->port); - - // receive a message from the client, that from now on will become the recognized one. - char bf[MAX_UDP_RECV_MESSAGE]; - SocketIp remoteAddr; -#ifdef _WIN32 - int -#else - unsigned int -#endif - remoteAddrLen = sizeof(SocketIp); - if (::recvfrom(**this->channel, &bf[0], static_cast(MAX_UDP_RECV_MESSAGE), 0, &remoteAddr, &remoteAddrLen) == SCK_SOCKET_ERROR) { - throwWithCode("recvfrom failed while identifying the target"); - } - IpPtr remoteConverted = convert(remoteAddr); - if(nullptr == remoteConverted) { - throw Error(remoteAddr.sa_data, " is an invalid data for udp serer remote address"); - } - this->remoteAddress = std::move(remoteConverted); - this->ConnectionOpenable::openSteps(); - } -} \ No newline at end of file diff --git a/CrossSocket/TypedSocket/CMakeLists.txt b/CrossSocket/TypedSocket/CMakeLists.txt deleted file mode 100644 index f40d314b..00000000 --- a/CrossSocket/TypedSocket/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -set(PROJECT_SHORTNAME "Typed-Cross-Socket") - -MakeLibrary(${PROJECT_SHORTNAME} include) - -if(COMPILE_ASYNCH) - target_link_libraries(${PROJECT_SHORTNAME} - PUBLIC - Asynch-Cross-Socket - ) -else() - target_link_libraries(${PROJECT_SHORTNAME} - PUBLIC - Synch-Cross-Socket - ) -endif() - -target_compile_definitions(${PROJECT_SHORTNAME} -PUBLIC - TYPED_ENABLED -) diff --git a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h b/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h deleted file mode 100644 index 201c2ba3..00000000 --- a/CrossSocket/TypedSocket/include/core/TypedAsyncMessanger.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifdef ASYNCH_ENABLED -#ifndef _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ -#define _CROSS_SOCKET_TYPEDASYNCMESSANGER_H_ - -#include -#include -#include -#include - -namespace sck::typed { - template - class TypedAsynchMessanger - : public StateAware - , public Openable - , public Closable - , protected async::MessangerListener - , public TypedSender - , public async::Talker> - , protected Decoder_ { - static_assert(std::is_base_of, Decoder_>::value, "Not valid Decoder_ type"); - public: - TypedAsynchMessanger(std::unique_ptr messanger, const std::size_t& bufferCapacity) - : asyncMessanger(std::move(messanger), bufferCapacity) { - this->sender = &this->asyncMessanger; - this->asyncMessanger.async::MessageTalker::resetListener(this); - }; - - inline bool isOpen() const override { return this->asyncMessanger.isOpen(); }; - - inline void close() override { this->asyncMessanger.close(); }; - - inline void open(const std::chrono::milliseconds& timeout) override { this->asyncMessanger.open(timeout); }; - - inline void resetErrorListener(async::ErrorListener* listener) { this->asyncMessanger.async::ErrorTalker::resetListener(listener); }; - - protected: - void handle(const std::pair& message) final { - RecvT typed; - if (this->Decoder_::decode(std::string(message.first, message.second), typed)) { - this->notify(typed); - } - } - - private: - async::AsyncMessanger asyncMessanger; - }; -} - -#endif -#endif diff --git a/CrossSocket/TypedSocket/include/core/TypedMessanger.h b/CrossSocket/TypedSocket/include/core/TypedMessanger.h deleted file mode 100644 index 2559c718..00000000 --- a/CrossSocket/TypedSocket/include/core/TypedMessanger.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TYPEDMESSANGER_H_ -#define _CROSS_SOCKET_TYPEDMESSANGER_H_ - -#include -#include -#include -#include - -namespace sck::typed { - std::pair extractComponents(SocketClosable* connection); - - template - class TypedMessanger - : public SocketDecorator - , public TypedSender - , public TypedReceiver { - public: - TypedMessanger(std::unique_ptr channel, const std::size_t bufferCapacity) - : SocketDecorator(std::move(channel)) - , TypedReceiver(bufferCapacity) { - auto components = extractComponents(this->wrapped.get()); - this->sender = components.first; - this->receiver = components.second; - }; - }; -} - -#endif diff --git a/CrossSocket/TypedSocket/include/core/TypedMessangerListener.h b/CrossSocket/TypedSocket/include/core/TypedMessangerListener.h deleted file mode 100644 index ff3d30b5..00000000 --- a/CrossSocket/TypedSocket/include/core/TypedMessangerListener.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TYPEDMESSANGERLISTENER_H_ -#define _CROSS_SOCKET_TYPEDMESSANGERLISTENER_H_ - -namespace sck::typed { - template - class TypedMessangerListener { - public: - virtual void handle(const T& message) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/TypedReceiver.h b/CrossSocket/TypedSocket/include/core/TypedReceiver.h deleted file mode 100644 index 76c9b094..00000000 --- a/CrossSocket/TypedSocket/include/core/TypedReceiver.h +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TYPEDRECEIVER_H_ -#define _CROSS_SOCKET_TYPEDRECEIVER_H_ - -#include -#include -#include -#include -#include -#include - -namespace sck::typed { - template - class TypedReceiver - : public TypedReceiveCapable - , private Buffer - , public Decoder_ { - static_assert(std::is_base_of, Decoder_>::value, "Not valid Decoder_ type"); - public: - bool receive(T& recipient, const std::chrono::milliseconds& timeout) final { - std::lock_guard lk(this->bufferMtx); - this->Buffer::resetBufferSize(); - this->Buffer::resetBufferSize(this->receiver->receive(this->getBuffer(), timeout)); - return this->decode(this->getStringBuffer(), recipient); - } - - void setBufferCapacity(const std::size_t bufferCapacity) { - std::lock_guard lk(this->bufferMtx); - this->Buffer::resetBufferCapacity(bufferCapacity); - }; - - protected: - TypedReceiver(const std::size_t bufferCapacity) - : Buffer(bufferCapacity) { - }; - - protected: - sck::ReceiveCapable* receiver; - - private: - std::mutex bufferMtx; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/TypedSender.h b/CrossSocket/TypedSocket/include/core/TypedSender.h deleted file mode 100644 index d6dc77bb..00000000 --- a/CrossSocket/TypedSocket/include/core/TypedSender.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TYPEDSENDER_H_ -#define _CROSS_SOCKET_TYPEDSENDER_H_ - -#include -#include -#include -#include - -namespace sck::typed { - template - class TypedSender - : public TypedSendCapable - , public Encoder_ { - static_assert(std::is_base_of, Encoder_>::value, "Not valid Encoder_ type"); - public: - bool send(const T& message) final { - std::string sendBuffer; - if (!this->encode(sendBuffer, message)) { - return false; - } - return this->sender->send(std::make_pair(sendBuffer.data(), sendBuffer.size())); - }; - - protected: - sck::SendCapable* sender; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/components/Decoder.h b/CrossSocket/TypedSocket/include/core/components/Decoder.h deleted file mode 100644 index f452d6d3..00000000 --- a/CrossSocket/TypedSocket/include/core/components/Decoder.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_DECODER_H_ -#define _CROSS_SOCKET_DECODER_H_ - -#include - -namespace sck::typed { - template - class Decoder { - protected: - virtual bool decode(const std::string& buffer, T& message) const = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/components/Encoder.h b/CrossSocket/TypedSocket/include/core/components/Encoder.h deleted file mode 100644 index 80572073..00000000 --- a/CrossSocket/TypedSocket/include/core/components/Encoder.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_ENCODER_H_ -#define _CROSS_SOCKET_ENCODER_H_ - -#include - -namespace sck::typed { - template - class Encoder { - protected: - virtual bool encode(std::string& buffer, const T& message) const = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h b/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h deleted file mode 100644 index 8edc9d43..00000000 --- a/CrossSocket/TypedSocket/include/core/components/TypedReceiveCapable.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TYPEDRECEIVECAPABLE_H_ -#define _CROSS_SOCKET_TYPEDRECEIVECAPABLE_H_ - -#include - -namespace sck::typed { - template - class TypedReceiveCapable { - public: - virtual bool receive(T& recipient, const std::chrono::milliseconds& timeout) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h b/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h deleted file mode 100644 index afeae560..00000000 --- a/CrossSocket/TypedSocket/include/core/components/TypedSendCapable.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef _CROSS_SOCKET_TYPEDSENDCAPABLE_H_ -#define _CROSS_SOCKET_TYPEDSENDCAPABLE_H_ - -namespace sck::typed { - template - class TypedSendCapable { - public: - virtual bool send(const T& message) = 0; - }; -} - -#endif \ No newline at end of file diff --git a/CrossSocket/TypedSocket/src/core/TypedMessanger.cpp b/CrossSocket/TypedSocket/src/core/TypedMessanger.cpp deleted file mode 100644 index 6c3677ed..00000000 --- a/CrossSocket/TypedSocket/src/core/TypedMessanger.cpp +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -namespace sck::typed { - std::pair extractComponents(SocketClosable* connection) { - return std::make_pair(dynamic_cast(connection), - dynamic_cast(connection)); - } -} diff --git a/samples/utils/Asker.h b/samples/utils/Asker.h index 2aba71d8..61a2b6fe 100644 --- a/samples/utils/Asker.h +++ b/samples/utils/Asker.h @@ -5,30 +5,26 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef SAMPLE_ASKER_H -#define SAMPLE_ASKER_H +#pragma once -#include -#include -#include +#include socket); +#include - void ask(); +namespace MinimalSocket::test { +class Asker : public Logger { +public: + Asker(std::unique_ptr socket); - void ask(const std::size_t times); + void ask(); - void askForever(const std::chrono::milliseconds& sampleTime); + void ask(const std::size_t times); - private: - std::unique_ptr socket; - NamesMap cursor; - char recvBuffer[1000]; - }; -} + void askForever(const std::chrono::milliseconds &sampleTime); -#endif \ No newline at end of file +private: + std::unique_ptr socket; + NamesMap cursor; + char recvBuffer[1000]; +}; +} // namespace MinimalSocket::test diff --git a/samples/utils/Names.cpp b/samples/utils/Names.cpp index 6f16f212..af07f38a 100644 --- a/samples/utils/Names.cpp +++ b/samples/utils/Names.cpp @@ -6,77 +6,18 @@ **/ #include -#include -namespace sck::sample { - NamesMap::NamesMap() { - this->cursor = namesSurnames.begin(); - } - - const std::map NamesMap::namesSurnames = { - {"Luciano", "Pavarotti"}, - {"Gengis", "Khan"}, - {"Giulio", "Cesare"}, - {"Theodor", "Roosvelt"}, - {"Immanuel", "Kant"} - }; - - const std::string NamesMap::unknown = "unknown"; - - NamesMap& NamesMap::operator++() { - ++this->cursor; - if (this->cursor == namesSurnames.end()) { - this->cursor = namesSurnames.begin(); - } - return *this; - } - - const std::string& NamesMap::getSurname(const std::string& name) { - auto it = namesSurnames.find(name); - if (it == namesSurnames.end()) return unknown; - return it->second; - } - -#ifdef TYPED_ENABLED - bool NamesDecoder::decode(const std::string& buffer, NamesCollection& message) const { - message.clear(); - if (buffer.empty()) { - return true; - } - std::list posSeparator; - for (std::size_t k = 0; k < buffer.size(); ++k) { - if (';' == buffer[k]) { - posSeparator.push_back(k); - } - } - if (posSeparator.empty()) { - message = { buffer }; - return true; - } - std::size_t posPrev = 0; - for (auto it = posSeparator.begin(); it != posSeparator.end(); ++it) { - message.push_back(std::string(buffer, posPrev, *it - posPrev)); - posPrev = *it + 1; - } - if (posPrev < buffer.size()) { - message.push_back(std::string(buffer, posPrev)); - } - return true; - } - - bool NamesEncoder::encode(std::string& buffer, const NamesCollection& message) const { - std::stringstream stream; - if (!message.empty()) { - auto it = message.begin(); - stream << *it; - ++it; - for (it; it != message.end(); ++it) { - stream << ';'; - stream << *it; - } - } - buffer = stream.str(); - return true; - } -#endif +namespace MinimalSocket::test { +Names NAMES_SURNAMES = {{"Luciano", "Pavarotti"}, + {"Gengis", "Khan"}, + {"Giulio", "Cesare"}, + {"Theodor", "Roosvelt"}, + {"Immanuel", "Kant"}}; + +void NamesCircularIterator::next() { + ++current_; + if (current_ == NAMES_SURNAMES.end()) { + current_ = NAMES_SURNAMES.begin(); + } } +} // namespace MinimalSocket::test diff --git a/samples/utils/Names.h b/samples/utils/Names.h index 7b282ef5..8cac8a59 100644 --- a/samples/utils/Names.h +++ b/samples/utils/Names.h @@ -5,51 +5,26 @@ * report any bug to andrecasa91@gmail.com. **/ -#ifndef SAMPLE_NAMES_H -#define SAMPLE_NAMES_H +#pragma once -#include #include -#ifdef TYPED_ENABLED -#include -#include -#include -#endif +#include -namespace sck::sample { - class NamesMap { - public: - NamesMap(); +namespace MinimalSocket::test { +using Names = std::unordered_multimap; +static const Names NAMES_SURNAMES; - inline const std::string& getCursorName() const { return this->cursor->first; }; - inline const std::string& getCursorSurname() const { return this->cursor->second; }; +using NamesIterator = Names::const_iterator; - static const std::string& getSurname(const std::string& name); +class NamesCircularIterator { +public: + NamesCircularIterator() : current_(NAMES_SURNAMES.begin()){}; - NamesMap& operator++(); + const NamesIterator ¤t() { return current_; }; - inline static std::size_t size() { return namesSurnames.size(); }; + void next(); - private: - static const std::map namesSurnames; - static const std::string unknown; - - std::map::const_iterator cursor; - }; - -#ifdef TYPED_ENABLED - typedef std::list NamesCollection; - - class NamesDecoder : public typed::Decoder { - protected: - bool decode(const std::string& buffer, NamesCollection& message) const final; - }; - - class NamesEncoder : public typed::Encoder { - protected: - bool encode(std::string& buffer, const NamesCollection& message) const final; - }; -#endif -} - -#endif +private: + NamesIterator current_; +}; +} // namespace MinimalSocket::test From 1293d49e215f2ea3a70624d889447c714b183d0c Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Sat, 14 May 2022 16:20:45 +0100 Subject: [PATCH 163/228] testing --- Samples/CMakeLists.txt.bak | 9 ++ Samples/Dummy.cpp | 177 +++++++++++++++++++++++++++++ Samples/cmake/MakeSample.cmake.bak | 12 ++ samples/CMakeLists.txt | 20 ++-- samples/cmake/MakeSample.cmake | 2 +- src/src/SocketAddress.cpp | 30 ++--- src/src/SocketId.cpp | 11 +- src/src/SocketId.h | 4 +- tests/TestAddress.cpp | 5 +- 9 files changed, 239 insertions(+), 31 deletions(-) create mode 100644 Samples/CMakeLists.txt.bak create mode 100644 Samples/Dummy.cpp create mode 100644 Samples/cmake/MakeSample.cmake.bak diff --git a/Samples/CMakeLists.txt.bak b/Samples/CMakeLists.txt.bak new file mode 100644 index 00000000..995eeb04 --- /dev/null +++ b/Samples/CMakeLists.txt.bak @@ -0,0 +1,9 @@ +project(MinimalCppSocket-Samples) + +include(cmake/MakeSample.cmake) +MakeSample(Dummy) + +# add_subdirectory(utils) + +# add_subdirectory(tcp) +# add_subdirectory(udp) diff --git a/Samples/Dummy.cpp b/Samples/Dummy.cpp new file mode 100644 index 00000000..93337f7f --- /dev/null +++ b/Samples/Dummy.cpp @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +std::optional toSocketAddressIpv4_NEW(const std::string& host, const std::uint16_t& port) { + std::optional result; + result.emplace(); + // set everything to 0 first + ::memset(&result.value(), 0, sizeof(SOCKADDR_IN)); + result->sin_family = AF_INET; + result->sin_port = htons(port); + + { + addrinfo* res, hints = addrinfo{}; + hints.ai_family = AF_INET; + hints.ai_socktype = 0; + hints.ai_protocol = 0; + + int gai_err = ::getaddrinfo(host.c_str(), NULL, &hints, &res); + if (gai_err != 0) { + return std::nullopt; + } + + auto ipv4 = reinterpret_cast(res->ai_addr); + ::freeaddrinfo(res); + result->sin_addr.s_addr = ipv4->sin_addr.s_addr; + } + + return result; +} + +std::optional toSocketAddressIpv4_OLD(const std::string& host, const std::uint16_t& port) { + auto tryConversion = [](SOCKADDR_IN& recipient, const std::string& host, const std::uint16_t& port) -> bool { + addrinfo* res, hints = addrinfo{}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + int gai_err = ::getaddrinfo(host.c_str(), NULL, &hints, &res); + if (gai_err != 0) { + return false; + } + + auto ipv4 = reinterpret_cast(res->ai_addr); + ::freeaddrinfo(res); + recipient.sin_addr.s_addr = ipv4->sin_addr.s_addr; + return true; + }; + + std::optional resolved; + resolved.emplace(); + // set everything to 0 first + ::memset(&(*resolved), 0, sizeof(SOCKADDR_IN)); + resolved->sin_family = AF_INET; + if (!tryConversion(*resolved, host, port)) { + return std::nullopt; + } + resolved->sin_port = htons(port); + return resolved; + } + +void send(const SOCKET& sock, const std::string& message) { + int sentBytes = ::send(sock, message.data(), + static_cast(message.size()), 0); + if (sentBytes == SOCKET_ERROR) { + throw std::runtime_error{ "" }; + } +} + +std::string receive(const SOCKET& sock, const std::size_t max_size) { + std::string message; + message.resize(max_size); + int recvBytes = ::recv(sock, message.data(), + static_cast(message.size()), 0); + if (recvBytes == SOCKET_ERROR) { + throw std::runtime_error{ "" }; + } + if (recvBytes > message.size()) { + throw std::runtime_error{ "" }; + } + message.resize(recvBytes); + return message; +} + +int main() { + WSADATA wsa; + const std::array version = { 2, 0 }; + const BYTE version_major = static_cast(version[0]); + const BYTE version_minor = static_cast(version[1]); + auto result = WSAStartup(MAKEWORD(version_major, version_minor), &wsa); + if (0 != result) { + throw std::runtime_error{ "" }; + } + + const std::uint16_t port = 35356; + + static constexpr std::size_t MAX_POSSIBLE_ADDRESS_SIZE = + std::max(sizeof(SOCKADDR_IN), sizeof(SOCKADDR_IN6)); + +#pragma omp parallel num_threads(2) + { + if (0 == omp_get_thread_num()) { + // server + auto acceptor_socket = ::socket(static_cast(AF_INET), SOCK_STREAM, 0); + if (acceptor_socket == INVALID_SOCKET) { + throw std::runtime_error{ "" }; + } + // bind port + { + int reusePortOptVal = 1; + ::setsockopt(acceptor_socket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reusePortOptVal), sizeof(int)); + SOCKADDR_IN addr; + ::memset(&addr, 0, sizeof(SOCKADDR_IN)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = ADDR_ANY; + if (::bind(acceptor_socket, reinterpret_cast(&addr), + sizeof(SOCKADDR_IN)) == SOCKET_ERROR) { + throw std::runtime_error{ "" }; + } + } + // listen + if (::listen(acceptor_socket, static_cast(50)) == SOCKET_ERROR) { + throw std::runtime_error{ "" }; + } +#pragma omp barrier + // accept + SOCKET accepted_socket = INVALID_SOCKET; + { + char acceptedClientAddress[MAX_POSSIBLE_ADDRESS_SIZE]; + int acceptedClientAddress_length = MAX_POSSIBLE_ADDRESS_SIZE; + + accepted_socket = + ::accept(acceptor_socket, + reinterpret_cast(&acceptedClientAddress[0]), + &acceptedClientAddress_length); + if (accepted_socket == INVALID_SOCKET) { + throw std::runtime_error{ "" }; + } + } + // receive + auto got = receive(accepted_socket, 100); + // send back + got = got + got; + send(accepted_socket, got); + } + else { + // client + auto client_socket = ::socket(static_cast(AF_INET), SOCK_STREAM, 0); + if (client_socket == INVALID_SOCKET) { + throw std::runtime_error{ "" }; + } + auto addr = toSocketAddressIpv4_NEW("127.0.0.1", port); + if (!addr) { + throw std::runtime_error{ "" }; + } +#pragma omp barrier + if (::connect(client_socket, reinterpret_cast(&(*addr)), + sizeof(SOCKADDR_IN)) == SOCKET_ERROR) { + throw std::runtime_error{ "" }; + } + // send back + send(client_socket, "hello_"); + // receive + auto got = receive(client_socket, 100); + std::cout << got << std::endl; + } + } + + return EXIT_SUCCESS; +} diff --git a/Samples/cmake/MakeSample.cmake.bak b/Samples/cmake/MakeSample.cmake.bak new file mode 100644 index 00000000..d10422f3 --- /dev/null +++ b/Samples/cmake/MakeSample.cmake.bak @@ -0,0 +1,12 @@ +function(MakeSample NAME) + add_executable(${NAME} ${NAME}.cpp) + + target_link_libraries(${NAME} PUBLIC + Utils + MinimalSocket + ) + + set_target_properties(${NAME} PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=%PATH%;${CMAKE_INSTALL_PREFIX}/bin/") + + install(TARGETS ${NAME}) +endfunction() diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 8e5c518c..15d93d33 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -1,8 +1,12 @@ -project(MinimalCppSocket-Samples) - -include(cmake/MakeSample.cmake) - -add_subdirectory(utils) - -# add_subdirectory(tcp) -# add_subdirectory(udp) +project(MinimalCppSocket-Samples) + +include(cmake/MakeSample.cmake) +MakeSample(Dummy) +find_package(OpenMP REQUIRED) +target_link_libraries(Dummy PUBLIC OpenMP::OpenMP_CXX) + + +# add_subdirectory(utils) + +# add_subdirectory(tcp) +# add_subdirectory(udp) diff --git a/samples/cmake/MakeSample.cmake b/samples/cmake/MakeSample.cmake index d10422f3..e9248253 100644 --- a/samples/cmake/MakeSample.cmake +++ b/samples/cmake/MakeSample.cmake @@ -2,7 +2,7 @@ function(MakeSample NAME) add_executable(${NAME} ${NAME}.cpp) target_link_libraries(${NAME} PUBLIC - Utils + # Utils MinimalSocket ) diff --git a/src/src/SocketAddress.cpp b/src/src/SocketAddress.cpp index ca844c9e..72d3bd55 100644 --- a/src/src/SocketAddress.cpp +++ b/src/src/SocketAddress.cpp @@ -23,17 +23,17 @@ std::optional toSocketAddressIpv4(const std::string &host, #endif std::optional result; - auto &result_ref = result.emplace(); + result.emplace(); // set everything to 0 first - ::memset(&result_ref, 0, sizeof(SocketAddressIpv4)); - result_ref.sin_family = AF_INET; - result_ref.sin_port = htons(port); + ::memset(&result.value(), 0, sizeof(SocketAddressIpv4)); + result->sin_family = AF_INET; + result->sin_port = htons(port); // try address conversion #if !defined(_WIN32) in_addr ia; if (1 == ::inet_pton(AF_INET, host.c_str(), &ia)) { - result_ref.sin_addr.s_addr = ia.s_addr; + result->sin_addr.s_addr = ia.s_addr; return result; } #endif @@ -54,8 +54,8 @@ std::optional toSocketAddressIpv4(const std::string &host, return std::nullopt; } - const auto &ipv4 = reinterpret_cast(res->ai_addr); - result_ref.sin_addr.s_addr = ipv4.sin_addr.s_addr; + const auto* ipv4 = reinterpret_cast(res->ai_addr); + result->sin_addr.s_addr = ipv4->sin_addr.s_addr; ::freeaddrinfo(res); return result; } @@ -67,18 +67,18 @@ std::optional toSocketAddressIpv6(const std::string &host, #endif std::optional result; - auto &result_ref = result.emplace(); + result.emplace(); // set everything to 0 first - ::memset(&result_ref, 0, sizeof(SocketAddressIpv6)); - result_ref.sin6_family = AF_INET6; - result_ref.sin6_flowinfo = 0; - result_ref.sin6_port = htons(port); + ::memset(&result.value(), 0, sizeof(SocketAddressIpv6)); + result->sin6_family = AF_INET6; + result->sin6_flowinfo = 0; + result->sin6_port = htons(port); // try address conversion #if !defined(_WIN32) in6_addr ia; if (1 == ::inet_pton(AF_INET6, host.c_str(), &ia)) { - result_ref.sin6_addr = ia; + result->sin6_addr = ia; return result; } #endif @@ -99,8 +99,8 @@ std::optional toSocketAddressIpv6(const std::string &host, return std::nullopt; } - const auto &ipv6 = reinterpret_cast(res->ai_addr); - result_ref.sin6_addr = ipv6.sin6_addr; + const auto* ipv6 = reinterpret_cast(res->ai_addr); + result->sin6_addr = ipv6->sin6_addr; ::freeaddrinfo(res); return result; } diff --git a/src/src/SocketId.cpp b/src/src/SocketId.cpp index 0b52d651..9c6c7641 100644 --- a/src/src/SocketId.cpp +++ b/src/src/SocketId.cpp @@ -11,9 +11,9 @@ namespace MinimalSocket { #ifdef _WIN32 -WSALazyInitializer::WSALazyInitializer() { +WSALazyInitializer::WSALazyInitializer(const WSAVersion& version) + : configured_version(version) { WSADATA wsa; - const auto version = WSAManager::getWsaVersion(); const BYTE version_major = static_cast(version[0]); const BYTE version_minor = static_cast(version[1]); auto result = WSAStartup(MAKEWORD(version_major, version_minor), &wsa); @@ -53,12 +53,13 @@ std::mutex WSALazyInitializer::lazy_proxy_mtx = std::mutex{}; std::unique_ptr WSALazyInitializer::lazy_proxy = nullptr; void WSALazyInitializer::lazyInit() { + auto version = WSAManager::getWsaVersion(); std::scoped_lock lock(WSALazyInitializer::lazy_proxy_mtx); - if (nullptr != WSALazyInitializer::lazy_proxy) { - return; + if ((nullptr != WSALazyInitializer::lazy_proxy) && (WSALazyInitializer::lazy_proxy->configured_version == version)) { + return; } try { - WSALazyInitializer::lazy_proxy.reset(new WSALazyInitializer{}); + WSALazyInitializer::lazy_proxy.reset(new WSALazyInitializer{version}); } catch (const Error &e) { WSALazyInitializer::lazy_proxy = nullptr; throw e; diff --git a/src/src/SocketId.h b/src/src/SocketId.h index d859ae46..33c39f4a 100644 --- a/src/src/SocketId.h +++ b/src/src/SocketId.h @@ -85,10 +85,12 @@ class WSALazyInitializer { ~WSALazyInitializer(); private: - WSALazyInitializer(); + WSALazyInitializer(const WSAVersion& version); static std::mutex lazy_proxy_mtx; static std::unique_ptr lazy_proxy; + + const WSAVersion configured_version; }; #endif } // namespace MinimalSocket diff --git a/tests/TestAddress.cpp b/tests/TestAddress.cpp index 057721fe..503960d0 100644 --- a/tests/TestAddress.cpp +++ b/tests/TestAddress.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -12,8 +13,10 @@ namespace { #ifdef _WIN32 TEST_CASE("Invalid WSA version", "[address]") { - WSAManager::setWsaVersion({0,0}); + 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 From 282506850f12e96d41cd73ec401e49e716e7122e Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Sat, 14 May 2022 16:30:19 +0100 Subject: [PATCH 164/228] testing --- src/src/core/Receiver.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 0f342e54..9d24eb60 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -47,13 +47,19 @@ ReceiverBase::lazyUpdateReceiveTimeout(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() == EAGAIN) && (timeout != NULL_TIMEOUT)) { + if ((error_with_code.getErrorCode() == TIMEOUT_CODE) && (timeout != NULL_TIMEOUT)) { // just out of time: tolerate return; } From 819e3c10eff818fbea36ebecd0bdb561b18719cb Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 14 May 2022 19:03:30 +0100 Subject: [PATCH 165/228] samples --- CMakeLists.txt | 2 +- samples/CMakeLists.txt | 12 ------ samples/utils/Asker.cpp | 43 ------------------- samples/utils/Asker.h | 30 ------------- samples/utils/Names.cpp | 23 ---------- samples/utils/Responder.cpp | 38 ---------------- samples/utils/Responder.h | 34 --------------- samples_new/CMakeLists.txt | 6 +++ .../cmake/MakeSample.cmake | 0 samples_new/utils/Ask.h | 41 ++++++++++++++++++ {samples => samples_new}/utils/CMakeLists.txt | 0 samples_new/utils/Names.cpp | 24 +++++++++++ {samples => samples_new}/utils/Names.h | 10 +++-- samples_new/utils/Respond.h | 40 +++++++++++++++++ 14 files changed, 118 insertions(+), 185 deletions(-) delete mode 100644 samples/CMakeLists.txt delete mode 100644 samples/utils/Asker.cpp delete mode 100644 samples/utils/Asker.h delete mode 100644 samples/utils/Names.cpp delete mode 100644 samples/utils/Responder.cpp delete mode 100644 samples/utils/Responder.h create mode 100644 samples_new/CMakeLists.txt rename {samples => samples_new}/cmake/MakeSample.cmake (100%) create mode 100644 samples_new/utils/Ask.h rename {samples => samples_new}/utils/CMakeLists.txt (100%) create mode 100644 samples_new/utils/Names.cpp rename {samples => samples_new}/utils/Names.h (69%) create mode 100644 samples_new/utils/Respond.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b71b7e4..2f0150e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ project(MinimalCppSocket) add_subdirectory(src) if(BUILD_MinimalCppSocket_SAMPLES) - add_subdirectory(samples) + add_subdirectory(samples_new) endif() if(BUILD_MinimalCppSocket_TESTS) diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt deleted file mode 100644 index 15d93d33..00000000 --- a/samples/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -project(MinimalCppSocket-Samples) - -include(cmake/MakeSample.cmake) -MakeSample(Dummy) -find_package(OpenMP REQUIRED) -target_link_libraries(Dummy PUBLIC OpenMP::OpenMP_CXX) - - -# add_subdirectory(utils) - -# add_subdirectory(tcp) -# add_subdirectory(udp) diff --git a/samples/utils/Asker.cpp b/samples/utils/Asker.cpp deleted file mode 100644 index 8a3117de..00000000 --- a/samples/utils/Asker.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include - -namespace sck::sample { - Asker::Asker(std::unique_ptr socket) - : Logger("Asker") - , socket(std::move(socket)) { - } - - void Asker::ask() { - this->log("sending: ", this->cursor.getCursorName()); - this->socket->send({ this->cursor.getCursorName().data(), this->cursor.getCursorName().size() }); - std::pair temp = { &this->recvBuffer[0], 1000 }; - std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); - std::string recStr(temp.first, recvBytes); - if (recStr.compare(this->cursor.getCursorSurname()) != 0) { - throw std::runtime_error("got wrong response"); - } - this->log("got: ", recStr); - ++this->cursor; - } - - void Asker::ask(const std::size_t times) { - for (std::size_t k = 0; k < times; ++k) { - this->ask(); - } - } - - void Asker::askForever(const std::chrono::milliseconds& sampleTime) { - while (true) { - this->ask(); - std::this_thread::sleep_for(sampleTime); - } - } -} diff --git a/samples/utils/Asker.h b/samples/utils/Asker.h deleted file mode 100644 index 61a2b6fe..00000000 --- a/samples/utils/Asker.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#pragma once - -#include - -namespace MinimalSocket::test { -class Asker : public Logger { -public: - Asker(std::unique_ptr socket); - - void ask(); - - void ask(const std::size_t times); - - void askForever(const std::chrono::milliseconds &sampleTime); - -private: - std::unique_ptr socket; - NamesMap cursor; - char recvBuffer[1000]; -}; -} // namespace MinimalSocket::test diff --git a/samples/utils/Names.cpp b/samples/utils/Names.cpp deleted file mode 100644 index af07f38a..00000000 --- a/samples/utils/Names.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -namespace MinimalSocket::test { -Names NAMES_SURNAMES = {{"Luciano", "Pavarotti"}, - {"Gengis", "Khan"}, - {"Giulio", "Cesare"}, - {"Theodor", "Roosvelt"}, - {"Immanuel", "Kant"}}; - -void NamesCircularIterator::next() { - ++current_; - if (current_ == NAMES_SURNAMES.end()) { - current_ = NAMES_SURNAMES.begin(); - } -} -} // namespace MinimalSocket::test diff --git a/samples/utils/Responder.cpp b/samples/utils/Responder.cpp deleted file mode 100644 index e15d18ca..00000000 --- a/samples/utils/Responder.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include - -namespace sck::sample { - Responder::Responder(std::unique_ptr socket) - : Logger("Responder") - , socket(std::move(socket)) { - } - - void Responder::respond() { - std::pair temp = { this->recvBuffer.data(), this->recvBuffer.size() }; - std::size_t recvBytes = this->socket->receive(temp, std::chrono::milliseconds(0)); - std::string recStr(temp.first, recvBytes); - - const std::string surname = NamesMap::getSurname(recStr); - this->log("request: ", recStr, " response: ", surname); - this->socket->send({ surname.data(), surname.size() }); - } - - void Responder::respond(const std::size_t times) { - for (std::size_t k = 0; k < times; ++k) { - this->respond(); - } - } - - void Responder::respondForever() { - while (true) { - this->respond(); - } - } -} \ No newline at end of file diff --git a/samples/utils/Responder.h b/samples/utils/Responder.h deleted file mode 100644 index ce3365cc..00000000 --- a/samples/utils/Responder.h +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef SAMPLE_RESPONDER_H -#define SAMPLE_RESPONDER_H - -#include -#include -#include -#include - -namespace sck::sample { - class Responder - : public Logger { - public: - Responder(std::unique_ptr socket); - - void respond(); - - void respond(const std::size_t times); - - void respondForever(); - - private: - std::unique_ptr socket; - std::array recvBuffer; - }; -} - -#endif \ No newline at end of file diff --git a/samples_new/CMakeLists.txt b/samples_new/CMakeLists.txt new file mode 100644 index 00000000..db696e29 --- /dev/null +++ b/samples_new/CMakeLists.txt @@ -0,0 +1,6 @@ +project(MinimalCppSocket-Samples) + +add_subdirectory(utils) + +# add_subdirectory(tcp) +# add_subdirectory(udp) diff --git a/samples/cmake/MakeSample.cmake b/samples_new/cmake/MakeSample.cmake similarity index 100% rename from samples/cmake/MakeSample.cmake rename to samples_new/cmake/MakeSample.cmake diff --git a/samples_new/utils/Ask.h b/samples_new/utils/Ask.h new file mode 100644 index 00000000..a2abb386 --- /dev/null +++ b/samples_new/utils/Ask.h @@ -0,0 +1,41 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include + +namespace MinimalSocket::samples { +namespace { +template +void ask(SocketT &channel, NamesCircularIterator &iterator) { + // send name of this person + std::cout << "Sending: " << iterator.current()->name; + channel.send(iterator.current()->first); + // expect to get back the corresponding surname + auto response = channel.receive(500); + std::cout << " , got as response: " << response; + iterator.next(); +} +} // namespace + +template +void ask(SocketT &channel, const std::size_t times) { + NamesCircularIterator iterator; + for (std::size_t k = 0; k < times; ++k) { + ask(channel, iterator); + } +} + +template void ask_forever(SocketT &channel) { + NamesCircularIterator iterator; + while (true) { + ask(channel, iterator); + } +} +} // namespace MinimalSocket::samples diff --git a/samples/utils/CMakeLists.txt b/samples_new/utils/CMakeLists.txt similarity index 100% rename from samples/utils/CMakeLists.txt rename to samples_new/utils/CMakeLists.txt diff --git a/samples_new/utils/Names.cpp b/samples_new/utils/Names.cpp new file mode 100644 index 00000000..159012db --- /dev/null +++ b/samples_new/utils/Names.cpp @@ -0,0 +1,24 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#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()) { + current_ = NAMES_SURNAMES.begin(); + } +} +} // namespace MinimalSocket::samples diff --git a/samples/utils/Names.h b/samples_new/utils/Names.h similarity index 69% rename from samples/utils/Names.h rename to samples_new/utils/Names.h index 8cac8a59..a96d47e1 100644 --- a/samples/utils/Names.h +++ b/samples_new/utils/Names.h @@ -7,17 +7,19 @@ #pragma once +#include #include #include -namespace MinimalSocket::test { -using Names = std::unordered_multimap; -static const Names NAMES_SURNAMES; +namespace MinimalSocket::samples { +using Names = std::unordered_map; using NamesIterator = Names::const_iterator; class NamesCircularIterator { public: + static const Names NAMES_SURNAMES; + NamesCircularIterator() : current_(NAMES_SURNAMES.begin()){}; const NamesIterator ¤t() { return current_; }; @@ -27,4 +29,4 @@ class NamesCircularIterator { private: NamesIterator current_; }; -} // namespace MinimalSocket::test +} // namespace MinimalSocket::samples diff --git a/samples_new/utils/Respond.h b/samples_new/utils/Respond.h new file mode 100644 index 00000000..d8fb229d --- /dev/null +++ b/samples_new/utils/Respond.h @@ -0,0 +1,40 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include + +namespace MinimalSocket::samples { +namespace { +template void respond_one(SocketT &channel) { + // receive name to search + std::cout << "Receiving "; + auto request = channel.receive(500); + std::cout << " , got as request: " << request; + // respond with corresponding surname + const auto &response = + NamesCircularIterator::NAMES_SURNAMES.find(request)->second; + std::cout << " , sending back: " << response; + channel.send(response); +} +} // namespace + +template +void respond(SocketT &channel, const std::size_t times) { + for (std::size_t k = 0; k < times; ++k) { + respond_one(channel); + } +} + +template void respond_forever(SocketT &channel) { + while (true) { + respond_one(channel); + } +} +} // namespace MinimalSocket::samples From 15f37c815ab36bce219c443e53f2a75552dfaef9 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 14 May 2022 21:27:37 +0100 Subject: [PATCH 166/228] samples --- samples_new/CMakeLists.txt | 4 +- samples_new/cmake/MakeSample.cmake | 2 +- samples_new/tcp/CMakeLists.txt | 5 ++ samples_new/tcp/TcpClient.cpp | 42 +++++++++++++++++ samples_new/tcp/TcpRepeater.cpp | 67 ++++++++++++++++++++++++++ samples_new/tcp/TcpServer.cpp | 75 ++++++++++++++++++++++++++++++ samples_new/utils/Args.cpp | 67 ++++++++++++++++++++++++++ samples_new/utils/Args.h | 45 ++++++++++++++++++ samples_new/utils/Ask.h | 20 +++++--- samples_new/utils/Respond.h | 4 -- 10 files changed, 318 insertions(+), 13 deletions(-) create mode 100644 samples_new/tcp/CMakeLists.txt create mode 100644 samples_new/tcp/TcpClient.cpp create mode 100644 samples_new/tcp/TcpRepeater.cpp create mode 100644 samples_new/tcp/TcpServer.cpp create mode 100644 samples_new/utils/Args.cpp create mode 100644 samples_new/utils/Args.h diff --git a/samples_new/CMakeLists.txt b/samples_new/CMakeLists.txt index db696e29..17b9714a 100644 --- a/samples_new/CMakeLists.txt +++ b/samples_new/CMakeLists.txt @@ -1,6 +1,8 @@ project(MinimalCppSocket-Samples) +include(cmake/MakeSample.cmake) + add_subdirectory(utils) -# add_subdirectory(tcp) +add_subdirectory(tcp) # add_subdirectory(udp) diff --git a/samples_new/cmake/MakeSample.cmake b/samples_new/cmake/MakeSample.cmake index e9248253..d10422f3 100644 --- a/samples_new/cmake/MakeSample.cmake +++ b/samples_new/cmake/MakeSample.cmake @@ -2,7 +2,7 @@ function(MakeSample NAME) add_executable(${NAME} ${NAME}.cpp) target_link_libraries(${NAME} PUBLIC - # Utils + Utils MinimalSocket ) diff --git a/samples_new/tcp/CMakeLists.txt b/samples_new/tcp/CMakeLists.txt new file mode 100644 index 00000000..53e8c0d8 --- /dev/null +++ b/samples_new/tcp/CMakeLists.txt @@ -0,0 +1,5 @@ +MakeSample(TcpClient) + +MakeSample(TcpServer) + +MakeSample(TcpRepeater) diff --git a/samples_new/tcp/TcpClient.cpp b/samples_new/tcp/TcpClient.cpp new file mode 100644 index 00000000..88955f01 --- /dev/null +++ b/samples_new/tcp/TcpClient.cpp @@ -0,0 +1,42 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include +#include + +using namespace std; + +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( + std::atoi(options->getValue("port").c_str())); + const auto rate = std::chrono::milliseconds{ + std::atoi(options->getValue("rate", "50").c_str())}; + const auto cycles = std::atoi(options->getValue("cycles", "0").c_str()); + + const MinimalSocket::Address server_address(server_host, server_port); + MinimalSocket::tcp::TcpClient client(server_address); + + if (!client.open()) { + cout << "Failed to open connection" << endl; + return EXIT_FAILURE; + } + + if (0 == cycles) { + MinimalSocket::samples::ask_forever(client, rate); + } else { + MinimalSocket::samples::ask(client, cycles, rate); + } + + // the connection will be close when destroying the client object + return EXIT_SUCCESS; +} diff --git a/samples_new/tcp/TcpRepeater.cpp b/samples_new/tcp/TcpRepeater.cpp new file mode 100644 index 00000000..f3b1dbc2 --- /dev/null +++ b/samples_new/tcp/TcpRepeater.cpp @@ -0,0 +1,67 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include +#include + +#include + +#include +using namespace std; + +class Repeater { +public: + Repeater(MinimalSocket::tcp::TcpClient &&connection_to_preceding, + MinimalSocket::tcp::TcpConnection &&connection_to_following) + : preceding(std::move(connection_to_preceding)), + following(std::move(connection_to_following)) {} + + void repeat() { + auto request = following.receive(500); + preceding.send(request); + auto response = preceding.receive(500); + following.send(response); + } + +private: + MinimalSocket::tcp::TcpClient preceding; + MinimalSocket::tcp::TcpConnection following; +}; + +int main(const int argc, const char **argv) { + cout << "----------------------- Repeater -----------------------" << endl; + PARSE_ARGS + + const auto preceding_host = options->getValue("host", "127.0.0.1"); + const auto preceding_port = static_cast( + std::atoi(options->getValue("prec_port").c_str())); + const auto port_to_reserve = static_cast( + std::atoi(options->getValue("port").c_str())); + + // ask connection to preceding + MinimalSocket::tcp::TcpClient connection_to_preceding( + MinimalSocket::Address{preceding_host, preceding_port}); + if (connection_to_preceding.open()) { + cout << "Unable to connect to preceding" << endl; + return EXIT_FAILURE; + } + // wait connection request from follower + MinimalSocket::tcp::TcpServer acceptor(port_to_reserve); + if (acceptor.open()) { + cout << "Unable to reserve port" << std::to_string(port_to_reserve) << endl; + return EXIT_FAILURE; + } + auto connection_to_following = acceptor.acceptNewClient(); + + Repeater repeater(std::move(connection_to_preceding), + std::move(connection_to_following)); + while (true) { + repeater.repeat(); + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/samples_new/tcp/TcpServer.cpp b/samples_new/tcp/TcpServer.cpp new file mode 100644 index 00000000..f49ab73f --- /dev/null +++ b/samples_new/tcp/TcpServer.cpp @@ -0,0 +1,75 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include +#include + +#include + +using namespace std; + +struct ActiveConnection { + MinimalSocket::tcp::TcpConnection channel; + std::future task; +}; +void accept_new_client(MinimalSocket::tcp::TcpServer &server, + std::list &active_connections) { + MinimalSocket::tcp::TcpConnection accepted_connection = + server.acceptNewClient(); + cout << "New client accepted" << endl; + + auto *channel = &active_connections + .emplace_back(ActiveConnection{ + std::move(accepted_connection), std::future{}}) + .channel; + active_connections.back().task = std::async([channel = channel]() { + MinimalSocket::samples::respond_forever(*channel); + }); +} + +int main(const int argc, const char **argv) { + cout << "----------------------- Server -----------------------" << endl; + PARSE_ARGS + + const auto server_port = static_cast( + std::atoi(options->getValue("port").c_str())); + const auto max_clients = std::atoi(options->getValue("clients", "0").c_str()); + + MinimalSocket::tcp::TcpServer server(server_port); + + if (!server.open()) { + cout << "Failed to bind and listen to specified port" << endl; + return EXIT_FAILURE; + } + cout << "Listening for new clients" << endl; + + std::list active_connections; + auto accept_clients_task = std::async([&]() { + if (0 == max_clients) { + while (true) { + accept_new_client(server, active_connections); + } + + } else { + for (int c = 0; c < max_clients; ++c) { + accept_new_client(server, active_connections); + } + } + }); + + accept_clients_task.get(); + for (auto &active_connection : active_connections) { + try { + active_connection.task.get(); + } catch (...) { + } + } + + return EXIT_SUCCESS; +} diff --git a/samples_new/utils/Args.cpp b/samples_new/utils/Args.cpp new file mode 100644 index 00000000..c7dfc88a --- /dev/null +++ b/samples_new/utils/Args.cpp @@ -0,0 +1,67 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include "Args.h" + +#include +#include + +namespace MinimalSocket::samples { +std::optional Args::parse(const int argc, const char **argv) { + std::optional result; + try { + Args parsed(argc, argv); + result.emplace(std::move(parsed)); + } catch (...) { + } + return result; +} + +Args::Args(const int argc, const char **argv) { + if (((argc - 1) % 2) != 0) { + throw std::runtime_error{""}; + } + for (int k = 1; k < argc; k += 2) { + std::string name = argv[k]; + if (name.size() < 3) { + throw std::runtime_error{""}; + } + if ((name[0] != '-') || (name[1] != '-')) { + throw std::runtime_error{""}; + } + + name = std::string{name, 2}; + std::string value = argv[k + 1]; + if (name.empty()) { + throw std::runtime_error{""}; + } + + arguments_map[name] = value; + } + + for (const auto &[name, value] : arguments_map) { + std::cout << "--" << name << ": " << value << std::endl; + } + 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; +} +} // namespace MinimalSocket::samples diff --git a/samples_new/utils/Args.h b/samples_new/utils/Args.h new file mode 100644 index 00000000..286e0378 --- /dev/null +++ b/samples_new/utils/Args.h @@ -0,0 +1,45 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include +#include +#include + +namespace MinimalSocket::samples { +using ArgsMap = std::unordered_map; + +// Group the passed args into an ordered table +class Args { +public: + Args(Args &&) = default; + + static std::optional parse(const int argc, const char **argv); + + // 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; + + // throw if this option does not exists + std::string getValue(const std::string &argument_name) const; + +private: + Args(const int argc, const char **argv); + + ArgsMap arguments_map; +}; + +#define PARSE_ARGS \ + auto options = MinimalSocket::samples::Args::parse(argc, argv); \ + if (std::nullopt == options) { \ + std::cout << "Invalid arguments" << std::endl; \ + return EXIT_FAILURE; \ + } +} // namespace MinimalSocket::samples diff --git a/samples_new/utils/Ask.h b/samples_new/utils/Ask.h index a2abb386..a3b78eed 100644 --- a/samples_new/utils/Ask.h +++ b/samples_new/utils/Ask.h @@ -8,34 +8,40 @@ #pragma once #include +#include #include +#include namespace MinimalSocket::samples { namespace { template -void ask(SocketT &channel, NamesCircularIterator &iterator) { +void ask(SocketT &channel, NamesCircularIterator &iterator, + const std::chrono::milliseconds &rate) { // send name of this person - std::cout << "Sending: " << iterator.current()->name; + std::cout << "Sending: " << iterator.current()->first; channel.send(iterator.current()->first); // expect to get back the corresponding surname auto response = channel.receive(500); - std::cout << " , got as response: " << response; + std::cout << " , got as response: " << response << std::endl; iterator.next(); + std::this_thread::sleep_for(rate); } } // namespace template -void ask(SocketT &channel, const std::size_t times) { +void ask(SocketT &channel, const std::size_t times, + const std::chrono::milliseconds &rate) { NamesCircularIterator iterator; for (std::size_t k = 0; k < times; ++k) { - ask(channel, iterator); + ask(channel, iterator, rate); } } -template void ask_forever(SocketT &channel) { +template +void ask_forever(SocketT &channel, const std::chrono::milliseconds &rate) { NamesCircularIterator iterator; while (true) { - ask(channel, iterator); + ask(channel, iterator, rate); } } } // namespace MinimalSocket::samples diff --git a/samples_new/utils/Respond.h b/samples_new/utils/Respond.h index d8fb229d..3d29527e 100644 --- a/samples_new/utils/Respond.h +++ b/samples_new/utils/Respond.h @@ -8,19 +8,15 @@ #pragma once #include -#include namespace MinimalSocket::samples { namespace { template void respond_one(SocketT &channel) { // receive name to search - std::cout << "Receiving "; auto request = channel.receive(500); - std::cout << " , got as request: " << request; // respond with corresponding surname const auto &response = NamesCircularIterator::NAMES_SURNAMES.find(request)->second; - std::cout << " , sending back: " << response; channel.send(response); } } // namespace From 643de44f71ee430eef0e8feea5f215a50e5a191f Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 14 May 2022 21:36:42 +0100 Subject: [PATCH 167/228] samples --- samples_new/tcp/TcpRepeater.cpp | 8 +++++--- samples_new/tcp/TcpServer.cpp | 4 +++- samples_new/utils/Args.cpp | 10 ++++++++++ samples_new/utils/Args.h | 4 ++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/samples_new/tcp/TcpRepeater.cpp b/samples_new/tcp/TcpRepeater.cpp index f3b1dbc2..bd7e9c0d 100644 --- a/samples_new/tcp/TcpRepeater.cpp +++ b/samples_new/tcp/TcpRepeater.cpp @@ -39,18 +39,20 @@ int main(const int argc, const char **argv) { const auto preceding_host = options->getValue("host", "127.0.0.1"); const auto preceding_port = static_cast( std::atoi(options->getValue("prec_port").c_str())); + MinimalSocket::Address preceding_address(preceding_host, preceding_port); + const auto port_to_reserve = static_cast( std::atoi(options->getValue("port").c_str())); // ask connection to preceding - MinimalSocket::tcp::TcpClient connection_to_preceding( - MinimalSocket::Address{preceding_host, preceding_port}); + MinimalSocket::tcp::TcpClient connection_to_preceding(preceding_address); if (connection_to_preceding.open()) { cout << "Unable to connect to preceding" << endl; return EXIT_FAILURE; } // wait connection request from follower - MinimalSocket::tcp::TcpServer acceptor(port_to_reserve); + MinimalSocket::tcp::TcpServer acceptor(port_to_reserve, + preceding_address.getFamily()); if (acceptor.open()) { cout << "Unable to reserve port" << std::to_string(port_to_reserve) << endl; return EXIT_FAILURE; diff --git a/samples_new/tcp/TcpServer.cpp b/samples_new/tcp/TcpServer.cpp index f49ab73f..8a55b55b 100644 --- a/samples_new/tcp/TcpServer.cpp +++ b/samples_new/tcp/TcpServer.cpp @@ -40,8 +40,10 @@ int main(const int argc, const char **argv) { const auto server_port = static_cast( std::atoi(options->getValue("port").c_str())); const auto max_clients = std::atoi(options->getValue("clients", "0").c_str()); + const auto family = + MinimalSocket::samples::to_family(options->getValue("family", "v4")); - MinimalSocket::tcp::TcpServer server(server_port); + MinimalSocket::tcp::TcpServer server(server_port, family); if (!server.open()) { cout << "Failed to bind and listen to specified port" << endl; diff --git a/samples_new/utils/Args.cpp b/samples_new/utils/Args.cpp index c7dfc88a..827162cb 100644 --- a/samples_new/utils/Args.cpp +++ b/samples_new/utils/Args.cpp @@ -64,4 +64,14 @@ std::string Args::getValue(const std::string &argument_name) const { } return args_it->second; } + +MinimalSocket::AddressFamily to_family(const std::string &family_as_string) { + if (family_as_string == "v4") { + return MinimalSocket::AddressFamily::IP_V4; + } + if (family_as_string == "v6") { + return MinimalSocket::AddressFamily::IP_V6; + } + throw std::runtime_error{"Invalid family address: it can be only v4 or v6"}; +} } // namespace MinimalSocket::samples diff --git a/samples_new/utils/Args.h b/samples_new/utils/Args.h index 286e0378..6af8a95d 100644 --- a/samples_new/utils/Args.h +++ b/samples_new/utils/Args.h @@ -7,6 +7,8 @@ #pragma once +#include + #include #include #include @@ -36,6 +38,8 @@ class Args { ArgsMap 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) { \ From ac8fc89dc432ea6cc25ad2245a4e8038eb2cba22 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 14 May 2022 22:21:08 +0100 Subject: [PATCH 168/228] samples --- Samples/CMakeLists.txt.bak | 9 ------ TODO | 2 ++ samples_new/CMakeLists.txt | 1 + samples_new/cmake/MakaLauncher.cmake | 11 +++++++ samples_new/tcp/CMakeLists.txt | 5 +-- samples_new/tcp/TcpScriptsGenerator.cpp | 24 ++++++++++++++ samples_new/utils/ScriptGenerator.cpp | 42 +++++++++++++++++++++++++ samples_new/utils/ScriptGenerator.h | 31 ++++++++++++++++++ 8 files changed, 114 insertions(+), 11 deletions(-) delete mode 100644 Samples/CMakeLists.txt.bak create mode 100644 samples_new/cmake/MakaLauncher.cmake create mode 100644 samples_new/tcp/TcpScriptsGenerator.cpp create mode 100644 samples_new/utils/ScriptGenerator.cpp create mode 100644 samples_new/utils/ScriptGenerator.h diff --git a/Samples/CMakeLists.txt.bak b/Samples/CMakeLists.txt.bak deleted file mode 100644 index 995eeb04..00000000 --- a/Samples/CMakeLists.txt.bak +++ /dev/null @@ -1,9 +0,0 @@ -project(MinimalCppSocket-Samples) - -include(cmake/MakeSample.cmake) -MakeSample(Dummy) - -# add_subdirectory(utils) - -# add_subdirectory(tcp) -# add_subdirectory(udp) diff --git a/TODO b/TODO index d73e3f41..3e02c509 100644 --- a/TODO +++ b/TODO @@ -25,3 +25,5 @@ stressare bene il fatto che c'e' possibilita' di usare con timeout varie operazi stressare che si possono fare in maniera safe send concorrenti per udp add test for udp concurrent send to + +error se os non e' win32 o linux diff --git a/samples_new/CMakeLists.txt b/samples_new/CMakeLists.txt index 17b9714a..404e6d2c 100644 --- a/samples_new/CMakeLists.txt +++ b/samples_new/CMakeLists.txt @@ -1,6 +1,7 @@ project(MinimalCppSocket-Samples) include(cmake/MakeSample.cmake) +include(cmake/MakaLauncher.cmake) add_subdirectory(utils) diff --git a/samples_new/cmake/MakaLauncher.cmake b/samples_new/cmake/MakaLauncher.cmake new file mode 100644 index 00000000..038880bb --- /dev/null +++ b/samples_new/cmake/MakaLauncher.cmake @@ -0,0 +1,11 @@ +function(MakeLauncher NAME) + add_executable(${NAME} ${NAME}.cpp) + + target_link_libraries(${NAME} PUBLIC + Utils + ) + + set_target_properties(${NAME} PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=%PATH%;${CMAKE_INSTALL_PREFIX}/bin/") + + install(TARGETS ${NAME}) +endfunction() diff --git a/samples_new/tcp/CMakeLists.txt b/samples_new/tcp/CMakeLists.txt index 53e8c0d8..47bcf210 100644 --- a/samples_new/tcp/CMakeLists.txt +++ b/samples_new/tcp/CMakeLists.txt @@ -1,5 +1,6 @@ MakeSample(TcpClient) - MakeSample(TcpServer) - MakeSample(TcpRepeater) + +MakeLauncher(TcpScriptsGenerator) +add_dependencies(TcpScriptsGenerator TcpClient TcpServer TcpRepeater) diff --git a/samples_new/tcp/TcpScriptsGenerator.cpp b/samples_new/tcp/TcpScriptsGenerator.cpp new file mode 100644 index 00000000..50429157 --- /dev/null +++ b/samples_new/tcp/TcpScriptsGenerator.cpp @@ -0,0 +1,24 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +int main() { + MinimalSocket::samples::ScriptGenerator server_client_launcher; + + server_client_launcher.add(MinimalSocket::samples::ProcessAndArgs{ + "proc_a", + std::unordered_map{{"name1", "val1"}}}); + + server_client_launcher.add(MinimalSocket::samples::ProcessAndArgs{ + "proc_b", std::unordered_map{ + {"name1", "val1"}, {"name2", "val2"}}}); + + server_client_launcher.generate("Temp"); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/samples_new/utils/ScriptGenerator.cpp b/samples_new/utils/ScriptGenerator.cpp new file mode 100644 index 00000000..eab992cf --- /dev/null +++ b/samples_new/utils/ScriptGenerator.cpp @@ -0,0 +1,42 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include + +namespace MinimalSocket::samples { +namespace { +#ifdef _WIN32 +static const std::string SCRIPT_EXTENSION = std::string{".bat"}; +#elif __linux__ +static const std::string SCRIPT_EXTENSION = std::string{".sh"}; +#endif +} // namespace + +void ScriptGenerator::generate(const std::string &file_name) { + std::string complete_file_name = file_name + SCRIPT_EXTENSION; + std::ofstream stream(complete_file_name); + for (const auto &process : processes) { +#ifdef _WIN32 + stream << "start \"\" \"" << process.process_name << "\""; + for (const auto &[name, val] : process.arguments) { + stream << " \"--" << name << "\" \"" << val << "\""; + } +#elif __linux__ + stream << "gnome-terminal -x sh -c \"./" << process.process_name; + for (const auto &[name, val] : process.arguments) { + stream << " --" << name << ' ' << val; + } + stream << " ; bash\""; +#endif + stream << std::endl; + } + + // "start \"\" \"" +} +} // namespace MinimalSocket::samples diff --git a/samples_new/utils/ScriptGenerator.h b/samples_new/utils/ScriptGenerator.h new file mode 100644 index 00000000..4aefe626 --- /dev/null +++ b/samples_new/utils/ScriptGenerator.h @@ -0,0 +1,31 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include +#include + +namespace MinimalSocket::samples { +struct ProcessAndArgs { + std::string process_name; + std::unordered_map arguments; +}; + +class ScriptGenerator { +public: + ScriptGenerator() = default; + + void add(const ProcessAndArgs &info) { processes.push_back(info); }; + + void generate(const std::string &file_name); + +private: + std::vector processes; +}; +} // namespace MinimalSocket::samples From ab0d2762b855f12e9cdaf83ee04830fda4ab3887 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 14 May 2022 23:52:29 +0100 Subject: [PATCH 169/228] samples --- samples_new/tcp/TcpClient.cpp | 2 +- samples_new/tcp/TcpScriptsGenerator.cpp | 77 ++++++++++++++++++++++--- samples_new/utils/ScriptGenerator.cpp | 54 ++++++++++++----- samples_new/utils/ScriptGenerator.h | 4 +- 4 files changed, 111 insertions(+), 26 deletions(-) diff --git a/samples_new/tcp/TcpClient.cpp b/samples_new/tcp/TcpClient.cpp index 88955f01..b3907811 100644 --- a/samples_new/tcp/TcpClient.cpp +++ b/samples_new/tcp/TcpClient.cpp @@ -20,7 +20,7 @@ int main(const int argc, const char **argv) { const auto server_port = static_cast( std::atoi(options->getValue("port").c_str())); const auto rate = std::chrono::milliseconds{ - std::atoi(options->getValue("rate", "50").c_str())}; + std::atoi(options->getValue("rate", "100").c_str())}; const auto cycles = std::atoi(options->getValue("cycles", "0").c_str()); const MinimalSocket::Address server_address(server_host, server_port); diff --git a/samples_new/tcp/TcpScriptsGenerator.cpp b/samples_new/tcp/TcpScriptsGenerator.cpp index 50429157..2beb914a 100644 --- a/samples_new/tcp/TcpScriptsGenerator.cpp +++ b/samples_new/tcp/TcpScriptsGenerator.cpp @@ -7,18 +7,79 @@ #include +#include +using namespace std; + int main() { - MinimalSocket::samples::ScriptGenerator server_client_launcher; + { + // 1 server 1 client + const std::string sample_name = "tcp_server_client"; + MinimalSocket::samples::ScriptGenerator generator; + + const std::string port = "35998"; + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpServer", + MinimalSocket::samples::ProcessArgs{{"port", port}, {"clients", "1"}}}); + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpClient", MinimalSocket::samples::ProcessArgs{{"port", port}}}); + + cout << "generating " << sample_name << endl; + generator.generate(sample_name); + } + + { + // 1 server 2 clients + const std::string sample_name = "tcp_server_2_clients"; + MinimalSocket::samples::ScriptGenerator generator; + + const std::string port = "35998"; + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpServer", + MinimalSocket::samples::ProcessArgs{{"port", port}, {"clients", "1"}}}); + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpClient", MinimalSocket::samples::ProcessArgs{{"port", port}}}); + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpClient", + MinimalSocket::samples::ProcessArgs{{"port", port}, {"rate", "300"}}}); + + cout << "generating " << sample_name << endl; + generator.generate(sample_name); + } + + { + // repeaters + const std::size_t repeaters = 1; + const std::string sample_name = + "tcp_chain_size_" + std::to_string(repeaters + 2); + MinimalSocket::samples::ScriptGenerator generator; + + std::size_t port = 35998; + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpServer", MinimalSocket::samples::ProcessArgs{ + {"port", std::to_string(port)}, {"clients", "1"}}}); - server_client_launcher.add(MinimalSocket::samples::ProcessAndArgs{ - "proc_a", - std::unordered_map{{"name1", "val1"}}}); + for (std::size_t r = 0; r < repeaters; ++r) { + auto new_port = port + 10; + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpRepeater", MinimalSocket::samples::ProcessArgs{ + {"port", std::to_string(new_port)}, + {"prec_port", std::to_string(port)}}}); + port = new_port; + } - server_client_launcher.add(MinimalSocket::samples::ProcessAndArgs{ - "proc_b", std::unordered_map{ - {"name1", "val1"}, {"name2", "val2"}}}); + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpClient", + MinimalSocket::samples::ProcessArgs{{"port", std::to_string(port)}}}); - server_client_launcher.generate("Temp"); + cout << "generating " << sample_name << endl; + generator.generate(sample_name); + } return EXIT_SUCCESS; } \ No newline at end of file diff --git a/samples_new/utils/ScriptGenerator.cpp b/samples_new/utils/ScriptGenerator.cpp index eab992cf..b08967b7 100644 --- a/samples_new/utils/ScriptGenerator.cpp +++ b/samples_new/utils/ScriptGenerator.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace MinimalSocket::samples { namespace { @@ -16,27 +17,48 @@ static const std::string SCRIPT_EXTENSION = std::string{".bat"}; #elif __linux__ static const std::string SCRIPT_EXTENSION = std::string{".sh"}; #endif -} // namespace -void ScriptGenerator::generate(const std::string &file_name) { - std::string complete_file_name = file_name + SCRIPT_EXTENSION; - std::ofstream stream(complete_file_name); - for (const auto &process : processes) { +std::string to_string(const ProcessAndArgs &subject) { + std::stringstream stream; #ifdef _WIN32 - stream << "start \"\" \"" << process.process_name << "\""; - for (const auto &[name, val] : process.arguments) { - stream << " \"--" << name << "\" \"" << val << "\""; - } + stream << '\"' << subject.process_name << '\"'; + for (const auto &[name, val] : subject.arguments) { + stream << " \"--" << name << "\" \"" << val << "\""; + } #elif __linux__ - stream << "gnome-terminal -x sh -c \"./" << process.process_name; - for (const auto &[name, val] : process.arguments) { - stream << " --" << name << ' ' << val; - } - stream << " ; bash\""; + stream << "./" << subject.process_name; + for (const auto &[name, val] : subject.arguments) { + stream << " --" << name << ' ' << val; + } #endif - stream << std::endl; + return stream.str(); +} + +void add_process(std::ofstream &stream, const ProcessAndArgs &proc_and_args, + const bool new_terminal) { +#ifdef _WIN32 + if (new_terminal) { + stream << "start \"\" "; } + stream << to_string(proc_and_args); +#elif __linux__ + if (new_terminal) { + stream << "gnome-terminal -x sh -c \"" << to_string(proc_and_args) + << " ; bash\""; + } else { + stream << to_string(proc_and_args); + } +#endif + stream << std::endl; +} +} // namespace - // "start \"\" \"" +void ScriptGenerator::generate(const std::string &file_name) { + std::ofstream stream(file_name + SCRIPT_EXTENSION); + + for (std::size_t k = 0; k < (processes.size() - 1); ++k) { + add_process(stream, processes[k], true); + } + add_process(stream, processes.back(), false); } } // namespace MinimalSocket::samples diff --git a/samples_new/utils/ScriptGenerator.h b/samples_new/utils/ScriptGenerator.h index 4aefe626..02bcc153 100644 --- a/samples_new/utils/ScriptGenerator.h +++ b/samples_new/utils/ScriptGenerator.h @@ -12,9 +12,11 @@ #include namespace MinimalSocket::samples { +using ProcessArgs = std::unordered_map; + struct ProcessAndArgs { std::string process_name; - std::unordered_map arguments; + ProcessArgs arguments; }; class ScriptGenerator { From 0590a32558952c1b6e07a7a17fd29b335e8f3f8a Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sat, 14 May 2022 23:54:01 +0100 Subject: [PATCH 170/228] samples --- CMakeLists.txt | 2 +- {Samples => Samples_old}/CMakeLists.txt | 0 {Samples => Samples_old}/Dummy.cpp | 0 {Samples => Samples_old}/Launcher.h | 0 {Samples => Samples_old}/Tcp/CMakeLists.txt | 0 {Samples => Samples_old}/Tcp/README.md | 0 .../Tcp/SampleTcp-01-server-client.cpp | 0 .../Tcp/SampleTcp-02-server-clients.cpp | 0 .../Tcp/SampleTcp-03-asyncserver-clients.cpp | 0 .../Tcp/SampleTcp-04-repeater.cpp | 0 {Samples => Samples_old}/Tcp/TcpClient.cpp | 0 {Samples => Samples_old}/Tcp/TcpRepeater.cpp | 0 {Samples => Samples_old}/Tcp/TcpServer.cpp | 0 .../Tcp/TcpServerAsync.cpp | 0 {Samples => Samples_old}/Udp/CMakeLists.txt | 0 {Samples => Samples_old}/Udp/README.md | 0 .../Udp/SampleUdp-01-connection.cpp | 0 .../Udp/SampleUdp-02-asyncconnection.cpp | 0 .../Udp/SampleUdp-03-server.cpp | 0 {Samples => Samples_old}/Udp/UdpAsker.cpp | 0 {Samples => Samples_old}/Udp/UdpResponder.cpp | 0 .../Udp/UdpResponderAsync.cpp | 0 {Samples => Samples_old}/Udp/UdpServer.cpp | 0 .../cmake/MakeSample.cmake.bak | 0 Utils/CMakeLists.txt | 22 --------- Utils/include/AsyncResponder.h | 35 ------------- Utils/include/Logger.h | 49 ------------------- Utils/src/AsyncResponder.cpp | 36 -------------- Utils/src/Logger.cpp | 36 -------------- {samples_new => samples}/CMakeLists.txt | 0 .../cmake/MakaLauncher.cmake | 0 .../cmake/MakeSample.cmake | 0 {samples_new => samples}/tcp/CMakeLists.txt | 0 {samples_new => samples}/tcp/TcpClient.cpp | 0 {samples_new => samples}/tcp/TcpRepeater.cpp | 0 .../tcp/TcpScriptsGenerator.cpp | 0 {samples_new => samples}/tcp/TcpServer.cpp | 0 {samples_new => samples}/utils/Args.cpp | 0 {samples_new => samples}/utils/Args.h | 0 {samples_new => samples}/utils/Ask.h | 0 {samples_new => samples}/utils/CMakeLists.txt | 0 {samples_new => samples}/utils/Names.cpp | 0 {samples_new => samples}/utils/Names.h | 0 {samples_new => samples}/utils/Respond.h | 0 .../utils/ScriptGenerator.cpp | 0 .../utils/ScriptGenerator.h | 0 46 files changed, 1 insertion(+), 179 deletions(-) rename {Samples => Samples_old}/CMakeLists.txt (100%) rename {Samples => Samples_old}/Dummy.cpp (100%) rename {Samples => Samples_old}/Launcher.h (100%) rename {Samples => Samples_old}/Tcp/CMakeLists.txt (100%) rename {Samples => Samples_old}/Tcp/README.md (100%) rename {Samples => Samples_old}/Tcp/SampleTcp-01-server-client.cpp (100%) rename {Samples => Samples_old}/Tcp/SampleTcp-02-server-clients.cpp (100%) rename {Samples => Samples_old}/Tcp/SampleTcp-03-asyncserver-clients.cpp (100%) rename {Samples => Samples_old}/Tcp/SampleTcp-04-repeater.cpp (100%) rename {Samples => Samples_old}/Tcp/TcpClient.cpp (100%) rename {Samples => Samples_old}/Tcp/TcpRepeater.cpp (100%) rename {Samples => Samples_old}/Tcp/TcpServer.cpp (100%) rename {Samples => Samples_old}/Tcp/TcpServerAsync.cpp (100%) rename {Samples => Samples_old}/Udp/CMakeLists.txt (100%) rename {Samples => Samples_old}/Udp/README.md (100%) rename {Samples => Samples_old}/Udp/SampleUdp-01-connection.cpp (100%) rename {Samples => Samples_old}/Udp/SampleUdp-02-asyncconnection.cpp (100%) rename {Samples => Samples_old}/Udp/SampleUdp-03-server.cpp (100%) rename {Samples => Samples_old}/Udp/UdpAsker.cpp (100%) rename {Samples => Samples_old}/Udp/UdpResponder.cpp (100%) rename {Samples => Samples_old}/Udp/UdpResponderAsync.cpp (100%) rename {Samples => Samples_old}/Udp/UdpServer.cpp (100%) rename {Samples => Samples_old}/cmake/MakeSample.cmake.bak (100%) delete mode 100644 Utils/CMakeLists.txt delete mode 100644 Utils/include/AsyncResponder.h delete mode 100644 Utils/include/Logger.h delete mode 100644 Utils/src/AsyncResponder.cpp delete mode 100644 Utils/src/Logger.cpp rename {samples_new => samples}/CMakeLists.txt (100%) rename {samples_new => samples}/cmake/MakaLauncher.cmake (100%) rename {samples_new => samples}/cmake/MakeSample.cmake (100%) rename {samples_new => samples}/tcp/CMakeLists.txt (100%) rename {samples_new => samples}/tcp/TcpClient.cpp (100%) rename {samples_new => samples}/tcp/TcpRepeater.cpp (100%) rename {samples_new => samples}/tcp/TcpScriptsGenerator.cpp (100%) rename {samples_new => samples}/tcp/TcpServer.cpp (100%) rename {samples_new => samples}/utils/Args.cpp (100%) rename {samples_new => samples}/utils/Args.h (100%) rename {samples_new => samples}/utils/Ask.h (100%) rename {samples_new => samples}/utils/CMakeLists.txt (100%) rename {samples_new => samples}/utils/Names.cpp (100%) rename {samples_new => samples}/utils/Names.h (100%) rename {samples_new => samples}/utils/Respond.h (100%) rename {samples_new => samples}/utils/ScriptGenerator.cpp (100%) rename {samples_new => samples}/utils/ScriptGenerator.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f0150e2..4b71b7e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ project(MinimalCppSocket) add_subdirectory(src) if(BUILD_MinimalCppSocket_SAMPLES) - add_subdirectory(samples_new) + add_subdirectory(samples) endif() if(BUILD_MinimalCppSocket_TESTS) diff --git a/Samples/CMakeLists.txt b/Samples_old/CMakeLists.txt similarity index 100% rename from Samples/CMakeLists.txt rename to Samples_old/CMakeLists.txt diff --git a/Samples/Dummy.cpp b/Samples_old/Dummy.cpp similarity index 100% rename from Samples/Dummy.cpp rename to Samples_old/Dummy.cpp diff --git a/Samples/Launcher.h b/Samples_old/Launcher.h similarity index 100% rename from Samples/Launcher.h rename to Samples_old/Launcher.h diff --git a/Samples/Tcp/CMakeLists.txt b/Samples_old/Tcp/CMakeLists.txt similarity index 100% rename from Samples/Tcp/CMakeLists.txt rename to Samples_old/Tcp/CMakeLists.txt diff --git a/Samples/Tcp/README.md b/Samples_old/Tcp/README.md similarity index 100% rename from Samples/Tcp/README.md rename to Samples_old/Tcp/README.md diff --git a/Samples/Tcp/SampleTcp-01-server-client.cpp b/Samples_old/Tcp/SampleTcp-01-server-client.cpp similarity index 100% rename from Samples/Tcp/SampleTcp-01-server-client.cpp rename to Samples_old/Tcp/SampleTcp-01-server-client.cpp diff --git a/Samples/Tcp/SampleTcp-02-server-clients.cpp b/Samples_old/Tcp/SampleTcp-02-server-clients.cpp similarity index 100% rename from Samples/Tcp/SampleTcp-02-server-clients.cpp rename to Samples_old/Tcp/SampleTcp-02-server-clients.cpp diff --git a/Samples/Tcp/SampleTcp-03-asyncserver-clients.cpp b/Samples_old/Tcp/SampleTcp-03-asyncserver-clients.cpp similarity index 100% rename from Samples/Tcp/SampleTcp-03-asyncserver-clients.cpp rename to Samples_old/Tcp/SampleTcp-03-asyncserver-clients.cpp diff --git a/Samples/Tcp/SampleTcp-04-repeater.cpp b/Samples_old/Tcp/SampleTcp-04-repeater.cpp similarity index 100% rename from Samples/Tcp/SampleTcp-04-repeater.cpp rename to Samples_old/Tcp/SampleTcp-04-repeater.cpp diff --git a/Samples/Tcp/TcpClient.cpp b/Samples_old/Tcp/TcpClient.cpp similarity index 100% rename from Samples/Tcp/TcpClient.cpp rename to Samples_old/Tcp/TcpClient.cpp diff --git a/Samples/Tcp/TcpRepeater.cpp b/Samples_old/Tcp/TcpRepeater.cpp similarity index 100% rename from Samples/Tcp/TcpRepeater.cpp rename to Samples_old/Tcp/TcpRepeater.cpp diff --git a/Samples/Tcp/TcpServer.cpp b/Samples_old/Tcp/TcpServer.cpp similarity index 100% rename from Samples/Tcp/TcpServer.cpp rename to Samples_old/Tcp/TcpServer.cpp diff --git a/Samples/Tcp/TcpServerAsync.cpp b/Samples_old/Tcp/TcpServerAsync.cpp similarity index 100% rename from Samples/Tcp/TcpServerAsync.cpp rename to Samples_old/Tcp/TcpServerAsync.cpp diff --git a/Samples/Udp/CMakeLists.txt b/Samples_old/Udp/CMakeLists.txt similarity index 100% rename from Samples/Udp/CMakeLists.txt rename to Samples_old/Udp/CMakeLists.txt diff --git a/Samples/Udp/README.md b/Samples_old/Udp/README.md similarity index 100% rename from Samples/Udp/README.md rename to Samples_old/Udp/README.md diff --git a/Samples/Udp/SampleUdp-01-connection.cpp b/Samples_old/Udp/SampleUdp-01-connection.cpp similarity index 100% rename from Samples/Udp/SampleUdp-01-connection.cpp rename to Samples_old/Udp/SampleUdp-01-connection.cpp diff --git a/Samples/Udp/SampleUdp-02-asyncconnection.cpp b/Samples_old/Udp/SampleUdp-02-asyncconnection.cpp similarity index 100% rename from Samples/Udp/SampleUdp-02-asyncconnection.cpp rename to Samples_old/Udp/SampleUdp-02-asyncconnection.cpp diff --git a/Samples/Udp/SampleUdp-03-server.cpp b/Samples_old/Udp/SampleUdp-03-server.cpp similarity index 100% rename from Samples/Udp/SampleUdp-03-server.cpp rename to Samples_old/Udp/SampleUdp-03-server.cpp diff --git a/Samples/Udp/UdpAsker.cpp b/Samples_old/Udp/UdpAsker.cpp similarity index 100% rename from Samples/Udp/UdpAsker.cpp rename to Samples_old/Udp/UdpAsker.cpp diff --git a/Samples/Udp/UdpResponder.cpp b/Samples_old/Udp/UdpResponder.cpp similarity index 100% rename from Samples/Udp/UdpResponder.cpp rename to Samples_old/Udp/UdpResponder.cpp diff --git a/Samples/Udp/UdpResponderAsync.cpp b/Samples_old/Udp/UdpResponderAsync.cpp similarity index 100% rename from Samples/Udp/UdpResponderAsync.cpp rename to Samples_old/Udp/UdpResponderAsync.cpp diff --git a/Samples/Udp/UdpServer.cpp b/Samples_old/Udp/UdpServer.cpp similarity index 100% rename from Samples/Udp/UdpServer.cpp rename to Samples_old/Udp/UdpServer.cpp diff --git a/Samples/cmake/MakeSample.cmake.bak b/Samples_old/cmake/MakeSample.cmake.bak similarity index 100% rename from Samples/cmake/MakeSample.cmake.bak rename to Samples_old/cmake/MakeSample.cmake.bak diff --git a/Utils/CMakeLists.txt b/Utils/CMakeLists.txt deleted file mode 100644 index 18b3a6cc..00000000 --- a/Utils/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -set(PROJECT_SHORTNAME "Utils") - -MakeLibrary(${PROJECT_SHORTNAME} include) - -if(COMPILE_ASYNCH) - target_link_libraries(${PROJECT_SHORTNAME} - PUBLIC - Asynch-Cross-Socket - ) -else() - target_link_libraries(${PROJECT_SHORTNAME} - PUBLIC - Synch-Cross-Socket - ) -endif() - -if(COMPILE_TYPED) - target_link_libraries(${PROJECT_SHORTNAME} - PUBLIC - Typed-Cross-Socket - ) -endif() diff --git a/Utils/include/AsyncResponder.h b/Utils/include/AsyncResponder.h deleted file mode 100644 index 065dc65d..00000000 --- a/Utils/include/AsyncResponder.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef SAMPLE_ASYNCRESPONDER_H -#define SAMPLE_ASYNCRESPONDER_H - -#ifdef ASYNCH_ENABLED -#include -#include -#include - -namespace sck::sample { - class AsyncResponder - : public sck::async::AsyncMessanger - , protected sck::async::MessangerListener - , protected sck::async::ErrorListener - , public Logger { - public: - AsyncResponder(std::unique_ptr socket); - - private: - void handle(const std::pair& message) final; - - void handle(const sck::Error& error) final; - - void handle(const std::exception& error) final; - }; -} -#endif - -#endif \ No newline at end of file diff --git a/Utils/include/Logger.h b/Utils/include/Logger.h deleted file mode 100644 index 1376b5cb..00000000 --- a/Utils/include/Logger.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifndef SAMPLE_LOGGER_H -#define SAMPLE_LOGGER_H - -#include -#include -#include - -namespace sck::sample { - class Logger { - public: - Logger(const std::string& logName); - - void log(const std::string& message) const; - - template - void log(Slices ... slices) const { - std::stringstream stream; - pack(stream, slices...); - this->log(stream.str()); - }; - - Logger& operator=(const Logger& ) = delete; - - private: - Logger(const Logger& o); - - template - void pack(std::stringstream& stream, const std::string& slice, Slices ... remaining) const { - stream << slice; - pack(stream, remaining...); - }; - - template - void pack(std::stringstream& stream, const std::string& slice) const { - stream << slice; - }; - - const std::string logName; - }; -} - -#endif diff --git a/Utils/src/AsyncResponder.cpp b/Utils/src/AsyncResponder.cpp deleted file mode 100644 index 72f1f4c2..00000000 --- a/Utils/src/AsyncResponder.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#ifdef ASYNCH_ENABLED -#include - -namespace sck::sample { - AsyncResponder::AsyncResponder(std::unique_ptr socket) - : AsyncMessanger(std::move(socket), 1000) - , Logger("AsynchResponder") { - - this->sck::async::MessageTalker::resetListener(this); - this->sck::async::ErrorTalker::resetListener(this); - } - - void AsyncResponder::handle(const std::pair& message) { - std::string recStr(message.first, message.second); - const std::string surname = NamesMap::getSurname(recStr); - this->log("request: ", recStr, " response: ", surname); - this->messPtr->send({ surname.data(), surname.size() }); - } - - void AsyncResponder::handle(const sck::Error& error) { - //this->close(); - }; - - void AsyncResponder::handle(const std::exception& error) { - //this->close(); - }; -} - -#endif diff --git a/Utils/src/Logger.cpp b/Utils/src/Logger.cpp deleted file mode 100644 index f05b966b..00000000 --- a/Utils/src/Logger.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -#include - -namespace sck::sample { - static std::map namesUsed; - - std::string makeName(const std::string& logName) { - auto it = namesUsed.find(logName); - if (it == namesUsed.end()) { - it = namesUsed.emplace(logName, 1).first; - } - else { - ++it->second; - } - return logName + std::to_string(it->second); - } - Logger::Logger(const std::string& logName) - : logName(makeName(logName)) { - } - - static std::mutex coutMtx; - - void Logger::log(const std::string& message) const { - std::lock_guard coutLk(coutMtx); - std::cout << this->logName << " : " << message << std::endl; - } -} diff --git a/samples_new/CMakeLists.txt b/samples/CMakeLists.txt similarity index 100% rename from samples_new/CMakeLists.txt rename to samples/CMakeLists.txt diff --git a/samples_new/cmake/MakaLauncher.cmake b/samples/cmake/MakaLauncher.cmake similarity index 100% rename from samples_new/cmake/MakaLauncher.cmake rename to samples/cmake/MakaLauncher.cmake diff --git a/samples_new/cmake/MakeSample.cmake b/samples/cmake/MakeSample.cmake similarity index 100% rename from samples_new/cmake/MakeSample.cmake rename to samples/cmake/MakeSample.cmake diff --git a/samples_new/tcp/CMakeLists.txt b/samples/tcp/CMakeLists.txt similarity index 100% rename from samples_new/tcp/CMakeLists.txt rename to samples/tcp/CMakeLists.txt diff --git a/samples_new/tcp/TcpClient.cpp b/samples/tcp/TcpClient.cpp similarity index 100% rename from samples_new/tcp/TcpClient.cpp rename to samples/tcp/TcpClient.cpp diff --git a/samples_new/tcp/TcpRepeater.cpp b/samples/tcp/TcpRepeater.cpp similarity index 100% rename from samples_new/tcp/TcpRepeater.cpp rename to samples/tcp/TcpRepeater.cpp diff --git a/samples_new/tcp/TcpScriptsGenerator.cpp b/samples/tcp/TcpScriptsGenerator.cpp similarity index 100% rename from samples_new/tcp/TcpScriptsGenerator.cpp rename to samples/tcp/TcpScriptsGenerator.cpp diff --git a/samples_new/tcp/TcpServer.cpp b/samples/tcp/TcpServer.cpp similarity index 100% rename from samples_new/tcp/TcpServer.cpp rename to samples/tcp/TcpServer.cpp diff --git a/samples_new/utils/Args.cpp b/samples/utils/Args.cpp similarity index 100% rename from samples_new/utils/Args.cpp rename to samples/utils/Args.cpp diff --git a/samples_new/utils/Args.h b/samples/utils/Args.h similarity index 100% rename from samples_new/utils/Args.h rename to samples/utils/Args.h diff --git a/samples_new/utils/Ask.h b/samples/utils/Ask.h similarity index 100% rename from samples_new/utils/Ask.h rename to samples/utils/Ask.h diff --git a/samples_new/utils/CMakeLists.txt b/samples/utils/CMakeLists.txt similarity index 100% rename from samples_new/utils/CMakeLists.txt rename to samples/utils/CMakeLists.txt diff --git a/samples_new/utils/Names.cpp b/samples/utils/Names.cpp similarity index 100% rename from samples_new/utils/Names.cpp rename to samples/utils/Names.cpp diff --git a/samples_new/utils/Names.h b/samples/utils/Names.h similarity index 100% rename from samples_new/utils/Names.h rename to samples/utils/Names.h diff --git a/samples_new/utils/Respond.h b/samples/utils/Respond.h similarity index 100% rename from samples_new/utils/Respond.h rename to samples/utils/Respond.h diff --git a/samples_new/utils/ScriptGenerator.cpp b/samples/utils/ScriptGenerator.cpp similarity index 100% rename from samples_new/utils/ScriptGenerator.cpp rename to samples/utils/ScriptGenerator.cpp diff --git a/samples_new/utils/ScriptGenerator.h b/samples/utils/ScriptGenerator.h similarity index 100% rename from samples_new/utils/ScriptGenerator.h rename to samples/utils/ScriptGenerator.h From 53e0b13578516fa82b8e307c6f57285e6be6c405 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 00:11:45 +0100 Subject: [PATCH 171/228] samples --- TODO | 2 + samples/CMakeLists.txt | 2 +- samples/udp/CMakeLists.txt | 5 ++ samples/udp/UdpAsker.cpp | 0 samples/udp/UdpResponder.cpp | 0 samples/udp/UdpScriptsGenerator.cpp | 85 +++++++++++++++++++++++++++++ 6 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 samples/udp/CMakeLists.txt create mode 100644 samples/udp/UdpAsker.cpp create mode 100644 samples/udp/UdpResponder.cpp create mode 100644 samples/udp/UdpScriptsGenerator.cpp diff --git a/TODO b/TODO index 3e02c509..b84e2f1d 100644 --- a/TODO +++ b/TODO @@ -27,3 +27,5 @@ stressare che si possono fare in maniera safe send concorrenti per udp add test for udp concurrent send to error se os non e' win32 o linux + +mettere bene in evidenza il fatto che udp possono essere usati da connessi o no diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 404e6d2c..5a78bd58 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -6,4 +6,4 @@ include(cmake/MakaLauncher.cmake) add_subdirectory(utils) add_subdirectory(tcp) -# add_subdirectory(udp) +add_subdirectory(udp) diff --git a/samples/udp/CMakeLists.txt b/samples/udp/CMakeLists.txt new file mode 100644 index 00000000..19eff40c --- /dev/null +++ b/samples/udp/CMakeLists.txt @@ -0,0 +1,5 @@ +MakeSample(UdpAsker) +MakeSample(UdpResponder) + +MakeLauncher(UdpScriptsGenerator) +add_dependencies(UdpScriptsGenerator UdpAsker UdpResponder) diff --git a/samples/udp/UdpAsker.cpp b/samples/udp/UdpAsker.cpp new file mode 100644 index 00000000..e69de29b diff --git a/samples/udp/UdpResponder.cpp b/samples/udp/UdpResponder.cpp new file mode 100644 index 00000000..e69de29b diff --git a/samples/udp/UdpScriptsGenerator.cpp b/samples/udp/UdpScriptsGenerator.cpp new file mode 100644 index 00000000..2beb914a --- /dev/null +++ b/samples/udp/UdpScriptsGenerator.cpp @@ -0,0 +1,85 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include +using namespace std; + +int main() { + { + // 1 server 1 client + const std::string sample_name = "tcp_server_client"; + MinimalSocket::samples::ScriptGenerator generator; + + const std::string port = "35998"; + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpServer", + MinimalSocket::samples::ProcessArgs{{"port", port}, {"clients", "1"}}}); + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpClient", MinimalSocket::samples::ProcessArgs{{"port", port}}}); + + cout << "generating " << sample_name << endl; + generator.generate(sample_name); + } + + { + // 1 server 2 clients + const std::string sample_name = "tcp_server_2_clients"; + MinimalSocket::samples::ScriptGenerator generator; + + const std::string port = "35998"; + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpServer", + MinimalSocket::samples::ProcessArgs{{"port", port}, {"clients", "1"}}}); + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpClient", MinimalSocket::samples::ProcessArgs{{"port", port}}}); + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpClient", + MinimalSocket::samples::ProcessArgs{{"port", port}, {"rate", "300"}}}); + + cout << "generating " << sample_name << endl; + generator.generate(sample_name); + } + + { + // repeaters + const std::size_t repeaters = 1; + const std::string sample_name = + "tcp_chain_size_" + std::to_string(repeaters + 2); + MinimalSocket::samples::ScriptGenerator generator; + + std::size_t port = 35998; + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpServer", MinimalSocket::samples::ProcessArgs{ + {"port", std::to_string(port)}, {"clients", "1"}}}); + + for (std::size_t r = 0; r < repeaters; ++r) { + auto new_port = port + 10; + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpRepeater", MinimalSocket::samples::ProcessArgs{ + {"port", std::to_string(new_port)}, + {"prec_port", std::to_string(port)}}}); + port = new_port; + } + + generator.add(MinimalSocket::samples::ProcessAndArgs{ + "TcpClient", + MinimalSocket::samples::ProcessArgs{{"port", std::to_string(port)}}}); + + cout << "generating " << sample_name << endl; + generator.generate(sample_name); + } + + return EXIT_SUCCESS; +} \ No newline at end of file From cafd41cb4bcde88db3c784d0eb636420c713fef4 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 00:30:22 +0100 Subject: [PATCH 172/228] samples --- samples/udp/UdpAsker.cpp | 44 ++++++++++++++++++ samples/udp/UdpResponder.cpp | 40 +++++++++++++++++ samples/udp/UdpScriptsGenerator.cpp | 69 +++++++++++++++-------------- 3 files changed, 119 insertions(+), 34 deletions(-) diff --git a/samples/udp/UdpAsker.cpp b/samples/udp/UdpAsker.cpp index e69de29b..7b0f2083 100644 --- a/samples/udp/UdpAsker.cpp +++ b/samples/udp/UdpAsker.cpp @@ -0,0 +1,44 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include +#include + +using namespace std; + +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( + std::atoi(options->getValue("port").c_str())); + const auto port_this = static_cast( + std::atoi(options->getValue("port_this").c_str())); + const auto rate = std::chrono::milliseconds{ + std::atoi(options->getValue("rate", "100").c_str())}; + const auto cycles = std::atoi(options->getValue("cycles", "0").c_str()); + + const MinimalSocket::Address remote_address(remote_host, remote_port); + MinimalSocket::udp::UdpBinded asker(port_this, remote_address.getFamily()); + + if (!asker.open()) { + cout << "Failed to reserve port " << port_this << endl; + return EXIT_FAILURE; + } + + // if (0 == cycles) { + // MinimalSocket::samples::ask_forever(client, rate); + // } else { + // MinimalSocket::samples::ask(client, cycles, rate); + // } + + // the connection will be close when destroying the client object + return EXIT_SUCCESS; +} diff --git a/samples/udp/UdpResponder.cpp b/samples/udp/UdpResponder.cpp index e69de29b..8a9b4f25 100644 --- a/samples/udp/UdpResponder.cpp +++ b/samples/udp/UdpResponder.cpp @@ -0,0 +1,40 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include +#include + +using namespace std; + +int main(const int argc, const char **argv) { + cout << "----------------------- Udp responder -----------------------" + << endl; + PARSE_ARGS + + const auto port_this = static_cast( + std::atoi(options->getValue("port_this").c_str())); + const auto family = + MinimalSocket::samples::to_family(options->getValue("family", "v4")); + const bool connect = options->getValue("connect", "no") == "yes"; + + MinimalSocket::udp::UdpBinded responder(port_this, family); + + if (!responder.open()) { + cout << "Failed to reserve port " << port_this << endl; + return EXIT_FAILURE; + } + + if (connect) { + // connect to first sending a request + + } else { + } + + return EXIT_SUCCESS; +} diff --git a/samples/udp/UdpScriptsGenerator.cpp b/samples/udp/UdpScriptsGenerator.cpp index 2beb914a..7593f495 100644 --- a/samples/udp/UdpScriptsGenerator.cpp +++ b/samples/udp/UdpScriptsGenerator.cpp @@ -12,70 +12,71 @@ using namespace std; int main() { { - // 1 server 1 client - const std::string sample_name = "tcp_server_client"; + // 1 responder 1 asker + const std::string sample_name = "udp_responder_asker"; MinimalSocket::samples::ScriptGenerator generator; - const std::string port = "35998"; + const std::size_t port_asker = 36995; + const std::size_t port_responder = port_asker + 10; generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpServer", - MinimalSocket::samples::ProcessArgs{{"port", port}, {"clients", "1"}}}); + "UdpResponder", MinimalSocket::samples::ProcessArgs{ + {"port_this", std::to_string(port_responder)}}}); generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpClient", MinimalSocket::samples::ProcessArgs{{"port", port}}}); + "UdpAsker", MinimalSocket::samples::ProcessArgs{ + {"port", std::to_string(port_responder)}, + {"port_this", std::to_string(port_asker)}}}); cout << "generating " << sample_name << endl; generator.generate(sample_name); } { - // 1 server 2 clients - const std::string sample_name = "tcp_server_2_clients"; + // 1 connecting responder 1 asker + const std::string sample_name = "udp_connecting_responder_asker"; MinimalSocket::samples::ScriptGenerator generator; - const std::string port = "35998"; + const std::size_t port_asker = 36995; + const std::size_t port_responder = port_asker + 10; generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpServer", - MinimalSocket::samples::ProcessArgs{{"port", port}, {"clients", "1"}}}); + "UdpResponder", MinimalSocket::samples::ProcessArgs{ + {"port_this", std::to_string(port_responder)}, + {"connect", "yes"}}}); generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpClient", MinimalSocket::samples::ProcessArgs{{"port", port}}}); - - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpClient", - MinimalSocket::samples::ProcessArgs{{"port", port}, {"rate", "300"}}}); + "UdpAsker", MinimalSocket::samples::ProcessArgs{ + {"port", std::to_string(port_responder)}, + {"port_this", std::to_string(port_asker)}}}); cout << "generating " << sample_name << endl; generator.generate(sample_name); } { - // repeaters - const std::size_t repeaters = 1; - const std::string sample_name = - "tcp_chain_size_" + std::to_string(repeaters + 2); + // 1 responder 2 askers + const std::string sample_name = "udp_responder_2_askers"; MinimalSocket::samples::ScriptGenerator generator; - std::size_t port = 35998; + 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(MinimalSocket::samples::ProcessAndArgs{ + "UdpResponder", MinimalSocket::samples::ProcessArgs{ + {"port_this", std::to_string(port_responder)}}}); generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpServer", MinimalSocket::samples::ProcessArgs{ - {"port", std::to_string(port)}, {"clients", "1"}}}); - - for (std::size_t r = 0; r < repeaters; ++r) { - auto new_port = port + 10; - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpRepeater", MinimalSocket::samples::ProcessArgs{ - {"port", std::to_string(new_port)}, - {"prec_port", std::to_string(port)}}}); - port = new_port; - } + "UdpAsker", MinimalSocket::samples::ProcessArgs{ + {"port", std::to_string(port_responder)}, + {"port_this", std::to_string(port_asker_1)}}}); generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpClient", - MinimalSocket::samples::ProcessArgs{{"port", std::to_string(port)}}}); + "UdpAsker", MinimalSocket::samples::ProcessArgs{ + {"port", std::to_string(port_responder)}, + {"port_this", std::to_string(port_asker_2)}, + {"rate", "300"}}}); cout << "generating " << sample_name << endl; generator.generate(sample_name); From cc0f9a9362ff556362ad21e78015c2f4d6b53961 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 00:43:37 +0100 Subject: [PATCH 173/228] refactoring --- src/header/MinimalSocket/udp/UdpSocket.h | 18 +++++++++------- src/src/udp/UdpSocket.cpp | 26 ++++++++++++++---------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 47e0514e..d9fe332d 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -38,10 +38,12 @@ class UdpBinded : public SenderTo, // leave this socket empty after success UdpConnected connect(const Address &remote_address); - UdpConnected connect(); // to first sending 1 byte, inf timeout + UdpConnected connect(std::string *initial_message = + nullptr); // to first sending 1 byte, inf timeout std::optional - connect(const Timeout &timeout); // to first sending 1 byte + connect(const Timeout &timeout, + std::string *initial_message = nullptr); // to first sending 1 byte protected: void open_() override; @@ -67,12 +69,14 @@ class UdpConnected : public Sender, void open_() override; }; +// to first sending 1 byte UdpConnected -makeUdpConnectedToUnknown(const Port &port, - const AddressFamily &accepted_connection_family); - -std::optional makeUdpConnectedToUnknown(const Port &port, const AddressFamily &accepted_connection_family, - const Timeout &timeout); + std::string *initial_message = nullptr); + +// to first sending 1 byte +std::optional makeUdpConnectedToUnknown( + const Port &port, const AddressFamily &accepted_connection_family, + const Timeout &timeout, std::string *initial_message = nullptr); } // namespace MinimalSocket::udp diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index 94ef7dc2..0f0ac606 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -47,16 +47,20 @@ UdpConnected UdpBinded::connect(const Address &remote_address) { return std::move(result); } -UdpConnected UdpBinded::connect() { - auto result = this->connect(NULL_TIMEOUT); +UdpConnected UdpBinded::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::optional UdpBinded::connect(const Timeout &timeout, + std::string *initial_message) { auto maybe_received = this->receive(MAX_UDP_RECV_MESSAGE, timeout); if (!maybe_received) { return std::nullopt; } + if (nullptr != initial_message) { + *initial_message = std::move(maybe_received->received_message); + } return connect(maybe_received->sender); } @@ -93,21 +97,21 @@ UdpBinded UdpConnected::disconnect() { UdpConnected makeUdpConnectedToUnknown(const Port &port, - const AddressFamily &accepted_connection_family) { - auto result = - makeUdpConnectedToUnknown(port, accepted_connection_family, NULL_TIMEOUT); + const AddressFamily &accepted_connection_family, + std::string *initial_message) { + auto result = makeUdpConnectedToUnknown(port, accepted_connection_family, + NULL_TIMEOUT, initial_message); return std::move(result.value()); } -std::optional -makeUdpConnectedToUnknown(const Port &port, - const AddressFamily &accepted_connection_family, - const Timeout &timeout) { +std::optional makeUdpConnectedToUnknown( + const Port &port, const AddressFamily &accepted_connection_family, + const Timeout &timeout, std::string *initial_message) { UdpBinded primal_socket(port, accepted_connection_family); auto success = primal_socket.open(); if (!success) { return std::nullopt; } - return primal_socket.connect(timeout); + return primal_socket.connect(timeout, initial_message); } } // namespace MinimalSocket::udp From 7524a8a0828e69515ba57c67afbf93f43914119d Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 01:14:49 +0100 Subject: [PATCH 174/228] samples --- samples/tcp/TcpClient.cpp | 7 +----- samples/udp/UdpAsker.cpp | 8 +----- samples/udp/UdpResponder.cpp | 13 +++++++++- samples/utils/Ask.h | 48 +++++++++++++++++++----------------- samples/utils/Respond.h | 35 +++++++++++++------------- 5 files changed, 58 insertions(+), 53 deletions(-) diff --git a/samples/tcp/TcpClient.cpp b/samples/tcp/TcpClient.cpp index b3907811..eab67d45 100644 --- a/samples/tcp/TcpClient.cpp +++ b/samples/tcp/TcpClient.cpp @@ -21,7 +21,6 @@ int main(const int argc, const char **argv) { std::atoi(options->getValue("port").c_str())); const auto rate = std::chrono::milliseconds{ std::atoi(options->getValue("rate", "100").c_str())}; - const auto cycles = std::atoi(options->getValue("cycles", "0").c_str()); const MinimalSocket::Address server_address(server_host, server_port); MinimalSocket::tcp::TcpClient client(server_address); @@ -31,11 +30,7 @@ int main(const int argc, const char **argv) { return EXIT_FAILURE; } - if (0 == cycles) { - MinimalSocket::samples::ask_forever(client, rate); - } else { - MinimalSocket::samples::ask(client, cycles, rate); - } + MinimalSocket::samples::ask_forever(client, rate); // the connection will be close when destroying the client object return EXIT_SUCCESS; diff --git a/samples/udp/UdpAsker.cpp b/samples/udp/UdpAsker.cpp index 7b0f2083..fcd8cd09 100644 --- a/samples/udp/UdpAsker.cpp +++ b/samples/udp/UdpAsker.cpp @@ -23,7 +23,6 @@ int main(const int argc, const char **argv) { std::atoi(options->getValue("port_this").c_str())); const auto rate = std::chrono::milliseconds{ std::atoi(options->getValue("rate", "100").c_str())}; - const auto cycles = std::atoi(options->getValue("cycles", "0").c_str()); const MinimalSocket::Address remote_address(remote_host, remote_port); MinimalSocket::udp::UdpBinded asker(port_this, remote_address.getFamily()); @@ -33,12 +32,7 @@ int main(const int argc, const char **argv) { return EXIT_FAILURE; } - // if (0 == cycles) { - // MinimalSocket::samples::ask_forever(client, rate); - // } else { - // MinimalSocket::samples::ask(client, cycles, rate); - // } + MinimalSocket::samples::ask_forever(asker, remote_address, rate); - // the connection will be close when destroying the client object return EXIT_SUCCESS; } diff --git a/samples/udp/UdpResponder.cpp b/samples/udp/UdpResponder.cpp index 8a9b4f25..9ff26ab4 100644 --- a/samples/udp/UdpResponder.cpp +++ b/samples/udp/UdpResponder.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include using namespace std; @@ -32,8 +32,19 @@ int main(const int argc, const char **argv) { if (connect) { // connect to first sending a request + std::string first_request; + auto connected_responder = responder.connect(&first_request); + const auto &first_response = + MinimalSocket::samples::NamesCircularIterator::NAMES_SURNAMES + .find(first_request) + ->second; + connected_responder.send(first_response); + + MinimalSocket::samples::respond_forever(connected_responder); } else { + // use as un-connected udp + MinimalSocket::samples::respond_forever(responder); } return EXIT_SUCCESS; diff --git a/samples/utils/Ask.h b/samples/utils/Ask.h index a3b78eed..b1b7d66e 100644 --- a/samples/utils/Ask.h +++ b/samples/utils/Ask.h @@ -7,41 +7,45 @@ #pragma once +#include + #include + #include #include #include namespace MinimalSocket::samples { -namespace { -template -void ask(SocketT &channel, NamesCircularIterator &iterator, - const std::chrono::milliseconds &rate) { - // send name of this person - std::cout << "Sending: " << iterator.current()->first; - channel.send(iterator.current()->first); - // expect to get back the corresponding surname - auto response = channel.receive(500); - std::cout << " , got as response: " << response << std::endl; - iterator.next(); - std::this_thread::sleep_for(rate); -} -} // namespace - template -void ask(SocketT &channel, const std::size_t times, - const std::chrono::milliseconds &rate) { +void ask_forever(SocketT &channel, const std::chrono::milliseconds &rate) { NamesCircularIterator iterator; - for (std::size_t k = 0; k < times; ++k) { - ask(channel, iterator, rate); + while (true) { + // send name of this person + std::cout << "Sending: " << iterator.current()->first; + channel.send(iterator.current()->first); + // expect to get back the corresponding surname + auto response = channel.receive(500); + std::cout << " , got as response: " << response << std::endl; + iterator.next(); + std::this_thread::sleep_for(rate); } } -template -void ask_forever(SocketT &channel, const std::chrono::milliseconds &rate) { +void ask_forever(MinimalSocket::udp::UdpBinded &channel, + const MinimalSocket::Address &target, + const std::chrono::milliseconds &rate) { NamesCircularIterator iterator; while (true) { - ask(channel, iterator, rate); + // send name of this person + std::cout << "Sending: " << iterator.current()->first; + channel.sendTo(iterator.current()->first, target); + // expect to get back the corresponding surname + auto response = channel.receive(500); + std::cout << "From " << MinimalSocket::to_string(response->sender) + << " , got as response: " << response->received_message + << std::endl; + iterator.next(); + std::this_thread::sleep_for(rate); } } } // namespace MinimalSocket::samples diff --git a/samples/utils/Respond.h b/samples/utils/Respond.h index 3d29527e..67af28c6 100644 --- a/samples/utils/Respond.h +++ b/samples/utils/Respond.h @@ -7,30 +7,31 @@ #pragma once +#include + #include namespace MinimalSocket::samples { -namespace { -template void respond_one(SocketT &channel) { - // receive name to search - auto request = channel.receive(500); - // respond with corresponding surname - const auto &response = - NamesCircularIterator::NAMES_SURNAMES.find(request)->second; - channel.send(response); -} -} // namespace - -template -void respond(SocketT &channel, const std::size_t times) { - for (std::size_t k = 0; k < times; ++k) { - respond_one(channel); +template void respond_forever(SocketT &channel) { + while (true) { + // receive name to search + auto request = channel.receive(500); + // respond with corresponding surname + const auto &response = + NamesCircularIterator::NAMES_SURNAMES.find(request)->second; + channel.send(response); } } -template void respond_forever(SocketT &channel) { +void respond_forever(MinimalSocket::udp::UdpBinded &channel) { while (true) { - respond_one(channel); + // receive name to search + auto request = channel.receive(500); + // respond with corresponding surname + const auto &response = + NamesCircularIterator::NAMES_SURNAMES.find(request->received_message) + ->second; + channel.sendTo(response, request->sender); } } } // namespace MinimalSocket::samples From 16a36788fdd5daa877e83291ff00f006b1d38ed3 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 01:21:38 +0100 Subject: [PATCH 175/228] samples --- Samples_old/CMakeLists.txt | 9 - Samples_old/Dummy.cpp | 177 ------------------ Samples_old/Launcher.h | 154 --------------- Samples_old/Tcp/CMakeLists.txt | 23 --- Samples_old/Tcp/README.md | 28 --- .../Tcp/SampleTcp-01-server-client.cpp | 11 -- .../Tcp/SampleTcp-02-server-clients.cpp | 12 -- .../Tcp/SampleTcp-03-asyncserver-clients.cpp | 12 -- Samples_old/Tcp/SampleTcp-04-repeater.cpp | 12 -- Samples_old/Tcp/TcpClient.cpp | 56 ------ Samples_old/Tcp/TcpRepeater.cpp | 54 ------ Samples_old/Tcp/TcpServer.cpp | 68 ------- Samples_old/Tcp/TcpServerAsync.cpp | 65 ------- Samples_old/Udp/CMakeLists.txt | 21 --- Samples_old/Udp/README.md | 20 -- Samples_old/Udp/SampleUdp-01-connection.cpp | 11 -- .../Udp/SampleUdp-02-asyncconnection.cpp | 11 -- Samples_old/Udp/SampleUdp-03-server.cpp | 11 -- Samples_old/Udp/UdpAsker.cpp | 69 ------- Samples_old/Udp/UdpResponder.cpp | 52 ----- Samples_old/Udp/UdpResponderAsync.cpp | 55 ------ Samples_old/Udp/UdpServer.cpp | 34 ---- Samples_old/cmake/MakeSample.cmake.bak | 12 -- samples/tcp/TcpScriptsGenerator.cpp | 8 +- samples/udp/UdpScriptsGenerator.cpp | 6 +- 25 files changed, 7 insertions(+), 984 deletions(-) delete mode 100644 Samples_old/CMakeLists.txt delete mode 100644 Samples_old/Dummy.cpp delete mode 100644 Samples_old/Launcher.h delete mode 100644 Samples_old/Tcp/CMakeLists.txt delete mode 100644 Samples_old/Tcp/README.md delete mode 100644 Samples_old/Tcp/SampleTcp-01-server-client.cpp delete mode 100644 Samples_old/Tcp/SampleTcp-02-server-clients.cpp delete mode 100644 Samples_old/Tcp/SampleTcp-03-asyncserver-clients.cpp delete mode 100644 Samples_old/Tcp/SampleTcp-04-repeater.cpp delete mode 100644 Samples_old/Tcp/TcpClient.cpp delete mode 100644 Samples_old/Tcp/TcpRepeater.cpp delete mode 100644 Samples_old/Tcp/TcpServer.cpp delete mode 100644 Samples_old/Tcp/TcpServerAsync.cpp delete mode 100644 Samples_old/Udp/CMakeLists.txt delete mode 100644 Samples_old/Udp/README.md delete mode 100644 Samples_old/Udp/SampleUdp-01-connection.cpp delete mode 100644 Samples_old/Udp/SampleUdp-02-asyncconnection.cpp delete mode 100644 Samples_old/Udp/SampleUdp-03-server.cpp delete mode 100644 Samples_old/Udp/UdpAsker.cpp delete mode 100644 Samples_old/Udp/UdpResponder.cpp delete mode 100644 Samples_old/Udp/UdpResponderAsync.cpp delete mode 100644 Samples_old/Udp/UdpServer.cpp delete mode 100644 Samples_old/cmake/MakeSample.cmake.bak diff --git a/Samples_old/CMakeLists.txt b/Samples_old/CMakeLists.txt deleted file mode 100644 index 472f1567..00000000 --- a/Samples_old/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -option(BUILD_SAMPLES_TCP "Tcp samples enabled (ON) or not (OFF)" ON) -if(BUILD_SAMPLES_TCP) - add_subdirectory(Tcp) -endif() - -option(BUILD_SAMPLES_UDP "Udp samples enabled (ON) or not (OFF)" ON) -if(BUILD_SAMPLES_UDP) - add_subdirectory(Udp) -endif() diff --git a/Samples_old/Dummy.cpp b/Samples_old/Dummy.cpp deleted file mode 100644 index 93337f7f..00000000 --- a/Samples_old/Dummy.cpp +++ /dev/null @@ -1,177 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -std::optional toSocketAddressIpv4_NEW(const std::string& host, const std::uint16_t& port) { - std::optional result; - result.emplace(); - // set everything to 0 first - ::memset(&result.value(), 0, sizeof(SOCKADDR_IN)); - result->sin_family = AF_INET; - result->sin_port = htons(port); - - { - addrinfo* res, hints = addrinfo{}; - hints.ai_family = AF_INET; - hints.ai_socktype = 0; - hints.ai_protocol = 0; - - int gai_err = ::getaddrinfo(host.c_str(), NULL, &hints, &res); - if (gai_err != 0) { - return std::nullopt; - } - - auto ipv4 = reinterpret_cast(res->ai_addr); - ::freeaddrinfo(res); - result->sin_addr.s_addr = ipv4->sin_addr.s_addr; - } - - return result; -} - -std::optional toSocketAddressIpv4_OLD(const std::string& host, const std::uint16_t& port) { - auto tryConversion = [](SOCKADDR_IN& recipient, const std::string& host, const std::uint16_t& port) -> bool { - addrinfo* res, hints = addrinfo{}; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - - int gai_err = ::getaddrinfo(host.c_str(), NULL, &hints, &res); - if (gai_err != 0) { - return false; - } - - auto ipv4 = reinterpret_cast(res->ai_addr); - ::freeaddrinfo(res); - recipient.sin_addr.s_addr = ipv4->sin_addr.s_addr; - return true; - }; - - std::optional resolved; - resolved.emplace(); - // set everything to 0 first - ::memset(&(*resolved), 0, sizeof(SOCKADDR_IN)); - resolved->sin_family = AF_INET; - if (!tryConversion(*resolved, host, port)) { - return std::nullopt; - } - resolved->sin_port = htons(port); - return resolved; - } - -void send(const SOCKET& sock, const std::string& message) { - int sentBytes = ::send(sock, message.data(), - static_cast(message.size()), 0); - if (sentBytes == SOCKET_ERROR) { - throw std::runtime_error{ "" }; - } -} - -std::string receive(const SOCKET& sock, const std::size_t max_size) { - std::string message; - message.resize(max_size); - int recvBytes = ::recv(sock, message.data(), - static_cast(message.size()), 0); - if (recvBytes == SOCKET_ERROR) { - throw std::runtime_error{ "" }; - } - if (recvBytes > message.size()) { - throw std::runtime_error{ "" }; - } - message.resize(recvBytes); - return message; -} - -int main() { - WSADATA wsa; - const std::array version = { 2, 0 }; - const BYTE version_major = static_cast(version[0]); - const BYTE version_minor = static_cast(version[1]); - auto result = WSAStartup(MAKEWORD(version_major, version_minor), &wsa); - if (0 != result) { - throw std::runtime_error{ "" }; - } - - const std::uint16_t port = 35356; - - static constexpr std::size_t MAX_POSSIBLE_ADDRESS_SIZE = - std::max(sizeof(SOCKADDR_IN), sizeof(SOCKADDR_IN6)); - -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - // server - auto acceptor_socket = ::socket(static_cast(AF_INET), SOCK_STREAM, 0); - if (acceptor_socket == INVALID_SOCKET) { - throw std::runtime_error{ "" }; - } - // bind port - { - int reusePortOptVal = 1; - ::setsockopt(acceptor_socket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reusePortOptVal), sizeof(int)); - SOCKADDR_IN addr; - ::memset(&addr, 0, sizeof(SOCKADDR_IN)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = ADDR_ANY; - if (::bind(acceptor_socket, reinterpret_cast(&addr), - sizeof(SOCKADDR_IN)) == SOCKET_ERROR) { - throw std::runtime_error{ "" }; - } - } - // listen - if (::listen(acceptor_socket, static_cast(50)) == SOCKET_ERROR) { - throw std::runtime_error{ "" }; - } -#pragma omp barrier - // accept - SOCKET accepted_socket = INVALID_SOCKET; - { - char acceptedClientAddress[MAX_POSSIBLE_ADDRESS_SIZE]; - int acceptedClientAddress_length = MAX_POSSIBLE_ADDRESS_SIZE; - - accepted_socket = - ::accept(acceptor_socket, - reinterpret_cast(&acceptedClientAddress[0]), - &acceptedClientAddress_length); - if (accepted_socket == INVALID_SOCKET) { - throw std::runtime_error{ "" }; - } - } - // receive - auto got = receive(accepted_socket, 100); - // send back - got = got + got; - send(accepted_socket, got); - } - else { - // client - auto client_socket = ::socket(static_cast(AF_INET), SOCK_STREAM, 0); - if (client_socket == INVALID_SOCKET) { - throw std::runtime_error{ "" }; - } - auto addr = toSocketAddressIpv4_NEW("127.0.0.1", port); - if (!addr) { - throw std::runtime_error{ "" }; - } -#pragma omp barrier - if (::connect(client_socket, reinterpret_cast(&(*addr)), - sizeof(SOCKADDR_IN)) == SOCKET_ERROR) { - throw std::runtime_error{ "" }; - } - // send back - send(client_socket, "hello_"); - // receive - auto got = receive(client_socket, 100); - std::cout << got << std::endl; - } - } - - return EXIT_SUCCESS; -} diff --git a/Samples_old/Launcher.h b/Samples_old/Launcher.h deleted file mode 100644 index 60735996..00000000 --- a/Samples_old/Launcher.h +++ /dev/null @@ -1,154 +0,0 @@ -/** -* Author: Andrea Casalino -* Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. -**/ - -#ifndef SAMPLE_LAUNCHER_H -#define SAMPLE_LAUNCHER_H - -#include -#include -#include - -/** - * @brief The object is used to print a script, that is able - * to launch in a precise sequence certain processes - */ -class Launcher { -public: - /** - * @param[in] the name of script to print - */ - Launcher(const std::string& nameFile) : nameFile(nameFile) {}; - - /** - * @brief Add 1 process to launch, with possibly some additional command arguments. - * @param[in] the name of the process to launch - * @param[in] the list of arguments to pass when launching the process - */ - template - void addProcess(const std::string& procName, Args ... args) { - std::list parsed; - this->parseArgs(parsed, args ...); - - this->commands.push_back({0, procName, parsed}); - }; - - /** - * @brief Add 1 process to launch, precedeed by a tunable sleep, with possibly some additional command arguments. - * @param[in] the name of the process to launch and the sleep in seconds - * @param[in] the list of arguments to pass when launching the process - */ - template - void addProcessSleep(const std::pair& procNameSleepTime, Args ... args) { - std::list parsed; - this->parseArgs(parsed, args ...); - - this->commands.push_back({ procNameSleepTime.second, procNameSleepTime.first, parsed }); - }; - - /** - * @brief Print the script and runs it - */ - void run() const; - -private: - std::string writeScript() const; - - template - void parseArgs(std::list& parsed , const std::string& arg, Args ... args) { - parsed.push_back(arg); - parseArgs(parsed, args ...); - }; - - template - void parseArgs(std::list& parsed, const std::string& arg) { - parsed.push_back(arg); - }; - - void parseArgs(std::list& parsed) { - return; - }; - - struct Info { - unsigned int msSleep; - std::string processName; - std::list processArguments; - }; - - std::string nameFile; - std::list commands; -}; - -#include - -std::string Launcher::writeScript() const { - std::string name = this->nameFile; -#ifdef _WIN32 - name += ".bat"; -#elif __linux__ - name += ".sh"; -#endif - - std::ofstream f(name); - auto addSleep = [&f](const unsigned int& sleep) { - if (0 == sleep) return; -#ifdef _WIN32 - f << std::endl << "timeout /t " << sleep; -#else - f << std::endl << "sleep " << sleep; -#endif - }; - - auto it = this->commands.begin(); - auto itEnd = this->commands.end(); - --itEnd; - for (it; it != itEnd; ++it) { - addSleep(it->msSleep); -#ifdef _WIN32 - f << std::endl << "start \"\" \"" << it->processName << "\""; - for (auto a : it->processArguments) f << " \"" << a << "\""; -#elif __linux__ - f << std::endl << "gnome-terminal -x sh -c \"./" << it->processName; - for (auto a : it->processArguments) f << " " << a; - f << "; bash\""; -#endif - } - addSleep(it->msSleep); -#ifdef _WIN32 - f << std::endl << "\"" << this->commands.back().processName << "\""; - for (auto a : this->commands.back().processArguments) f << " \"" << a << "\""; -#elif __linux__ - f << std::endl << "./" << this->commands.back().processName; - for (auto a : this->commands.back().processArguments) f << " " << a; -#endif - f.close(); - return name; -} - -#include - -void Launcher::run() const { - std::string scriptName; - // run - std::stringstream runCmd; -#if __linux__ - runCmd << "sh ./"; -#endif - scriptName = this->writeScript(); - runCmd << scriptName; - std::system(runCmd.str().c_str()); - // delete file - std::stringstream delCmd; -#ifdef _WIN32 - delCmd << "del "; -#elif __linux__ - delCmd << "rm "; -#endif - delCmd << scriptName; - std::system(delCmd.str().c_str()); -}; - -#endif \ No newline at end of file diff --git a/Samples_old/Tcp/CMakeLists.txt b/Samples_old/Tcp/CMakeLists.txt deleted file mode 100644 index 84f9ca24..00000000 --- a/Samples_old/Tcp/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -project("Tcp-Samples") - -MakeSample(TcpClient) - -MakeSample(TcpServer) - -if(COMPILE_ASYNCH) - MakeSample(TcpServerAsync) -endif() - -MakeSample(TcpRepeater) - -## samples launchers ## - -MakeLaunch(SampleTcp-01-server-client TcpClient TcpServer) - -MakeLaunch(SampleTcp-02-server-clients TcpClient TcpServer) - -if(COMPILE_ASYNCH) - MakeLaunch(SampleTcp-03-asyncserver-clients TcpClient TcpServerAsync) -endif() - -MakeLaunch(SampleTcp-04-repeater TcpClient TcpServer TcpRepeater) diff --git a/Samples_old/Tcp/README.md b/Samples_old/Tcp/README.md deleted file mode 100644 index 16903f20..00000000 --- a/Samples_old/Tcp/README.md +++ /dev/null @@ -1,28 +0,0 @@ -This folder contains some samples showing how to create and use tcp sockets (with server or client roles). -In order to execute each sample more than 1 processes need to be run. -You don't need to manually do that, since this is done running one of the launcher application named SampleTcp-xx-... - -**Samples description** - - *SampleTcp-01-server-client, run: - - * a Tcp server ready to accept 1 client - * a Tcp client that connects to the server and exchange messages - - *SampleTcp-02-server-clients, run: - - * a Tcp server ready to accept 2 client - * a first Tcp client that connects to the server and exchange messages with an high frequency - * a second Tcp client that connects to the server and exchange messages with a low frequency - - *SampleTcp-03-asyncserver-clients, run: - - * a Tcp server accepting 1 single client and asynchronously exchange message with it - * a Tcp client that connects to the async server and exchange messages - - *SampleTcp-04-repeater, run: - - * a Tcp server ready to accept 1 client - * a Tcp client that connects to an intermediate repeater - * a repeater creating 2 connections in order to receive the request of the client and forward them to the server, sending back the response - diff --git a/Samples_old/Tcp/SampleTcp-01-server-client.cpp b/Samples_old/Tcp/SampleTcp-01-server-client.cpp deleted file mode 100644 index eb4fc7ba..00000000 --- a/Samples_old/Tcp/SampleTcp-01-server-client.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "../Launcher.h" - -int main() { - - Launcher lnc("Launcher"); - lnc.addProcess("TcpServer", "20000"); - lnc.addProcessSleep(std::make_pair("TcpClient", 1), "20000"); - lnc.run(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Tcp/SampleTcp-02-server-clients.cpp b/Samples_old/Tcp/SampleTcp-02-server-clients.cpp deleted file mode 100644 index 281d22fa..00000000 --- a/Samples_old/Tcp/SampleTcp-02-server-clients.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "../Launcher.h" - -int main() { - - Launcher lnc("Launcher"); - lnc.addProcess("TcpServer", "25000", "2"); - lnc.addProcessSleep(std::make_pair("TcpClient", 1), "25000", "100"); - lnc.addProcess("TcpClient","25000", "500"); - lnc.run(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Tcp/SampleTcp-03-asyncserver-clients.cpp b/Samples_old/Tcp/SampleTcp-03-asyncserver-clients.cpp deleted file mode 100644 index 37738609..00000000 --- a/Samples_old/Tcp/SampleTcp-03-asyncserver-clients.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "../Launcher.h" - -int main() { - - Launcher lnc("Launcher"); - lnc.addProcess("TcpServerAsync", "45000", "2"); - lnc.addProcessSleep(std::make_pair("TcpClient", 1), "45000", "100"); - lnc.addProcess("TcpClient", "45000", "500"); - lnc.run(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Tcp/SampleTcp-04-repeater.cpp b/Samples_old/Tcp/SampleTcp-04-repeater.cpp deleted file mode 100644 index 0c802b8d..00000000 --- a/Samples_old/Tcp/SampleTcp-04-repeater.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "../Launcher.h" - -int main() { - - Launcher lnc("Launcher"); - lnc.addProcess("TcpServer","3000"); - lnc.addProcessSleep(std::make_pair("TcpRepeater", 1)); - lnc.addProcessSleep(std::make_pair("TcpClient", 1), "4000"); - lnc.run(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Tcp/TcpClient.cpp b/Samples_old/Tcp/TcpClient.cpp deleted file mode 100644 index 63f6e41e..00000000 --- a/Samples_old/Tcp/TcpClient.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char **argv){ - cout << "----------------------- Client -----------------------" << endl; - - if (argc == 1) { - cout << "correct syntax is: 'server port', 'rate', 'server host'" << endl; - return EXIT_FAILURE; - } - - std::uint16_t serverPort = std::atoi(argv[1]); - - std::chrono::milliseconds rate(200); - if (argc > 2) { - rate = std::chrono::milliseconds(std::atoi(argv[2])); - } - cout << "rate assumed " << rate.count() << " [ms]" << endl; - - sck::IpPtr serverAddress; - if (argc > 3) { - serverAddress = sck::Ip::create(std::string(argv[3]), serverPort); - } - else { - serverAddress = sck::Ip::createLocalHost(serverPort); - } - if (nullptr == serverAddress) { - cout << "invalid server address" << endl; - return EXIT_FAILURE; - } - - std::unique_ptr client = std::make_unique(*serverAddress); - cout << "Asking connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - - // blocking open - client->open(std::chrono::milliseconds(0)); - if (!client->isOpen()) { - cout << "connection failed" << endl; - return EXIT_FAILURE; - } - cout << "connection opened" << endl; - - sck::sample::Asker ask(std::move(client)); - ask.askForever(rate); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Tcp/TcpRepeater.cpp b/Samples_old/Tcp/TcpRepeater.cpp deleted file mode 100644 index 4f262152..00000000 --- a/Samples_old/Tcp/TcpRepeater.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(){ - cout << "----------------------- Repeater -----------------------" << endl; - - // connecting to the server - std::unique_ptr connection2Server = std::make_unique(*sck::Ip::createLocalHost(3000)); - cout << "Asking connection to " << connection2Server->getRemoteAddress().getHost() << ":" << connection2Server->getRemoteAddress().getPort() << endl; - - // blocking open - connection2Server->open(std::chrono::milliseconds(0)); - if (!connection2Server->isOpen()) { - cout << "connection failed" << endl; - return EXIT_FAILURE; - } - - // accepting client - std::unique_ptr connection2Client; - { - sck::tcp::TcpServer server(4000); - server.open(std::chrono::milliseconds(0)); - if (!server.isOpen()) { - cout << "server open failed" << endl; - return EXIT_FAILURE; - } - connection2Client = server.acceptClient(); - cout << "client connected" << endl; - } - - char buffer[1000]; - std::pair temp = { &buffer[0], 1000 }; - std::size_t recvBytes; - while (true) { - recvBytes = connection2Client->receive(temp, std::chrono::milliseconds(0)); - cout << "forwarding to server " << std::string(buffer, recvBytes); - connection2Server->send({&buffer[0], recvBytes}); - - recvBytes = connection2Server->receive(temp, std::chrono::milliseconds(0)); - cout << " reply to client " << std::string(buffer, recvBytes) << endl; - connection2Client->send({ &buffer[0], recvBytes }); - } - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Tcp/TcpServer.cpp b/Samples_old/Tcp/TcpServer.cpp deleted file mode 100644 index b89188ef..00000000 --- a/Samples_old/Tcp/TcpServer.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -#include -#include -using namespace std; - -int main(int argc, char** argv){ - cout << "----------------------- Server -----------------------" << endl; - - if (argc == 1) { - cout << "correct syntax is: 'port to bind', 'number of clients to accept'" << endl; - return EXIT_FAILURE; - } - - std::uint16_t port = std::atoi(argv[1]); - std::size_t clientNumbers = 1; - if (argc > 2) { - clientNumbers = std::atoi(argv[2]); - } - if (0 == clientNumbers) { - cout << "invalid number of clients" << endl; - return EXIT_FAILURE; - } - cout << "clients excepted " << clientNumbers << endl; - - //build the server object - sck::tcp::TcpServer server(port); - cout << "Binding port " << port << endl; - - // blocking open - server.open(std::chrono::milliseconds(0)); - if (!server.isOpen()) { - cout << "server open failed" << endl; - return EXIT_FAILURE; - } - cout << "connection opened" << endl; - - std::list responders; - std::list respThreads; - for (std::size_t c = 0; c < clientNumbers; ++c) { - //accept the client - cout << "waiting client " << c << endl; - responders.emplace_back(server.acceptClient()); - cout << "new client connected" << endl; - respThreads.emplace_back(&sck::sample::Responder::respondForever, &responders.back()); - } - - while (!respThreads.empty()) { - auto it = respThreads.begin(); - while (it != respThreads.end()) { - if (it->joinable()) { - it->join(); - it = respThreads.erase(it); - } - else ++it; - } - } - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Tcp/TcpServerAsync.cpp b/Samples_old/Tcp/TcpServerAsync.cpp deleted file mode 100644 index 8cd3965d..00000000 --- a/Samples_old/Tcp/TcpServerAsync.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 -* -* report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -#include -using namespace std; - -int main(int argc, char** argv) { - cout << "----------------------- Server Async -----------------------" << endl; - - if (argc == 1) { - cout << "correct syntax is: 'port to bind', 'number of clients to accept'" << endl; - return EXIT_FAILURE; - } - - std::uint16_t port = std::atoi(argv[1]); - std::size_t clientNumbers = 1; - if (argc > 2) { - clientNumbers = std::atoi(argv[2]); - } - if (0 == clientNumbers) { - cout << "invalid number of clients" << endl; - return EXIT_FAILURE; - } - cout << "clients excepted " << clientNumbers << endl; - - //build the server object - sck::tcp::TcpServer server(port); - cout << "Binding port " << port << endl; - - // blocking open - server.open(std::chrono::milliseconds(0)); - if (!server.isOpen()) { - cout << "server open failed" << endl; - return EXIT_FAILURE; - } - cout << "connection opened" << endl; - - std::list responders; - for (std::size_t c = 0; c < clientNumbers; ++c) { - //accept the client - cout << "waiting client " << c << endl; - responders.emplace_back(server.acceptClient()); - cout << "new client connected" << endl; - responders.back().open(std::chrono::milliseconds(0)); - } - - while (!responders.empty()) { - auto it = responders.begin(); - while (it != responders.end()) { - if (!it->isOpen()) { - it = responders.erase(it); - } - else ++it; - } - } - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Udp/CMakeLists.txt b/Samples_old/Udp/CMakeLists.txt deleted file mode 100644 index a62bcc64..00000000 --- a/Samples_old/Udp/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -project("Udp-Samples") - -MakeSample(UdpAsker) - -MakeSample(UdpResponder) - -if(COMPILE_ASYNCH) - MakeSample(UdpResponderAsync) -endif() - -MakeSample(UdpServer) - -## samples launchers ## - -MakeLaunch(SampleUdp-01-connection UdpAsker UdpResponder) - -if(COMPILE_ASYNCH) - MakeLaunch(SampleUdp-02-asyncconnection UdpAsker UdpResponderAsync) -endif() - -MakeLaunch(SampleUdp-03-server UdpAsker UdpServer) diff --git a/Samples_old/Udp/README.md b/Samples_old/Udp/README.md deleted file mode 100644 index 219db85b..00000000 --- a/Samples_old/Udp/README.md +++ /dev/null @@ -1,20 +0,0 @@ -This folder contains some samples showing how to create and use udp connections. -In order to execute each sample more than 1 processes need to be run. -You don't need to manually do that, since this is done running one of the launcher application named SampleUdp-xx-... - -**Samples description** - - *SampleUdp-01-connection, run: - - * a first UdpConnection reserving a specific port, ready to respond to another UdpConnection - * a second UdpConnection exchanging messages with the first - - *SampleUdp-02-asyncconnection, run: - - * a first UdpConnection reserving a specific port, ready to asynchrously respond to another UdpClient - * a second UdpConnection exchanging messages with the first - - *SampleUdp-03-server, run: - - * a UdpServer ready to receive an initial message from another peer to detect it - * a UdpConnection that connects to the server by sending one message and exchange messages diff --git a/Samples_old/Udp/SampleUdp-01-connection.cpp b/Samples_old/Udp/SampleUdp-01-connection.cpp deleted file mode 100644 index 02e744a7..00000000 --- a/Samples_old/Udp/SampleUdp-01-connection.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "../Launcher.h" - -int main() { - - Launcher lnc("Launcher"); - lnc.addProcess("UdpResponder", "10000", "15000"); - lnc.addProcessSleep(std::make_pair("UdpAsker", 1), "15000", "250", "10000"); - lnc.run(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Udp/SampleUdp-02-asyncconnection.cpp b/Samples_old/Udp/SampleUdp-02-asyncconnection.cpp deleted file mode 100644 index 7e14dc65..00000000 --- a/Samples_old/Udp/SampleUdp-02-asyncconnection.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "../Launcher.h" - -int main() { - - Launcher lnc("Launcher"); - lnc.addProcess("UdpResponderAsync", "10000", "15000"); - lnc.addProcessSleep(std::make_pair("UdpAsker", 1), "15000", "250", "10000"); - lnc.run(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Udp/SampleUdp-03-server.cpp b/Samples_old/Udp/SampleUdp-03-server.cpp deleted file mode 100644 index d5f19db3..00000000 --- a/Samples_old/Udp/SampleUdp-03-server.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "../Launcher.h" - -int main() { - - Launcher lnc("Launcher"); - lnc.addProcess("UdpServer", "35000"); - lnc.addProcessSleep(std::make_pair("UdpAsker", 1), "35000", "250", "30000", "1"); - lnc.run(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Udp/UdpAsker.cpp b/Samples_old/Udp/UdpAsker.cpp deleted file mode 100644 index b9bae222..00000000 --- a/Samples_old/Udp/UdpAsker.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char **argv){ - cout << "----------------------- Connection Asker -----------------------" << endl; - - if (argc == 1) { - cout << "correct syntax is: 'server port', 'rate', 'port to reserve', 'send initial handshake flag 0/1', 'server host'" << endl; - return EXIT_FAILURE; - } - - std::uint16_t serverPort = std::atoi(argv[1]); - - std::chrono::milliseconds rate(200); - if (argc > 2) { - rate = std::chrono::milliseconds(std::atoi(argv[2])); - } - cout << "rate assumed " << rate.count() << " [ms]" << endl; - - std::uint16_t port = 0; - if (argc > 3) { - port = std::atoi(argv[3]); - } - cout << "port reserved by this udp " << port << endl; - - bool handShake = false; - if (argc > 4) { - handShake = static_cast(std::atoi(argv[4])); - } - - sck::IpPtr serverAddress; - if (argc > 5) { - serverAddress = sck::Ip::create(std::string(argv[5]), serverPort); - } - else { - serverAddress = sck::Ip::createLocalHost(serverPort); - } - if (nullptr == serverAddress) { - cout << "invalid server address" << endl; - return EXIT_FAILURE; - } - - std::unique_ptr client = std::make_unique(*serverAddress, port); - cout << "Expecting connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - - // blocking open - client->open(std::chrono::milliseconds(0)); - cout << "connection opened" << endl; - - if (handShake) { - std::string hello = "hello"; - client->send({hello.data(), hello.size()}); - cout << "handshake sent" << endl; - } - - sck::sample::Asker ask(std::move(client)); - ask.askForever(rate); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Udp/UdpResponder.cpp b/Samples_old/Udp/UdpResponder.cpp deleted file mode 100644 index 00fbb081..00000000 --- a/Samples_old/Udp/UdpResponder.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char** argv) { - cout << "----------------------- Connection Responder -----------------------" << endl; - - if (argc == 1) { - cout << "correct syntax is: 'server port', 'port to reserve', 'server host'" << endl; - return EXIT_FAILURE; - } - - std::uint16_t serverPort = std::atoi(argv[1]); - - std::uint16_t port = 0; - if (argc > 2) { - port = std::atoi(argv[2]); - } - cout << "port reserved by this udp " << port << endl; - - sck::IpPtr serverAddress; - if (argc > 3) { - serverAddress = sck::Ip::create(std::string(argv[3]), serverPort); - } - else { - serverAddress = sck::Ip::createLocalHost(serverPort); - } - if (nullptr == serverAddress) { - cout << "invalid server address" << endl; - return EXIT_FAILURE; - } - - std::unique_ptr client = std::make_unique(*serverAddress, port); - cout << "Expecting connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - - // blocking open - client->open(std::chrono::milliseconds(0)); - cout << "connection opened" << endl; - - sck::sample::Responder responder(std::move(client)); - responder.respondForever(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Udp/UdpResponderAsync.cpp b/Samples_old/Udp/UdpResponderAsync.cpp deleted file mode 100644 index e8858adb..00000000 --- a/Samples_old/Udp/UdpResponderAsync.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char** argv) { - cout << "----------------------- Connection Responder -----------------------" << endl; - - if (argc == 1) { - cout << "correct syntax is: 'server port', 'port to reserve', 'server host'" << endl; - return EXIT_FAILURE; - } - - std::uint16_t serverPort = std::atoi(argv[1]); - - std::uint16_t port = 0; - if (argc > 2) { - port = std::atoi(argv[2]); - } - cout << "port reserved by this udp " << port << endl; - - sck::IpPtr serverAddress; - if (argc > 3) { - serverAddress = sck::Ip::create(std::string(argv[3]), serverPort); - } - else { - serverAddress = sck::Ip::createLocalHost(serverPort); - } - if (nullptr == serverAddress) { - cout << "invalid server address" << endl; - return EXIT_FAILURE; - } - - std::unique_ptr client = std::make_unique(*serverAddress, port); - cout << "Expecting connection to " << client->getRemoteAddress().getHost() << ":" << client->getRemoteAddress().getPort() << endl; - - // blocking open - client->open(std::chrono::milliseconds(0)); - cout << "connection opened" << endl; - - sck::sample::AsyncResponder responder(std::move(client)); - responder.open(std::chrono::milliseconds(0)); - - while (responder.isOpen()) { - } - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/Udp/UdpServer.cpp b/Samples_old/Udp/UdpServer.cpp deleted file mode 100644 index 6bfe030a..00000000 --- a/Samples_old/Udp/UdpServer.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include -#include -#include -using namespace std; - -int main(int argc, char** argv) { - cout << "----------------------- Server -----------------------" << endl; - - if (argc == 1) { - cout << "correct syntax is: 'port to reserve'" << endl; - return EXIT_FAILURE; - } - - std::uint16_t serverPort = std::atoi(argv[1]); - - std::unique_ptr server = std::make_unique(serverPort); - cout << "waiting for the client" << endl; - - // blocking open - server->open(std::chrono::milliseconds(0)); - cout << "connection opened" << endl; - - sck::sample::Responder responder(std::move(server)); - responder.respondForever(); - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/Samples_old/cmake/MakeSample.cmake.bak b/Samples_old/cmake/MakeSample.cmake.bak deleted file mode 100644 index d10422f3..00000000 --- a/Samples_old/cmake/MakeSample.cmake.bak +++ /dev/null @@ -1,12 +0,0 @@ -function(MakeSample NAME) - add_executable(${NAME} ${NAME}.cpp) - - target_link_libraries(${NAME} PUBLIC - Utils - MinimalSocket - ) - - set_target_properties(${NAME} PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=%PATH%;${CMAKE_INSTALL_PREFIX}/bin/") - - install(TARGETS ${NAME}) -endfunction() diff --git a/samples/tcp/TcpScriptsGenerator.cpp b/samples/tcp/TcpScriptsGenerator.cpp index 2beb914a..5a4b201c 100644 --- a/samples/tcp/TcpScriptsGenerator.cpp +++ b/samples/tcp/TcpScriptsGenerator.cpp @@ -13,7 +13,7 @@ using namespace std; int main() { { // 1 server 1 client - const std::string sample_name = "tcp_server_client"; + const std::string sample_name = "tcp01_server_client"; MinimalSocket::samples::ScriptGenerator generator; const std::string port = "35998"; @@ -31,7 +31,7 @@ int main() { { // 1 server 2 clients - const std::string sample_name = "tcp_server_2_clients"; + const std::string sample_name = "tcp02_server_2_clients"; MinimalSocket::samples::ScriptGenerator generator; const std::string port = "35998"; @@ -53,9 +53,9 @@ int main() { { // repeaters - const std::size_t repeaters = 1; + const std::size_t repeaters = 2; const std::string sample_name = - "tcp_chain_size_" + std::to_string(repeaters + 2); + "tcp03_chain_size_" + std::to_string(repeaters + 2); MinimalSocket::samples::ScriptGenerator generator; std::size_t port = 35998; diff --git a/samples/udp/UdpScriptsGenerator.cpp b/samples/udp/UdpScriptsGenerator.cpp index 7593f495..ce308fd6 100644 --- a/samples/udp/UdpScriptsGenerator.cpp +++ b/samples/udp/UdpScriptsGenerator.cpp @@ -13,7 +13,7 @@ using namespace std; int main() { { // 1 responder 1 asker - const std::string sample_name = "udp_responder_asker"; + const std::string sample_name = "udp01_responder_asker"; MinimalSocket::samples::ScriptGenerator generator; const std::size_t port_asker = 36995; @@ -34,7 +34,7 @@ int main() { { // 1 connecting responder 1 asker - const std::string sample_name = "udp_connecting_responder_asker"; + const std::string sample_name = "udp02_connecting_responder_asker"; MinimalSocket::samples::ScriptGenerator generator; const std::size_t port_asker = 36995; @@ -56,7 +56,7 @@ int main() { { // 1 responder 2 askers - const std::string sample_name = "udp_responder_2_askers"; + const std::string sample_name = "udp03_responder_2_askers"; MinimalSocket::samples::ScriptGenerator generator; const std::size_t port_responder = 36995; From 249234ef16336f1baf6c02787edd816009b49d11 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 01:24:55 +0100 Subject: [PATCH 176/228] samples --- samples/tcp/README.md | 0 samples/udp/README.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 samples/tcp/README.md create mode 100644 samples/udp/README.md diff --git a/samples/tcp/README.md b/samples/tcp/README.md new file mode 100644 index 00000000..e69de29b diff --git a/samples/udp/README.md b/samples/udp/README.md new file mode 100644 index 00000000..e69de29b From c8f6496dda75384d9850ef372d24163410703244 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 15:21:35 +0100 Subject: [PATCH 177/228] samples --- samples/tcp/TcpClient.cpp | 4 +++- samples/tcp/TcpRepeater.cpp | 19 ++++++++++++------- samples/tcp/TcpScriptsGenerator.cpp | 9 ++++----- samples/tcp/TcpServer.cpp | 2 +- samples/udp/UdpAsker.cpp | 2 +- samples/udp/UdpScriptsGenerator.cpp | 2 +- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/samples/tcp/TcpClient.cpp b/samples/tcp/TcpClient.cpp index eab67d45..5ed509a6 100644 --- a/samples/tcp/TcpClient.cpp +++ b/samples/tcp/TcpClient.cpp @@ -20,15 +20,17 @@ int main(const int argc, const char **argv) { const auto server_port = static_cast( std::atoi(options->getValue("port").c_str())); const auto rate = std::chrono::milliseconds{ - std::atoi(options->getValue("rate", "100").c_str())}; + std::atoi(options->getValue("rate", "250").c_str())}; const MinimalSocket::Address server_address(server_host, server_port); MinimalSocket::tcp::TcpClient client(server_address); + cout << "Connecting to " << MinimalSocket::to_string(server_address) << endl; if (!client.open()) { cout << "Failed to open connection" << endl; return EXIT_FAILURE; } + cout << "Connected" << endl; MinimalSocket::samples::ask_forever(client, rate); diff --git a/samples/tcp/TcpRepeater.cpp b/samples/tcp/TcpRepeater.cpp index bd7e9c0d..2b46061d 100644 --- a/samples/tcp/TcpRepeater.cpp +++ b/samples/tcp/TcpRepeater.cpp @@ -44,21 +44,26 @@ int main(const int argc, const char **argv) { const auto port_to_reserve = static_cast( std::atoi(options->getValue("port").c_str())); - // ask connection to preceding - MinimalSocket::tcp::TcpClient connection_to_preceding(preceding_address); - if (connection_to_preceding.open()) { - cout << "Unable to connect to preceding" << endl; - return EXIT_FAILURE; - } // wait connection request from follower MinimalSocket::tcp::TcpServer acceptor(port_to_reserve, preceding_address.getFamily()); if (acceptor.open()) { - cout << "Unable to reserve port" << std::to_string(port_to_reserve) << endl; + cout << "Failed to bind and listen to specified port" << endl; return EXIT_FAILURE; } + cout << "Waiting follower on port " << port_to_reserve << endl; auto connection_to_following = acceptor.acceptNewClient(); + // ask connection to preceding + MinimalSocket::tcp::TcpClient connection_to_preceding(preceding_address); + cout << "Connecting to preceding on " + << MinimalSocket::to_string(preceding_address) << endl; + if (connection_to_preceding.open()) { + cout << "Unable to connect to preceding" << endl; + return EXIT_FAILURE; + } + cout << "Connected" << endl; + Repeater repeater(std::move(connection_to_preceding), std::move(connection_to_following)); while (true) { diff --git a/samples/tcp/TcpScriptsGenerator.cpp b/samples/tcp/TcpScriptsGenerator.cpp index 5a4b201c..15d485ed 100644 --- a/samples/tcp/TcpScriptsGenerator.cpp +++ b/samples/tcp/TcpScriptsGenerator.cpp @@ -37,15 +37,14 @@ int main() { const std::string port = "35998"; generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpServer", - MinimalSocket::samples::ProcessArgs{{"port", port}, {"clients", "1"}}}); + "TcpServer", MinimalSocket::samples::ProcessArgs{{"port", port}}}); generator.add(MinimalSocket::samples::ProcessAndArgs{ "TcpClient", MinimalSocket::samples::ProcessArgs{{"port", port}}}); generator.add(MinimalSocket::samples::ProcessAndArgs{ "TcpClient", - MinimalSocket::samples::ProcessArgs{{"port", port}, {"rate", "300"}}}); + MinimalSocket::samples::ProcessArgs{{"port", port}, {"rate", "800"}}}); cout << "generating " << sample_name << endl; generator.generate(sample_name); @@ -61,8 +60,8 @@ int main() { std::size_t port = 35998; generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpServer", MinimalSocket::samples::ProcessArgs{ - {"port", std::to_string(port)}, {"clients", "1"}}}); + "TcpServer", + MinimalSocket::samples::ProcessArgs{{"port", std::to_string(port)}}}); for (std::size_t r = 0; r < repeaters; ++r) { auto new_port = port + 10; diff --git a/samples/tcp/TcpServer.cpp b/samples/tcp/TcpServer.cpp index 8a55b55b..706027eb 100644 --- a/samples/tcp/TcpServer.cpp +++ b/samples/tcp/TcpServer.cpp @@ -49,7 +49,7 @@ int main(const int argc, const char **argv) { cout << "Failed to bind and listen to specified port" << endl; return EXIT_FAILURE; } - cout << "Listening for new clients" << endl; + cout << "Listening for new clients on port " << server_port << endl; std::list active_connections; auto accept_clients_task = std::async([&]() { diff --git a/samples/udp/UdpAsker.cpp b/samples/udp/UdpAsker.cpp index fcd8cd09..84504789 100644 --- a/samples/udp/UdpAsker.cpp +++ b/samples/udp/UdpAsker.cpp @@ -22,7 +22,7 @@ int main(const int argc, const char **argv) { const auto port_this = static_cast( std::atoi(options->getValue("port_this").c_str())); const auto rate = std::chrono::milliseconds{ - std::atoi(options->getValue("rate", "100").c_str())}; + std::atoi(options->getValue("rate", "250").c_str())}; const MinimalSocket::Address remote_address(remote_host, remote_port); MinimalSocket::udp::UdpBinded asker(port_this, remote_address.getFamily()); diff --git a/samples/udp/UdpScriptsGenerator.cpp b/samples/udp/UdpScriptsGenerator.cpp index ce308fd6..cd8487e8 100644 --- a/samples/udp/UdpScriptsGenerator.cpp +++ b/samples/udp/UdpScriptsGenerator.cpp @@ -76,7 +76,7 @@ int main() { "UdpAsker", MinimalSocket::samples::ProcessArgs{ {"port", std::to_string(port_responder)}, {"port_this", std::to_string(port_asker_2)}, - {"rate", "300"}}}); + {"rate", "800"}}}); cout << "generating " << sample_name << endl; generator.generate(sample_name); From e2ef0e331b5233252d66148fadf8615d27c933af Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 16:09:20 +0100 Subject: [PATCH 178/228] samples --- samples/tcp/TcpRepeater.cpp | 67 +++++++++++++---------------- samples/tcp/TcpScriptsGenerator.cpp | 32 +++++--------- samples/utils/ScriptGenerator.h | 4 +- 3 files changed, 43 insertions(+), 60 deletions(-) diff --git a/samples/tcp/TcpRepeater.cpp b/samples/tcp/TcpRepeater.cpp index 2b46061d..aeed81c0 100644 --- a/samples/tcp/TcpRepeater.cpp +++ b/samples/tcp/TcpRepeater.cpp @@ -13,61 +13,54 @@ #include using namespace std; -class Repeater { -public: - Repeater(MinimalSocket::tcp::TcpClient &&connection_to_preceding, - MinimalSocket::tcp::TcpConnection &&connection_to_following) - : preceding(std::move(connection_to_preceding)), - following(std::move(connection_to_following)) {} - - void repeat() { - auto request = following.receive(500); - preceding.send(request); - auto response = preceding.receive(500); - following.send(response); - } - -private: - MinimalSocket::tcp::TcpClient preceding; - MinimalSocket::tcp::TcpConnection following; -}; +void repeat(MinimalSocket::tcp::TcpConnection &preceding, + MinimalSocket::tcp::TcpClient &following) { + auto request = preceding.receive(500); + cout << "forwarding request: " << request << endl; + following.send(request); + auto response = following.receive(500); + cout << "backwarding response: " << response << endl; + preceding.send(response); +} int main(const int argc, const char **argv) { cout << "----------------------- Repeater -----------------------" << endl; PARSE_ARGS - const auto preceding_host = options->getValue("host", "127.0.0.1"); - const auto preceding_port = static_cast( - std::atoi(options->getValue("prec_port").c_str())); - MinimalSocket::Address preceding_address(preceding_host, preceding_port); + const auto following_host = options->getValue("host", "127.0.0.1"); + const auto following_port = static_cast( + std::atoi(options->getValue("next_port").c_str())); + MinimalSocket::Address following_address(following_host, following_port); const auto port_to_reserve = static_cast( std::atoi(options->getValue("port").c_str())); - // wait connection request from follower + // reserve port MinimalSocket::tcp::TcpServer acceptor(port_to_reserve, - preceding_address.getFamily()); - if (acceptor.open()) { + following_address.getFamily()); + if (!acceptor.open()) { cout << "Failed to bind and listen to specified port" << endl; return EXIT_FAILURE; } - cout << "Waiting follower on port " << port_to_reserve << endl; - auto connection_to_following = acceptor.acceptNewClient(); + cout << "Listening on port " << port_to_reserve << endl; - // ask connection to preceding - MinimalSocket::tcp::TcpClient connection_to_preceding(preceding_address); - cout << "Connecting to preceding on " - << MinimalSocket::to_string(preceding_address) << endl; - if (connection_to_preceding.open()) { - cout << "Unable to connect to preceding" << endl; + // ask connection to follower + 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()) { + cout << "Unable to connect to next on chain" << endl; return EXIT_FAILURE; } - cout << "Connected" << endl; + cout << "Connected to next on chain" << endl; + + // wait connection request from preceding + cout << "Waiting preceding on chain on port " << port_to_reserve << endl; + auto connection_to_previous = acceptor.acceptNewClient(); + cout << "Connected to preceding on chain" << endl; - Repeater repeater(std::move(connection_to_preceding), - std::move(connection_to_following)); while (true) { - repeater.repeat(); + repeat(connection_to_previous, connection_to_following); } return EXIT_SUCCESS; diff --git a/samples/tcp/TcpScriptsGenerator.cpp b/samples/tcp/TcpScriptsGenerator.cpp index 15d485ed..7aaf5b92 100644 --- a/samples/tcp/TcpScriptsGenerator.cpp +++ b/samples/tcp/TcpScriptsGenerator.cpp @@ -18,12 +18,9 @@ int main() { const std::string port = "35998"; - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpServer", - MinimalSocket::samples::ProcessArgs{{"port", port}, {"clients", "1"}}}); + generator.add("TcpServer", {{"port", port}, {"clients", "1"}}); - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpClient", MinimalSocket::samples::ProcessArgs{{"port", port}}}); + generator.add("TcpClient", {{"port", port}}); cout << "generating " << sample_name << endl; generator.generate(sample_name); @@ -36,15 +33,11 @@ int main() { const std::string port = "35998"; - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpServer", MinimalSocket::samples::ProcessArgs{{"port", port}}}); + generator.add("TcpServer", {{"port", port}}); - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpClient", MinimalSocket::samples::ProcessArgs{{"port", port}}}); + generator.add("TcpClient", {{"port", port}}); - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpClient", - MinimalSocket::samples::ProcessArgs{{"port", port}, {"rate", "800"}}}); + generator.add("TcpClient", {{"port", port}, {"rate", "800"}}); cout << "generating " << sample_name << endl; generator.generate(sample_name); @@ -59,22 +52,17 @@ int main() { std::size_t port = 35998; - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpServer", - MinimalSocket::samples::ProcessArgs{{"port", std::to_string(port)}}}); + generator.add("TcpServer", + {{"port", std::to_string(port)}, {"clients", "1"}}); for (std::size_t r = 0; r < repeaters; ++r) { auto new_port = port + 10; - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpRepeater", MinimalSocket::samples::ProcessArgs{ - {"port", std::to_string(new_port)}, - {"prec_port", std::to_string(port)}}}); + generator.add("TcpRepeater", {{"port", std::to_string(new_port)}, + {"next_port", std::to_string(port)}}); port = new_port; } - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "TcpClient", - MinimalSocket::samples::ProcessArgs{{"port", std::to_string(port)}}}); + generator.add("TcpClient", {{"port", std::to_string(port)}}); cout << "generating " << sample_name << endl; generator.generate(sample_name); diff --git a/samples/utils/ScriptGenerator.h b/samples/utils/ScriptGenerator.h index 02bcc153..41e345d5 100644 --- a/samples/utils/ScriptGenerator.h +++ b/samples/utils/ScriptGenerator.h @@ -23,7 +23,9 @@ class ScriptGenerator { public: ScriptGenerator() = default; - void add(const ProcessAndArgs &info) { processes.push_back(info); }; + void add(const std::string &process_name, const ProcessArgs &args) { + processes.push_back(ProcessAndArgs{process_name, args}); + }; void generate(const std::string &file_name); From fb612bfa1b5d41b0878d6de5c89f9a9e302be201 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 16:38:14 +0100 Subject: [PATCH 179/228] samples --- TODO | 2 ++ samples/udp/UdpAsker.cpp | 3 +- samples/udp/UdpResponder.cpp | 7 ++++- samples/udp/UdpScriptsGenerator.cpp | 43 +++++++++++------------------ src/src/core/Receiver.cpp | 9 +++--- 5 files changed, 31 insertions(+), 33 deletions(-) diff --git a/TODO b/TODO index b84e2f1d..f6e1dd47 100644 --- a/TODO +++ b/TODO @@ -29,3 +29,5 @@ add test for udp concurrent send to error se os non e' win32 o linux mettere bene in evidenza il fatto che udp possono essere usati da connessi o no + +cambiare nome a repository git (MinimalSocket) diff --git a/samples/udp/UdpAsker.cpp b/samples/udp/UdpAsker.cpp index 84504789..9ff13e8f 100644 --- a/samples/udp/UdpAsker.cpp +++ b/samples/udp/UdpAsker.cpp @@ -28,9 +28,10 @@ int main(const int argc, const char **argv) { MinimalSocket::udp::UdpBinded asker(port_this, remote_address.getFamily()); if (!asker.open()) { - cout << "Failed to reserve port " << port_this << endl; + cout << "Failed to reserve specified port" << endl; return EXIT_FAILURE; } + cout << "Port successfully reserved" << endl; MinimalSocket::samples::ask_forever(asker, remote_address, rate); diff --git a/samples/udp/UdpResponder.cpp b/samples/udp/UdpResponder.cpp index 9ff26ab4..e7cccd5a 100644 --- a/samples/udp/UdpResponder.cpp +++ b/samples/udp/UdpResponder.cpp @@ -26,15 +26,20 @@ int main(const int argc, const char **argv) { MinimalSocket::udp::UdpBinded responder(port_this, family); if (!responder.open()) { - cout << "Failed to reserve port " << port_this << endl; + cout << "Failed to reserve specified port" << endl; return EXIT_FAILURE; } + cout << "Port successfully reserved" << endl; if (connect) { // connect to first sending a request std::string first_request; auto connected_responder = responder.connect(&first_request); + cout << "Connected to " + << MinimalSocket::to_string(connected_responder.getRemoteAddress()) + << endl; + const auto &first_response = MinimalSocket::samples::NamesCircularIterator::NAMES_SURNAMES .find(first_request) diff --git a/samples/udp/UdpScriptsGenerator.cpp b/samples/udp/UdpScriptsGenerator.cpp index cd8487e8..d532a2d0 100644 --- a/samples/udp/UdpScriptsGenerator.cpp +++ b/samples/udp/UdpScriptsGenerator.cpp @@ -19,14 +19,11 @@ int main() { const std::size_t port_asker = 36995; const std::size_t port_responder = port_asker + 10; - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "UdpResponder", MinimalSocket::samples::ProcessArgs{ - {"port_this", std::to_string(port_responder)}}}); + generator.add("UdpResponder", + {{"port_this", std::to_string(port_responder)}}); - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "UdpAsker", MinimalSocket::samples::ProcessArgs{ - {"port", std::to_string(port_responder)}, - {"port_this", std::to_string(port_asker)}}}); + 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); @@ -40,15 +37,12 @@ int main() { const std::size_t port_asker = 36995; const std::size_t port_responder = port_asker + 10; - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "UdpResponder", MinimalSocket::samples::ProcessArgs{ - {"port_this", std::to_string(port_responder)}, - {"connect", "yes"}}}); + generator.add( + "UdpResponder", + {{"port_this", std::to_string(port_responder)}, {"connect", "yes"}}); - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "UdpAsker", MinimalSocket::samples::ProcessArgs{ - {"port", std::to_string(port_responder)}, - {"port_this", std::to_string(port_asker)}}}); + 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); @@ -63,20 +57,15 @@ int main() { const std::size_t port_asker_1 = port_responder + 10; const std::size_t port_asker_2 = port_responder + 20; - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "UdpResponder", MinimalSocket::samples::ProcessArgs{ - {"port_this", std::to_string(port_responder)}}}); + generator.add("UdpResponder", + {{"port_this", std::to_string(port_responder)}}); - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "UdpAsker", MinimalSocket::samples::ProcessArgs{ - {"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_1)}}); - generator.add(MinimalSocket::samples::ProcessAndArgs{ - "UdpAsker", MinimalSocket::samples::ProcessArgs{ - {"port", std::to_string(port_responder)}, - {"port_this", std::to_string(port_asker_2)}, - {"rate", "800"}}}); + 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); diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 9d24eb60..566aec66 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -48,9 +48,9 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { namespace { #ifdef _WIN32 - static constexpr int TIMEOUT_CODE = 10060; +static constexpr int TIMEOUT_CODE = 10060; #else - static constexpr int TIMEOUT_CODE = EAGAIN; +static constexpr int TIMEOUT_CODE = EAGAIN; #endif void check_received_bytes(int &recvBytes, const Timeout &timeout) { @@ -59,7 +59,8 @@ void check_received_bytes(int &recvBytes, const Timeout &timeout) { } SocketError error_with_code("receive failed"); recvBytes = 0; - if ((error_with_code.getErrorCode() == TIMEOUT_CODE) && (timeout != NULL_TIMEOUT)) { + if ((error_with_code.getErrorCode() == TIMEOUT_CODE) && + (timeout != NULL_TIMEOUT)) { // just out of time: tolerate return; } @@ -127,7 +128,7 @@ ReceiverUnkownSender::receive(std::size_t expected_max_bytes, if (!result) { return std::nullopt; } - buffer.resize(buffer_temp.buffer_size); + buffer.resize(result->received_bytes); return ReceiveStringResult{result->sender, std::move(buffer)}; } } // namespace MinimalSocket From d5332ab630a1dca8acb2f3662bc701a081e94f54 Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Sun, 15 May 2022 17:08:16 +0100 Subject: [PATCH 180/228] samples --- samples/tcp/TcpScriptsGenerator.cpp | 10 +++++++--- samples/udp/UdpAsker.cpp | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/samples/tcp/TcpScriptsGenerator.cpp b/samples/tcp/TcpScriptsGenerator.cpp index 7aaf5b92..f9bc4d68 100644 --- a/samples/tcp/TcpScriptsGenerator.cpp +++ b/samples/tcp/TcpScriptsGenerator.cpp @@ -27,8 +27,10 @@ int main() { } { - // 1 server 2 clients - const std::string sample_name = "tcp02_server_2_clients"; + // 1 server many clients + const std::size_t clients = 3; + + const std::string sample_name = "tcp02_server_" + std::to_string(clients) + "_clients"; MinimalSocket::samples::ScriptGenerator generator; const std::string port = "35998"; @@ -37,7 +39,9 @@ int main() { generator.add("TcpClient", {{"port", port}}); - generator.add("TcpClient", {{"port", port}, {"rate", "800"}}); + for (std::size_t c = 1; c < clients; ++c) { + generator.add("TcpClient", { {"port", port}, {"rate", "800"} }); + } cout << "generating " << sample_name << endl; generator.generate(sample_name); diff --git a/samples/udp/UdpAsker.cpp b/samples/udp/UdpAsker.cpp index 9ff13e8f..041a046e 100644 --- a/samples/udp/UdpAsker.cpp +++ b/samples/udp/UdpAsker.cpp @@ -26,7 +26,8 @@ int main(const int argc, const char **argv) { const MinimalSocket::Address remote_address(remote_host, remote_port); MinimalSocket::udp::UdpBinded asker(port_this, remote_address.getFamily()); - + + std::this_thread::sleep_for(std::chrono::seconds{1}); // just to be sure the responder has already prepared the receive if (!asker.open()) { cout << "Failed to reserve specified port" << endl; return EXIT_FAILURE; From fb75ad10518219f35f84edb6a1f29a2d4324becf Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 18:22:40 +0100 Subject: [PATCH 181/228] enum class --- src/header/MinimalSocket/Error.h | 1 - src/header/MinimalSocket/core/Address.h | 2 +- src/header/MinimalSocket/core/Definitions.h | 2 +- src/src/tcp/TcpClient.cpp | 2 +- src/src/tcp/TcpServer.cpp | 2 +- src/src/udp/UdpSocket.cpp | 4 ++-- tests/TestTCP.cpp | 12 ++++++------ tests/TestUDP.cpp | 12 ++++++------ 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/header/MinimalSocket/Error.h b/src/header/MinimalSocket/Error.h index 48fc1b7b..733c84a8 100644 --- a/src/header/MinimalSocket/Error.h +++ b/src/header/MinimalSocket/Error.h @@ -12,7 +12,6 @@ #include namespace MinimalSocket { - class Error : public std::runtime_error { public: Error(const std::string &what) : std::runtime_error(what){}; diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index a749ce21..2723a18a 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -15,7 +15,7 @@ namespace MinimalSocket { * @brief The address family. Refer to * https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzab6/address.htm */ -enum AddressFamily { IP_V4, IP_V6 }; +enum class AddressFamily { IP_V4, IP_V6 }; using Port = std::uint16_t; diff --git a/src/header/MinimalSocket/core/Definitions.h b/src/header/MinimalSocket/core/Definitions.h index 53a184ba..23fd8f63 100644 --- a/src/header/MinimalSocket/core/Definitions.h +++ b/src/header/MinimalSocket/core/Definitions.h @@ -24,7 +24,7 @@ struct ConstBuffer { }; ConstBuffer makeStringConstBuffer(const std::string &subject); -enum SocketType { UDP, TCP }; +enum class SocketType { UDP, TCP }; using Timeout = std::chrono::milliseconds; diff --git a/src/src/tcp/TcpClient.cpp b/src/src/tcp/TcpClient.cpp index efa9bce6..b26d6417 100644 --- a/src/src/tcp/TcpClient.cpp +++ b/src/src/tcp/TcpClient.cpp @@ -27,7 +27,7 @@ TcpClient::TcpClient(const Address &server_address) void TcpClient::open_() { auto &socket = getIDWrapper(); const auto remote_address = getRemoteAddress(); - socket.reset(TCP, remote_address.getFamily()); + socket.reset(SocketType::TCP, remote_address.getFamily()); MinimalSocket::connect(socket.accessId(), remote_address); } } // namespace MinimalSocket::tcp diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 6cc573d1..9c52a5d3 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -33,7 +33,7 @@ void TcpServer::open_() { auto &socket = getIDWrapper(); const auto port = getPortToBind(); const auto family = getRemoteAddressFamily(); - socket.reset(TCP, family); + socket.reset(SocketType::TCP, family); auto binded_port = MinimalSocket::bind(socket.accessId(), family, port); setPort(binded_port); MinimalSocket::listen(socket.accessId(), client_queue_size); diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index 0f0ac606..03f1bcac 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -29,7 +29,7 @@ UdpBinded &UdpBinded::operator=(UdpBinded &&o) { } void UdpBinded::open_() { - getIDWrapper().reset(UDP, getRemoteAddressFamily()); + getIDWrapper().reset(SocketType::UDP, getRemoteAddressFamily()); auto binded_port = MinimalSocket::bind( getIDWrapper().accessId(), getRemoteAddressFamily(), getPortToBind()); setPort(binded_port); @@ -81,7 +81,7 @@ UdpConnected &UdpConnected::operator=(UdpConnected &&o) { void UdpConnected::open_() { const auto &socket_id = getIDWrapper().accessId(); const auto &remote_address = getRemoteAddress(); - getIDWrapper().reset(UDP, remote_address.getFamily()); + getIDWrapper().reset(SocketType::UDP, remote_address.getFamily()); auto binded_port = MinimalSocket::bind(socket_id, remote_address.getFamily(), getPortToBind()); setPort(binded_port); diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index bff1433c..fdbe28fa 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -85,7 +85,7 @@ void send_response(const SenderReceiver &requester, TEST_CASE("Establish tcp connection", "[tcp]") { const auto port = PortFactory::makePort(); - const auto family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); #if !defined(_WIN32) SECTION("expected failure") { @@ -147,7 +147,7 @@ TEST_CASE("Establish tcp connection", "[tcp]") { TEST_CASE("Establish many tcp connections to same server", "[tcp]") { const auto port = PortFactory::makePort(); - const auto family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); TcpServer server(port, family); server.open(); @@ -191,7 +191,7 @@ 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 family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); TcpServer server(port, family); server.open(); @@ -212,7 +212,7 @@ TEST_CASE("Open multiple times tcp clients", "[tcp]") { TEST_CASE("Open tcp client with timeout", "[tcp]") { const auto port = PortFactory::makePort(); - const auto family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const auto timeout = Timeout{500}; @@ -251,7 +251,7 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { } TEST_CASE("Reserve random port for tcp server", "[tcp]") { - const auto family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); TcpServer server(ANY_PORT, family); REQUIRE(server.open()); @@ -278,7 +278,7 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { } TEST_CASE("Accept client with timeout", "[tcp]") { - const auto family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const auto port = PortFactory::makePort(); TcpServer server(port, family); diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 3e1807e2..4b76ea78 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -24,7 +24,7 @@ bool are_same(const Address &a, const Address &b, const AddressFamily &family) { } // namespace TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { - const auto family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const std::size_t cycles = 5; const auto requester_port = PortFactory::makePort(); @@ -90,7 +90,7 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { } TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { - const auto family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const std::size_t cycles = 5; const auto requester_port = PortFactory::makePort(); @@ -152,7 +152,7 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { TEST_CASE( "Receive from thirdy peer expected to fail after udp socket connected", "[udp]") { - const auto family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const auto requester_port = PortFactory::makePort(); const Address requester_address = Address(requester_port, family); @@ -202,7 +202,7 @@ TEST_CASE( } TEST_CASE("Metamorphosis of udp connections", "[udp]") { - const auto family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const std::size_t cycles = 5; const auto requester_port = PortFactory::makePort(); @@ -294,7 +294,7 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { } TEST_CASE("Open connection with timeout", "[udp]") { - const auto family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const auto requester_port = PortFactory::makePort(); const Address requester_address = Address(requester_port, family); @@ -331,7 +331,7 @@ TEST_CASE("Open connection with timeout", "[udp]") { } TEST_CASE("Reserve random port for udp connection", "[udp]") { - const auto family = GENERATE(IP_V4, IP_V6); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); auto requester_port = ANY_PORT; UdpBinded requester(requester_port, family); From a6ca0c07a811975fb49da9c28df2b185ee6220fa Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 19:14:56 +0100 Subject: [PATCH 182/228] documenting --- TODO | 6 ------ 1 file changed, 6 deletions(-) diff --git a/TODO b/TODO index f6e1dd47..00fd87e7 100644 --- a/TODO +++ b/TODO @@ -2,24 +2,18 @@ use inet_pton also in windows support for bluetooth connection -in receive from, resize buffer a max size udp message - usare noexpect keyword in c'tors? e' thread safety assicurata nelle varie funzioni? nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema -accept tcp client con timeout - add negative test per capire se vegono thrownati giusti SocketError add test dove request e spezzettata in piu frammenti (piu send diverse) negative test provando a bindare piu' volte la stessa porta per tcp e udp -ricontrollare se vari buffer char sono da ripulire a zeros prima di uso - stressare bene il fatto che c'e' possibilita' di usare con timeout varie operazioni stressare che si possono fare in maniera safe send concorrenti per udp From 83d0fbbb614f4ccd0f82c73c177ea17b696b8d15 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 19:15:06 +0100 Subject: [PATCH 183/228] documenting --- src/header/MinimalSocket/core/Address.h | 26 ++++--- src/header/MinimalSocket/core/Definitions.h | 19 +++++ src/header/MinimalSocket/core/Receiver.h | 70 ++++++++++++++----- src/header/MinimalSocket/core/Sender.h | 36 ++++++++-- src/header/MinimalSocket/core/Socket.h | 24 +++++-- src/header/MinimalSocket/core/SocketContext.h | 16 +++++ src/src/core/Socket.cpp | 11 --- tests/TestTCP.cpp | 6 -- 8 files changed, 153 insertions(+), 55 deletions(-) diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index 2723a18a..ef5f4e66 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -19,26 +19,26 @@ enum class AddressFamily { IP_V4, IP_V6 }; using Port = std::uint16_t; -static constexpr Port ANY_PORT = 0; - /** - * @brief representation of a network ip address + * @brief Passing this value as Port implies to ask the system to reserve a + * random port. */ +static constexpr Port ANY_PORT = 0; + class Address { public: /** - * @brief Internally the protocol Family is deduced according to the + * @brief Internally the AddressFamily is deduced according to the * hostIp content. - * @return nullptr if the host is invalid, otherwise a smart pointer - * storing a usable ip + * In case of invalid host, the object is built but left empty (i.e. *this == + * nullptr would be true) */ Address(const std::string &hostIp, const Port &port); /** - * @return an ipv4 or ipv6 with localhost as host and the passed port + * @brief A representation of a local host address is created. */ - Address(const Port &port, const AddressFamily &family = - AddressFamily::IP_V4); // local host assumed + Address(const Port &port, const AddressFamily &family = AddressFamily::IP_V4); const std::string &getHost() const { return this->host; }; const Port &getPort() const { return this->port; }; @@ -57,10 +57,15 @@ class Address { AddressFamily family; }; +/** + * @return host:port into a string. + */ std::string to_string(const Address &subject); /** - * @return nullopt in case the address in invalid + * @brief Tries to deduce the family from the host. + * @return nullopt in case the host is invalid, otherwise the deduced family + * value is returned. */ std::optional deduceAddressFamily(const std::string &host_address); @@ -69,5 +74,4 @@ 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 23fd8f63..3d2e4bf0 100644 --- a/src/header/MinimalSocket/core/Definitions.h +++ b/src/header/MinimalSocket/core/Definitions.h @@ -15,18 +15,37 @@ struct Buffer { char *buffer; const std::size_t buffer_size; }; + +/** + * @brief sets all values inside the passed buffer to 0 + */ void clear(Buffer &subject); + +/** + * @param subject the string buffer to convert + * @return a buffer pointing to the first element of the subject, and a lenght + * equal to the current size of subject + */ Buffer makeStringBuffer(std::string &subject); struct ConstBuffer { const char *buffer; const std::size_t buffer_size; }; + +/** + * @param subject the string buffer to convert + * @return an immutable buffer pointing to the first element of the subject, and + * a lenght equal to the current size of subject + */ ConstBuffer makeStringConstBuffer(const std::string &subject); enum class SocketType { UDP, TCP }; using Timeout = std::chrono::milliseconds; +/** + * @brief actually, a null timeout is associated to a possibly infinite wait + */ static constexpr Timeout NULL_TIMEOUT = Timeout{0}; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Receiver.h b/src/header/MinimalSocket/core/Receiver.h index 75ccdc54..d9668910 100644 --- a/src/header/MinimalSocket/core/Receiver.h +++ b/src/header/MinimalSocket/core/Receiver.h @@ -23,40 +23,63 @@ class ReceiverBase : public virtual Socket { 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 { public: /** - * @param[in] the buffer that will receive the message: - * first element of the pair is the data pointer - * of the buffer second element of the pair is the buffer size A request to - * receive a message of maximal size equal to message.second will be forwarded - * to the socket api. - * @param[in] the timeout to consider + * @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 (can be also less than the buffer size) + * message. It can be also lower then buffer size, as less bytes might be + * received. */ std::size_t receive(Buffer &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. + * @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. + */ std::string receive(std::size_t expected_max_bytes, const Timeout &timeout = NULL_TIMEOUT); }; +/** + * @brief Typically associated to a non connected socket, whose remote peer that + * sends bytes is known and may change over the time. + * 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 { public: - /** - * @param[in] the buffer that will receive the message: - * first element of the pair is the data pointer - * of the buffer second element of the pair is the buffer size A request to - * receive a message of maximal size equal to message.second will be forwarded - * to the socket api. - * @param[in] the timeout to consider - * @return the number of received bytes actually received and copied into - * message (can be also less than the buffer size) - */ 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 + * 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. + */ std::optional receive(Buffer &message, const Timeout &timeout = NULL_TIMEOUT); @@ -64,6 +87,19 @@ class ReceiverUnkownSender : public ReceiverBase { 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 + * 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. + * @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. + */ std::optional receive(std::size_t expected_max_bytes, const Timeout &timeout = NULL_TIMEOUT); diff --git a/src/header/MinimalSocket/core/Sender.h b/src/header/MinimalSocket/core/Sender.h index 750c1b84..125adb06 100644 --- a/src/header/MinimalSocket/core/Sender.h +++ b/src/header/MinimalSocket/core/Sender.h @@ -16,29 +16,55 @@ #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: /** - * @return true if the message was completely sent - * @param[in] the message to send + * @param message the buffer storing the bytes to send + * @return true in case all the bytes were successfully sent */ bool send(const ConstBuffer &message); + /** + * @param message the buffer storing the bytes to send as a string + * @return true in case all the bytes were successfully sent + */ bool send(const std::string &message); private: std::mutex send_mtx; }; +/** + * @brief Typically associated to a non connected socket, whose remote peer that + * sends bytes is known and may change over the time. + * 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: /** - * @return true if the message was completely sent - * @param[in] the message to send + * @param message the buffer storing the bytes to send + * @param recipient the recpient of the message + * @return true in case all the bytes were successfully sent to the specified + * recipient */ - // TODO spiegare che possono essere concorrenti e come funziona bool sendTo(const ConstBuffer &message, const Address &recipient); + /** + * @param message the buffer storing the bytes to send as a string + * @param recipient the recpient of the message + * @return true in case all the bytes were successfully sent to the specified + * recipient + */ bool sendTo(const std::string &message, const Address &recipient); private: diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 22c8ff5f..71a93859 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -21,6 +21,22 @@ namespace MinimalSocket { #ifdef _WIN32 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. + * + * 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. + */ class WSAManager { public: static void setWsaVersion(const WSAVersion &version); @@ -34,6 +50,9 @@ class WSAManager { class SocketIdWrapper; +/** + * @brief The base onject of any kind of socket. + */ class Socket { public: virtual ~Socket(); @@ -41,8 +60,6 @@ class Socket { Socket(const Socket &) = delete; Socket &operator=(const Socket &) = delete; - bool empty() const; - int accessSocketID() const; protected: @@ -58,9 +75,6 @@ class Socket { std::unique_ptr socket_id_wrapper; }; -bool operator==(std::nullptr_t, const Socket &subject); -bool operator==(const Socket &subject, std::nullptr_t); - class Openable : public virtual Socket { public: bool wasOpened() const { return opened; } diff --git a/src/header/MinimalSocket/core/SocketContext.h b/src/header/MinimalSocket/core/SocketContext.h index 8d92f979..4d352d24 100644 --- a/src/header/MinimalSocket/core/SocketContext.h +++ b/src/header/MinimalSocket/core/SocketContext.h @@ -12,12 +12,20 @@ namespace MinimalSocket { class RemoteAddressAware { public: + /** + * @return the address of the peer that can exchange messages with this + * socket. + */ const Address &getRemoteAddress() const { return remote_address; } RemoteAddressAware(const RemoteAddressAware &) = default; RemoteAddressAware &operator=(const RemoteAddressAware &) = default; protected: + /** + * @throw in case the passed address is invalid (i.e. address == nullptr is + * true). + */ RemoteAddressAware(const Address &address); private: @@ -26,6 +34,10 @@ class RemoteAddressAware { class PortToBindAware { public: + /** + * @return the port that will be reserved, in case the socket was not already + * opened, or the port actually reserved when the socket was opened. + */ Port getPortToBind() const { return port_to_bind; } PortToBindAware(const PortToBindAware &) = default; @@ -42,6 +54,10 @@ class PortToBindAware { class RemoteAddressFamilyAware { public: + /** + * @return the address family of the peer that can exchange messages with this + * socket. + */ AddressFamily getRemoteAddressFamily() const { return remote_address_family; } RemoteAddressFamilyAware(const RemoteAddressFamilyAware &) = default; diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index c73b058e..f628e24f 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -31,21 +31,10 @@ Socket::~Socket() = default; Socket::Socket() { resetIDWrapper(); } -bool Socket::empty() const { - return socket_id_wrapper->accessId() == SCK_INVALID_SOCKET; -} - int Socket::accessSocketID() const { return static_cast(getIDWrapper().accessId()); } -bool operator==(std::nullptr_t, const Socket &subject) { - return subject.empty(); -} -bool operator==(const Socket &subject, std::nullptr_t) { - return subject.empty(); -} - void Socket::transfer(Socket &receiver, Socket &giver) { receiver.socket_id_wrapper = std::move(giver.socket_id_wrapper); giver.resetIDWrapper(); diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index fdbe28fa..d833259c 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -32,7 +32,6 @@ Peers make_peers(const Port &port, const AddressFamily &family) { REQUIRE(server.open()); #pragma omp barrier auto accepted = server.acceptNewClient(); - REQUIRE_FALSE(nullptr == accepted); server_side = std::make_unique(std::move(accepted)); }, [&]() { @@ -40,7 +39,6 @@ Peers make_peers(const Port &port, const AddressFamily &family) { TcpClient client(Address(port, family)); #pragma omp barrier REQUIRE(client.open()); - REQUIRE_FALSE(nullptr == client); REQUIRE(client.wasOpened()); client_side = std::make_unique(std::move(client)); }); @@ -100,9 +98,7 @@ TEST_CASE("Establish tcp connection", "[tcp]") { auto &server_side = *peers.server_side.get(); auto &client_side = *peers.client_side.get(); - REQUIRE_FALSE(nullptr == client_side); REQUIRE(client_side.wasOpened()); - REQUIRE_FALSE(nullptr == server_side); const std::size_t cycles = 5; const std::string request = "Hello"; @@ -262,7 +258,6 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { [&]() { #pragma omp barrier auto accepted = server.acceptNewClient(); - REQUIRE_FALSE(nullptr == accepted); auto received_request = accepted.receive(request.size()); CHECK(received_request == request); }, @@ -271,7 +266,6 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { TcpClient client(Address(port, family)); #pragma omp barrier REQUIRE(client.open()); - REQUIRE_FALSE(nullptr == client); REQUIRE(client.wasOpened()); client.send(request); }); From 83fdfb9cb201ace437991347e97d28b9289ddcd9 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 19:27:51 +0100 Subject: [PATCH 184/228] documenting --- src/header/MinimalSocket/core/Socket.h | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 71a93859..d96aaada 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -60,6 +60,26 @@ class Socket { Socket(const Socket &) = delete; Socket &operator=(const Socket &) = delete; + /** + * @return the socket id associated to this object. + * + * This might 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 + * underlying socket and returned by ::socket(...) + * + * https://man7.org/linux/man-pages/man2/socket.2.html + * in UNIX is the integer used to identify the underlying socket and returned + * by ::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. + */ int accessSocketID() const; protected: @@ -78,6 +98,17 @@ class Socket { class Openable : public virtual Socket { public: 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: From b8bbffcefaa9b7f9bdc3a48294989720f6b2f9c8 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 21:31:16 +0100 Subject: [PATCH 185/228] documenting --- TODO | 2 + src/header/MinimalSocket/tcp/TcpClient.h | 6 ++ src/header/MinimalSocket/tcp/TcpServer.h | 34 ++++++++- src/header/MinimalSocket/udp/UdpSocket.h | 94 ++++++++++++++++++++---- 4 files changed, 121 insertions(+), 15 deletions(-) diff --git a/TODO b/TODO index 00fd87e7..2da952a2 100644 --- a/TODO +++ b/TODO @@ -25,3 +25,5 @@ error se os non e' win32 o linux mettere bene in evidenza il fatto che udp possono essere usati da connessi o no cambiare nome a repository git (MinimalSocket) + +document throw diff --git a/src/header/MinimalSocket/tcp/TcpClient.h b/src/header/MinimalSocket/tcp/TcpClient.h index baea3b60..5260885d 100644 --- a/src/header/MinimalSocket/tcp/TcpClient.h +++ b/src/header/MinimalSocket/tcp/TcpClient.h @@ -20,6 +20,12 @@ class TcpClient : public Openable, TcpClient(TcpClient &&o); TcpClient &operator=(TcpClient &&o); + /** + * @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(...) + * @param server_address the server to reach when opening this socket + */ TcpClient(const Address &server_address); protected: diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index dbf353dd..2bf83cee 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -15,7 +15,11 @@ namespace MinimalSocket::tcp { class TcpServer; - +/** + * @brief An already accepted connection to a client. + * An istance of this object can be built by before creating a TcpServer, open + * it and call acceptNewClient(). + */ class TcpConnection : public Sender, public Receiver, public RemoteAddressAware { @@ -37,13 +41,41 @@ class TcpServer : public PortToBindAware, TcpServer(TcpServer &&o); TcpServer &operator=(TcpServer &&o); + /** + * @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 + */ TcpServer(const Port port_to_bind = ANY_PORT, const AddressFamily &accepted_client_family = AddressFamily::IP_V4); + /** + * @brief Wait till accepting the connection from a new client. This is a + * blocking operation. + */ TcpConnection acceptNewClient(); // blocking + /** + * @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. + */ std::optional acceptNewClient(const Timeout &timeout); + /** + * @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. + */ void setClientQueueSize(const std::size_t queue_size); protected: diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index d9fe332d..eb785175 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -20,7 +20,14 @@ static constexpr std::size_t MAX_UDP_RECV_MESSAGE = 65507; class UdpConnected; -// can send and receive (from anyone hitting this socket) as a port was reserved +/** + * @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 SenderTo, public ReceiverUnkownSender, public PortToBindAware, @@ -30,27 +37,62 @@ class UdpBinded : public SenderTo, UdpBinded(UdpBinded &&o); UdpBinded &operator=(UdpBinded &&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( const Port port_to_bind = ANY_PORT, const AddressFamily &accepted_connection_family = AddressFamily::IP_V4); - // throw in case address family is inconsistent - // leave this socket empty after success + /** + * @brief Connects the udo 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(std::string *initial_message = - nullptr); // to first sending 1 byte, inf timeout - - std::optional - connect(const Timeout &timeout, - std::string *initial_message = nullptr); // to first sending 1 byte + /** + * @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. + */ + UdpConnected connect(std::string *initial_message = nullptr); + + /** + * @brief similar to connect(std::string *initial_message), but non blocking. + * If no remote peer sends a message within the timeout, a nullopt is + * returned. + * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to + * begin a blocking connect. + * @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); protected: void open_() override; }; -// can send and receive only from the specific remote address the socket was -// connected to +/** + * @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 Sender, public Receiver, public PortToBindAware, @@ -60,22 +102,46 @@ class UdpConnected : public Sender, UdpConnected(UdpConnected &&o); UdpConnected &operator=(UdpConnected &&o); + /** + * @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 + */ UdpConnected(const Address &remote_address, const Port &port = ANY_PORT); - // leave this socket empty after success + /** + * @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. + */ UdpBinded disconnect(); protected: void open_() override; }; -// to first sending 1 byte +/** + * @brief builds from 0 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 + * @param accepted_connection_family the kind of remote udp that can asks the + * connection + * @param initial_message the message sent from the remote peer to detect its + * address + */ UdpConnected makeUdpConnectedToUnknown(const Port &port, const AddressFamily &accepted_connection_family, std::string *initial_message = nullptr); -// to first sending 1 byte +/** + * @brief non blocking version of makeUdpConnectedToUnknown(const Port &, const + * AddressFamily &, std::string *). In case no remote peer sends at least 1 byte + * within the timeout, a nullopt is returned. + */ std::optional makeUdpConnectedToUnknown( const Port &port, const AddressFamily &accepted_connection_family, const Timeout &timeout, std::string *initial_message = nullptr); From 2391775faad430aa17ea186e377596cdf81ee0b1 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Sun, 15 May 2022 21:57:54 +0100 Subject: [PATCH 186/228] static error if not valid os --- src/src/SocketId.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/src/SocketId.h b/src/src/SocketId.h index 33c39f4a..1eaf41ad 100644 --- a/src/src/SocketId.h +++ b/src/src/SocketId.h @@ -15,12 +15,14 @@ #include #include #include -#else +#elif __linux__ #include #include #include //memset #include #include //close +#else +#error "Not supported system by MinimalSocket" #endif namespace MinimalSocket { @@ -85,7 +87,7 @@ class WSALazyInitializer { ~WSALazyInitializer(); private: - WSALazyInitializer(const WSAVersion& version); + WSALazyInitializer(const WSAVersion &version); static std::mutex lazy_proxy_mtx; static std::unique_ptr lazy_proxy; From 5481270719837e48cd9397905ba273a83aeeb0fa Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Mon, 16 May 2022 00:18:24 +0100 Subject: [PATCH 187/228] testing --- TODO | 18 +++---- src/header/MinimalSocket/core/Definitions.h | 2 +- src/header/MinimalSocket/core/Receiver.h | 5 +- src/src/core/Definitions.cpp | 2 +- src/src/core/Receiver.cpp | 4 +- tests/TestTCP.cpp | 53 +++++++++++++++++++++ 6 files changed, 66 insertions(+), 18 deletions(-) diff --git a/TODO b/TODO index 2da952a2..f66938cb 100644 --- a/TODO +++ b/TODO @@ -2,28 +2,22 @@ use inet_pton also in windows support for bluetooth connection -usare noexpect keyword in c'tors? - e' thread safety assicurata nelle varie funzioni? -nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema - add negative test per capire se vegono thrownati giusti SocketError add test dove request e spezzettata in piu frammenti (piu send diverse) negative test provando a bindare piu' volte la stessa porta per tcp e udp -stressare bene il fatto che c'e' possibilita' di usare con timeout varie operazioni - -stressare che si possono fare in maniera safe send concorrenti per udp - add test for udp concurrent send to -error se os non e' win32 o linux - -mettere bene in evidenza il fatto che udp possono essere usati da connessi o no - cambiare nome a repository git (MinimalSocket) document throw + +in the main README.md: + - nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema + - stressare bene il fatto che c'e' possibilita' di usare con timeout varie operazioni + - stressare che si possono fare in maniera safe send concorrenti per udp + - mettere bene in evidenza il fatto che udp possono essere usati da connessi o no diff --git a/src/header/MinimalSocket/core/Definitions.h b/src/header/MinimalSocket/core/Definitions.h index 3d2e4bf0..e4ad3e37 100644 --- a/src/header/MinimalSocket/core/Definitions.h +++ b/src/header/MinimalSocket/core/Definitions.h @@ -19,7 +19,7 @@ struct Buffer { /** * @brief sets all values inside the passed buffer to 0 */ -void clear(Buffer &subject); +void clear(const Buffer &subject); /** * @param subject the string buffer to convert diff --git a/src/header/MinimalSocket/core/Receiver.h b/src/header/MinimalSocket/core/Receiver.h index d9668910..caeeaf61 100644 --- a/src/header/MinimalSocket/core/Receiver.h +++ b/src/header/MinimalSocket/core/Receiver.h @@ -40,7 +40,8 @@ class Receiver : public ReceiverBase { * message. It can be also lower then buffer size, as less bytes might be * received. */ - std::size_t receive(Buffer &message, const Timeout &timeout = NULL_TIMEOUT); + std::size_t receive(const Buffer &message, + const Timeout &timeout = NULL_TIMEOUT); /** * @brief Similar to Receiver::receive(Buffer &, const Timeout &), but @@ -80,7 +81,7 @@ class ReceiverUnkownSender : public ReceiverBase { * also lower then buffer size, as less bytes might be received. * In case no bytes were received within the timeout, a nullopt is returned. */ - std::optional receive(Buffer &message, + std::optional receive(const Buffer &message, const Timeout &timeout = NULL_TIMEOUT); struct ReceiveStringResult { diff --git a/src/src/core/Definitions.cpp b/src/src/core/Definitions.cpp index 1dbbfc1f..b5556011 100644 --- a/src/src/core/Definitions.cpp +++ b/src/src/core/Definitions.cpp @@ -10,7 +10,7 @@ #include "../SocketId.h" namespace MinimalSocket { -void clear(Buffer &subject) { +void clear(const Buffer &subject) { ::memset(subject.buffer, 0, subject.buffer_size); } diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 566aec66..7be1e71f 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -68,7 +68,7 @@ void check_received_bytes(int &recvBytes, const Timeout &timeout) { } } // namespace -std::size_t Receiver::receive(Buffer &message, const Timeout &timeout) { +std::size_t Receiver::receive(const Buffer &message, const Timeout &timeout) { auto lock = lazyUpdateReceiveTimeout(timeout); clear(message); int recvBytes = ::recv(getIDWrapper().accessId(), message.buffer, @@ -92,7 +92,7 @@ std::string Receiver::receive(std::size_t expected_max_bytes, } std::optional -ReceiverUnkownSender::receive(Buffer &message, const Timeout &timeout) { +ReceiverUnkownSender::receive(const Buffer &message, const Timeout &timeout) { auto lock = lazyUpdateReceiveTimeout(timeout); clear(message); diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index d833259c..2694f411 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -346,3 +346,56 @@ TEST_CASE("Accept client with timeout", "[tcp]") { }); } } + +TEST_CASE("Send Receive messages split into multiple pieces", "[tcp]") { + const auto port = PortFactory::makePort(); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); + + auto peers = make_peers(port, family); + auto &server_side = *peers.server_side.get(); + auto &client_side = *peers.client_side.get(); + + const std::string request = "This is a simulated long message"; + + const std::size_t delta = 4; + + SECTION("split receive") { + parallel([&]() { client_side.send(request); }, + [&]() { + std::size_t received_bytes = 0; + std::string buffer; + buffer.resize(request.size()); + char *buffer_data = buffer.data(); + while (received_bytes != request.size()) { + std::size_t bytes_to_receive = std::min( + delta, request.size() - received_bytes); + auto received_bytes_delta = + server_side.receive(Buffer{buffer_data, bytes_to_receive}); + received_bytes += received_bytes_delta; + buffer_data += received_bytes_delta; + } + CHECK(buffer == request); + }); + } + + SECTION("split send") { + parallel( + [&]() { + std::size_t sent_bytes = 0; + const char *buffer_data = request.data(); + while (sent_bytes != request.size()) { + std::size_t bytes_to_send = + std::min(delta, request.size() - sent_bytes); + client_side.send(ConstBuffer{buffer_data, bytes_to_send}); + sent_bytes += bytes_to_send; + buffer_data += bytes_to_send; + } +#pragma omp barrier + }, + [&]() { +#pragma omp barrier + auto received_request = server_side.receive(request.size()); + CHECK(received_request == request); + }); + } +} From 5370587c2d290b8de3a104c2f22aad0257cfc91b Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Tue, 17 May 2022 15:23:59 +0100 Subject: [PATCH 188/228] testing --- TODO | 2 - samples/utils/ScriptGenerator.cpp | 4 ++ tests/SlicedOps.cpp | 78 +++++++++++++++++++++++++++++ tests/SlicedOps.h | 39 +++++++++++++++ tests/TestTCP.cpp | 29 +++-------- tests/TestUDP.cpp | 82 +++++++++++++++++++++++++++++++ 6 files changed, 209 insertions(+), 25 deletions(-) create mode 100644 tests/SlicedOps.cpp create mode 100644 tests/SlicedOps.h diff --git a/TODO b/TODO index f66938cb..33c6f3b9 100644 --- a/TODO +++ b/TODO @@ -6,8 +6,6 @@ e' thread safety assicurata nelle varie funzioni? add negative test per capire se vegono thrownati giusti SocketError -add test dove request e spezzettata in piu frammenti (piu send diverse) - negative test provando a bindare piu' volte la stessa porta per tcp e udp add test for udp concurrent send to diff --git a/samples/utils/ScriptGenerator.cpp b/samples/utils/ScriptGenerator.cpp index b08967b7..eea1398a 100644 --- a/samples/utils/ScriptGenerator.cpp +++ b/samples/utils/ScriptGenerator.cpp @@ -56,6 +56,10 @@ void add_process(std::ofstream &stream, const ProcessAndArgs &proc_and_args, void ScriptGenerator::generate(const std::string &file_name) { std::ofstream stream(file_name + SCRIPT_EXTENSION); +#ifdef __linux__ + stream << "#!/bin/sh" << std::endl; +#endif + for (std::size_t k = 0; k < (processes.size() - 1); ++k) { add_process(stream, processes[k], true); } diff --git a/tests/SlicedOps.cpp b/tests/SlicedOps.cpp new file mode 100644 index 00000000..31232eb6 --- /dev/null +++ b/tests/SlicedOps.cpp @@ -0,0 +1,78 @@ +#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(ConstBuffer{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(ConstBuffer{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(Buffer{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(Buffer{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 new file mode 100644 index 00000000..4b15de0f --- /dev/null +++ b/tests/SlicedOps.h @@ -0,0 +1,39 @@ +#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/TestTCP.cpp b/tests/TestTCP.cpp index 2694f411..466585be 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -11,6 +11,7 @@ #include "Parallel.h" #include "PortFactory.h" +#include "SlicedOps.h" using namespace MinimalSocket; using namespace MinimalSocket::tcp; @@ -347,7 +348,7 @@ TEST_CASE("Accept client with timeout", "[tcp]") { } } -TEST_CASE("Send Receive messages split into multiple pieces", "[tcp]") { +TEST_CASE("Send Receive messages split into multiple pieces (tcp)", "[tcp]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); @@ -362,34 +363,16 @@ TEST_CASE("Send Receive messages split into multiple pieces", "[tcp]") { SECTION("split receive") { parallel([&]() { client_side.send(request); }, [&]() { - std::size_t received_bytes = 0; - std::string buffer; - buffer.resize(request.size()); - char *buffer_data = buffer.data(); - while (received_bytes != request.size()) { - std::size_t bytes_to_receive = std::min( - delta, request.size() - received_bytes); - auto received_bytes_delta = - server_side.receive(Buffer{buffer_data, bytes_to_receive}); - received_bytes += received_bytes_delta; - buffer_data += received_bytes_delta; - } - CHECK(buffer == request); + auto received_request = + sliced_receive(server_side, request.size(), 4); + CHECK(received_request == request); }); } SECTION("split send") { parallel( [&]() { - std::size_t sent_bytes = 0; - const char *buffer_data = request.data(); - while (sent_bytes != request.size()) { - std::size_t bytes_to_send = - std::min(delta, request.size() - sent_bytes); - client_side.send(ConstBuffer{buffer_data, bytes_to_send}); - sent_bytes += bytes_to_send; - buffer_data += bytes_to_send; - } + sliced_send(client_side, request, 4); #pragma omp barrier }, [&]() { diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 4b76ea78..71a26a41 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -8,6 +8,7 @@ #include "Parallel.h" #include "PortFactory.h" +#include "SlicedOps.h" using namespace MinimalSocket; using namespace MinimalSocket::udp; @@ -365,3 +366,84 @@ TEST_CASE("Reserve random port for udp connection", "[udp]") { #pragma omp barrier }); } + +TEST_CASE("Send Receive messages split into multiple pieces (udp)", "[udp]") { + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); + + auto requester_port = ANY_PORT; + UdpBinded 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); + REQUIRE(responder.open()); + responder_port = responder.getPortToBind(); + const Address responder_address = Address(responder_port, family); + + const std::string request = "This is a simulated long message"; + + const std::size_t delta = 4; + + SECTION("un connected") { + SECTION("split receive") { + parallel( + [&]() { + requester.sendTo(request, responder_address); +#pragma omp barrier + }, + [&]() { +#pragma omp barrier + auto received_request = + sliced_receive(responder, request.size(), 4); + CHECK(received_request == request); + }); + } + + SECTION("split send") { + parallel( + [&]() { + sliced_send(requester, request, responder_address, 4); +#pragma omp barrier + }, + [&]() { +#pragma omp barrier + auto received_request = responder.receive(request.size()); + CHECK(received_request); + CHECK(received_request->received_message == request); + }); + } + } + + SECTION("connected") { + auto requester_conn = requester.connect(responder_address); + auto responder_conn = responder.connect(requester_address); + SECTION("split receive") { + parallel( + [&]() { + requester_conn.send(request); +#pragma omp barrier + }, + [&]() { +#pragma omp barrier + auto received_request = + sliced_receive(responder_conn, request.size(), 4); + CHECK(received_request == request); + }); + } + + SECTION("split send") { + parallel( + [&]() { + sliced_send(requester_conn, request, 4); +#pragma omp barrier + }, + [&]() { +#pragma omp barrier + auto received_request = responder_conn.receive(request.size()); + CHECK(received_request == request); + }); + } + } +} From 02b115cccabcc25d18f957221d905cf76ecff6db Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Tue, 17 May 2022 15:25:33 +0100 Subject: [PATCH 189/228] testing --- tests/TestUDP.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 71a26a41..d496ea91 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -367,7 +367,8 @@ TEST_CASE("Reserve random port for udp connection", "[udp]") { }); } -TEST_CASE("Send Receive messages split into multiple pieces (udp)", "[udp]") { +TEST_CASE("Send Receive messages split into multiple pieces (udp)", + "[udp][!mayfail]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); auto requester_port = ANY_PORT; From 2547660d62c11b4b82459e7c98e4b4d2b836bc13 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Tue, 17 May 2022 16:33:05 +0100 Subject: [PATCH 190/228] testing --- tests/ConnectionsUtils.cpp | 38 ++++++++++++++++++++++++++++ tests/ConnectionsUtils.h | 52 ++++++++++++++++++++++++++++++++++++++ tests/TestTCP.cpp | 44 +++++--------------------------- tests/TestThreadSafety.cpp | 0 tests/TestUDP.cpp | 35 +++---------------------- 5 files changed, 101 insertions(+), 68 deletions(-) create mode 100644 tests/ConnectionsUtils.cpp create mode 100644 tests/ConnectionsUtils.h create mode 100644 tests/TestThreadSafety.cpp diff --git a/tests/ConnectionsUtils.cpp b/tests/ConnectionsUtils.cpp new file mode 100644 index 00000000..7931e853 --- /dev/null +++ b/tests/ConnectionsUtils.cpp @@ -0,0 +1,38 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include + +#include "ConnectionsUtils.h" +#include "Parallel.h" + +namespace MinimalSocket::test { +TcpPeers::TcpPeers(const Port &port, const AddressFamily &family) + : client_side(Address{port, family}) { + parallel( + [&]() { + // server + tcp::TcpServer server(port, family); + REQUIRE(server.open()); +#pragma omp barrier + auto accepted = server.acceptNewClient(); + server_side = std::make_unique(std::move(accepted)); + }, + [&]() { + // client +#pragma omp barrier + REQUIRE(client_side.open()); + }); +} + +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()); +} +} // namespace MinimalSocket::test diff --git a/tests/ConnectionsUtils.h b/tests/ConnectionsUtils.h new file mode 100644 index 00000000..af53f32a --- /dev/null +++ b/tests/ConnectionsUtils.h @@ -0,0 +1,52 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#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; } + +private: + std::unique_ptr server_side; + tcp::TcpClient client_side; +}; + +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()}; + }; + +private: + udp::UdpBinded peer_a; + udp::UdpBinded 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.getPeerA(); \ + const auto responder_address = peers.addressPeerB(); +} // namespace MinimalSocket::test diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 466585be..37b23ed6 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -6,9 +6,8 @@ #include #include -#include -#include +#include "ConnectionsUtils.h" #include "Parallel.h" #include "PortFactory.h" #include "SlicedOps.h" @@ -18,35 +17,6 @@ using namespace MinimalSocket::tcp; using namespace MinimalSocket::test; namespace { -struct Peers { - std::unique_ptr server_side; - std::unique_ptr client_side; -}; -Peers make_peers(const Port &port, const AddressFamily &family) { - std::unique_ptr server_side; - std::unique_ptr client_side; - - parallel( - [&]() { - // server - TcpServer server(port, family); - REQUIRE(server.open()); -#pragma omp barrier - auto accepted = server.acceptNewClient(); - server_side = std::make_unique(std::move(accepted)); - }, - [&]() { - // client - TcpClient client(Address(port, family)); -#pragma omp barrier - REQUIRE(client.open()); - REQUIRE(client.wasOpened()); - client_side = std::make_unique(std::move(client)); - }); - - return Peers{std::move(server_side), std::move(client_side)}; -} - static const std::string request = "Hello"; static const std::string response = "Welcome"; @@ -95,9 +65,9 @@ TEST_CASE("Establish tcp connection", "[tcp]") { #endif SECTION("expected success") { - auto peers = make_peers(port, family); - auto &server_side = *peers.server_side.get(); - auto &client_side = *peers.client_side.get(); + test::TcpPeers peers(port, family); + auto &server_side = peers.getServerSide(); + auto &client_side = peers.getClientSide(); REQUIRE(client_side.wasOpened()); @@ -352,9 +322,9 @@ TEST_CASE("Send Receive messages split into multiple pieces (tcp)", "[tcp]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - auto peers = make_peers(port, family); - auto &server_side = *peers.server_side.get(); - auto &client_side = *peers.client_side.get(); + TcpPeers peers(port, family); + auto &server_side = peers.getServerSide(); + auto &client_side = peers.getClientSide(); const std::string request = "This is a simulated long message"; diff --git a/tests/TestThreadSafety.cpp b/tests/TestThreadSafety.cpp new file mode 100644 index 00000000..e69de29b diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index d496ea91..17317418 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -4,8 +4,7 @@ #include #include -#include - +#include "ConnectionsUtils.h" #include "Parallel.h" #include "PortFactory.h" #include "SlicedOps.h" @@ -28,15 +27,7 @@ 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; - const auto requester_port = PortFactory::makePort(); - const Address requester_address = Address(requester_port, family); - UdpBinded requester(requester_port, family); - REQUIRE(requester.open()); - - const auto responder_port = PortFactory::makePort(); - const Address responder_address = Address(responder_port, family); - UdpBinded responder(responder_port, family); - REQUIRE(responder.open()); + UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family); parallel( [&]() { @@ -297,15 +288,7 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { TEST_CASE("Open connection with timeout", "[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); - UdpBinded requester(requester_port, family); - REQUIRE(requester.open()); - - const auto responder_port = PortFactory::makePort(); - const Address responder_address = Address(responder_port, family); - UdpBinded responder(responder_port, family); - REQUIRE(responder.open()); + UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family); const auto timeout = Timeout{500}; @@ -371,17 +354,7 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", "[udp][!mayfail]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - auto requester_port = ANY_PORT; - UdpBinded 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); - REQUIRE(responder.open()); - responder_port = responder.getPortToBind(); - const Address responder_address = Address(responder_port, family); + UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family); const std::string request = "This is a simulated long message"; From a1584301633555651cb518182924d1d5f7a4b7ad Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Tue, 17 May 2022 21:19:55 +0100 Subject: [PATCH 191/228] testing --- TODO | 6 +- src/header/MinimalSocket/core/SocketContext.h | 16 +- src/src/SocketFunctions.cpp | 20 +- src/src/SocketFunctions.h | 2 +- src/src/core/SocketContext.cpp | 8 + src/src/tcp/TcpServer.cpp | 3 +- src/src/udp/UdpSocket.cpp | 7 +- tests/TestRobustness.cpp | 212 ++++++++++++++++++ tests/TestThreadSafety.cpp | 0 9 files changed, 254 insertions(+), 20 deletions(-) create mode 100644 tests/TestRobustness.cpp delete mode 100644 tests/TestThreadSafety.cpp diff --git a/TODO b/TODO index 33c6f3b9..f169fc6e 100644 --- a/TODO +++ b/TODO @@ -6,14 +6,12 @@ e' thread safety assicurata nelle varie funzioni? add negative test per capire se vegono thrownati giusti SocketError -negative test provando a bindare piu' volte la stessa porta per tcp e udp - -add test for udp concurrent send to - cambiare nome a repository git (MinimalSocket) document throw +in sample far vedere come si potrebbe costruire server asincrono + in the main README.md: - nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema - stressare bene il fatto che c'e' possibilita' di usare con timeout varie operazioni diff --git a/src/header/MinimalSocket/core/SocketContext.h b/src/header/MinimalSocket/core/SocketContext.h index 4d352d24..9c59568e 100644 --- a/src/header/MinimalSocket/core/SocketContext.h +++ b/src/header/MinimalSocket/core/SocketContext.h @@ -9,6 +9,8 @@ #include +#include + namespace MinimalSocket { class RemoteAddressAware { public: @@ -40,8 +42,17 @@ class PortToBindAware { */ Port getPortToBind() const { return port_to_bind; } - PortToBindAware(const PortToBindAware &) = default; - PortToBindAware &operator=(const PortToBindAware &) = default; + PortToBindAware(const PortToBindAware &); + PortToBindAware &operator=(const PortToBindAware &); + + /** + * @brief Used to enforce the fact that this port should be not 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. + */ + void mustBeFreePort() { must_be_free_port = true; }; + bool shallBeFreePort() const { return must_be_free_port; } protected: PortToBindAware(const Port &port) : port_to_bind(port){}; @@ -50,6 +61,7 @@ class PortToBindAware { private: Port port_to_bind; + std::atomic_bool must_be_free_port = false; }; class RemoteAddressFamilyAware { diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index ece64ead..fe5a5cb8 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -21,18 +21,20 @@ namespace { } // namespace Port bind(const SocketID &socket_id, const AddressFamily &family, - const Port &port) { - int reusePortOptVal = 1; - ::setsockopt(socket_id, SOL_SOCKET, REBIND_OPTION, - reinterpret_cast(&reusePortOptVal), - sizeof(int)); + >(&reusePortOptVal), + sizeof(int)); + } // bind the socket to the port visitAddress( diff --git a/src/src/SocketFunctions.h b/src/src/SocketFunctions.h index 8c93a2f2..026b9f4e 100644 --- a/src/src/SocketFunctions.h +++ b/src/src/SocketFunctions.h @@ -12,7 +12,7 @@ namespace MinimalSocket { // return port actually binded (as you could pass to the function also AnyPort) Port bind(const SocketID &socket_id, const AddressFamily &family, - const Port &port); + const Port &port, const bool must_be_free_port); void listen(const SocketID &socket_id, const std::size_t backlog_size); diff --git a/src/src/core/SocketContext.cpp b/src/src/core/SocketContext.cpp index 45eb2c13..de8e57e8 100644 --- a/src/src/core/SocketContext.cpp +++ b/src/src/core/SocketContext.cpp @@ -9,6 +9,14 @@ #include namespace MinimalSocket { +PortToBindAware::PortToBindAware(const PortToBindAware &o) { *this = o; } + +PortToBindAware &PortToBindAware::operator=(const PortToBindAware &o) { + this->port_to_bind = o.port_to_bind; + this->must_be_free_port = o.shallBeFreePort(); + return *this; +} + RemoteAddressAware::RemoteAddressAware(const Address &address) : remote_address(address) { if (nullptr == getRemoteAddress()) { diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 9c52a5d3..04fdd02c 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -34,7 +34,8 @@ void TcpServer::open_() { const auto port = getPortToBind(); const auto family = getRemoteAddressFamily(); socket.reset(SocketType::TCP, family); - auto binded_port = MinimalSocket::bind(socket.accessId(), family, port); + auto binded_port = + MinimalSocket::bind(socket.accessId(), family, port, shallBeFreePort()); setPort(binded_port); MinimalSocket::listen(socket.accessId(), client_queue_size); } diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index 03f1bcac..e3512120 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -30,8 +30,9 @@ UdpBinded &UdpBinded::operator=(UdpBinded &&o) { void UdpBinded::open_() { getIDWrapper().reset(SocketType::UDP, getRemoteAddressFamily()); - auto binded_port = MinimalSocket::bind( - getIDWrapper().accessId(), getRemoteAddressFamily(), getPortToBind()); + auto binded_port = + MinimalSocket::bind(getIDWrapper().accessId(), getRemoteAddressFamily(), + getPortToBind(), shallBeFreePort()); setPort(binded_port); } @@ -83,7 +84,7 @@ void UdpConnected::open_() { const auto &remote_address = getRemoteAddress(); getIDWrapper().reset(SocketType::UDP, remote_address.getFamily()); auto binded_port = MinimalSocket::bind(socket_id, remote_address.getFamily(), - getPortToBind()); + getPortToBind(), shallBeFreePort()); setPort(binded_port); MinimalSocket::connect(socket_id, remote_address); } diff --git a/tests/TestRobustness.cpp b/tests/TestRobustness.cpp new file mode 100644 index 00000000..ee7b3310 --- /dev/null +++ b/tests/TestRobustness.cpp @@ -0,0 +1,212 @@ +#include +#include + +#include + +#include +#include +#include + +#include "ConnectionsUtils.h" +#include "Parallel.h" +#include "PortFactory.h" + +using namespace MinimalSocket; +using namespace MinimalSocket::udp; +using namespace MinimalSocket::test; + +TEST_CASE("Thread safe d'tor tcp case", "[robustness]") { + const auto port = PortFactory::makePort(); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); + + SECTION("on connected sockets") { + test::TcpPeers peers(port, family); + + SECTION("close client while receiving") { + std::unique_ptr client = + std::make_unique(std::move(peers.getClientSide())); + parallel( + [&]() { +#pragma omp barrier + client->receive(500); + }, + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(std::chrono::milliseconds{50}); + client.reset(); + }); + } + + SECTION("close server side while receiving") { + std::unique_ptr server = + std::make_unique( + std::move(peers.getServerSide())); + parallel( + [&]() { +#pragma omp barrier + server->receive(500); + }, + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(std::chrono::milliseconds{50}); + server.reset(); + }); + } + } + + SECTION("close while accepting client") { + std::unique_ptr server = + std::make_unique(port, family); + REQUIRE(server->open()); + parallel( + [&]() { +#pragma omp barrier + server->acceptNewClient(); + }, + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(std::chrono::milliseconds{50}); + server.reset(); + }); + } +} + +namespace { +std::string make_repeated_message(const std::string &to_repeat, + const std::size_t times) { + std::stringstream stream; + for (std::size_t k = 0; k < times; ++k) { + stream << to_repeat; + } + return stream.str(); +} + +static const std::string MESSAGE = "A simple message"; +} // namespace + +TEST_CASE("Receive from multiple threads tcp case", "[robustness]") { + const auto port = PortFactory::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(); + + const std::size_t threads = 3; + std::vector tasks; + tasks.emplace_back( + [&]() { client_side.send(make_repeated_message(MESSAGE, threads)); }); + for (std::size_t t = 0; t < threads; ++t) { + tasks.emplace_back([&]() { + const auto received_request = server_side.receive(MESSAGE.size()); + CHECK(received_request == MESSAGE); + }); + } + parallel(tasks); +} + +TEST_CASE("Send from multiple threads tcp case", "[robustness]") { + const auto port = PortFactory::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(); + + const std::size_t threads = 3; + std::vector tasks; + for (std::size_t t = 0; t < threads; ++t) { + tasks.emplace_back([&]() { client_side.send(MESSAGE); }); + } + tasks.emplace_back([&]() { + for (std::size_t t = 0; t < threads; ++t) { + const auto received_request = server_side.receive(MESSAGE.size()); + CHECK(received_request == MESSAGE); + } + }); + parallel(tasks); +} + +TEST_CASE("Thread safe d'tor udp case", "[robustness]") { + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); + + std::unique_ptr connection = + std::make_unique(PortFactory::makePort()); + + parallel( + [&]() { +#pragma omp barrier + connection->receive(500); + }, + [&]() { +#pragma omp barrier + std::this_thread::sleep_for(std::chrono::milliseconds{50}); + connection.reset(); + }); +} + +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) + + const std::size_t threads = 3; + std::vector tasks; + tasks.emplace_back([&]() { + requester.sendTo(make_repeated_message(MESSAGE, threads), + responder_address); + }); + for (std::size_t t = 0; t < threads; ++t) { + tasks.emplace_back([&]() { + const auto received_request = responder.receive(MESSAGE.size()); + CHECK(received_request); + CHECK(received_request->received_message == MESSAGE); + }); + } + parallel(tasks); +} + +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) + + const std::size_t threads = 3; + std::vector tasks; + for (std::size_t t = 0; t < threads; ++t) { + tasks.emplace_back([&]() { requester.sendTo(MESSAGE, responder_address); }); + } + tasks.emplace_back([&]() { + for (std::size_t t = 0; t < threads; ++t) { + const auto received_request = responder.receive(MESSAGE.size()); + CHECK(received_request); + CHECK(received_request->received_message == MESSAGE); + } + }); + parallel(tasks); +} + +TEST_CASE("Use tcp socket before opening it", "[robustness]") { + const auto port = PortFactory::makePort(); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); + + SECTION("server") { + tcp::TcpServer socket(port, family); + socket.acceptNewClient(); + } + + SECTION("client") { + tcp::TcpClient socket(Address{port, family}); + socket.receive(500); + socket.send("dummy"); + } +} + +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); + socket.receive(500); + socket.sendTo("dummy", Address{PortFactory::makePort(), family}); + socket.connect(); +} diff --git a/tests/TestThreadSafety.cpp b/tests/TestThreadSafety.cpp deleted file mode 100644 index e69de29b..00000000 From f2bf3c793ee84b353cd6ab4d184cfd28699c9a92 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Tue, 17 May 2022 21:30:43 +0100 Subject: [PATCH 192/228] refactoring --- src/header/MinimalSocket/core/SocketContext.h | 17 +++++------ src/header/MinimalSocket/tcp/TcpServer.h | 3 +- src/src/core/SocketContext.cpp | 28 +++++++++++++++++-- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/header/MinimalSocket/core/SocketContext.h b/src/header/MinimalSocket/core/SocketContext.h index 9c59568e..b0a60379 100644 --- a/src/header/MinimalSocket/core/SocketContext.h +++ b/src/header/MinimalSocket/core/SocketContext.h @@ -10,6 +10,7 @@ #include #include +#include namespace MinimalSocket { class RemoteAddressAware { @@ -18,10 +19,10 @@ class RemoteAddressAware { * @return the address of the peer that can exchange messages with this * socket. */ - const Address &getRemoteAddress() const { return remote_address; } + Address getRemoteAddress() const; - RemoteAddressAware(const RemoteAddressAware &) = default; - RemoteAddressAware &operator=(const RemoteAddressAware &) = default; + RemoteAddressAware(const RemoteAddressAware &); + RemoteAddressAware &operator=(const RemoteAddressAware &); protected: /** @@ -31,6 +32,7 @@ class RemoteAddressAware { RemoteAddressAware(const Address &address); private: + mutable std::mutex remote_address_mtx; Address remote_address; }; @@ -60,7 +62,7 @@ class PortToBindAware { void setPort(const Port &port) { port_to_bind = port; }; private: - Port port_to_bind; + std::atomic port_to_bind; std::atomic_bool must_be_free_port = false; }; @@ -72,15 +74,14 @@ class RemoteAddressFamilyAware { */ AddressFamily getRemoteAddressFamily() const { return remote_address_family; } - RemoteAddressFamilyAware(const RemoteAddressFamilyAware &) = default; - RemoteAddressFamilyAware & - operator=(const RemoteAddressFamilyAware &) = default; + RemoteAddressFamilyAware(const RemoteAddressFamilyAware &); + RemoteAddressFamilyAware &operator=(const RemoteAddressFamilyAware &); protected: RemoteAddressFamilyAware(const AddressFamily &family) : remote_address_family(family){}; private: - AddressFamily remote_address_family; + std::atomic remote_address_family; }; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index 2bf83cee..99adc837 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -11,6 +11,7 @@ #include #include +#include #include namespace MinimalSocket::tcp { @@ -82,7 +83,7 @@ class TcpServer : public PortToBindAware, void open_() override; private: - std::size_t client_queue_size = + std::atomic client_queue_size = 50; // maximum number of clients put in the queue wiating for connection // to be accepted diff --git a/src/src/core/SocketContext.cpp b/src/src/core/SocketContext.cpp index de8e57e8..49dc30b7 100644 --- a/src/src/core/SocketContext.cpp +++ b/src/src/core/SocketContext.cpp @@ -9,10 +9,23 @@ #include namespace MinimalSocket { +Address RemoteAddressAware::getRemoteAddress() const { + std::scoped_lock lock(remote_address_mtx); + return remote_address; +} + +RemoteAddressAware::RemoteAddressAware(const RemoteAddressAware &o) + : remote_address(o.getRemoteAddress()) {} + +RemoteAddressAware &RemoteAddressAware::operator=(const RemoteAddressAware &o) { + this->remote_address = o.getRemoteAddress(); + return *this; +} + PortToBindAware::PortToBindAware(const PortToBindAware &o) { *this = o; } PortToBindAware &PortToBindAware::operator=(const PortToBindAware &o) { - this->port_to_bind = o.port_to_bind; + this->port_to_bind = o.getPortToBind(); this->must_be_free_port = o.shallBeFreePort(); return *this; } @@ -22,5 +35,16 @@ RemoteAddressAware::RemoteAddressAware(const Address &address) if (nullptr == getRemoteAddress()) { throw Error{"Invalid address"}; } -}; +} + +RemoteAddressFamilyAware::RemoteAddressFamilyAware( + const RemoteAddressFamilyAware &o) { + *this = o; +} + +RemoteAddressFamilyAware & +RemoteAddressFamilyAware::operator=(const RemoteAddressFamilyAware &o) { + this->remote_address_family = o.getRemoteAddressFamily(); + return *this; +} } // namespace MinimalSocket From 059962af6c19b58c0a4d969b5413b48748c6939e Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Tue, 17 May 2022 22:07:03 +0100 Subject: [PATCH 193/228] testing --- TODO | 4 --- tests/TestRobustness.cpp | 73 ++++++++++++++++++++-------------------- 2 files changed, 36 insertions(+), 41 deletions(-) diff --git a/TODO b/TODO index f169fc6e..f8a9dcc8 100644 --- a/TODO +++ b/TODO @@ -2,10 +2,6 @@ use inet_pton also in windows support for bluetooth connection -e' thread safety assicurata nelle varie funzioni? - -add negative test per capire se vegono thrownati giusti SocketError - cambiare nome a repository git (MinimalSocket) document throw diff --git a/tests/TestRobustness.cpp b/tests/TestRobustness.cpp index ee7b3310..8acd0ea6 100644 --- a/tests/TestRobustness.cpp +++ b/tests/TestRobustness.cpp @@ -15,6 +15,23 @@ using namespace MinimalSocket; using namespace MinimalSocket::udp; using namespace MinimalSocket::test; +namespace { +std::string make_repeated_message(const std::string &to_repeat, + const std::size_t times) { + std::stringstream stream; + for (std::size_t k = 0; k < times; ++k) { + stream << to_repeat; + } + return stream.str(); +} + +static const std::string MESSAGE = "A simple message"; + +template void close(SocketT &subject) { + SocketT{std::move(subject)}; +} +} // namespace + TEST_CASE("Thread safe d'tor tcp case", "[robustness]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); @@ -23,67 +40,48 @@ TEST_CASE("Thread safe d'tor tcp case", "[robustness]") { test::TcpPeers peers(port, family); SECTION("close client while receiving") { - std::unique_ptr client = - std::make_unique(std::move(peers.getClientSide())); parallel( [&]() { #pragma omp barrier - client->receive(500); + CHECK(peers.getClientSide().receive(500).empty()); }, [&]() { #pragma omp barrier std::this_thread::sleep_for(std::chrono::milliseconds{50}); - client.reset(); + close(peers.getClientSide()); }); } SECTION("close server side while receiving") { - std::unique_ptr server = - std::make_unique( - std::move(peers.getServerSide())); parallel( [&]() { #pragma omp barrier - server->receive(500); + CHECK(peers.getClientSide().receive(500).empty()); }, [&]() { #pragma omp barrier std::this_thread::sleep_for(std::chrono::milliseconds{50}); - server.reset(); + close(peers.getServerSide()); }); } } SECTION("close while accepting client") { - std::unique_ptr server = - std::make_unique(port, family); - REQUIRE(server->open()); + tcp::TcpServer server(port, family); + REQUIRE(server.open()); parallel( [&]() { #pragma omp barrier - server->acceptNewClient(); + CHECK_THROWS_AS(server.acceptNewClient(), SocketError); }, [&]() { #pragma omp barrier std::this_thread::sleep_for(std::chrono::milliseconds{50}); - server.reset(); + close(server); }); } } -namespace { -std::string make_repeated_message(const std::string &to_repeat, - const std::size_t times) { - std::stringstream stream; - for (std::size_t k = 0; k < times; ++k) { - stream << to_repeat; - } - return stream.str(); -} - -static const std::string MESSAGE = "A simple message"; -} // namespace - TEST_CASE("Receive from multiple threads tcp case", "[robustness]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); @@ -130,18 +128,17 @@ 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); - std::unique_ptr connection = - std::make_unique(PortFactory::makePort()); + udp::UdpBinded connection(PortFactory::makePort()); parallel( [&]() { #pragma omp barrier - connection->receive(500); + CHECK_THROWS_AS(connection.receive(500), Error); }, [&]() { #pragma omp barrier std::this_thread::sleep_for(std::chrono::milliseconds{50}); - connection.reset(); + close(connection); }); } @@ -192,13 +189,13 @@ TEST_CASE("Use tcp socket before opening it", "[robustness]") { SECTION("server") { tcp::TcpServer socket(port, family); - socket.acceptNewClient(); + CHECK_THROWS_AS(socket.acceptNewClient(), Error); } SECTION("client") { tcp::TcpClient socket(Address{port, family}); - socket.receive(500); - socket.send("dummy"); + CHECK_THROWS_AS(socket.receive(500), SocketError); + CHECK_THROWS_AS(socket.send("dummy"), SocketError); } } @@ -206,7 +203,9 @@ 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); - socket.receive(500); - socket.sendTo("dummy", Address{PortFactory::makePort(), family}); - socket.connect(); + CHECK_THROWS_AS(socket.receive(500), SocketError); + CHECK_THROWS_AS( + socket.sendTo("dummy", Address{PortFactory::makePort(), family}), + SocketError); + CHECK_THROWS_AS(socket.connect(), SocketError); } From 0d15e79e525258a40ef099f4fdbc3bdad2dcf190 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Tue, 17 May 2022 23:43:23 +0100 Subject: [PATCH 194/228] testing --- TODO | 1 + tests/ConnectionsUtils.h | 2 +- tests/TestRobustness.cpp | 17 ++++++++++++++--- tests/TestUDP.cpp | 2 ++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/TODO b/TODO index f8a9dcc8..86fd9291 100644 --- a/TODO +++ b/TODO @@ -13,3 +13,4 @@ in the main README.md: - stressare bene il fatto che c'e' possibilita' di usare con timeout varie operazioni - stressare che si possono fare in maniera safe send concorrenti per udp - mettere bene in evidenza il fatto che udp possono essere usati da connessi o no + - le funzionalita offerte sono perfettamente thread safe diff --git a/tests/ConnectionsUtils.h b/tests/ConnectionsUtils.h index af53f32a..4204f3a5 100644 --- a/tests/ConnectionsUtils.h +++ b/tests/ConnectionsUtils.h @@ -47,6 +47,6 @@ class UdpPeers { UdpPeers peers(PORT_A, PORT_B, FAMILY); \ auto &requester = peers.getPeerA(); \ const auto requester_address = peers.addressPeerA(); \ - auto &responder = peers.getPeerA(); \ + auto &responder = peers.getPeerB(); \ const auto responder_address = peers.addressPeerB(); } // namespace MinimalSocket::test diff --git a/tests/TestRobustness.cpp b/tests/TestRobustness.cpp index 8acd0ea6..a08488a8 100644 --- a/tests/TestRobustness.cpp +++ b/tests/TestRobustness.cpp @@ -142,6 +142,8 @@ TEST_CASE("Thread safe d'tor udp case", "[robustness]") { }); } +/* + TEST_CASE("Receive from multiple threads udp case", "[robustness]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); @@ -150,9 +152,12 @@ TEST_CASE("Receive from multiple threads udp case", "[robustness]") { const std::size_t threads = 3; std::vector tasks; tasks.emplace_back([&]() { - requester.sendTo(make_repeated_message(MESSAGE, threads), - responder_address); + for (std::size_t t = 0; t < threads; ++t) { + requester.sendTo(MESSAGE, responder_address); + } +#pragma omp barrier }); +#pragma omp barrier for (std::size_t t = 0; t < threads; ++t) { tasks.emplace_back([&]() { const auto received_request = responder.receive(MESSAGE.size()); @@ -171,9 +176,13 @@ TEST_CASE("Send from multiple threads udp case", "[robustness]") { const std::size_t threads = 3; std::vector tasks; for (std::size_t t = 0; t < threads; ++t) { - tasks.emplace_back([&]() { requester.sendTo(MESSAGE, responder_address); }); + tasks.emplace_back([&]() { + requester.sendTo(MESSAGE, responder_address); +#pragma omp barrier + }); } tasks.emplace_back([&]() { +#pragma omp barrier for (std::size_t t = 0; t < threads; ++t) { const auto received_request = responder.receive(MESSAGE.size()); CHECK(received_request); @@ -183,6 +192,8 @@ TEST_CASE("Send from multiple threads udp case", "[robustness]") { parallel(tasks); } +*/ + TEST_CASE("Use tcp socket before opening it", "[robustness]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index 17317418..f31c6ec6 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -350,6 +350,7 @@ TEST_CASE("Reserve random port for udp connection", "[udp]") { }); } +/* TEST_CASE("Send Receive messages split into multiple pieces (udp)", "[udp][!mayfail]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); @@ -421,3 +422,4 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", } } } + */ \ No newline at end of file From 15c82ca216da45d0cd95744edf10f55a6c9e4e61 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Wed, 18 May 2022 10:33:50 +0100 Subject: [PATCH 195/228] documenting --- samples/tcp/README.md | 25 +++++++++++++++++++++++++ samples/tcp/TcpClient.cpp | 6 ++++++ samples/tcp/TcpRepeater.cpp | 6 ++++++ samples/tcp/TcpScriptsGenerator.cpp | 9 +++++++-- samples/tcp/TcpServer.cpp | 6 ++++++ 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/samples/tcp/README.md b/samples/tcp/README.md index e69de29b..3b7bfd61 100644 --- a/samples/tcp/README.md +++ b/samples/tcp/README.md @@ -0,0 +1,25 @@ +## TCP samples + +This folder stores some examples showing how to set up and use the objects inside **MinimalSocket** in order to use **tcp** connections. +Each sample requires to run multiple processes at the same time. This is automatically done by some shell scripts, which are NOT contained in this folder. Indeed, such scipts are generated in the bin folder, after running **TcpScriptsGenerator**, which generates all scripts without launching it. +Each process pertaining to a sample is run in a dedicated window. + +After generating the scripts, you can run them to see what the samples do. +In particular, 3 scripts can be generated, representative of 3 classes of samples: + +- **tcp01_server_client**: + -runs **TcpServer**, creating a tcp server that binds and listen to a specified port + -runs **TcpClient**, creating a tcp client that connections to the previous server, exchanging messages with it. + +- **tcp02_server_3_clients**: + -runs **TcpServer**, 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. + +- **tcp03_chain_size_4** has the aim of creating a chain of connected processes. More in detail: + -runs **TcpServer**, creating a tcp server that binds and listen to a specified port + -runs a series of **TcpRepeater**, creating a tcp client that connects to the previous process in the chain and a tcp server waitning for the connection request from the next element in the chain + -runs **TcpClient**, connecting to the last spawned process of the chain + The last client sends some requests, which are forward along the chain till the first server. This latter, sends a response that is backwarded along the chain till xoming back to the first client. + +**TcpServer** and **TcpClient** 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 **TcpScriptsGenerator**) for the syntax of the accepted arguments. diff --git a/samples/tcp/TcpClient.cpp b/samples/tcp/TcpClient.cpp index 5ed509a6..9e1ec79d 100644 --- a/samples/tcp/TcpClient.cpp +++ b/samples/tcp/TcpClient.cpp @@ -5,8 +5,14 @@ * 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 diff --git a/samples/tcp/TcpRepeater.cpp b/samples/tcp/TcpRepeater.cpp index aeed81c0..07c727a5 100644 --- a/samples/tcp/TcpRepeater.cpp +++ b/samples/tcp/TcpRepeater.cpp @@ -5,9 +5,15 @@ * report any bug to andrecasa91@gmail.com. **/ +/////////////////////////////////////////////////////////////////////////// +// Have a look to README.md // +/////////////////////////////////////////////////////////////////////////// + +// elements from the MinimalSocket library #include #include +// just a bunch of utilities #include #include diff --git a/samples/tcp/TcpScriptsGenerator.cpp b/samples/tcp/TcpScriptsGenerator.cpp index f9bc4d68..b75abaa8 100644 --- a/samples/tcp/TcpScriptsGenerator.cpp +++ b/samples/tcp/TcpScriptsGenerator.cpp @@ -5,6 +5,10 @@ * report any bug to andrecasa91@gmail.com. **/ +/////////////////////////////////////////////////////////////////////////// +// Have a look to README.md // +/////////////////////////////////////////////////////////////////////////// + #include #include @@ -30,7 +34,8 @@ int main() { // 1 server many clients const std::size_t clients = 3; - const std::string sample_name = "tcp02_server_" + std::to_string(clients) + "_clients"; + const std::string sample_name = + "tcp02_server_" + std::to_string(clients) + "_clients"; MinimalSocket::samples::ScriptGenerator generator; const std::string port = "35998"; @@ -40,7 +45,7 @@ int main() { generator.add("TcpClient", {{"port", port}}); for (std::size_t c = 1; c < clients; ++c) { - generator.add("TcpClient", { {"port", port}, {"rate", "800"} }); + generator.add("TcpClient", {{"port", port}, {"rate", "800"}}); } cout << "generating " << sample_name << endl; diff --git a/samples/tcp/TcpServer.cpp b/samples/tcp/TcpServer.cpp index 706027eb..bf7343d5 100644 --- a/samples/tcp/TcpServer.cpp +++ b/samples/tcp/TcpServer.cpp @@ -5,8 +5,14 @@ * 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 From 43693007b706af6fded22ad8403db0b39379948a Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Wed, 18 May 2022 11:26:21 +0100 Subject: [PATCH 196/228] documenting --- samples/udp/README.md | 26 ++++++++++++++++++++++++++ samples/udp/UdpAsker.cpp | 11 +++++++++-- samples/udp/UdpResponder.cpp | 6 ++++++ samples/udp/UdpScriptsGenerator.cpp | 4 ++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/samples/udp/README.md b/samples/udp/README.md index e69de29b..baca66c3 100644 --- a/samples/udp/README.md +++ b/samples/udp/README.md @@ -0,0 +1,26 @@ +## UDP samples + +This folder stores some examples showing how to set up and use the objects inside **MinimalSocket** in order to use **udp** connections. +Each sample requires to run multiple processes at the same time. This is automatically done by some shell scripts, which are NOT contained in this folder. Indeed, such scipts are generated in the bin folder, after running **UdpScriptsGenerator**, which generates all scripts without launching it. +Each process pertaining to a sample is run in a dedicated window. + +After generating the scripts, you can run them to see what the samples do. +In particular, 3 scripts can be generated, representative of 3 classes of samples: + +- **udp01_responder_asker**: + -runs **UdpResponder**, creating a udp socket that binds a specified port + -runs **UdpAsker**, creating a udp socket that binds another port and exchanges messages with the first spawned udp socket. + +- **udp02_connecting_responder_asker**: + -runs **UdpResponder**, creating a udp socket that binds a specified port, then connects to the first udp socket reaching it. + -runs **UdpAsker**, creating a udp socket that binds another port and exchanges messages with the first spawned udp socket. + After the connection, the first socket can't exchange messages with other peers. You can verify this by running a second **UdpAsker** sending requests to the port specified for the first **UdpResponder**: they will be always not satisfied. + +- **udp03_responder_2_askers**: + -runs **UdpResponder**, creating a udp socket that binds a specified port + -runs **UdpAsker**, creating a first udp socket that binds another port and exchanges messages with the first spawned udp socket. + -runs **UdpAsker**, creating a second udp socket that binds another port and exchanges messages with the first spawned udp socket. + The socket created in **UdpResponder** is able fufllfill the requests of both clients, as it is an un-connected udp socket. On the opposite, the one created in **udp02_connecting_responder_asker** is connecting to the first peer sending a message and that's why it would not be able to answer the requests of a third udp socket. + +**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/UdpAsker.cpp b/samples/udp/UdpAsker.cpp index 041a046e..ae43648e 100644 --- a/samples/udp/UdpAsker.cpp +++ b/samples/udp/UdpAsker.cpp @@ -5,8 +5,14 @@ * 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 @@ -26,8 +32,9 @@ int main(const int argc, const char **argv) { const MinimalSocket::Address remote_address(remote_host, remote_port); MinimalSocket::udp::UdpBinded asker(port_this, remote_address.getFamily()); - - std::this_thread::sleep_for(std::chrono::seconds{1}); // just to be sure the responder has already prepared the receive + + std::this_thread::sleep_for(std::chrono::seconds{ + 1}); // just to be sure the responder has already prepared the receive if (!asker.open()) { cout << "Failed to reserve specified port" << endl; return EXIT_FAILURE; diff --git a/samples/udp/UdpResponder.cpp b/samples/udp/UdpResponder.cpp index e7cccd5a..d8fd315e 100644 --- a/samples/udp/UdpResponder.cpp +++ b/samples/udp/UdpResponder.cpp @@ -5,8 +5,14 @@ * 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 diff --git a/samples/udp/UdpScriptsGenerator.cpp b/samples/udp/UdpScriptsGenerator.cpp index d532a2d0..09e7e057 100644 --- a/samples/udp/UdpScriptsGenerator.cpp +++ b/samples/udp/UdpScriptsGenerator.cpp @@ -5,6 +5,10 @@ * report any bug to andrecasa91@gmail.com. **/ +/////////////////////////////////////////////////////////////////////////// +// Have a look to README.md // +/////////////////////////////////////////////////////////////////////////// + #include #include From 823ab3d4a36be081270024618c5b6b7a394234f9 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 13:42:57 +0100 Subject: [PATCH 197/228] documenting --- README.md | 77 +++++++++++++++++++++++++++++++++---------- TODO | 9 ++--- samples/tcp/README.md | 2 ++ samples/udp/README.md | 2 ++ 4 files changed, 66 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 016319a8..13daf3a8 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,72 @@ ![binaries_compilation](https://github.com/andreacasalino/Cross-Platform-Socket/actions/workflows/installArtifacts.yml/badge.svg) ![binaries_compilation](https://github.com/andreacasalino/Cross-Platform-Socket/actions/workflows/runTests.yml/badge.svg) -This repository contains the minimal functionalities to create and use from **C++** both **tcp** and **udp** sockets in a -completely platform independent way. You can decide to compile just the very core package named SynchSocket, with the raw socket implementations or compile also the more advanced ones. +- [What is this library about](#intro) +- [Features](#features) +- [Usage](#usage) +- [Samples](#samples) +- [CMake support](#cmake-support) -**Content** +## INTRO - * The core packages are contained in the ./CrossSocket folder: - * SynchSocket implements the minimal functionalities to create and use raw tcp and udp connections, sending and receiving buffer of bytes - * AsynchSocket contains implementation of asynchronous sockets, i.e. sockets storing a private service constantly receiving new messages. It is possible to subscribe to the received messages (and react to it) by attaching a listener to the asynchronous socket. You can decide to not compile this package by setting to OFF the CMake option named COMPILE_ASYNCH - * TypedSocket contains usefull functionalities to build typed messangers, i.e. sockets exchanging complex data structures. These functionalities can be used in combination with [Google Protocol Buffers](https://developers.google.com/protocol-buffers/docs/cpptutorial) or similar implementations like [NanoPb](https://jpa.kapsi.fi/nanopb/) to automatically encode and decode data. You can decide to not compile this package by setting to OFF the CMake option named COMPILE_TYPED - - * Samples showing the library usage are contained in the ./Samples folder. 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.) - -**Build from sources** +**MinimalSocket** gives you a modern **C++** interface to create, connect and handle **tcp** and **udp** sockets, in a +completely platform independent way. The supported systems are: **Windows**, any **Linux** distro and **MacOS**. -Use [CMake](https://cmake.org) to configure and compile the library and the samples. By setting to ON the CMake option LIB_OPT, the above 3 packages will be compiled as shared libraries, otherwise as static. +Check [Features](#features) to see details about the various features of **MinimalSocket**. You can refer to [Usage](#usage) and [Samples](#samples) to see how to use **MinimalSocket**. -**Run the Samples** +This is a **CMake** project, check [CMake support](#cmake-support) to see how this library can be integrated. -Check the *README.md* inside Samples/Tcp/ and Samples/Udp/ to understand the samples purpose and how to run them +Remember to leave a **star** in case you have found this library useful. -**Download compiled binaries** +## FEATURES -You can simply download and use the binaries of the latest master version [here](https://github.com/andreacasalino/Cross-Platform-Socket/actions/runs/640613596) +Haven't left a **star** already? Do it now ;)! -**What Else?** +**MinimalSocket** allows you to build and set up **tcp** and **udp** connections. Messages can be sent and received in terms of both low level buffer of chars or high level string. Indeed, this is actually the only capability you need for a socket, as more complex messages can be encoded and decoded using among the others approaches like [Google Protocol Buffers](https://developers.google.com/protocol-buffers/docs/cpptutorial) or [NanoPb](https://jpa.kapsi.fi/nanopb/). -Have fun and leave a **star**. +This are the most notable properties of **MinimalSocket**: +- A modern **C++** interface allows you to set up and build connections in terms of objects. Sockets are not opened as soon as the wrapping object is created, but you after calling a proper method, allowing you to decouple socket creation from socket opening. Sockets are automatically closed (and all relevant information cleaned after destroying the wrapping object). +- You don't need to access low level functions from system modules: let **MinimalSocket** do it for you. Actually, all the system specific modules, functions, linkages are kept completely private. +- Many sockets operations (like for instance receive, accept clients, wait for server acceptance, etc...) are by default blocking. However, +**MinimalSocket** allows you also to opt for non-blocking versions off such operations, specifying the **timeout** to use. +- **MinimalSocket** is tested to be **thread safe**. Morevoer, you can also send while receiving in different dedicated threads. +- **Udp** sockets acn be used both as un-connected or connected, check [here](./samples/udp/README.md) for further details. Moreover, the same **udp** socket can be connected or sconnected during its lifetime. +-Under **Windows** systems, [**WSAStartup**](https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup) is automatically called before using any functionalities. From the outside, you can specify the Windows Sockets specification version. +## USAGE + +Haven't left a **star** already? Do it now ;)! + +## SAMPLES + +Haven't left a **star** already? Do it now ;)! + +Instructions about the **tcp** samples are contained [here](./samples/tcp/README.md), while [here](./samples/udp/README.md) the **udp** samples are explained. + +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.) + +## CMAKE SUPPORT + +Haven't left a **star** already? Do it now ;)! + +To consume this library you can rely on [CMake](https://cmake.org). +More precisely, You can fetch this package and link to the **MinimalSocket** library: +```cmake +include(FetchContent) +FetchContent_Declare( +min_sock +GIT_REPOSITORY https://github.com/andreacasalino/MinimalSocket +GIT_TAG master +) +FetchContent_MakeAvailable(min_sock) +``` + +and then link to the **MinimalSocket** library: +```cmake +target_link_libraries(${TARGET_NAME} + MinimalSocket +) +``` + +All the system specific modules are internally inlcluded and don't exposed to the outside. +Moreover, under **Windows**, **wsock32** and **ws2_32** are privately linked and you don't need to link them again when integrating **MinimalSocket**. diff --git a/TODO b/TODO index 86fd9291..20c35d56 100644 --- a/TODO +++ b/TODO @@ -8,9 +8,6 @@ document throw in sample far vedere come si potrebbe costruire server asincrono -in the main README.md: - - nella descrizione stressare il fatto che si usa c++ moderno per addomesticare socket di sistema - - stressare bene il fatto che c'e' possibilita' di usare con timeout varie operazioni - - stressare che si possono fare in maniera safe send concorrenti per udp - - mettere bene in evidenza il fatto che udp possono essere usati da connessi o no - - le funzionalita offerte sono perfettamente thread safe +is mac supported with linux code? + +google protobuf in tag diff --git a/samples/tcp/README.md b/samples/tcp/README.md index 3b7bfd61..7445c8e2 100644 --- a/samples/tcp/README.md +++ b/samples/tcp/README.md @@ -4,6 +4,8 @@ This folder stores some examples showing how to set up and use the objects insid Each sample requires to run multiple processes at the same time. This is automatically done by some shell scripts, which are NOT contained in this folder. Indeed, such scipts are generated in the bin folder, after running **TcpScriptsGenerator**, which generates all scripts without launching it. Each process pertaining to a sample is run in a dedicated window. +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.) + After generating the scripts, you can run them to see what the samples do. In particular, 3 scripts can be generated, representative of 3 classes of samples: diff --git a/samples/udp/README.md b/samples/udp/README.md index baca66c3..475b62d3 100644 --- a/samples/udp/README.md +++ b/samples/udp/README.md @@ -4,6 +4,8 @@ This folder stores some examples showing how to set up and use the objects insid Each sample requires to run multiple processes at the same time. This is automatically done by some shell scripts, which are NOT contained in this folder. Indeed, such scipts are generated in the bin folder, after running **UdpScriptsGenerator**, which generates all scripts without launching it. Each process pertaining to a sample is run in a dedicated window. +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.) + After generating the scripts, you can run them to see what the samples do. In particular, 3 scripts can be generated, representative of 3 classes of samples: From 9dad15de2eb1f9711cb2c9f2882da13851f68887 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 14:40:22 +0100 Subject: [PATCH 198/228] documenting --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++- TODO | 6 +----- samples/CMakeLists.txt | 3 +++ samples/README.cpp | 25 +++++++++++++++++++++++ 4 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 samples/README.cpp diff --git a/README.md b/README.md index 13daf3a8..75c24c5a 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,49 @@ This are the most notable properties of **MinimalSocket**: Haven't left a **star** already? Do it now ;)! +### TCP + +#### SERVER + +To create a **tcp** server you just need to build a **tcp::TcpServer** object: +```cpp +#include + +MinimalSocket::Port port = 15768; // port the server needs to bind +MinimalSocket::tcp::TcpServer tcp_server(port, + MinimalSocket::AddressFamily::IP_V4); +``` + +open it: +```cpp +// open the server: binds the port and start to listen on the port +bool success = tcp_server.open(); +``` + +and now you are ready to accept new clients: +```cpp +// accepts next client asking connection +MinimalSocket::tcp::TcpConnection accepted_connection = + tcp_server.acceptNewClient(); // blocing till a client actually asks the + // connection +``` + +you can now receive and send information with the accepted client by simply doing this: +```cpp +// receive a message +std::size_t message_max_size = 1000; +std::string + received_message // resized to the nunber of bytes actually received + = accepted_connection.receive(message_max_size); +// send a message +accepted_connection.send("a message to send"); +``` + +#### CLIENT + + +### UDP + ## SAMPLES Haven't left a **star** already? Do it now ;)! @@ -55,7 +98,7 @@ More precisely, You can fetch this package and link to the **MinimalSocket** lib include(FetchContent) FetchContent_Declare( min_sock -GIT_REPOSITORY https://github.com/andreacasalino/MinimalSocket +GIT_REPOSITORY https://github.com/andreacasalino/Minimal-Socket GIT_TAG master ) FetchContent_MakeAvailable(min_sock) diff --git a/TODO b/TODO index 20c35d56..5788cba6 100644 --- a/TODO +++ b/TODO @@ -2,12 +2,8 @@ use inet_pton also in windows support for bluetooth connection -cambiare nome a repository git (MinimalSocket) - -document throw - in sample far vedere come si potrebbe costruire server asincrono is mac supported with linux code? -google protobuf in tag +document usage in README diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 5a78bd58..1e130d25 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -1,3 +1,6 @@ +add_executable(README README.cpp) +target_link_libraries(README PUBLIC MinimalSocket) + project(MinimalCppSocket-Samples) include(cmake/MakeSample.cmake) diff --git a/samples/README.cpp b/samples/README.cpp new file mode 100644 index 00000000..f8ec8827 --- /dev/null +++ b/samples/README.cpp @@ -0,0 +1,25 @@ +// tcp server +#include +int main() { + MinimalSocket::Port port = 15768; // port the server needs to bind + MinimalSocket::tcp::TcpServer tcp_server(port, + MinimalSocket::AddressFamily::IP_V4); + + // open the server: binds the port and start to listen on the port + bool success = tcp_server.open(); + + // accepts next client asking connection + MinimalSocket::tcp::TcpConnection accepted_connection = + tcp_server.acceptNewClient(); // blocing till a client actually asks the + // connection + + // receive a message + std::size_t message_max_size = 1000; + std::string + received_message // resized to the nunber of bytes actually received + = accepted_connection.receive(message_max_size); + // send a message + accepted_connection.send("a message to send"); +} + +// tcp client From d687b73d8f2c8bafe15265830d1bffb9ee2b9ccf Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 15:04:20 +0100 Subject: [PATCH 199/228] documenting --- README.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++ samples/README.cpp | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/README.md b/README.md index 75c24c5a..c6aef6ae 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Haven't left a **star** already? Do it now ;)! This are the most notable properties of **MinimalSocket**: - A modern **C++** interface allows you to set up and build connections in terms of objects. Sockets are not opened as soon as the wrapping object is created, but you after calling a proper method, allowing you to decouple socket creation from socket opening. Sockets are automatically closed (and all relevant information cleaned after destroying the wrapping object). - You don't need to access low level functions from system modules: let **MinimalSocket** do it for you. Actually, all the system specific modules, functions, linkages are kept completely private. +- **AF_INET** (**ip v4**) and **AF_INET6** (**ip v6**), refer to [this](https://www.ibm.com/docs/en/i/7.1?topic=characteristics-socket-address-family) link, are both supported - Many sockets operations (like for instance receive, accept clients, wait for server acceptance, etc...) are by default blocking. However, **MinimalSocket** allows you also to opt for non-blocking versions off such operations, specifying the **timeout** to use. - **MinimalSocket** is tested to be **thread safe**. Morevoer, you can also send while receiving in different dedicated threads. @@ -77,9 +78,89 @@ accepted_connection.send("a message to send"); #### CLIENT +To create a **tcp** client you just need to build a **tcp::TcpClient** object: +```cpp +#include + +MinimalSocket::Port server_port = 15768; +std::string server_address = "192.168.125.85"; +MinimalSocket::tcp::TcpClient tcp_client( + MinimalSocket::Address{server_address, server_port}); +``` + +open it: +```cpp +// open the client: asks connection to server +bool success = tcp_client.open(); +``` + +you can now receive and send information with the remote server by simply doing this: +```cpp +// send a message +tcp_client.send("a message to send"); +// receive a message +std::size_t message_max_size = 1000; +std::string + received_message // resized to the nunber of bytes actually received + = tcp_client.receive(message_max_size); +``` ### UDP +To create a normal **udp** socket you just need to build a **udp::UdpBinded** object: +```cpp +#include + +MinimalSocket::Port this_socket_port = 15768; +MinimalSocket::udp::UdpBinded udp_socket(this_socket_port, + MinimalSocket::AddressFamily::IP_V6); +``` + +open it: +```cpp +// open the client: reserve port for this cocket +bool success = udp_socket.open(); +``` + +you can now receive and send information with any other opened **udp** socket: +```cpp +// send a message to another udp +MinimalSocket::Address other_recipient_udp = + MinimalSocket::Address{"192.168.125.85", 15768}; +udp_socket.sendTo("a message to send", other_recipient_udp); +// receive a message from another udp reaching this one +std::size_t message_max_size = 1000; +auto received_message = udp_socket.receive(message_max_size); +// check the sender address +MinimalSocket::Address other_sender_udp = received_message->sender; +// access the received message +std::string received_message_content // resized to the nunber of bytes + // actually received + = 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: +```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) +``` + +Now you can send and receive data without having to specify the recpient/sender: +```cpp +// receive a message +std::size_t message_max_size = 1000; +std::string + received_message // resized to the nunber of bytes actually received + = udp_connected_socket.receive(message_max_size); +// send a message +udp_connected_socket.send("a message to send"); +``` + ## SAMPLES Haven't left a **star** already? Do it now ;)! diff --git a/samples/README.cpp b/samples/README.cpp index f8ec8827..8b234360 100644 --- a/samples/README.cpp +++ b/samples/README.cpp @@ -23,3 +23,62 @@ int main() { } // tcp client +#include +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}); + + // open the client: asks connection to server + bool success = tcp_client.open(); + + // send a message + tcp_client.send("a message to send"); + // receive a message + std::size_t message_max_size = 1000; + std::string + received_message // resized to the nunber of bytes actually received + = tcp_client.receive(message_max_size); +} + +// udp socket +#include +int main() { + MinimalSocket::Port this_socket_port = 15768; + MinimalSocket::udp::UdpBinded udp_socket(this_socket_port, + MinimalSocket::AddressFamily::IP_V6); + + // open the client: reserve port for this cocket + bool success = udp_socket.open(); + + // send a message to another udp + MinimalSocket::Address other_recipient_udp = + MinimalSocket::Address{"192.168.125.85", 15768}; + udp_socket.sendTo("a message to send", other_recipient_udp); + // receive a message from another udp reaching this one + std::size_t message_max_size = 1000; + auto received_message = udp_socket.receive(message_max_size); + // check the sender address + MinimalSocket::Address other_sender_udp = received_message->sender; + // access the received message + std::string received_message_content // resized to the nunber of bytes + // actually received + = received_message->received_message; + + 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) + + // receive a message + std::size_t message_max_size = 1000; + std::string + received_message // resized to the nunber of bytes actually received + = udp_connected_socket.receive(message_max_size); + // send a message + udp_connected_socket.send("a message to send"); +} From cac71a0011d0b724b5fb1915852007a447531513 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 15:13:18 +0100 Subject: [PATCH 200/228] testing macOS --- .github/workflows/runTests.yml | 3 +++ TODO | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index bab21dcd..0f8d7890 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -23,6 +23,9 @@ jobs: - name: windows-VS os: windows-2019 compiler_opt: "" + - name: macOS + os: macos-latest + compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++" runs-on: ${{ matrix.os }} steps: diff --git a/TODO b/TODO index 5788cba6..a490a4de 100644 --- a/TODO +++ b/TODO @@ -1,9 +1,5 @@ use inet_pton also in windows -support for bluetooth connection - in sample far vedere come si potrebbe costruire server asincrono is mac supported with linux code? - -document usage in README From 68e57fb6126e30feac9147986ecf2d20d4b8a65f Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 15:19:49 +0100 Subject: [PATCH 201/228] testing mac os --- src/src/SocketId.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/src/SocketId.h b/src/src/SocketId.h index 1eaf41ad..41e7be4f 100644 --- a/src/src/SocketId.h +++ b/src/src/SocketId.h @@ -10,12 +10,13 @@ #include #include +// https://stackoverflow.com/questions/6649936/c-compiling-on-windows-and-linux-ifdef-switch #ifdef _WIN32 #include #include #include #include -#elif __linux__ +#elif defined(__linux__) || defined(__APPLE__) #include #include #include //memset From e4e6d553e11990be94254e10a1796ef5d5d99eb1 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 15:35:42 +0100 Subject: [PATCH 202/228] support for mac os --- .github/workflows/installArtifacts.yml | 8 ++++++++ samples/utils/ScriptGenerator.cpp | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/installArtifacts.yml b/.github/workflows/installArtifacts.yml index 7bc96846..78b8466d 100644 --- a/.github/workflows/installArtifacts.yml +++ b/.github/workflows/installArtifacts.yml @@ -27,6 +27,10 @@ jobs: os: windows-latest compiler_opt: "" lib_opt: "" + - name: macOS-static + os: macos-latest + compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++" + lib_opt: "" - name: ubuntu-gcc-shared os: ubuntu-latest compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -G \"Unix Makefiles\"" @@ -39,6 +43,10 @@ jobs: os: windows-latest compiler_opt: "" lib_opt: "-DLIB_OPT=ON" + - name: macOS-shared + os: macos-latest + compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++" + lib_opt: "-DLIB_OPT=ON" runs-on: ${{ matrix.os }} steps: diff --git a/samples/utils/ScriptGenerator.cpp b/samples/utils/ScriptGenerator.cpp index eea1398a..fa5572bd 100644 --- a/samples/utils/ScriptGenerator.cpp +++ b/samples/utils/ScriptGenerator.cpp @@ -14,7 +14,7 @@ namespace MinimalSocket::samples { namespace { #ifdef _WIN32 static const std::string SCRIPT_EXTENSION = std::string{".bat"}; -#elif __linux__ +#elif defined(__linux__) || defined(__APPLE__) static const std::string SCRIPT_EXTENSION = std::string{".sh"}; #endif @@ -25,7 +25,7 @@ std::string to_string(const ProcessAndArgs &subject) { for (const auto &[name, val] : subject.arguments) { stream << " \"--" << name << "\" \"" << val << "\""; } -#elif __linux__ +#elif defined(__linux__) || defined(__APPLE__) stream << "./" << subject.process_name; for (const auto &[name, val] : subject.arguments) { stream << " --" << name << ' ' << val; @@ -41,7 +41,7 @@ void add_process(std::ofstream &stream, const ProcessAndArgs &proc_and_args, stream << "start \"\" "; } stream << to_string(proc_and_args); -#elif __linux__ +#elif defined(__linux__) || defined(__APPLE__) if (new_terminal) { stream << "gnome-terminal -x sh -c \"" << to_string(proc_and_args) << " ; bash\""; @@ -56,7 +56,7 @@ void add_process(std::ofstream &stream, const ProcessAndArgs &proc_and_args, void ScriptGenerator::generate(const std::string &file_name) { std::ofstream stream(file_name + SCRIPT_EXTENSION); -#ifdef __linux__ +#if defined(__linux__) || defined(__APPLE__) stream << "#!/bin/sh" << std::endl; #endif From dfd3b45be01f1174d51206ecf47f94d83abf79ff Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Thu, 19 May 2022 16:50:46 +0100 Subject: [PATCH 203/228] using inet_pton also in windows --- src/src/SocketAddress.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/src/SocketAddress.cpp b/src/src/SocketAddress.cpp index 72d3bd55..29f42ade 100644 --- a/src/src/SocketAddress.cpp +++ b/src/src/SocketAddress.cpp @@ -29,12 +29,18 @@ std::optional toSocketAddressIpv4(const std::string &host, result->sin_family = AF_INET; result->sin_port = htons(port); - // try address conversion -#if !defined(_WIN32) + // try address conversion with inet_pton first +#ifdef _WIN32 in_addr ia; if (1 == ::inet_pton(AF_INET, host.c_str(), &ia)) { - result->sin_addr.s_addr = ia.s_addr; - return result; + ::memcpy(&result->sin_addr, &ia, sizeof(in_addr)); + return result; + } +#else + in_addr ia; + if (1 == ::inet_pton(AF_INET, host.c_str(), &ia)) { + result->sin_addr.s_addr = ia.s_addr; + return result; } #endif @@ -74,8 +80,14 @@ std::optional toSocketAddressIpv6(const std::string &host, result->sin6_flowinfo = 0; result->sin6_port = htons(port); - // try address conversion -#if !defined(_WIN32) + // try address conversion with inet_pton first +#ifdef _WIN32 + in6_addr ia; + if (1 == ::inet_pton(AF_INET6, host.c_str(), &ia)) { + ::memcpy(&result->sin6_addr, &ia, sizeof(in6_addr)); + return result; + } +#else in6_addr ia; if (1 == ::inet_pton(AF_INET6, host.c_str(), &ia)) { result->sin6_addr = ia; From 1c5f932a841ec550d7484c19bc2749a391c3ac1c Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 17:53:59 +0100 Subject: [PATCH 204/228] minor --- TODO | 4 ---- 1 file changed, 4 deletions(-) diff --git a/TODO b/TODO index a490a4de..6dd79e01 100644 --- a/TODO +++ b/TODO @@ -1,5 +1 @@ -use inet_pton also in windows - in sample far vedere come si potrebbe costruire server asincrono - -is mac supported with linux code? From 90b093552177765fc5a95f4a9a69739db5ee0189 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 21:10:55 +0100 Subject: [PATCH 205/228] clone functions --- src/header/MinimalSocket/tcp/TcpClient.h | 2 ++ src/header/MinimalSocket/tcp/TcpServer.h | 2 ++ src/header/MinimalSocket/udp/UdpSocket.h | 4 ++++ src/src/tcp/TcpClient.cpp | 2 ++ src/src/tcp/TcpServer.cpp | 4 ++++ src/src/udp/UdpSocket.cpp | 8 ++++++++ 6 files changed, 22 insertions(+) diff --git a/src/header/MinimalSocket/tcp/TcpClient.h b/src/header/MinimalSocket/tcp/TcpClient.h index 5260885d..51d3b1a7 100644 --- a/src/header/MinimalSocket/tcp/TcpClient.h +++ b/src/header/MinimalSocket/tcp/TcpClient.h @@ -31,4 +31,6 @@ class TcpClient : public Openable, protected: void open_() override; }; + +TcpClient clone(const TcpClient &o); } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index 99adc837..cadd4ac4 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -89,4 +89,6 @@ class TcpServer : public PortToBindAware, std::mutex accept_mtx; }; + +TcpServer clone(const TcpServer &o); } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index eb785175..c632cf40 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -84,6 +84,8 @@ class UdpBinded : public SenderTo, void open_() override; }; +UdpBinded clone(const UdpBinded &o); + /** * @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. @@ -122,6 +124,8 @@ class UdpConnected : public Sender, void open_() override; }; +UdpConnected clone(const UdpConnected &o); + /** * @brief builds from 0 a connected udp socket. The connection is done to the * first udp sending a message on the specified port. diff --git a/src/src/tcp/TcpClient.cpp b/src/src/tcp/TcpClient.cpp index b26d6417..0ae01de0 100644 --- a/src/src/tcp/TcpClient.cpp +++ b/src/src/tcp/TcpClient.cpp @@ -30,4 +30,6 @@ void TcpClient::open_() { socket.reset(SocketType::TCP, remote_address.getFamily()); MinimalSocket::connect(socket.accessId(), remote_address); } + +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 04fdd02c..18f32118 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -112,4 +112,8 @@ TcpConnection &TcpConnection::operator=(TcpConnection &&o) { Socket::transfer(*this, o); return *this; } + +TcpServer clone(const TcpServer &o) { + return TcpServer{o.getPortToBind(), o.getRemoteAddressFamily()}; +} } // namespace MinimalSocket::tcp diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index e3512120..9e307e04 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -65,6 +65,10 @@ std::optional UdpBinded::connect(const Timeout &timeout, return connect(maybe_received->sender); } +UdpBinded clone(const UdpBinded &o) { + return UdpBinded{o.getPortToBind(), o.getRemoteAddressFamily()}; +} + UdpConnected::UdpConnected(const Address &remote_address, const Port &port) : PortToBindAware(port), RemoteAddressAware(remote_address) {} @@ -115,4 +119,8 @@ std::optional makeUdpConnectedToUnknown( } return primal_socket.connect(timeout, initial_message); } + +UdpConnected clone(const UdpConnected &o) { + return UdpConnected{o.getRemoteAddress(), o.getPortToBind()}; +} } // namespace MinimalSocket::udp From 40fd7b334f471e7303504d7f3f8eb8a2322e45da Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 21:40:54 +0100 Subject: [PATCH 206/228] refactoring --- src/header/MinimalSocket/tcp/TcpClient.h | 4 ++++ src/header/MinimalSocket/tcp/TcpServer.h | 2 -- src/header/MinimalSocket/udp/UdpSocket.h | 4 ---- src/src/tcp/TcpServer.cpp | 4 ---- src/src/udp/UdpSocket.cpp | 8 -------- 5 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/header/MinimalSocket/tcp/TcpClient.h b/src/header/MinimalSocket/tcp/TcpClient.h index 51d3b1a7..e39d1e54 100644 --- a/src/header/MinimalSocket/tcp/TcpClient.h +++ b/src/header/MinimalSocket/tcp/TcpClient.h @@ -32,5 +32,9 @@ class TcpClient : public Openable, void open_() override; }; +/** + * @return a client ready to ask the connection to the same server. + * Beware that a closed socket is returned, which can be later opened. + */ TcpClient clone(const TcpClient &o); } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index cadd4ac4..99adc837 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -89,6 +89,4 @@ class TcpServer : public PortToBindAware, std::mutex accept_mtx; }; - -TcpServer clone(const TcpServer &o); } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index c632cf40..eb785175 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -84,8 +84,6 @@ class UdpBinded : public SenderTo, void open_() override; }; -UdpBinded clone(const UdpBinded &o); - /** * @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. @@ -124,8 +122,6 @@ class UdpConnected : public Sender, void open_() override; }; -UdpConnected clone(const UdpConnected &o); - /** * @brief builds from 0 a connected udp socket. The connection is done to the * first udp sending a message on the specified port. diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 18f32118..04fdd02c 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -112,8 +112,4 @@ TcpConnection &TcpConnection::operator=(TcpConnection &&o) { Socket::transfer(*this, o); return *this; } - -TcpServer clone(const TcpServer &o) { - return TcpServer{o.getPortToBind(), o.getRemoteAddressFamily()}; -} } // namespace MinimalSocket::tcp diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index 9e307e04..e3512120 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -65,10 +65,6 @@ std::optional UdpBinded::connect(const Timeout &timeout, return connect(maybe_received->sender); } -UdpBinded clone(const UdpBinded &o) { - return UdpBinded{o.getPortToBind(), o.getRemoteAddressFamily()}; -} - UdpConnected::UdpConnected(const Address &remote_address, const Port &port) : PortToBindAware(port), RemoteAddressAware(remote_address) {} @@ -119,8 +115,4 @@ std::optional makeUdpConnectedToUnknown( } return primal_socket.connect(timeout, initial_message); } - -UdpConnected clone(const UdpConnected &o) { - return UdpConnected{o.getRemoteAddress(), o.getPortToBind()}; -} } // namespace MinimalSocket::udp From 70a5c16eb13f18f1bc1513ca239a304a52ac13b5 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 21:51:01 +0100 Subject: [PATCH 207/228] refactoring --- README.md | 9 +++++---- TODO | 1 - src/header/MinimalSocket/core/SocketContext.h | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 TODO diff --git a/README.md b/README.md index c6aef6ae..c4a9c9d3 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,11 @@ This are the most notable properties of **MinimalSocket**: - A modern **C++** interface allows you to set up and build connections in terms of objects. Sockets are not opened as soon as the wrapping object is created, but you after calling a proper method, allowing you to decouple socket creation from socket opening. Sockets are automatically closed (and all relevant information cleaned after destroying the wrapping object). - You don't need to access low level functions from system modules: let **MinimalSocket** do it for you. Actually, all the system specific modules, functions, linkages are kept completely private. - **AF_INET** (**ip v4**) and **AF_INET6** (**ip v6**), refer to [this](https://www.ibm.com/docs/en/i/7.1?topic=characteristics-socket-address-family) link, are both supported -- Many sockets operations (like for instance receive, accept clients, wait for server acceptance, etc...) are by default blocking. However, -**MinimalSocket** allows you also to opt for non-blocking versions off such operations, specifying the **timeout** to use. -- **MinimalSocket** is tested to be **thread safe**. Morevoer, you can also send while receiving in different dedicated threads. -- **Udp** sockets acn be used both as un-connected or connected, check [here](./samples/udp/README.md) for further details. Moreover, the same **udp** socket can be connected or sconnected during its lifetime. +- Many sockets operations are by default blocking. However, **MinimalSocket** allows you also to opt for non-blocking versions off such operations, specifying a **timeout** to use, after which the operation terminates in any case. In particular, the operations allowing for such possibility are: + - non blocking receive (send are always intrinsically non blocking) + - acceptance of a new client from the tcp server side +- **MinimalSocket** is tested to be **thread safe**. Morevoer, you can also send while receiving in different dedicated threads. This allows you to easily create your own asynchronous sockets, building upon the classes offered by this library. +- **Udp** sockets can be used both as un-connected or connected, check [here](./samples/udp/README.md) for further details. Moreover, the same **udp** socket can be connected or sconnected during its lifetime. -Under **Windows** systems, [**WSAStartup**](https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup) is automatically called before using any functionalities. From the outside, you can specify the Windows Sockets specification version. ## USAGE diff --git a/TODO b/TODO deleted file mode 100644 index 6dd79e01..00000000 --- a/TODO +++ /dev/null @@ -1 +0,0 @@ -in sample far vedere come si potrebbe costruire server asincrono diff --git a/src/header/MinimalSocket/core/SocketContext.h b/src/header/MinimalSocket/core/SocketContext.h index b0a60379..f931a6b8 100644 --- a/src/header/MinimalSocket/core/SocketContext.h +++ b/src/header/MinimalSocket/core/SocketContext.h @@ -48,10 +48,10 @@ class PortToBindAware { PortToBindAware &operator=(const PortToBindAware &); /** - * @brief Used to enforce the fact that this port should be not 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 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. */ void mustBeFreePort() { must_be_free_port = true; }; bool shallBeFreePort() const { return must_be_free_port; } From bc89e8a2c1c7ed0c94afa716d2f83e03bc5b50d5 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 22:36:44 +0100 Subject: [PATCH 208/228] picture --- README.md | 2 + sockets.svg | 4827 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 4829 insertions(+) create mode 100644 sockets.svg diff --git a/README.md b/README.md index c4a9c9d3..c8592522 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ - [Samples](#samples) - [CMake support](#cmake-support) +![Sockets](./sockets.svg) + ## INTRO **MinimalSocket** gives you a modern **C++** interface to create, connect and handle **tcp** and **udp** sockets, in a diff --git a/sockets.svg b/sockets.svg new file mode 100644 index 00000000..31762fb1 --- /dev/null +++ b/sockets.svg @@ -0,0 +1,4827 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Socket connections + + + + + + + + + + + + + + + + Server + Controller + Peer + Peer + Peer + + From 230ddcc58c0b6ff05c4148c8432c67f4fb5f83fd Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 22:45:15 +0100 Subject: [PATCH 209/228] picture --- README.md | 2 +- sockets.png | Bin 0 -> 232365 bytes sockets.svg | 4494 +-------------------------------------------------- 3 files changed, 31 insertions(+), 4465 deletions(-) create mode 100644 sockets.png diff --git a/README.md b/README.md index c8592522..1bd4cfb7 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ - [Samples](#samples) - [CMake support](#cmake-support) -![Sockets](./sockets.svg) +![Sockets](./sockets.png) ## INTRO diff --git a/sockets.png b/sockets.png new file mode 100644 index 0000000000000000000000000000000000000000..ef93b65c406c1e2516515438a1414507424c64fc GIT binary patch literal 232365 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 + sodipodi:docname="sockets.svg" + inkscape:export-filename="/home/andrea/Desktop/GitProj/Cross-Platform-Socket/sockets.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> + d="M 0,0 5,-5 -12.5,0 5,5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="matrix(-0.8,0,0,-0.8,-10,0)" + inkscape:connector-curvature="0" /> image/svg+xml - + @@ -203,1628 +207,19 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" - transform="translate(173.53894,22.216612)"> + transform="translate(177.39424,27.40365)"> + @@ -1843,553 +238,7 @@ Qz0Z3On1+pGkpKRE/nQZhmEYhmG+PfwvbF8MqCB3MqgAAAAASUVORK5CYII= y="-19.807041" x="-93.892616" id="image4541" - xlink:href="data:image/png;base64,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 -MUUKDG0wLpUT3MBRRBGa1vMPnHap/sFuUepeEbLaMl5zonHg9igfOm32cAeaIT1Yq8gIXdFJGqCA -BVobFgXutcoEQIE3M5OSS4u3bSdt5MKLYib8rbnUizA1B9UEF9P0TwfyLrumcEVSH8EQQSGu0o7m -1sbB3X/VKruhueO0+lRbbeVZXt6SI8+y2lZF3/iLrK7XxzwgLFqW1ho1RHGEe/zsN0N0RuMx5X6y -qj6FoWb5Zow+gPKYo+ovD39JJv9i0sugX8x5GWR/M+kzg5L57zzzsuJcEvGbZ/bLlP6pZ9bLkPgY -4p4I6Ybe/OKZ8eYZ/yfP5DfPpJdn8kwvk7Sk8Ytv9rtv+qe++ZNJ0JpfvTMe7/jPvZMf76SXd9Lv -aWP+bd58TBvzb/PmY9qYf5s3H9PG/Ku8+aSgzN97BgpxWQVBQ2Yv4EWffTYL/rZpZnlqbyxYrSS6 -1yeKmFUz/wuPSVtgoSj9qQAADRppVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBi -ZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1s -bnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDQuNC4wLUV4aXYyIj4KIDxy -ZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4 -LW5zIyI+CiAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOnhtcE1NPSJo -dHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9u -cy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgIHhtbG5zOmRjPSJo -dHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93 -d3cuZ2ltcC5vcmcveG1wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlm -Zi8xLjAvIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4 -bXBNTTpEb2N1bWVudElEPSJnaW1wOmRvY2lkOmdpbXA6NTU3YTc4NjQtMzQ5YS00NjI3LWJjMjMt -YTQ2MjhkODljNWVlIgogICB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjE5ZmUwMjJmLTg0MWEt -NDIzMy04YTVlLWYxMDg5YzUyNDg0NCIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAu -ZGlkOmNmYTM0NDM3LWVkM2UtNDQ0Yy1iODY4LTMwNWFlYWM4ODkwZSIKICAgZGM6Rm9ybWF0PSJp -bWFnZS9wbmciCiAgIEdJTVA6QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IkxpbnV4IgogICBH -SU1QOlRpbWVTdGFtcD0iMTY1Mjk5NTA5NDM2NjU2NyIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjI4 -IgogICB0aWZmOk9yaWVudGF0aW9uPSIxIgogICB4bXA6Q3JlYXRvclRvb2w9IkdJTVAgMi4xMCI+ -CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpCYWc+CiAgICAgPHJkZjpsaQogICAgICBzdEV2 -dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0 -YW5jZUlEPSJ4bXAuaWlkOjViNjg5YjhlLWNiMWQtNDFjZS1iNWE4LWIzYmNkZWU0ZTQxNyIKICAg -ICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iR2ltcCAyLjEwIChMaW51eCkiCiAgICAgIHN0RXZ0Ondo -ZW49IjIwMjItMDUtMTlUMjI6MTg6MTQrMDE6MDAiLz4KICAgIDwvcmRmOkJhZz4KICAgPC94bXBN -TTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4K -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hw -YWNrZXQgZW5kPSJ3Ij8+DagxLAAAASRpQ0NQSUNDIHByb2ZpbGUAACiRnZC/SsNQFMZ/qX8RHaTq -IB0yuBZczORSFYKgEGMFq1OapFhMYkhSim/gm+jDdBAEH8EHUHD2u9HBwSxeOHw/Dud8370XWnYS -puX8LqRZVbh+b3A5uLKX3ligzSbrdIKwzHued0Lj+XzFMvrSNV7Nc3+exSguQ+lMlYV5UYG1L3am -VW5YxcZt3z8UP4jtKM0i8ZN4J0ojw2bXT5NJ+ONpbrMaZxfnpq/q4HLMKR42QyaMSajoSjN1jnDY -k7oUBNxTEkoTYvWmmqm4EZVycjkQ9UW6TUPedp3nKWUoj7G8TMIdqTxNHuZ/v9c+zupNa2uWB0VQ -t+ZUrdEI3h9hbQDtZ1i5bsha/v22hhmnnvnnG78A3tVQYKG8AB8AAAACYktHRAChC1ywTAAAAAlw -SFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+YFExUSDgMkGXEAACAASURBVHja7L15nGVnVe/9ffZ4 -xpq6OwmBAAkISARBBgWTe4MGEBUV9FVRVPT1BRVfL4iCE/ciV0C9iogjXBUVEeGiiDjgDVwmUWaQ -KIEgYcjY6bGqzrTHdf/Y65xU19npqn260l0d1vd8IENXdu2z9/N7nvWsZw1OMAzjSwHPHoFhmNgN -wzCxG4ZhYjcMw8RuGMa5I9j/t5gTABkpLXwEwVGSUhASk9s7NM6qYFIyfCI8BIcg5ESzkXrH6Zbb -d/fu9vvRW8kER0AIwCZ98lPmqNLGn3FOTOFqHGaEswVpQkHXVvYzebgdANbp4dMCbpLr+A++yC0c -Zax/ahhnhyFtDnIxl3B/Hsx9XEIIZEBAa5/f+75b2WWbAZQxwtHFBz4u7+K13M5hMv1Tn8LGn3FW -F59ytk5exAX8EF/LQ51HyZiSWA16E/suzXa3ReyCY0ILeKe8nHcyxCFARKi798zGn3EWiRAckJMA -Hj4hV/I8Hu8gIbaVvdnK7mZ/J5QMWeaT8qu8lpIuQ0JKHB65zrB2nGCc3cWosigDXWxS+iTAU/lJ -HulSAtyWMSn7yk23jx10JSUlES+RlzICHAFtUjIKHB6OiJh1G3/GWWSZhFQXIp+QPsd0AjjAT/Jz -LsXhz+RuYt+12AsKHi+f5DhdhAmVP7HygjpyCsC38WecRQrAI0Bm3niPgJicMSs8hH90HgGeitzE -flqGdIEJHjnjX77qBZ+08WWcRzyIt9NxHYSI/baL34cr+xifiISND3/DIz5qe3LjPNvTP5S3seZi -JpT77GB4H2pJiCgZXf0Dj/iojR3jvOMT/CDjy0pa+y7ga9+t7AkxIzyeK79PzBLHbPQY5xEHOUnK -s3iFy+kxpm1iP9267oDXydNZ4ziBhcMa5xU+GQc4xmt5upuG0ZrY75QREZfIMSKG2Dm6cb7t2T06 -jFnjZpfS3lfjd99pKaXDy+Q2PIbE9Gz0GOcVHWKGeBzhl6XLxPbsp+dW+WpuIsYxpkVq48c4j/DI -6ZCSc28+yIVuf93bOd+jb/+nt3AjbYQxHZO6cZ5REDMCWnyRvwRKxMR+5w/rnUBJgmcZbcZ5h6ME -Lanyrn2WprXvxH78w2/XFd4nx6raG+ef2H0dwe/guJjYT8NnH3GckBwoEBO7cZ4hVJlxBS2O8zlb -2U/HxwnwKehQ4u/DOl6GcXpB+QgdcnwiPm5iPx2fIp5ls3l2ym6cd2a809GbE/EpE/vpGDNEgBR/ -X3kyDWM3lJR4pIAwZGRiPx1tICAiJZpVozGM84WCnIiECB/ZV5Hx+6C67NZ6cwBDPHIEjwRvFyt7 -i4KEiJQlSkbnQyF84zwipYXHiBYTOvgMd1w9HSk+BSUeIxz7p4TFea+NET0yPGBMBtY0wthjEhWJ -B2ye116k817sL+YqCnwyQlJb1427YG2PyIkRPN7JfzOxnzvuzZVagNIw7gpKvJkp/vnz+puc92JP -gIyCNlMPvmHsHZHmpI/xaJ/ntYzPe7F3gZAYB/u+/Y5xfsq9GmclsGRiP5dsULJBF5+UtrrqDGPv -LMeIMS0cY/r7LD/9S07sbTxasw5bvond2FM6oD2EQzjPw7fPQ204qixhH0+dJxEl0EZM6saeU4XG -lCr4Eg9P4+S888xDZOowjC8RTOyGYWI3DMPEbhiGid0wDBO7YRgmdsMwTOyGYZjYDcMwsRuGYWI3 -DBO7YRgmdsMwTOyGYZjYDcMwsRuGYWI3DMPEbhiGid0wDBO7YRgmdsMwsRuGYWI3DMPEbhiGid0w -DBO7YRgmdsMwTOyGYZjYDcMwsRuGid0wDBO7YRgmdsMwTOyGYZjYDcMwsRuGYWI3DMPEbhiGid0w -DBO7YZjYDcMwsRuGYWI3DMPEbhiGid0wDBO7YRgmdsMwTOyGYZjYDcPEbhiGid0wDBO7YRgmdsMw -TOyGYZjYDcMwsRuGYWI3DMPEbhiGid0wTOyGYZjYDcMwsRuGYWI3DMPEbhiGid0wDBO7YRgmdsMw -TOyGYWI3DMPEbhiGid0wDBO7YRgmdsMwTOyGYZjYDcMwsRuGYWI3DMPEbhgmdsMwTOyGYZjYDcMw -sRuGYWI3DMPEbhiGid0wDBO7YRgmdsMwTOyGYWI3DMPEbhiGid0wDBO7YRgmdsMwTOyGYZjYDcMw -sRuGYWI3DBO7YRgmdsMwTOyGYZjYDcMwsRuGYWI3DMPEbhiGid0wDBO7YRgmdsMwsRuGYWI3DMPE -bhiGid0wDBO7YRgmdsMwTOyGYZjYDcMwsRuGid0wDBO7YRgmdsMwTOyGYZjYDcMwsRuGYWI3DMPE -bhiGid0wDBO7YZjYDcMwsRuGYWI3DMPEbhiGid0wDBO7YRgmdsMwTOyGYZjYDcPEbhiGid0wDBO7 -YRgmdsMwTOyGYZjYDcMwsRuGYWI3DMPEbhiGid0wTOyGYZjYDcMwsRuGYWI3DMPEbhiGid0wDBO7 -YRgmdsMwTOyGYWI3DMPEbhiGid0wDBO7YRgmdsMwTOyGYZjYDcMwsRuGYWI3DMPEbhgmdsMwTOyG -YZjYDcMwsRuGYWI3DMPEbhiGid0wDBO7YRgmdsMwTOyGYWI3DMPEbhiGid0wDBP7XYJs+6th2Cj7 -EljZxSRv2Ji6e4vdRG7YGPuSM+NN9MZdMb7uHmZ8cPd4IW72V5O7Yfv1u63YZYvcobTRaewh7pRR -ZmLfY0pCEjwiJrX7DNF/WwAeOQ6PjBYJsRnyxl0g9gktUkIcBR6ljr/yTnbBBS0mQEQ+mypM7LX4 -+oBLSsDfUby38SGGFPTYoEVOaOPT2ENSAhL6DAnoctuOP+/pyK0mAt/EfvqHBQ6HUAIB2Q4//0L+ -BxtAREpEus/mUuP83ySGZIRkwDLrO3q0p2u+24e+7313R8XMeJItO6bTzVYpEFICkY1NY8+JVfKQ -7sJudCqryiYtbGU//UwKBU4NoJ134D4TwCfVr2Pxv8beLj4ClMRkTIh3lO/UhHeU7DcP0r4Te+uU -NX1n33qiO6sSGNjYNPacIVCSAsJkl4vJdBvaMrGfjmVCoMTb5e47xOGT4+FR4pk33thjSnxKHDEF -Qr7L/6YkIGTVxH46+sSkDYITM3xKchwBKYGJ3dhzQz6g0BFZ7LgEeYBQAI6YJRP76Vwbj2WAw+GT -EezipNJDEDwg1TNQw9hLPDKc7sF3tjZTfAoCSgo2+Zp9dTq07/xZl3IAaJNxiHyfzYyGsfM2tOAA -OW3gIJfus2lrn3EP92iEEbAJ6mM3jPOFlMpRPKTkq7nImdhP69r4fgB6TAgZ2+gxzitGBCR0AeH7 -9pkHye03h1bGmAfJrfh02bBTc+O8o6THiJJ7cp1r7avw7X2nphDHs4GQEXeTDFzjS4gAGBECz8bt -s0yNfbh0Jm98Bl9BQK4RyYZx/pARUBLwEH6A5Jf3173tOzM+ISblz+UHiQkZmCFvnGdGfJeUjD/m -e12gadcm9jvlVrmHK/leeStD+qTkFPhag8Y1Ti3Ya3eoBe3ss9XqHL/fFgkljkD/25AxXZ7IX7pq -JNvKfhpOXrZyw4mru28vuFI+RkCKr2kubUqSuZd7tp+mnOPf/6WO7LP3L7QJ2IRZ4E2L+/FRVwBt -RnRM7KcjJaLA5/bjX716IxEZARFDCuAgx0zsJvZ99P5XOAGEhCQ4Ogy4mH9zPj0y/H22Cd2HR28h -CRGOIbfJ9/FBNdwjHAnzHsWd7t+d5ZdvnF9mO2c4nsotazrAI/gbghMXrI1p779nt98Ga86EHhke -PglH5Bl8hJO0iTkJ9BjVPOzTcVfPrRaNf3Y51+9z++9f5gTQp2TIGg/hz1h1XY4/c+3VMCHYV4fH -bj+uTAN6DOkypk3Br8hvcJQQj7TmVe/0cvZ6JXAm9n0l9r0ev9Lw9xf4tMhJWOb5/JyrrlGlwiT7 -bHXfh2LP8BnSZ0iX248fXPP4sLyUNwNd8l3mE991g8H26Pt7D3+236+jxQD4Fn6Gxzg4LBe6lJCC -Am+fBdXsO7GPac+qwA/oUTKmC7xXXsE/MJ6r17mTWVfcxYPB4gDOLuVdLHa/4e8vaPF4nsvjHIwJ -CLecrQupnbOfjsm2Uj651ul0wLXyDl7PF7kNZr1fdpqJhT4DBIgIGZ6xOAOtYhuQA2FjS8M4M8JZ -3QKfgvCMJ/OSmIAhATmrnNhxPG2td3wP7sNTeCJf6UpSWsCAWH3wVUEVW9l3YZrlCD6OkoDKaVey -RFWu6ovyb9zAF7iJdYodxZbxXoLZfj84Y3EKl/BwNogpSRnRM/2dVYaE9JkQEXIdn92DybugRAhx -pFy5C3n6rHIf7stlfDn3cXeMsxxfC56WlLOSqSb2HaXuzR5VqvOjMMbDEWvpaEexq8f5bnkcPjmO -mMkumk7s7CB6Li+ihbPd+zm2/zZ5Gb+yJ3t+R8SEDiPew5U7vtYEIcaRU+KTk+LTARJCPIYERLNy -qdm+MuOD/Sx10dfgCBCNRRKdPYWUEn/HL9CmRQa0yIHyjCVaGWceCRBrtTHjbA7YAiGjRaQlRvdC -7CETSmLiHS2/gFh9SoGu8zFVbboYGNGd7dY9wn0l9X0odkeggswQPAJaCIW+1jE3yhFu5EZu4ShD -coY7rgFjIiBmjEe0B7VvXs9nOEkAlIz3WbHguz8JAW3GtIm4dg+u12ZMieDIKHgO7R1Mv8rL3uMA -F3Exa1zKA7jQQaLWXoKHUO6zE/Z9vGdHS0NXYo3wyLhWPsg/81FuZcSk0bV8nDpfWmRnbMY7CqKZ -k8hKXJ5tfAp8CvW/nPlmyqekoEMya8i40zbujsrHARFtfC7msTyRR3Khm24spxbHJn0T+51T1X7P -tJXT0Tce/M73yt/yTj5OBtrNzSckxCMn21FsAV2OAh1GtBmfsdnXZsgBhkRsAF2rkneWiRngExDj -s0moTULOZLy1mKjMD7G547YswyfGR8hmo6+LMKLF43gC38maa1GQ6truTOynF3uVDAM3y3X8Av/K -BEdLa3GnuNnc6rSI9OmvNzXWqo5wcsaDbYw/WwvslP3sb/MKIkpty9Tag8k2ZkxMpl0Dd3qjAaXu -6308hDYDBI9IT4a6fA/P5BGuivDYXx75cy72Ylvvl2lIwomrP3HNs/n3uYfvU1Cq+VZ1Zm/mlHBk -BHRZp88mAH0G+OR05uLudzf4hD6bxCRUfT59HDFDYIWTC1yxwxihw4guwy09bhwOp4Pc053hzkEl -bXIyDnAMT/0WpxKRILQZz2yfnb7v/D9XbQwLBI+cFhN6TIBcDe5m3z4hIMGjZJl1NZ39mfmc1zjY -TkeIoyQnIGZIZ85QL2mRUrLGcYJdlDgNEO0yXOh78Cj1qNjXGvM5P8SLuNhVESInL1u+wTEiICI7 -pyfv51zs02g5Ub+oA46+0X/1K655GQ5/zkybeuALhBLZsXPr9slghQ0KYhJiMkJ8hDERKT1GC5yO -ejp8OngkZKwwmA3IgLxxZ9kUh4fg6527bSILtSnGNKhIdnE9oceANmVNS+sSj5IOE2LGu2iMOR9B -mOqwr1yWAaFOfV3GlAt9/wgPR6o9frb6WeZPX3bz/UPt6pITzG37WggpBX0G2px5p8Wp3LJnD5jo -olO1cqym4h45F/BSvtedvGzpBk/t1JzxOd7B7wszfmuzJ48NlvgG+d/677wad1tlSDki/B1X42DO -9ceWNber3vzpX1sNnX+ok65y1E1r5rVx+Dq4mloL09VcgICOlkXY/pR2z1S+qX63+efZ48TsSUUL -WTcR0wr/HiUhPgWZXjFf6HneceUUCGjjSBHCxs07A3wSnRpD6sKny9nEuTtLztOptthyn54uQNUy -Ul3vAp7P81zlohvShXO8ru8TsZezNR1gws/IbxKTsMbxucFZqih9Eg1YPT3bB9sqBes4IgRHojKP -KYlJd9WBe/v1Qwp6rIOm5o63bAjixt76DDSAs8NYLZ+tYo/wZiaz25UZm82+WVabItxlSIhPTkm5 -4/ffvrJfwAYbOtxzSpb0n4QVJowbP8/K5VbQJienhyM7xbSOGq7sGU6ryYxw5HPjKSClT4bgSDUm -Y6cn4DReoyQnIp9Fb1RNy7rkJBzkKKv8I49ylcSrmjX5OT2Q2wdily0DGko+Io9mlYwBrib2vKXD -NSTDp73jTO9qBkcVtFMQaW/3lq7r/gIhMtM4bZ8CR05Mgk/BKsOZK6cJPSY4MqYtAu/4FjL7Z7ew -m3HeobW1ql+fyYL1fGN1T1V7/q5OxIuGJy+xwRpD3cJV25pqx9vc994nU4um1DSVO//+TZ9soFtJ -D08N+JiSDI+SGCHnMbzHDenhEMa0z7Fvfh+c/Mts3w4lx9/480ScAA5wrObhjOjSRRiS6250pz3b -qVzEF+mTqAOp2qWV6siKiBoP9iFdVrhZh0y1kseMOAFczNHGe9bK1x9zkJvxdLKb+jOqhKA2Pikl -Ad4uJqdpMFFlxRyeW9k6bHJvcg5TkJM1jvXvsE4+654bkZMRMaSDx4CVxtuiCTlrbHBcnWspHgEl -KSktLmpsxm8S0CajT8KITs10cQm3ENJCSGbRHacbraG6lT0SXXhKrbZQuSTBJ2JCgM/7eYN8t3Ns -sERnLsnrS3BlL2cmaUbGn8qPEpJxIYdpI3NiLfkOnsWX4RPjMd5x5dhuRt7C9/NpBFhmSE6PAfAY -XsEDGTBp7KATugx4Op/niHqfHREJ8GD+jEONV7aSJd3rP41/YwMf0emw8vZ+gD7RTOzZjmuFwzHB -x5ES8Sv8wdzvi3kNV5ER4xbYsbd5Ka8EYmJG5OpB7/MbPI0TjBuvJj1GFPwv/js5k5k3xafk3vww -P8ZGY8srYIUjFLyN5zGZE/OLeDYn8AkQ3d3vNLl3iBCEnOMc4WlsUuDja8RnySGEE5TqUXo4f8xD -3dE3Ln1nBOdY7vsopq8kI7n6RXQZEnOYkKxmj9Xm67kaGNDSne1O3tNT+RCfIsCnZAOhSwb0+Aq+ -igCfezY2kEs8hnxA/c4xIzxKHG0O8ZVbDs6a+ACGdMm4lU0toS0zc9bnXizjgaYB7RzrnxNuiUc8 -VjMZ5NyHe7BBi2gXe2CZE9NFHOIIjgElMes4AjZJ6BDTPN9/wkHgoQzoAm2Nruixzhc4yhorO2zT -5r/fiA4rLHMVF/KFuT//R36aNSAno81ua9ClBHhcyph7czPHKHQr6rPKUYQ2Eev4RHyMv+AyDn5n -QUJ8jlf2cx4VUuAxxgEFHZ51zRHGeGR4s3j4yv/epsQHruAZCEM9ye3ounfnn0rYVTDEiIAXskZO -QomHIyEABjybqvRvtuP1tn98ct5OQEhKnwSIyGgx4gcZkzS+XkBBl+Mc44hev8TRokVOwCWsqPw9 -Xe93vr8JARkbjKkroiUE5IjGAo53vN489+UIbSaUdLTkdwZ8HDTLsOnzTMn4Gi5nyDJjJnQQ1oEW -b2SiVmCJo2BMseP1Kv/4MikP5utxBJR6fCsIIe/n3WS6EamO1U7/KSk18bqkJObXOcbBmYs34wQO -j4RNPIScDi/jC1LFj2yc4/Kk+0Ds0KNgRMjfyTtqTI+MmJARPTKEn6Clp+sBaFjMTnNxRAFELPFP -3Mg61Qlp9epGwA/wIGKSXQTf1u2x13kbKQEdSp26qn36l9NaIOtpTAn0uJ6BOr5iQiaMETa5L8W2 -z26umBGzRBv0kO3UPb2o+y+GhWqm3QNfmyRMZk7EkKOwixyyeSIiQmIeTFWTvTpP8QFhwMdwFHq2 -HdLeha+/wCfWs/pvpEVJREhGqsIv+F09Mo1raxzuxNU8nqPAmAtqzx5GOF4IZBQsneNSJ/si3rNa -eRNeyvHafcaEDCEDvpdvnu3EW0BbY5ju/LOhZ/KVSfurrFNoymzlRRcc36QmeIt4x+tt/7Qp+Vdg -TEJCwKoK7FIejU/a+HodhISIT9IiICOn0PXyED4PItj22fmKq3rePaTulDkjpco1DCkZNb5fuD8w -Igc82oTqffm4ZgY2vd5IvfpPALVG0lm/lU3+Xn9KZne+0/UiHS8x8K08YksCta/RlO/gOn1Ci9yv -8F9ZJqbN7bRq3Js+wpt5nSxz7Pi5Dp8953v2EHCM6fJy+WfiOW/4iDYJMR1OcE+eCxyjp+kQLY1n -Pv0xDkzjqD7C+wn1iKzQIZ7xGB6jtUU8Jo0fSMHf8zkNXUl1V9wm5avUHZM2XtmqqMGPkmmslqe/ -Z0LBPebWhp2r6wYktMg0aGY7XTbJqM6BPTXEmzkoD7LKBpHWE6rOngPW2WSFYWNboQOkCA9TD82p -fpv/w8/SIdY9suzibiM2gT5wjAM8hX8h18MyT5eBMb/F72rIDgvE2l/BN/J6+sBBbqrxMQ2AF/NU -1tbOtTe+8Z5q7z/HrxZSPifLgnTF2/YJZW32o88TEZFSpoxkZyYykFT//gXipCOhOHGCIL5EgrxU -rzqRbMu1d88PCoKE0hZPkFja4oknrxeRieSNrzb9TlcLEssFgsQSiK9P4L2Nr1eKyFj/PpGHzj1f -JJRrRKQQkXWZLHC/iVw6e0NL0pJAr/uuLb9594xlJGMpZSgPlFCQSBBfAkFagqzJp6Ro/AQyHQki -t8m9pCWIL+3ZM43kUrldfzZf4P2P5dPSF6Qjvri55xtISxDkp0U4eY6Vtg+88f23Z4T8FOsss14T -MVeduGZcztO4nSEdUjZZIuTkLmbKHhkFIRvcxl8jTDQPvQpvTLk/D+IGMkJNZWi6az/CR2mRkJER -0mJERMkyB7kJdLPQ1NYZsMFJHCkngFRzqjxCPsMljS2PPpsIE9qc4GDtWnoDn6dFNEvnaXb0VnIp -J9kkBxINJApp8xnuT8Dhxit7pufq9+bwLJFouhVb5xOaf+DoUDDZ0XIYsUZCTkqPMcscZJMJpfo7 -WkxIOcwf8w2sMabX+Ps7Mi7mW3gDCQX9uWIquW4mfp+nycPduV3bz/k5e4nHias/fM0TWGFMUtsG -ItIY8/vyedqEDCnpENY2eqw7Wsrw6HCIz6krxum5fhX1dB8GnGCZRF9NM5Y5jE+HghEejoIuBRNW -OUFU8/J3czohrHEbEJNpcQXUi3zhnEtyp8lkwhKOnCFLdLl17vvFpKziWCfT2mpNj8oOcQRoqyfd -kdIhJ6UNC1TyERICHGscxqPPQCu9edpNTehrhdkqFHbn77/ChIKcHhNiVjiunokciFnmKCUe92DA -xi7KnM1fP8Zp5H5c4+L1yegAI76D1zkaB1ndrcRezdkPkJvwmBDV+CvbDPGJCNnQWjOlhmV2GO0i -n70KYazWzGrYVNVrq7WizYhIy0OzQGniaTR4FfdW0uUkji6DhTPep3vJQCvdTdNMKgk19bCeelwW -1ez5g1mOQbFAeGupobKBhgpPM8em9968qYdokm+LiQa5Or1mprEFwjTpKNpFzbgUiKkKWuX6zPwt -OWrLbBDqEx41fl8+GT4FfUbaB2beKZYRkhLxFzzlnMbLngOxl3jaiHmTPhNavFB+iRU2KWrKDC1x -UgtLRaS7cCB5cyt7lVATI6QEOD1VTshpaTePptNTjGNCjF8zOAJSOiSEOnU1HTwtTbUMNH+9JKOr -599RzTntTu+voMMQ6JMzrp0cqgwE0Xi8pv7iKnM/oiRnhXXdImWUxCSszFk2O+ffb+o2YEMzALc7 -3CaEhOSkRMQ75p/XLS7TUiYZLSZ0GRJR6IYnbHi/JULMBGjp1J7otVc4qe3GI805fATvcDEthJxA -owXc3VnsJR4ZoR50HH/m2qs/LE/iGCHpnWS5LTFGiAjYIGwsnhyhpEuCI2QEOgf3CDipc36zHfVQ -1xyI8ecGc4SnQacBISyQ9VYlbAiJWh+iOWQJPptzZqDbUezVCUemcV2bc5PLiJCCkkgTc5uuxDnQ -odCo/uqe2njEHKvx/+90vxETuqwTkhLQnnu+1STSIeEAty9gdocEnCTWdb5FgdMF5CBHWZqL5Xc7 -Ts4jQnqsk+CrfVjSZ4NAoxer0iAtJvT5GX7OHZYLXZXdGO6yHPp5K/acYBYhXH3hp8vrCMhpM64x -o6syv+HMEN/JbKtrz+TpKX2PESVLjMh1R9lpvDI4SjXZk9qadiURGX029MW7xuKp7gzdB1dhxKJm -cvOCmdNSySlVNtm85VNNfAPNtWt+v5WdA6Fme41mGeJRTbGM3dxvWwOThKymkWMLYUJVFah54pLo -mXqbVKvKilaxCRf8/pVbcaLVbtB1vOpK2NI4z5HaPvfiHdzL3ZHsKnfvlb36eiM65ByTC92b5Pt0 -zyeaHbzdTBzzZL6ek8Aqh3f0vs6XTZpwiP/KgEL3gdXpekTKk7iy8UqW6UvqMMTx23PR5iXwfXw5 -Q5ZJ9aebbhKGvJJE469TIkTTcb+HhzX2Flf2UKw2ya/NTW4lPk/lYZREpAsMvpyIdV5JRkE0E2cV -xfAMLm88mQbEjMnoknGS35h7PyURz+QAy1r7rfGAZwPHr+BTsMwGEYkmPMNLSRuvtI4Ox4nI6bLO -yxBN3anOT0p8rSQwTcd+Oq91heYvbi/JdhbEd7Y/JcJA/3erXK4n3m1B+sLcOaWTg/IeyaSQscjs -xLzZufXH5ZC0pCNIWwJB2tKTSJB3Nz6zFcn0xLaUsdwgF83dbyxOPiiJDEX09LopE7lZDkh3yyPz -pC1d8eSvRKTc9tnNHZf63D4vl83dryeH5I9lJBMRKRaIMyikkKNyqcSCxIIgTkLxBVmS/7nQ/Yps -aFTASB42d7e+RPJqyfTNLnIuvi4D+SpBnPT06SKBrAjyRwvc72hLPMMX5EmCdKSlzyAUpK2/wUlL -4wX+QURzMDZ3Edt/np+zlwhdNbBfwr8TIpSMOcjRmnlOeDpfw5BlhCHdxiuxB7yaowi9Wf53TkbO -FVy5rXTBbs22FB8h5OPcNre25BzgwURqo5QL1LQrOc4xQhyijqpS67xdDDXbnJ3u18ORqQf6tpq3 -sUlLk1emUehNOaBFIVrk+BqNCBt8uibafOcULzXeKQAAIABJREFU0ioTMaWgzWV8Yu6o8zj/yDPY -YAkW6MiT0CHg2/goBzg68xJUYbUv5P+ZOyp0O1p6ETkxJT734of535rFkenbz3WjUG09YnJexJV0 -9ezj7Earn4PY+GrjkNLm3fLnWlhA1FU2fwp5T76HkGWqOPhRY0sl5t95k3aTqVpGRWTkwNNxNYkh -uzNchZCcv6x1iH0d3dkRT/NT1RTHh/QoLOCO2m4eF/AVNd9vNw5KVMRRTey2x0QLeVRJKM2ZAJfh -E2p6b05Bi4iAGzXdqMn9LuGzBuo2e+Lcnx8H/oWjdIHxAhUDOwSMeSIrnMDRp40PpJwg4GbevdD9 -VhEfKR5P5puBsZaRltlBpOgkWtDlA7xGBlR1bc8u52Rlj9QL+RKOEwEniIi5ufZg7Yd5FMdZY0BL -T9yb8hZuJ5idJlflHvq0+XbQtJUm+KSazXWcN9SUJoYnABN6OMotDSp3P/s6rgVCRviUTKgixEsi -ujVBKm7H+606kUHCoOZue2xQbDk18Bd4n3BvPFqaf19NuSlwU82edGdLpGrZ0KZPwuNrv88RPsLj -yQkXytJLiHg0j+PNRIy1d1+mbsyX8I0N7zehpM00pj/ix/k/DIEWoWYdhKQU2pnOZ4TjV3mSdF3J -tD/C3Xhlr8rp+/xPeaeeRHvkDPE0kaLU5MM2cAnPZ0Kfkh6Beqp3s7ZXntwCSPgdLQHR0vUrYYkJ -385BWCjlMNLaqe/RBNntXKFhl/5CzaEcGdcBKR0ydXhVg+Uy0GitrZ+dn8YYX0+Poxr33tRJtOhA -yOgD98SxCVqeKmAMtPgEg8b3W2UztrSp5335Mnw6lHSADiUlbTLeQUFYU2h8N1NTzIiUH6KqkOep -o6xKGP4AHwFtK7Wxq/uNt4zJCLiaK1kGJgzpAx5jLWEFnkYp3sjz9T7ObjzdWV/ZC3xG9LlVflcr -dm03izbwSfEZIbxAH/gdRlW54+CbFpWsdkR/znGNlqoquvn4bADPYKgGWPPThCps8224mpX94Rwi -mq2ozZ0iHsJN+Br20WWTgByflPuSMmlcI85p/ZUqVbas2SNXB32OMU6z2psQMsbn3qS0KEn1qK/N -iIQJn+PyMxwvj+DTdHCMKRkAHgNavJcAj/KUcs67fb5V9fYreSCfrsmy/APuy0EG9FgG1htXehd+ -jitYIWXEZs0k6jHhAG/hGvkGd9Y7AJ9tX3yOsInwPEGQVk0WFrIkHWkL8lhJJT/Fo93UWzyUR2ne -GOKkLajP+AoRSRfKcStnV763eDVZTj8p+QJnBlv5nAQSSUcCcYJEM7/8ry10tYmMJNVstk/UnHZ0 -JJbfE5l57Bf5DSIf0nt06pEPBPEklH+UM+UaQXzxBfHEFyTU/31YT0YW8cbnckREfk598ad+Dsgn -RWRTRmfwFv+LXtlJp+a0psrfu0o2VQ13Y2+8zwZLvF9eToRfY0aHoAEpHj+uTQGcBmnILs6BU20o -AI51jvAhvFkxItGV8wKewoRNVtXUb3b/JULKdRwl1oJaW3kkwkliPC0o1XTlaXMjESP9Bj6plq5o -scLtdBsH1fTItJP9gCOszBVsHNFlxICQgjFpY6eRx5gMWGFCho9oe4wqdee9PPYM95VV6JLT3LER -Pg5HzD/zQIZcuEC4c5+MNeBJ/FHN6cQx3sU9WAFO4mqr0e7ESf4Lf8JJpDacGnwmrPAuXiP/v0sX -8josyjmIjR/Q4z/Je2nh18Q+l3QY0cEx5GHcsCUpU9hNxNGENh4ZARFjjrNBj4Emw/igNeK/ktuJ -CBg3Nlsrr3XAgJu139z2ofQgbtOEjUWO3jaoCmD0yJkQ4pFo04l70WXc+Hrr+MSECB4rfLQm623M -RfQ5TnuhnrSVQ83jhi0715KCEI8JlzReTeZzI/4V0fc3lX2VnPoAjpE23tZUwbgxGctcX1NcRFgj -JiAho7+QJ6Mg4AibUJuyLTPP/MW8nQec1Ziac5L19mp5lvbNmN8zOVqMKTVRtN04AsufNT9sUWU6 -RTMffuUTncxinNIFXGjT2nVoAal5B1Dzez71aGjMQY6o+6ivdVaG+NrbpLlTpurEVgBxTQpxqU9q -QqRBxc1ttRJhmQkZIQltUnWfTfZktFRRjxEJfTYJEAJ6nKBcqHFm1d89xDFWB912y6JKlSmI8Rdq -zFn11fO1FHbdYlbod/lB/uisiv2sm/HCJ+XXZ3P4fGxzwYQOqaaBZGrmSO0qWr8ylCr3TGvNVS67 -SoQyG4Bdbb/QaXj/I3xNbQgZaf3bU7ch09aTQnuB2PAU4eTs6GaTCGGkFkIAC7SnqjLfKtdoPjdd -VD3Iqp+KNfe66fMISEhImLZGqo7y7qgI3Gx8bBeHrw7GWJ1/OYVOsil+Y8uszTGEiXr3k7nx5xNo -elOKT9DYXz6h4CBHdWNTV88gZETMJkv8Gd8lTzyLcj/rK3vB8+Q3QVvybsyJocOYgjWOE3KQW0+R -8W56m1WpDtOssWoerwodVGkgLc1826QK40kbz45CrokOfo3XwdPfWB2yJI3FXkWg9TXR0yehz4ba -IeEumkLMP40WpU55fq2YqmaMoh1nm67tHiEppYaPZPpm2ziNE+AMxe5T0MabNejyNHinjTBaoC6/ -4LPEidm5z/zzqHrDLJEzotV4zx4zIaCj28f5bYJPNhuF8C285e4k9jFtqk6WVbDE++QJmh/UYURM -wFjDK0UHSmXgH2BEsoAHMWONkzigywbg6JKQEdDnBFVa6oRS64RHjZsidFknItSNQJW4UpVQXuUE -df3gd5r8Qi2u4eORa36bPzO7q+HeYUyAz6Txyl5S4NPSAszxLBp7encRCSEBIzqMtI5rs+uH+mar -dl3bv++KVgz2iEm0qHdT03PMKie1pn6kLjtwXMBhDeNxsyLWXuPxEhGzqQKddvEN9VCv1zjltXJS -lloxYYUhGWj76rr8/NfwFNdXhQwaeyD2mdgLTd8/cfXq2xMiniBv1xWgmvkmMOu4Dh7LrJNyL27S -Ye4WWhs82gw5gOMEBbH2+fY1jNHXIsQlu4noqvPnbhIQzSrHhOpWLLSZVJPBIWr6TotlXcAtoJXy -BaFFyEiln+2iA8x2qlZFGdAn4MQsgPWOuyt1ogq13swiK/EyAwpgae77lxrvkGvNmMVSSKffJJ81 -8m4zZIWMIXe0tGaB63cZgJbWqrIMD3GSTD0POzsQ530uQ6qTiBCPsfpMqhj5Vk1K9CX8mxP6lKxf -vfr283xlv2NXO6LDb8lPaPZw9dUjrVlT6oOt7uYF/BCf4xChlnxuOr2EdPkdfpuOZinHuhfuMuRV -PIAVxni0KMkbi72P8HpeoQdazEpatSj4Yf7fOcPV7fh8mBVJ8gn5PV5DQTFrJZ3oTvM5PEUDjZta -VtU04RHyt7x41qNlenchLZ7BDzChzSbD2pKUp38+fTYZ8z5eXOuQ+gm+n4wWCRETbU7ZhD4jPsxP -6ZGhh9Oe9S0mPJ3n6+HnHXJvKva/58UUWlqs+v/KAnkqP6+en2bvc5OQAJ8Bq/wrz8ARab5/FSk6 -77B7Eb/gKoslvovz2+9yB12VkZ0T4PNZeTFV4SWnrotC290WurIVBDyEF7DK/fAZ0Wr85YfqCb1W -HYDT8g/VsFnhCu5Dd8sU1NSMH9PmM2xqoQZPSz1Ve/fv5hGNxe602m2pgrwnVce4qWHfY0LBmIfw -KE4S0G06m2/5+0/VfNuEhCUePquZ0nTyP8EqgqPNf9fp81QO81BS2rMo8KbXT2aWX3WNMRkdrRR0 -nIfoyHFb7KRmPJA/4/NsEOvEuMoGjpRreSADVhd4n9V9ZETcnz/hnRS0cIju1ud997/It8jDXTXJ -xHexFs+CSy4lZ0LMCzmpVd8CEgpijamrDJ1YQyB/gFVt1hwucHs9fDL+infjk2v4ZqlmYMGTeNAs -vTZntMBRVpshHyWiDWR6Tj+hQ8HFPKRxLdnpEC21kDV8RD34AQE+YwbkdAg4BLQX2NON2aQgYaAy -2c4yjoKCDYYaDNOMNZy6FMdMav77z+DTnm0Ymj/viBZfzoOJCCgYEzJBdDvzLj60q8y00zvUvh5P -R2RGyAkKVgn4NK9fKHI9odTRe5I2L6VDToavKVF+jWUn/DyQ11QROu/EPqRNRkSLv5PX0yUBAk1h -iGcFjKp/k1DyMH4UOKB1RTcWCMedIPytlqOahuL0cBzD8QwtCVjqY3aNr+/xz1xPqrHrgRpHBcJj -WKW9QCmPKivb0xbM18+OcO4YGikt7gELBVe26eIT0wPWaxJ3hgghPqt08bVdZLP7rzYgF2mbqe3c -zAnQUl7Vd236vMHjQfrEu7O1b0iLkhsp9R5klgDV9P6/nwRmDtdqw5kDv7HA0xB8PFapAq9KHsKV -OG3vWZ9uNKHLP/BaCVi6y83su1zsuTo+1vlZ0Ia+bV1rBzPv7/QEuMcvEDGgSsh0dBv33goI+BB/ -S0FGpq+vJKcFPIHHaoBs1eixzc69vfxtH+FNBIR0NCy0SnH0gP9Mynju53e6fomnlWSh4Bjrs7IX -VUpuX08YLgO6ZAv0YvPY4AQQ1hq5PrBJwph0gat7ZJrOtMLF1KUp3c4XqBpd+Pga9trkkzPC51to -4QETBvgE9IAJE96ka6a/wJ1XnxaP5nFqQwTkLBPoucW/8xFc4/cZIKQMmNAjJObZXDo7Lah7Pi3G -+LyI62WR1lP7TOzLbOADvyXXsUxVMnKTlBifcktOkQMu5KE8hdvoAbG2xW06s8KIN3OCFl31lodU -zYPhSXQQfOLZ/jTfZYveOz7wT3TIGOLjEWh5yAFtrmZCd+7nd7ZEqq1OCWxwK7eQkdDWM+8qCiti -jRbjmW+9yWcMLNEFcgY1Jm/V0tHX39h8ZYxnpSF7auls38Qd18m11DCm5i2s4XKNAfTxaLOpCaMx -793ynFno/nPGPFv/yxEB6+Ta3CPj9xk0fp9wjBY9AjIEx5P5Mo30EOoaa44JibiBP+c8FPt8iOTk -eMCn5GXkrGvndU+DTjwGpDpAHHCYVwIXzQ4xIJzLh97pU3Abr6LDRF1CTs85B1zI0zVwZPq1/Vl1 -9jv/bJ+5P8in2MQjIiXHY6zOvq/kvpq/fOpnp+tPD6Q8hqzyQTzt753jzTY5KffEqV++6fNo6+Hg -mBbH8FQWd2SXJxRsasedUDO5m3xgQsA6IfdjXCN2n49QhS952sSz2SfTlolfOztJH+FphfaMW/gA -ISc0J//IAtcPiLiC+4N2za2aZWzi4fEWblHXKbt8mw70PCMgxOGT8zuUpFpToKzx1xREwK9zrXR0 -ecv0XQ/2WJt7vk1ozVbM6kApYG0t55dI9Z/nB4NPrj7XH+ahZxRXXjnQ3sQAD5+MKr480Pir7+IA -5ULXd1s88+8BjRmDkISINjlDvgZHUpvHt9P9ntCg3hD4bI0DycfxFVQFiZvXUy1wlET4VE2pJ3PX -T3XbMJlF2jUbQIn2QbmoNuDHcRNVZbmUYlbJpsk2sFrFH8Y16qQ7lTdwBWuMyFliCRq/34KSVb6V -X2ekTUMCHAUhwoC/4Kfw8SjIKdSN2pQL+BleRsEmSwznbCuPnE0g4b/xWjp4OukKe98q6i7wCciW -HtjVYdob5HV6/FYXJJPrafJ9eRZh4wix7dzI60CjlarHFuKRssq3U5KccUrhX6vYRcWe6sHh1xEQ -aNvf5pEIsXpyP1fjQAOfixnTxp3B0ynJ2ahJuUz0LGDx2K1lqsbYFxIzqCnAeR0pJcEZDDXB8XW8 -QvvHn8p71GaI1H5o/n4LfL6NP2Bdz1JCjfL3gNfyI6ye8Zj8Ef4Xn0XYoK6YRUlJALyZa+TbXKFe -+6pSz96euwd3pdQFh89t8j+ogk/qpF7FzUX0WOMmPr5Qoait/Duf1N6caAPAMT4tDvAZPqvNipp9 -n1NX9g8zDYTJVfBdThJzHUfJF+jw0eEYS0CJY8zH5v68S0nBdfwBy4xpN47Vng6egpj/qPnzLgk3 -8OdMKDTqoRlV4rBHm/+Aml5nPl/gTyk1SjxtLByfBJ8OtxKTa07aVr7Ib+Or73uywHCOmBCQ0iMh -0lyNkmkEyH/wSg5pJ+BWbSLRzvtkoeQAhwnYUM/LqZNhh5HK/Vf52g+vPrLQBmMC5Gc80ZxiZe19 -BN10Nsq0jPHL5IV0SfFIahIPqjjnUs/IB2fsROjMgnCrePu+BpuiHVq8Bb6NO8X5U/UFq7qW5JoC -wsySaBrh1lZDNaSkXfv9S6reMCV5TZbdboZbJZALOVrbaHIaLutmQcvNno9PMetjMy/2EoiJ2dB3 -3fT+pyFHkbZenMz9+dRXE5HWpEzv5vrMEo8yLXPiaaecIUKMrw3IXG2X4Z1W06rKYqpT13ziTfXN -Atps8ou80DmGdHAU+Au977Mq9mq+mq4R75Pv4jYK4ADH7mQwRARkrHGsJra6KaluCqJZNFe1f19m -QLemN1vTPXumZRtb2vLIpyBjjeP4+Lrzbna/ba2RPgYu0FrmW8UUzSrv+AtYDkJMQaKJt8HcyhKr -GZ8RL+BxqOrSB4wQIjJW5zLAY0I26DIioMtG47W3ymHcAC7mVvpz42OVY/RIyOjqXTR9/pXYerPJ -u9DtUkqLjEC7xTvtpruIz6Q686gqA2/3uIcaeeIocPT5Jy531blDuv/37KUaLtWFj77xd7lZ00Pq -O2lFlEBGxmHQfLUzIdZGBVXhXg+I8MlYB22tvIidMs3KE01hLcgIyCnVEXgSWGa4QNOCKqd+2oT4 -RM2fluoYipjQblwQoqAgoqOHeFnN0Y/D11owyQLPp+od76kfeb4x51hjGHukrC/wfKaFSCZsIDVR -ZseY1uev8uqaXj9UiyPT+LxpH78qn6Kc5c47UrUgmt5/lV5TAknNUhDoUxfNBX05L5GLXLaAK/Mc -rOylFj+OgJvlOh6Pr/udIUHNnitQI6aaT8sFMqC3r8I5B7QDW0gxM/CqNIdoATP1VLGXs1W9q1ZC -TJfjs31x3nhyGmn+XRcYzb3igEDztqvEoaLx8ygpNdMvrAlY9fWsvzKAm+dvR9qh1FNjta7mWo8T -tEgQeo295VXab4uMgi753HRVavpK1UTSXyB4ttCYBo+CmJCBJl8XlJQa5elp+fCg8fUL3doJqxyn -O/f9q+O5UqP1Oox4J1e5qYW4t6v7XWDG5xTqXT4s/5nPzL2caqYLCRjTUXFP9J+7jcWSaEriIY7M -pCB67lpSaoHjiC6ZGrNNHVAOjy4n8VnjSE3W0kFOkmuH2OXGK29Ki5SIQv9/fk93iCN4dBjqiUbT -59NjQEi/dqihbYaFgIQ+wwUaURa0ESZcyOGaPXNIQpdMzWV/getH2q2+qp6fc0cyq8ORE9HjOGhG -etz4+XQYE+MI2dDOsD6lFo5q4THSsVlN7/EC95/iaJGQ0WNAR8/dyzvxoFzOx1yoDT33dnXfc7FP -dKeTEvPT8ts1Bf0qk8/X9aTascQM8YgXOAWfdvNoM4KZ8TO1EKoVeVqrzBE3FmM8W+vqHXwe+Sxu -LlvoiU27mvuzwTzvQKpaAi9m6bRn/eLzO/GZTB1Ui9W4y9UyOTTX0RbQmiwHWMcjXSBffuubXuHI -bO2W2dPLgQOcxK/JudvtTtaR0QWG2uO3Khgy0t40Y1oUs3oIiziNU3JCWmyqhVNZI3UFKX1ifoKX -uHWWYYHJ/ayKfUyb6vDgn+SpNSuhY1qdpcrfnvBjPJYlJpQcZHOBuew4B1hjyB/wD2TqECm1HERA -weW8iJwlChI12ZrN/B59hvS5hleTz01HPgG/xr04RofuLvLj5y2Hkg6OkJt4AUfnnlcH4Uf4Wi2W -2dxbXhKQanHMl/GRuT/v4/hKnkOuIStl48kk151uwBv4k7n7b1Gwwkvp0CYlXKD4RklKQosx6zxn -NqFOs936DOjxX7mEeOZGa3b9dTwixvTY5Je4Tsty+kBGSMEaGzyR53KMeIFqxG026ZBzghWO8Xf8 -JQG5bgHr3NEtRqzyPr78rkhs3+tC9AVCSoZwlcQ1RfgDbdgQiCd9CcSXfxORTREZynihkvxVE+Ub -5WsE8bVtgy+BBBJKJMjPyESSWfPipiSSi8hARJ4rvoQ1TS0eKcnCDQuqdhO5TETkY9KreV4I8jfa -nHixRg6ZjPW/f9ydXP9btbl0sdC3yGSiz/UP7+T6K/If2k4iWeD6hZR6X7fImraiqJp5TwddS14u -IqUkCz2faRuSQk7I92oLh0AicRLMfsMVs59tzng26sbyN+JJLG1BYvEFieeeVyhOPHm6CAPNltvn -TSIcAa+Xd9GqOXcutPKqh8eIgudzOTCmR7s2H3pnB05Aisdb+QTQY3OWaFjVIT3ItxOrQbRI2aXq -7LkE3kVR4yDzeIzW0QsYMmZlgWflkzPkeoY11Uh7JNybKhUyW+BwLCegBYzo6658+8q+SaGuoKRm -G7Hz9VsElOSEXFqzVnUQ1rmd++mJQ9PrV30ACjyEe3Alb4Et0f3VkVnBX/NctRvyxpZonyr0aMgK -P8IbSDVzrdqi9IAx1/JmnkxKp/H1q/rIKQEeLZ7MU3kTLaDNOnWlPKpylG/ku+WbXL7HurwLIujA -5yZ5Ed1ad4+Qa5XUjJJLeDYZYw5SVVxv7pIoyAlx/AkjItZnsXuFes4fxoORLYUUm37hyh/a53o+ -CTVBIR6Px6PKCeg2riMzjXCLCfkUUhMFMGRZI+wWi5Webiz6wEk2a5oWTM9+nUbQNz96m3oyujVH -Yyklwsd4NCX+AgUte7PfIvg8irdyR7uQqv0iOP6JWzigndCbekxgolWH4DE8gg/Mugk4YMAyBeu8 -iqdQ6lLVVGDVSBySs8zP8lYmhCSIRnjO+0BG+LyYx9HZY2/8nme9ecCJZ/42199JO2RfmwcKJR1+ -jHtTsITTuTRboFhASc5b+IDmHhezSLCSkA7Pp0OhaR7BAtuWCKFkwutJ6NSs2wEPpaRNymCh3l0+ -PkcpKPk0vZpKMsIlHGJa3715ym8Lx0gnkW7NVFoVEal+Lp/Vnm2S4goDhhTA/Woq2JVEBHxOGzMs -Zn4mxMS0gKuICbUk9nTXHtHB4/3ECzydqm+ATwdfO75/n3Z1vaO9d3UOfi2f1HOLpsUxqqZaHl2W -SfkqfohpS2tXqw8P6PFB/lBkj+W552J3jPniq34NuJl2zdeJmZYo8rmUHyehBQzIaVMQzeUP7/RJ -cYT8KczcJ5VhXA3uQ1ylVeJaeBoG0/RTVZF5Hx6jmg4hj+Q+DIC2hlM2vXp1Yu9T8DEGNQ4bxz3p -bXH8Nb1+jkeHLnCyNp+9yuMa6EDzG19/DPToqnXTqxF7jnA9VTJtudD1A3wKRmzyKEIN4UGl5FMw -pOBtFHqg1eyD9gs6oqPzKfRBc/xzhA4TWpTcwqsY0m58/apWIWzooWHJc7iAHEebtMaSSmgRsg78 -EjdLsP/ELmpm5WqM/yiB5qzPH0aNOMgm0KXgF4m1HEOXAMHTvXyTT0TOtfw9HhOm/U8SckJCNnge -Aa1ZRVVZoKJJScCE47wfnw7TiLYIR0kLeBIT+iRqanYXqJRSZcqd4ObazDPhflRBlykhyQKVexIy -MoSV2oOcnIIRfRXVoPH122R6ZlHSmdUiONVMLrgOiHALPP+qmpDg0aZHyFUMCRGW9KiwpKCPcM2s -QHizj9DBIRxUF9bFPJmYlExN9pG2serxh7NC5E0q11TxkUIfHyFiwAN4Gj4R+Z3U2h1p3Odx/j+2 -puyWZ5wktgdiH+Lw9YQyxeO35FoSWlrrfN7sPUpIzDrfzH8iPGMP4xHavJyJJmmW9BA9i80I+aYz -vn7GGJ+/YZOMnEAN74m2ASh5CK0tRQ+zBcy8CUPgxjt5lR59DTn1YIEadxMiQkIcCYOa39HTHaqn -3WKbVwYqtmTZr9bs6T06bHBMizKc6ft4gu6DNzRuoo9jE7idD7C8J6dL38GEFQJKIi2egm5VXkNJ -se3T9OpLJPwCBylnHXTqnWgFOdfzVmlzWCDVerrZuRa7zC5UEHG9/K6Wya+npT8b8WMconllke2f -Q9zAX6s56sgZAD5t+sR8B5ed8fUDfELeh09ba9MUmtQJBffkq3VAV1HW0vj6Hm2WgOsZ1ZSDhJCv -oE2LjJyCyQLXR2vUDmelp7YyoNRKv6gLrdmn1JbYVf7ZfWtdnDm3cwMenQUqD23/XE5VbLLaY1cR -83ABY/4KOHLG13d8Gw9mrBZPRpXUVY3mP2F8xlff5DAHeYb2vl26U0GWwA38FgVrDq2m685Qrme8 -KcjoadvECT4+v8en6DFhpKuFmxtcHUZEfAdPINuDfN2A39E5coLg00IYEXMceNYCWUp1hzO38A8U -pMR0GZLoJDAEruACqky7YsuGpgkjYiIKPk+Ijz8XtJNwSI+g+jBrptEkaAT1NFcHbPMtm30upMAn -YDLbDzfxZnu6hSvweODcny+zQUDK53kUGZMzLh5yFQ/jU0zU9TdBCMlIET6KcOiMTd1NVnkmz8Gn -xKfDQC1FR8y/8m6+ofY0okmQTR/4ed7G9Yx1vZ5fOqcx8+/hlfJct0lHd/f+uRV7dWOJRoH9nbwK -yLVUNDVlhNBiCpfwLxyhdcYv5yivVW9BVTF2qMM75Z6UvPWMjy4CQj7OulZXnx6F3DGFXcOIjB6F -5qP7jcWS40h5vyZ5bH/5K3yOt+Izok++QMjsmJgBES2O1baZTAj4In9Bn85C5/jT5o05QveURpzT -yb1yo/0LfdIFztnn77etJwhjYgIKfDI2gU/w61y6B0dVHqtEdNkgZ0jEBI8EyCl5xdwbaBqBOqHN -BhfyID7BCoPa8GVR31dJwm/ybXKpq5aFzhmGz+5BuGyVwOHhSPk6eZ8WESgQiprKKn1yhkyjsTnj -UP8VJoxBGwcHanQVeLq/OdPrizZs7JAjGh8dk+ATM+JijpBxRx+25nECTjPehnoENh8bv8TGlqj1 -ptcP1XUJK5ysCXKqkjZXOaGx/4v11nMJ/FqfAAAQfUlEQVRaCHxQc26catGKabmnM5WiIydghaOg -pbgdVf63p7UGznRTOo33b2mnwv/b3rnF2Fldd/y3v8u5zpmLxw4X1wY7AWwnQFVIlKhVK4RfKvUx -ldKH9KWSValKGymKhNTiBuLg0khOE6Ctg2greksNCVLSQiQnqDcqUlAThZYmoiZGRkTG9mRu5/pd -dh/2Osdzi8+c831zxpmz/paxLRsfz977v9fa6/JfRhp52eCqGm52XY2OeIjrfz8Q/821cMExThun -fVulMfBI7ZzJ3qIEJESctp8UVRKPEENrgy4oK6omLSy7RKUl201flfisq49zPUaWEhNcHqILbX20 -2pWKuNaeWOa/ujF9LSxl0U1HqsgGhU9DRClcs4Vdd5k1aOFRoMAiuwYW33CNRm72rJXu6dVupaWB -kfbT8sAhIBcPL0mHYbTBiOKEKr402mR1RJHQb7cpZZIObVmzKo1MKn0rP2MGSx0jyTxLkQ4FfBrr -/n5v4Ms3lQabSZo9k7Tycu/O7u2Kn81whqPGxYOyKdfk0gjTpAycs7/Cj1f0j9dY2sCSFGgxxaJM -L8/+op7hikwD774PE5GbDGHD6VqDIu1Z7lDizykl2rItgUSkXcf44BpxKYbdXKIIG8p2pcBu5khF -AmTQr6fCcs9yFzawvCmBaKmGEmcZ3I1PZF49G5TbVlmWd7UTXsq6H859b+MxzZysvkeJpZz2O+3N -K3Kmw40UqbEkJiTK6MZ3uwzr4rN5P4XsZZk5FAG/xrOmCMwfnH6T7SR7W+rh4KP2haHbPMcXTp+k -TKsX7d9Z8KR9tA5Ms4TVLR/wfFSJ+FN+03SDodtq2Zep4PEN+3EWKSjZByZDSLNXjV3ecetniDFS -ujKMZ6Jkn2KBO3nJQE186GGRQzTe4jF/8PMswBDqnuMOp4FiqRHRoTOkPML1jSrLUsq8XlBT0d93 -9niNk/YREbTYRsveIaXEo/YBppkf4s2qgFkuScdWsOPc3IgCE1ymSJmIulr2gT2jlFkW6fA6+011 -e8keE/BD+0HqBIS6mUO4aZ/md0kpUMcbomjm+j+sLu1WxFDn93hRt3wgFGlJMPsoZ0221FtmNz7A -8iBL+DnrZY0LCtzHz9HEYypzddn1CdvTBfSo6oYPbAxctmeWb/EV+zHDdpIdnrPPMMsVqtTVjR/C -zb2RDqkQvZE5D339XWZNKkQ0iJnlXr6hWz7g+YBJGiwCx7n/1T33biPZ546dBJYI6cCODC9tLW7i -sEhJRgSZnLTrFR0qpEyRAgd0wwe27BM0iCkwxRv82T3HM/xdQz2xW/LfBDh5+ke4KHzExJaPk98J -mxfI8AE3wvowRZDCFnYk1afFpnjUuYeu6ERKaYgxkuMHjwapDHb2eIjvWneKulJdW0x2R2jXZviS -/Vuu0B0ApHZ9c3Q3GDrSBX6zVF57sCMLTvxVLv00kxipZk9ymP8zfvgjYAGPJXwuvWq2muzO6azS -Ak7xYyCU7h2tjuoP18ZxleDv56dNwdtJZDeiLTvLzSAWPVayD4EzPGunpLimMuD7fWCytyXoAkWe -sc9RooYlwk1WU7dsM0ESI0ufAHfveI/ISmdaCoQclPEdbmqp0QMxMD6Dlal21QFPzsDsDKVMv0OL -T2Jpidp7BY9Ib+pNLbiV9o2YgNuyBlB+Jp4t7scUuAOXTvJ6jz/FIKjwPzxiS7QxxFv9ZvdkXlqB -h+w70twaEBLJ1HLFZmydJcQC+7iRbkpkZz6E0p79dnNKj4igtJdZZGk8keBxiu/b0orB6FtG9gSI -6fBf9lH2EBHKpK+d/O7Md7PcojtNmCNU6KrXdQm/E30Z96NPym0EQnaLhnQHR0yNOR5ZoZq0hWR3 -I3cMv4/HJQwRbXEnykPMSB1Psvu9n92KEZfWypyTnYmrl9h7CHq6OxrQHQZNAv6Bb9viwInuTZB9 -ia4mfCQbFPNX9ptM45Q3PSwGj3Yu8o47HYaQpoxODrizRwM3NzTYgV/vauv+Pio9HT+168PjU6Ij -H9EVmK73vTw3wc4KkNCR+9mnw9LRR4E5cUAVg77XkQGEAfvH8LLbh9OOs3LBKQbzDN0cmfM8ZCss -E8pquuFeSTayt2SyVlmo3cDjobPnCNmJUgujOOzOD0qBMofG8Ou/k0hGiAQa5RkYoXxf5gl+YCdw -YmZd8ZM0G9m7RRFuPltChX+zX6SCr1Qf8rAnvQkyu7h5DFfgbhLJunvqyA8MXygdcoXjQCRxshRI -+3ja3mZuEhfib5HiA5/B0KBFh1hTbQPDE2fMw+fWDcZj7XSk3EF3MqrVEN3A6BDQAUoUeYZ/tKG0 -DQebyOT0JXt3yliblALwhP1XypJfr2j0fYg3u7NoAXDnWB72AxgJ7KpdH+78+EBMG48T1AmIJKHr -9TlPfcmeksgHVIDv2RNAQ2ZoFnTthwiweITizt81hmT32MsNYtcjDdANjAIxliJNoMZ3eMp2hOQx -9EnGbeLNbnuTPH5y9G+4iKHKu8As8+rGD3Ez+72X1f4xTFUaptmFiyCnms0ZGAFutiAUWSDkS7xt -fQIQ4dJMZF8ZMz179imMqIp5zMmYX8Wgtq0u0/EOg7R77mRyr/1Vh7twg559PT8Dw3FvGY8Ij4Rz -nBTlwhr1PsZ3E6k390bosMxfMq+rnfnwuxo6w96xVGRL8NkLeAS9FhlFFtPxVV60vvSrkI3sBZlu -VubP7b/o2maGT4rBx+MQk2MYokrwOYKL+Fgle2ak/IRTtCX1lpHsHh5tDD+0X8hhmrrCFdVYEg6N -ZcGoBQ4DcS5jHhWwixd42lawBH0GYvcleyKu/MO8Q6Cbk8Nh9+QOft+YXnawnyJJr/9PkW09U+Bz -vDtXx6eR1bJbpnjWfq3Xya7I5nb5JKQE3IEdw8vTx7KLfVxtq1JkQYV5fN7i5EwAfaJAXv+bo8NF -+8d08PWFlZtl99jF7b3J7uMEj4gSB/FJCLSsJjNi3Ejpv+Al2+889SV7myJ/xysY0Lx6bo6sxww3 -jaVdc9pzNxJi8ZXsmdGmjMcSTR7rK1O1juwtYP7g1V8X+T/7ADBBJF3timyWrUmZlCPYMR2XldLh -XloYfNUszIwSESlFIr7Oc7ZNA1bw1F6b7AEw+WYbCywTEXMcD8MCJX1j5WLZDIaYg9L/Nn4ICURQ -WvPs2dEipkRAFctnaR4NcVo2MU3WJuO8ja2Py4BWCfm6/XuREbLaupDLm93Z81+AsdRNTyngcTsF -jEqP54IyHnViJvhfvng2pEGFpij3+v0sexOnEtvBMHfshNwPVdqaeMsxqHI3jGU03vkytzBJKi3T -iqyeUgNo4zHJCf7TVoSxwWYsuyeiFD4Rp05/l0ni3v+mblf2w54SUWU/45tlTphkr8hXKLKhwCIw -S8AiVRIeBS7aqkxziPqRvSuo4PMd+zhFFkko06Q0lpZoK+huOcjEGL9YE+AQaIAuJxSZpwxcJuR5 -TtsbjBO3aKwRR/E2elWFJMDlM1+WsBxi1VUPNDvccIT3r5BTHrdvDh8ATb3lcnG6uQ1NdhMR0+Ik -bRZ6l2ofsndw6qcv//pfU2KBaVy2vYmv0fhcyB6wT4juJpmO0zdLgiFhn5I9txPVoELMkkwZushx -O8XlM1CjuepPmrX0bVOkTcDcq798zw8oqqhkzkip0OB57iehvA2ufCKCUFYms4z6861UGrzCh/Bl -Lo4iz/NVAl5jrwkJaK9y5IP1/n9MmyJ/eM8FPNoaQsn9Hm5j+Aqv0ZAOuFF/vsu2WFoYqiMnm0dK -mykuMEFDx4rkjoAWM3yCF1igvObNbtY75k3KvGo/QkyJlm5GzihTl02JgdLIW4tcrKDQe66N+mFW -IJLPDEgoqOeYO9k7GCzP86tm7tiuL1+T7E2KeNxvX8THQ0fv5e9mGWaIKFKnvQ1Nw03KUk4ZYmlR -HvnnlyhiaBOyiCbf8vecqixQ4VZeMRWWmbgW2SNCnrTHmGAZbxtu/p2/GVctuqHSpwM5f8ywiCUB -QlIqLI/4832pG6xSx+mmKfL13BJ8ijR4kIdNg8q13fg37C/SZglLSQUBt8Cy7+aj/BaGCENr5HLc -j/BPOP0hiDjKwyP+/ASfNpNUeYKnWVDLnvtlGlGgQ4kJvs1dZrWLv87NepxL7GERn5hQ31S54wof -4ucJSLfloB+Q6nwPj4hb+Mg2/BtaeBT4JR7Xw7AFnqNHREDIZT7H09eOxv+3fYzdXMKjxrym3nLH -BMvcRYAlItiGaLwBKiySMk1THPpRokGNIk0KHAadKbQF++uSuw2KnOG37X1mFdldZr1Igg98Ho8F -XAmeCgJmh+1RzK36Mrs5QIcCRToURh4TaYOUWjRA8vxX/3Vb38Rc4yI3UCHlBm7hvLrxW4AmIRFF -4BT3ycMpxCNw8VDn2MEF+88klIjkCtDwSf64lRkimhhK29BJ2JGdTuXVnqy5jLaa7B6zJLQpUuNG -zutxyP2JBCEd3EjWf+cte4uB0LHcTWh1g2B9vs8likQYaT/UjuP8cVim5KWwDWo1bQyBTFF1uZdR -f34RqABlDvCyHofc4TrZDS0C5vke+2TqUERgMKR4WCLgPCVKzFMgIQKNx28Bbu9Z9ZIk4kZr2S0J -PkakCuMVb/lRWPaEbq5/mYPaMp07ikTEQJEWNWIukGCIKJAQuOVOsRh8ykQYXB2ORfvXtyKA8mHK -zFEiJN6GObgGFw1PiQF/lWcxih0PsKRAnRof1OOQO6z4jAVatIBJAmIZORa4d1QqQtFHSGgSyLYH -tJTuuW/GHpxmQPdCHS26DcwAhTX23I7kskkpy6UyqwOgtsBzK5CSYgiIgA9gpFs1ILCrxIEOvffD -514mJMb1r+tm5I3beJ0YS5Mai3RGXq76FglFIiH9Bf5DSN6l+1bHaNr4lPFpU+IN9vO2Hokt8N2g -g0/MfdxhwCN0fLZ0KJDg0yLE50n7KRqShlP1z3zcqpXR7pSAiBpL0Hs5jxIlWpRpYSnQYYqlVWQf -xVG0dPMBAbEGgHNGt5/FkFLlSX7DdCRkt0G5bMIn7GlSqiSkRITSpVQgICFatzlaOz/4Zqy/h0eH -tUU0o0792XXPCsXgdnvlevoSgUnxCUhEqyChwDG+tOqPb1AbX+cP7J/gEUquPZR6r2TDw6FkVyi2 -j+yJmJACRuLwPrNcwuMED5hrGxpSPL5gvsYsbd5DShWfiBYJIRPM6GorFNcRdlHFI6VFhE+JElXe -5SDf5NPm8pnVj8QNZalccuQp+1neFp0wjxqGRdJ1ySK17Nnc+FGvn93mZ4RRNz7X9esAExiaxECB -Dod5kI+ZhIC1RVPryG6pExAQEbB07M3Tj/EjXucKYChi1kjYKRSK7USJVArfbuIIB/gdDpkSdQyB -q4a/9pv9qjsPLUo0edee5w3O8Q6L67qU1LIrFNtn2SeocRPv5XYOsMcUiEmxFIGIeE1idx3Zl6iK -jskyCVPEUm4Tk+JtELtVsv9sudH69e8sshusKAYjbrtz3V0PwgJT/S17t9SmTlXcBCdFH69r3FCy -Z3uza+pNkYXsiaTeEgwhYKXXBVqY/uqyCoViHAyNQqFQsisUCiW7QqFQsisUCiW7QqFQsisUCiW7 -QqFQsisUCiW7QqFk1yVQKJTsCoVCya5QKJTsCoXiusT/A94nbSmQ4K3dAAAAAElFTkSuQmCC -" + xlink:href="data:image/png;base64,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 MUUKDG0wLpUT3MBRRBGa1vMPnHap/sFuUepeEbLaMl5zonHg9igfOm32cAeaIT1Yq8gIXdFJGqCA BVobFgXutcoEQIE3M5OSS4u3bSdt5MKLYib8rbnUizA1B9UEF9P0TwfyLrumcEVSH8EQQSGu0o7m 1sbB3X/VKruhueO0+lRbbeVZXt6SI8+y2lZF3/iLrK7XxzwgLFqW1ho1RHGEe/zsN0N0RuMx5X6y qj6FoWb5Zow+gPKYo+ovD39JJv9i0sugX8x5GWR/M+kzg5L57zzzsuJcEvGbZ/bLlP6pZ9bLkPgY 4p4I6Ybe/OKZ8eYZ/yfP5DfPpJdn8kwvk7Sk8Ytv9rtv+qe++ZNJ0JpfvTMe7/jPvZMf76SXd9Lv aWP+bd58TBvzb/PmY9qYf5s3H9PG/Ku8+aSgzN97BgpxWQVBQ2Yv4EWffTYL/rZpZnlqbyxYrSS6 1yeKmFUz/wuPSVtgoSj9qQAADRppVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBi ZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1s bnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDQuNC4wLUV4aXYyIj4KIDxy ZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4 LW5zIyI+CiAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOnhtcE1NPSJo dHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9u cy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgIHhtbG5zOmRjPSJo dHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93 d3cuZ2ltcC5vcmcveG1wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlm Zi8xLjAvIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4 bXBNTTpEb2N1bWVudElEPSJnaW1wOmRvY2lkOmdpbXA6NTU3YTc4NjQtMzQ5YS00NjI3LWJjMjMt YTQ2MjhkODljNWVlIgogICB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjE5ZmUwMjJmLTg0MWEt NDIzMy04YTVlLWYxMDg5YzUyNDg0NCIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAu ZGlkOmNmYTM0NDM3LWVkM2UtNDQ0Yy1iODY4LTMwNWFlYWM4ODkwZSIKICAgZGM6Rm9ybWF0PSJp bWFnZS9wbmciCiAgIEdJTVA6QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IkxpbnV4IgogICBH SU1QOlRpbWVTdGFtcD0iMTY1Mjk5NTA5NDM2NjU2NyIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjI4 IgogICB0aWZmOk9yaWVudGF0aW9uPSIxIgogICB4bXA6Q3JlYXRvclRvb2w9IkdJTVAgMi4xMCI+ CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpCYWc+CiAgICAgPHJkZjpsaQogICAgICBzdEV2 dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0 YW5jZUlEPSJ4bXAuaWlkOjViNjg5YjhlLWNiMWQtNDFjZS1iNWE4LWIzYmNkZWU0ZTQxNyIKICAg ICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iR2ltcCAyLjEwIChMaW51eCkiCiAgICAgIHN0RXZ0Ondo ZW49IjIwMjItMDUtMTlUMjI6MTg6MTQrMDE6MDAiLz4KICAgIDwvcmRmOkJhZz4KICAgPC94bXBN TTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4K ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 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 " preserveAspectRatio="none" height="67.002686" width="75.532013" /> @@ -2405,168 +254,7 @@ QqFQsisUCiW7QqFk1yVQKJTsCoVCya5QKJTsCoXiusT/A94nbSmQ4K3dAAAAAElFTkSuQmCC y="148.75871" x="-119.91073" id="image4567" - xlink:href="data:image/png;base64,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 -" + xlink:href="data:image/png;base64,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 " preserveAspectRatio="none" height="116.87744" width="109.92844" /> @@ -2574,885 +262,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7/D64nHap5JtwKAAAAAElFTkSuQmCC y="-22.216612" x="233.11163" id="image4593" - xlink:href="data:image/png;base64,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 -YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDQuNC4wLUV4aXYyIj4KIDxyZGY6UkRGIHhtbG5zOnJkZj0i -aHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgPHJkZjpEZXNj -cmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29t -L3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEu -MC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMv -ZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93d3cuZ2ltcC5vcmcveG1wLyIK -ICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgeG1sbnM6 -eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4bXBNTTpEb2N1bWVudElEPSJn -aW1wOmRvY2lkOmdpbXA6YWFkODkyODAtMzIxNy00NWMzLTk0MTktZTA5MDAyMTAxNzRiIgogICB4 -bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmFmMTVjNjM3LTdlYjQtNDNiMy04MTk2LTIxMjQ3NTRk -ZGJlMCIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjkxNzVjZTBhLTVjYjQt -NGNmMi1iN2VkLTczMjVhYmViYWRhNCIKICAgZGM6Rm9ybWF0PSJpbWFnZS9wbmciCiAgIEdJTVA6 -QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IkxpbnV4IgogICBHSU1QOlRpbWVTdGFtcD0iMTY1 -Mjk5NTQwNDMzNzIxMiIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjI4IgogICB0aWZmOk9yaWVudGF0 -aW9uPSIxIgogICB4bXA6Q3JlYXRvclRvb2w9IkdJTVAgMi4xMCI+CiAgIDx4bXBNTTpIaXN0b3J5 -PgogICAgPHJkZjpCYWc+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgog -ICAgICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjA3 -NDk1NzkxLWU0NzMtNDUxMy1iYTA3LWEwZWMzMGFkNWUxNiIKICAgICAgc3RFdnQ6c29mdHdhcmVB -Z2VudD0iR2ltcCAyLjEwIChMaW51eCkiCiAgICAgIHN0RXZ0OndoZW49IjIwMjItMDUtMTlUMjI6 -MjM6MjQrMDE6MDAiLz4KICAgIDwvcmRmOkJhZz4KICAgPC94bXBNTTpIaXN0b3J5PgogICA8ZGM6 -Y3JlYXRvcj4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGk+VmVjdG9yU3RvY2suY29tLzE3ODQw -MzQ4PC9yZGY6bGk+CiAgICA8L3JkZjpTZXE+CiAgIDwvZGM6Y3JlYXRvcj4KICA8L3JkZjpEZXNj -cmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -IAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pq950zAAAAGF -aUNDUElDQyBwcm9maWxlAAAokX2RPUjDQBzFX1tLVSoOrSLikKE6WRAVcdQqFKFCqBVadTC59ENo -0pCkuDgKrgUHPxarDi7Oujq4CoLgB4ijk5Oii5T4v6TQIsaD4368u/e4ewf462Wmmh1jgKpZRjqZ -ELK5FSH0iiAi6EM/uiRm6rOimILn+LqHj693cZ7lfe7P0aPkTQb4BOIZphsW8Trx1Kalc94njrKS -pBCfE48adEHiR67LLr9xLjrs55lRI5OeI44SC8U2ltuYlQyVeJI4pqga5fuzLiuctzir5Spr3pO/ -MJzXlpe4TnMISSxgESIEyKhiA2VYiNOqkWIiTfsJD/+g4xfJJZNrA4wc86hAheT4wf/gd7dmYWLc -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 -mGw20awphscAAAAASUVORK5CYII= -" + xlink:href="data:image/png;base64,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 YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDQuNC4wLUV4aXYyIj4KIDxyZGY6UkRGIHhtbG5zOnJkZj0i aHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgPHJkZjpEZXNj cmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29t L3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEu MC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMv ZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93d3cuZ2ltcC5vcmcveG1wLyIK ICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgeG1sbnM6 eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4bXBNTTpEb2N1bWVudElEPSJn aW1wOmRvY2lkOmdpbXA6YWFkODkyODAtMzIxNy00NWMzLTk0MTktZTA5MDAyMTAxNzRiIgogICB4 bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmFmMTVjNjM3LTdlYjQtNDNiMy04MTk2LTIxMjQ3NTRk ZGJlMCIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjkxNzVjZTBhLTVjYjQt NGNmMi1iN2VkLTczMjVhYmViYWRhNCIKICAgZGM6Rm9ybWF0PSJpbWFnZS9wbmciCiAgIEdJTVA6 QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IkxpbnV4IgogICBHSU1QOlRpbWVTdGFtcD0iMTY1 Mjk5NTQwNDMzNzIxMiIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjI4IgogICB0aWZmOk9yaWVudGF0 aW9uPSIxIgogICB4bXA6Q3JlYXRvclRvb2w9IkdJTVAgMi4xMCI+CiAgIDx4bXBNTTpIaXN0b3J5 PgogICAgPHJkZjpCYWc+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgog ICAgICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjA3 NDk1NzkxLWU0NzMtNDUxMy1iYTA3LWEwZWMzMGFkNWUxNiIKICAgICAgc3RFdnQ6c29mdHdhcmVB Z2VudD0iR2ltcCAyLjEwIChMaW51eCkiCiAgICAgIHN0RXZ0OndoZW49IjIwMjItMDUtMTlUMjI6 MjM6MjQrMDE6MDAiLz4KICAgIDwvcmRmOkJhZz4KICAgPC94bXBNTTpIaXN0b3J5PgogICA8ZGM6 Y3JlYXRvcj4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGk+VmVjdG9yU3RvY2suY29tLzE3ODQw MzQ4PC9yZGY6bGk+CiAgICA8L3JkZjpTZXE+CiAgIDwvZGM6Y3JlYXRvcj4KICA8L3JkZjpEZXNj cmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg IAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pq950zAAAAGF aUNDUElDQyBwcm9maWxlAAAokX2RPUjDQBzFX1tLVSoOrSLikKE6WRAVcdQqFKFCqBVadTC59ENo 0pCkuDgKrgUHPxarDi7Oujq4CoLgB4ijk5Oii5T4v6TQIsaD4368u/e4ewf462Wmmh1jgKpZRjqZ ELK5FSH0iiAi6EM/uiRm6rOimILn+LqHj693cZ7lfe7P0aPkTQb4BOIZphsW8Trx1Kalc94njrKS pBCfE48adEHiR67LLr9xLjrs55lRI5OeI44SC8U2ltuYlQyVeJI4pqga5fuzLiuctzir5Spr3pO/ MJzXlpe4TnMISSxgESIEyKhiA2VYiNOqkWIiTfsJD/+g4xfJJZNrA4wc86hAheT4wf/gd7dmYWLc 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 mGw20awphscAAAAASUVORK5CYII= " preserveAspectRatio="none" height="113.66782" width="79.098289" /> @@ -3460,1251 +270,7 @@ mGw20awphscAAAAASUVORK5CYII= y="124.67392" x="218.95842" id="image4604" - xlink:href="data:image/png;base64,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 -r6/h+3fcr6/h+9fdr6/h+yW4X1/D90tw/01b/HEJ7ifnQuAgdPPEZiGMQzpj4VOgiIbD6nA30N/z -uutogHzacjrs8+h3d/f/o0YD7Y8aaVoAAA2HaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hw -YWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBt -ZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2 -MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRm -LXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4 -bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJo -dHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxu -czpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczpHSU1QPSJo -dHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUu -Y29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4w -LyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOmQxNTFkODk0LWIzNWEtNDcy -Ni1iY2QxLWRkMGIzMWMyNTJhMyIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyYzA4MGZm -Ny03ZGIxLTRhMTAtODYxMi0zZDgzYWFlNWU1ODgiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJ -RD0ieG1wLmRpZDozODc2OTJlOC04OWEyLTQ3MGItYTAyNi01ZmQ4NmQ3NDRiZGEiCiAgIGRjOkZv -cm1hdD0iaW1hZ2UvcG5nIgogICBHSU1QOkFQST0iMi4wIgogICBHSU1QOlBsYXRmb3JtPSJMaW51 -eCIKICAgR0lNUDpUaW1lU3RhbXA9IjE2NTI5OTU1MzI2NTY5NzUiCiAgIEdJTVA6VmVyc2lvbj0i -Mi4xMC4yOCIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1Q -IDIuMTAiPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6QmFnPgogICAgIDxyZGY6bGkKICAg -ICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iLyIKICAgICAgc3RF -dnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo0MGM2NTYxOC0wOTMxLTQ4ZGItYWY3YS01ZjdiZDNmM2Q4 -M2IiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkdpbXAgMi4xMCAoTGludXgpIgogICAgICBz -dEV2dDp3aGVuPSIyMDIyLTA1LTE5VDIyOjI1OjMyKzAxOjAwIi8+CiAgICA8L3JkZjpCYWc+CiAg -IDwveG1wTU06SGlzdG9yeT4KICAgPGRjOmNyZWF0b3I+CiAgICA8cmRmOlNlcT4KICAgICA8cmRm -OmxpPlZlY3RvclN0b2NrLmNvbS8xMzgxNzE4OTwvcmRmOmxpPgogICAgPC9yZGY6U2VxPgogICA8 -L2RjOmNyZWF0b3I+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRh -PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAog -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/ -eHBhY2tldCBlbmQ9InciPz6AI1lOAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TiyIV -h1Yo4pChOlkRFXHUKhShQqgVWnUwufQLmjQkKS6OgmvBwY/FqoOLs64OroIg+AHi6OSk6CIl/i8p -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 -AAAASUVORK5CYII= -" + xlink:href="data:image/png;base64,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 r6/h+3fcr6/h+9fdr6/h+yW4X1/D90tw/01b/HEJ7ifnQuAgdPPEZiGMQzpj4VOgiIbD6nA30N/z uutogHzacjrs8+h3d/f/o0YD7Y8aaVoAAA2HaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hw YWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBt ZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2 MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRm LXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4 bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJo dHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxu czpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczpHSU1QPSJo dHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUu Y29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4w LyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOmQxNTFkODk0LWIzNWEtNDcy Ni1iY2QxLWRkMGIzMWMyNTJhMyIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyYzA4MGZm Ny03ZGIxLTRhMTAtODYxMi0zZDgzYWFlNWU1ODgiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJ RD0ieG1wLmRpZDozODc2OTJlOC04OWEyLTQ3MGItYTAyNi01ZmQ4NmQ3NDRiZGEiCiAgIGRjOkZv cm1hdD0iaW1hZ2UvcG5nIgogICBHSU1QOkFQST0iMi4wIgogICBHSU1QOlBsYXRmb3JtPSJMaW51 eCIKICAgR0lNUDpUaW1lU3RhbXA9IjE2NTI5OTU1MzI2NTY5NzUiCiAgIEdJTVA6VmVyc2lvbj0i Mi4xMC4yOCIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1Q IDIuMTAiPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6QmFnPgogICAgIDxyZGY6bGkKICAg ICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iLyIKICAgICAgc3RF dnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo0MGM2NTYxOC0wOTMxLTQ4ZGItYWY3YS01ZjdiZDNmM2Q4 M2IiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkdpbXAgMi4xMCAoTGludXgpIgogICAgICBz dEV2dDp3aGVuPSIyMDIyLTA1LTE5VDIyOjI1OjMyKzAxOjAwIi8+CiAgICA8L3JkZjpCYWc+CiAg IDwveG1wTU06SGlzdG9yeT4KICAgPGRjOmNyZWF0b3I+CiAgICA8cmRmOlNlcT4KICAgICA8cmRm OmxpPlZlY3RvclN0b2NrLmNvbS8xMzgxNzE4OTwvcmRmOmxpPgogICAgPC9yZGY6U2VxPgogICA8 L2RjOmNyZWF0b3I+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRh PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAog ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/ eHBhY2tldCBlbmQ9InciPz6AI1lOAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TiyIV h1Yo4pChOlkRFXHUKhShQqgVWnUwufQLmjQkKS6OgmvBwY/FqoOLs64OroIg+AHi6OSk6CIl/i8p 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 AAAASUVORK5CYII= " preserveAspectRatio="none" height="116.56785" width="108.25237" /> From 5eb088a439bf6810a8840108045dfb15a89eb48c Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 23:02:17 +0100 Subject: [PATCH 210/228] fixing tests --- tests/TestTCP.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 37b23ed6..2fd9d6f2 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -318,7 +318,13 @@ TEST_CASE("Accept client with timeout", "[tcp]") { } } -TEST_CASE("Send Receive messages split into multiple pieces (tcp)", "[tcp]") { +TEST_CASE("Send Receive messages split into multiple pieces (tcp)", +#ifdef __APPLE__ + "[tcp][!mayfail]" +#else + "[tcp]" +#endif +) { const auto port = PortFactory::makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); From 216c79e8f8cc6ebe736dcc50c8e68e6e989fe4d9 Mon Sep 17 00:00:00 2001 From: AndreaCasalino Date: Thu, 19 May 2022 23:08:12 +0100 Subject: [PATCH 211/228] fixing tests --- tests/TestTCP.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 2fd9d6f2..66be76ee 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -318,13 +318,8 @@ TEST_CASE("Accept client with timeout", "[tcp]") { } } -TEST_CASE("Send Receive messages split into multiple pieces (tcp)", -#ifdef __APPLE__ - "[tcp][!mayfail]" -#else - "[tcp]" -#endif -) { +#if !defined(__APPLE__) +TEST_CASE("Send Receive messages split into multiple pieces (tcp)", "[tcp]") { const auto port = PortFactory::makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); @@ -358,3 +353,4 @@ TEST_CASE("Send Receive messages split into multiple pieces (tcp)", }); } } +#endif From c87d6bca6b1222921092bbb63d3c973dbde1c056 Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Fri, 20 May 2022 09:59:46 +0100 Subject: [PATCH 212/228] Update README.md --- samples/tcp/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/samples/tcp/README.md b/samples/tcp/README.md index 7445c8e2..1da67df6 100644 --- a/samples/tcp/README.md +++ b/samples/tcp/README.md @@ -10,18 +10,18 @@ After generating the scripts, you can run them to see what the samples do. In particular, 3 scripts can be generated, representative of 3 classes of samples: - **tcp01_server_client**: - -runs **TcpServer**, creating a tcp server that binds and listen to a specified port - -runs **TcpClient**, creating a tcp client that connections to the previous server, exchanging messages with it. + - runs **TcpServer**, creating a tcp server that binds and listen to a specified port + - runs **TcpClient**, creating a tcp client that connections to the previous server, exchanging messages with it. - **tcp02_server_3_clients**: - -runs **TcpServer**, 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. + - runs **TcpServer**, 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. - **tcp03_chain_size_4** has the aim of creating a chain of connected processes. More in detail: - -runs **TcpServer**, creating a tcp server that binds and listen to a specified port - -runs a series of **TcpRepeater**, creating a tcp client that connects to the previous process in the chain and a tcp server waitning for the connection request from the next element in the chain - -runs **TcpClient**, connecting to the last spawned process of the chain + - runs **TcpServer**, creating a tcp server that binds and listen to a specified port + - runs a series of **TcpRepeater**, creating a tcp client that connects to the previous process in the chain and a tcp server waitning for the connection request from the next element in the chain + - runs **TcpClient**, connecting to the last spawned process of the chain The last client sends some requests, which are forward along the chain till the first server. This latter, sends a response that is backwarded along the chain till xoming back to the first client. **TcpServer** and **TcpClient** 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 **TcpScriptsGenerator**) for the syntax of the accepted arguments. From 98c31bf113315af7f380321cf6e570352733cb94 Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Fri, 20 May 2022 10:01:06 +0100 Subject: [PATCH 213/228] Update README.md --- samples/udp/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/samples/udp/README.md b/samples/udp/README.md index 475b62d3..283fcb71 100644 --- a/samples/udp/README.md +++ b/samples/udp/README.md @@ -10,18 +10,18 @@ After generating the scripts, you can run them to see what the samples do. In particular, 3 scripts can be generated, representative of 3 classes of samples: - **udp01_responder_asker**: - -runs **UdpResponder**, creating a udp socket that binds a specified port - -runs **UdpAsker**, creating a udp socket that binds another port and exchanges messages with the first spawned udp socket. + - runs **UdpResponder**, creating a udp socket that binds a specified port + - runs **UdpAsker**, creating a udp socket that binds another port and exchanges messages with the first spawned udp socket. - **udp02_connecting_responder_asker**: - -runs **UdpResponder**, creating a udp socket that binds a specified port, then connects to the first udp socket reaching it. - -runs **UdpAsker**, creating a udp socket that binds another port and exchanges messages with the first spawned udp socket. + - runs **UdpResponder**, creating a udp socket that binds a specified port, then connects to the first udp socket reaching it. + - runs **UdpAsker**, creating a udp socket that binds another port and exchanges messages with the first spawned udp socket. After the connection, the first socket can't exchange messages with other peers. You can verify this by running a second **UdpAsker** sending requests to the port specified for the first **UdpResponder**: they will be always not satisfied. - **udp03_responder_2_askers**: - -runs **UdpResponder**, creating a udp socket that binds a specified port - -runs **UdpAsker**, creating a first udp socket that binds another port and exchanges messages with the first spawned udp socket. - -runs **UdpAsker**, creating a second udp socket that binds another port and exchanges messages with the first spawned udp socket. + - runs **UdpResponder**, creating a udp socket that binds a specified port + - runs **UdpAsker**, creating a first udp socket that binds another port and exchanges messages with the first spawned udp socket. + - runs **UdpAsker**, creating a second udp socket that binds another port and exchanges messages with the first spawned udp socket. The socket created in **UdpResponder** is able fufllfill the requests of both clients, as it is an un-connected udp socket. On the opposite, the one created in **udp02_connecting_responder_asker** is connecting to the first peer sending a message and that's why it would not be able to answer the requests of a third udp socket. **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. From 5a6530f72cb60b6f7dbd37d21dd16c81b793c37f Mon Sep 17 00:00:00 2001 From: Andrea <47759758+andreacasalino@users.noreply.github.com> Date: Sat, 28 May 2022 23:58:13 +0100 Subject: [PATCH 214/228] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bd4cfb7..0aa3c6bb 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ This are the most notable properties of **MinimalSocket**: - acceptance of a new client from the tcp server side - **MinimalSocket** is tested to be **thread safe**. Morevoer, you can also send while receiving in different dedicated threads. This allows you to easily create your own asynchronous sockets, building upon the classes offered by this library. - **Udp** sockets can be used both as un-connected or connected, check [here](./samples/udp/README.md) for further details. Moreover, the same **udp** socket can be connected or sconnected during its lifetime. --Under **Windows** systems, [**WSAStartup**](https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup) is automatically called before using any functionalities. From the outside, you can specify the Windows Sockets specification version. +- Under **Windows** systems, [**WSAStartup**](https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup) is automatically called before using any functionalities. From the outside, you can specify the Windows Sockets specification version. ## USAGE From 3e1b3668d23d697cbaad69d298d100ef251ac944 Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Sat, 23 Jul 2022 00:00:36 +0200 Subject: [PATCH 215/228] fixing cmake include --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b71b7e4..7898ba21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,9 +13,9 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") # include cmake custom functions set(WITH_SOURCE_TREE ON) -include(GroupSources) -include(AutoCollect) -include(MakeLibrary) +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GroupSources.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AutoCollect.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/MakeLibrary.cmake) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") From 952b7825d03fc56c79c3d45bd5010ea475d089c5 Mon Sep 17 00:00:00 2001 From: Andrea Casalino Date: Sun, 21 Aug 2022 18:19:26 +0200 Subject: [PATCH 216/228] avoid compile readme --- samples/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 1e130d25..95c57dd3 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -1,5 +1,5 @@ -add_executable(README README.cpp) -target_link_libraries(README PUBLIC MinimalSocket) +# add_executable(README README.cpp) +# target_link_libraries(README PUBLIC MinimalSocket) project(MinimalCppSocket-Samples) From 4d2441213da1a439711bec5804cf33a7de32257c Mon Sep 17 00:00:00 2001 From: Foo Date: Sun, 28 May 2023 16:34:48 +0200 Subject: [PATCH 217/228] simpler cmake std::barrier instead omp barrier --- .github/workflows/runTests.yml | 12 +-- CMakeLists.txt | 19 +--- cmake/AutoCollect.cmake | 82 -------------- cmake/GroupSources.cmake | 49 --------- cmake/MakeLibrary.cmake | 22 ---- samples/CMakeLists.txt | 2 - samples/utils/ScriptGenerator.cpp | 8 +- src/CMakeLists.txt | 25 ++++- tests/CMakeLists.txt | 25 ++--- tests/ConnectionsUtils.cpp | 14 +-- tests/Parallel.cpp | 36 ------- tests/Parallel.h | 19 ---- tests/ParallelSection.cpp | 35 ++++++ tests/ParallelSection.h | 51 +++++++++ tests/TestRobustness.cpp | 81 ++++++-------- tests/TestTCP.cpp | 133 +++++++++++------------ tests/TestUDP.cpp | 173 ++++++++++++++---------------- 17 files changed, 319 insertions(+), 467 deletions(-) delete mode 100644 cmake/AutoCollect.cmake delete mode 100644 cmake/GroupSources.cmake delete mode 100644 cmake/MakeLibrary.cmake delete mode 100644 tests/Parallel.cpp delete mode 100644 tests/Parallel.h create mode 100644 tests/ParallelSection.cpp create mode 100644 tests/ParallelSection.h diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index 0f8d7890..55842a7e 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -12,7 +12,7 @@ jobs: unitTests: strategy: matrix: - name: [ubuntu-gcc, ubuntu-clang, windows-VS] + name: [ubuntu-gcc, ubuntu-clang, windows-VS, macOS] include: - name: ubuntu-gcc os: ubuntu-latest @@ -21,7 +21,7 @@ jobs: os: ubuntu-latest compiler_opt: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -G \"Unix Makefiles\"" - name: windows-VS - os: windows-2019 + os: windows-latest compiler_opt: "" - name: macOS os: macos-latest @@ -40,13 +40,13 @@ jobs: - name: Install run: cmake --install ./build --config Release - name: Test-open - run: ./artifacts/bin/Tests "[open]" + run: ./artifacts/bin/MinimalSocket-tests "[open]" - name: Test-address - run: ./artifacts/bin/Tests "[address]" + run: ./artifacts/bin/MinimalSocket-tests "[address]" - name: Test-tcp - run: ./artifacts/bin/Tests "[tcp]" + run: ./artifacts/bin/MinimalSocket-tests "[tcp]" - name: Test-udp - run: ./artifacts/bin/Tests "[udp]" + run: ./artifacts/bin/MinimalSocket-tests "[udp]" - uses: actions/upload-artifact@v2 with: path: artifacts diff --git a/CMakeLists.txt b/CMakeLists.txt index 7898ba21..c83cf707 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,31 +1,22 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -option(LIB_OPT "Compile shared libraries (ON) or static (OFF)" OFF) -option(BUILD_MinimalCppSocket_SAMPLES "Build the samples showing how to use Minimal Cpp Socket" ON) -option(BUILD_MinimalCppSocket_TESTS "" OFF) - -# set macro-directory and find scripts -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") - -# include cmake custom functions -set(WITH_SOURCE_TREE ON) -include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GroupSources.cmake) -include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AutoCollect.cmake) -include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/MakeLibrary.cmake) - set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") project(MinimalCppSocket) + +option(LIB_OPT "Compile shared libraries (ON) or static (OFF)" OFF) add_subdirectory(src) +option(BUILD_MinimalCppSocket_SAMPLES "Build the samples showing how to use Minimal Cpp Socket" ON) if(BUILD_MinimalCppSocket_SAMPLES) add_subdirectory(samples) endif() +option(BUILD_MinimalCppSocket_TESTS "" OFF) if(BUILD_MinimalCppSocket_TESTS) add_subdirectory(tests) endif() diff --git a/cmake/AutoCollect.cmake b/cmake/AutoCollect.cmake deleted file mode 100644 index 5bc7bf63..00000000 --- a/cmake/AutoCollect.cmake +++ /dev/null @@ -1,82 +0,0 @@ -# This file is free software; as a special exception the author gives -# unlimited permission to copy and/or distribute it, with or without -# modifications, as long as this notice is preserved. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -# Collects all source files into the given variable, -# which is useful to include all sources in subdirectories. -# Ignores full qualified directories listed in the variadic arguments. -# -# Use it like: -# CollectSourceFiles( -# ${CMAKE_CURRENT_SOURCE_DIR} -# COMMON_PRIVATE_SOURCES -# # Exclude -# ${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders -# ${CMAKE_CURRENT_SOURCE_DIR}/Platform) -# -function(CollectSourceFiles current_dir variable) - list(FIND ARGN "${current_dir}" IS_EXCLUDED) - if(IS_EXCLUDED EQUAL -1) - file(GLOB COLLECTED_SOURCES - ${current_dir}/*.c - ${current_dir}/*.cc - ${current_dir}/*.cpp - ${current_dir}/*.inl - ${current_dir}/*.def - ${current_dir}/*.h - ${current_dir}/*.hh - ${current_dir}/*.hpp) - list(APPEND ${variable} ${COLLECTED_SOURCES}) - - file(GLOB SUB_DIRECTORIES ${current_dir}/*) - foreach(SUB_DIRECTORY ${SUB_DIRECTORIES}) - if (IS_DIRECTORY ${SUB_DIRECTORY}) - CollectSourceFiles("${SUB_DIRECTORY}" "${variable}" "${ARGN}") - endif() - endforeach() - set(${variable} ${${variable}} PARENT_SCOPE) - endif() -endfunction() - -# Collects all subdirectoroies into the given variable, -# which is useful to include all subdirectories. -# Ignores full qualified directories listed in the variadic arguments. -# -# Use it like: -# CollectIncludeDirectories( -# ${CMAKE_CURRENT_SOURCE_DIR} -# COMMON_PUBLIC_INCLUDES -# # Exclude -# ${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders -# ${CMAKE_CURRENT_SOURCE_DIR}/Platform) -# -function(CollectIncludeDirectories current_dir variable) - list(FIND ARGN "${current_dir}" IS_EXCLUDED) - if(IS_EXCLUDED EQUAL -1) - list(APPEND ${variable} ${current_dir}) - file(GLOB SUB_DIRECTORIES ${current_dir}/*) - foreach(SUB_DIRECTORY ${SUB_DIRECTORIES}) - if (IS_DIRECTORY ${SUB_DIRECTORY}) - CollectIncludeDirectories("${SUB_DIRECTORY}" "${variable}" "${ARGN}") - endif() - endforeach() - set(${variable} ${${variable}} PARENT_SCOPE) - endif() -endfunction() - -# Resolve all symlinks and add them to the list -# @param symlinks: list of symlinks -# @return resolved_links: content of "symlinks" + resolved paths -function(ResolveSymlinks symlinks resolved_links) - foreach(_symlink IN LISTS symlinks) - if(IS_SYMLINK ${_symlink}) - get_filename_component(_resolved_link ${_symlink} REALPATH) - set(symlinks ${symlinks} ${_resolved_link}) - endif() - endforeach() - set(${resolved_links} ${symlinks} PARENT_SCOPE) -endfunction() diff --git a/cmake/GroupSources.cmake b/cmake/GroupSources.cmake deleted file mode 100644 index 0c6c9100..00000000 --- a/cmake/GroupSources.cmake +++ /dev/null @@ -1,49 +0,0 @@ -# This file is free software; as a special exception the author gives -# unlimited permission to copy and/or distribute it, with or without -# modifications, as long as this notice is preserved. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -macro(GroupSources dir) - # Skip this if WITH_SOURCE_TREE is not set (empty string). - if (NOT ${WITH_SOURCE_TREE} STREQUAL "") - # Include all header and c files - file(GLOB_RECURSE elements RELATIVE ${dir} *.h *.hpp *.c *.cpp *.cc) - - foreach(element ${elements}) - # Extract filename and directory - get_filename_component(element_name ${element} NAME) - get_filename_component(element_dir ${element} DIRECTORY) - - if (NOT ${element_dir} STREQUAL "") - # If the file is in a subdirectory use it as source group. - if (${WITH_SOURCE_TREE} STREQUAL "flat") - # Build flat structure by using only the first subdirectory. - string(FIND ${element_dir} "/" delemiter_pos) - if (NOT ${delemiter_pos} EQUAL -1) - string(SUBSTRING ${element_dir} 0 ${delemiter_pos} group_name) - source_group("${group_name}" FILES ${dir}/${element}) - else() - # Build hierarchical structure. - # File is in root directory. - source_group("${element_dir}" FILES ${dir}/${element}) - endif() - else() - # Use the full hierarchical structure to build source_groups. - string(REPLACE "/" "\\" group_name ${element_dir}) - source_group("${group_name}" FILES ${dir}/${element}) - endif() - else() - # If the file is in the root directory, place it in the root source_group. - source_group("\\" FILES ${dir}/${element}) - endif() - endforeach() - endif() -endmacro() - -if (WITH_SOURCE_TREE STREQUAL "hierarchical-folders") - # Use folders - set_property(GLOBAL PROPERTY USE_FOLDERS ON) -endif() \ No newline at end of file diff --git a/cmake/MakeLibrary.cmake b/cmake/MakeLibrary.cmake deleted file mode 100644 index edf7c2d6..00000000 --- a/cmake/MakeLibrary.cmake +++ /dev/null @@ -1,22 +0,0 @@ -function(MakeLibrary LIBRARY_NAME INCLUDE_DIR) -CollectSourceFiles(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) -GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) - -if(LIB_OPT) - if (WIN32) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) - endif () - - add_library(${LIBRARY_NAME} SHARED ${SOURCES}) -else() - add_library(${LIBRARY_NAME} STATIC ${SOURCES}) -endif() - -target_include_directories(${LIBRARY_NAME} - PUBLIC - $ -) - -install(TARGETS ${LIBRARY_NAME}) -install (DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_DIR}/ DESTINATION include/${LIBRARY_NAME} FILES_MATCHING PATTERN "*.h*") -endfunction() \ No newline at end of file diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 95c57dd3..fc95a66d 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -1,8 +1,6 @@ # add_executable(README README.cpp) # target_link_libraries(README PUBLIC MinimalSocket) -project(MinimalCppSocket-Samples) - include(cmake/MakeSample.cmake) include(cmake/MakaLauncher.cmake) diff --git a/samples/utils/ScriptGenerator.cpp b/samples/utils/ScriptGenerator.cpp index fa5572bd..97ca9895 100644 --- a/samples/utils/ScriptGenerator.cpp +++ b/samples/utils/ScriptGenerator.cpp @@ -7,7 +7,9 @@ #include +#include #include +#include #include namespace MinimalSocket::samples { @@ -54,7 +56,8 @@ void add_process(std::ofstream &stream, const ProcessAndArgs &proc_and_args, } // namespace void ScriptGenerator::generate(const std::string &file_name) { - std::ofstream stream(file_name + SCRIPT_EXTENSION); + const std::string recipient = file_name + SCRIPT_EXTENSION; + std::ofstream stream(recipient); #if defined(__linux__) || defined(__APPLE__) stream << "#!/bin/sh" << std::endl; @@ -64,5 +67,8 @@ void ScriptGenerator::generate(const std::string &file_name) { add_process(stream, processes[k], true); } add_process(stream, processes.back(), false); + + std::cout << "launcher script generated at: " + << std::filesystem::absolute(recipient) << std::endl; } } // namespace MinimalSocket::samples diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0fe080de..b398f1aa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,10 +1,25 @@ -set(PROJECT_SHORTNAME "MinimalSocket") +set(LIBRARY_NAME "MinimalSocket") +set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/header) -MakeLibrary(${PROJECT_SHORTNAME} header) +file(GLOB_RECURSE SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h) -if(WIN32) - target_link_libraries(${PROJECT_SHORTNAME} PRIVATE wsock32 ws2_32) +if(LIB_OPT) + if (WIN32) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + endif () + + add_library(${LIBRARY_NAME} SHARED ${SRC_FILES}) +else() + add_library(${LIBRARY_NAME} STATIC ${SRC_FILES}) endif() +target_include_directories(${LIBRARY_NAME} PUBLIC ${INCLUDE_DIR}) + find_package(Threads) -target_link_libraries(${PROJECT_SHORTNAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(${LIBRARY_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) +if(WIN32) + target_link_libraries(${LIBRARY_NAME} PRIVATE wsock32 ws2_32) +endif() + +install(TARGETS ${LIBRARY_NAME}) +install (DIRECTORY ${INCLUDE_DIR}/ DESTINATION include/${LIBRARY_NAME} FILES_MATCHING PATTERN "*.h*") diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 631322ff..02c7924a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,3 @@ -project(MinimalCppSocket-Tests) - include(FetchContent) FetchContent_Declare( catch2 @@ -8,21 +6,16 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(catch2) -function(MakeTests) - set(TEST_NAME "Tests") +set(TEST_NAME "MinimalSocket-tests") - file(GLOB SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h) - add_executable(${TEST_NAME} ${SRC_FILES}) +file(GLOB SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h) +add_executable(${TEST_NAME} ${SRC_FILES}) - find_package(OpenMP) - target_link_libraries(${TEST_NAME} PUBLIC - MinimalSocket - OpenMP::OpenMP_CXX - Catch2::Catch2 - Catch2::Catch2WithMain - ) +target_link_libraries(${TEST_NAME} PUBLIC + MinimalSocket + Catch2::Catch2 + Catch2::Catch2WithMain +) - install(TARGETS ${TEST_NAME}) -endfunction() +install(TARGETS ${TEST_NAME}) -MakeTests() diff --git a/tests/ConnectionsUtils.cpp b/tests/ConnectionsUtils.cpp index 7931e853..e6dfdaf5 100644 --- a/tests/ConnectionsUtils.cpp +++ b/tests/ConnectionsUtils.cpp @@ -8,23 +8,23 @@ #include #include "ConnectionsUtils.h" -#include "Parallel.h" +#include "ParallelSection.h" namespace MinimalSocket::test { TcpPeers::TcpPeers(const Port &port, const AddressFamily &family) : client_side(Address{port, family}) { - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { // server tcp::TcpServer server(port, family); REQUIRE(server.open()); -#pragma omp barrier + br.arrive_and_wait(); auto accepted = server.acceptNewClient(); server_side = std::make_unique(std::move(accepted)); }, - [&]() { - // client -#pragma omp barrier + [&](Barrier &br) { + // client + br.arrive_and_wait(); REQUIRE(client_side.open()); }); } diff --git a/tests/Parallel.cpp b/tests/Parallel.cpp deleted file mode 100644 index 93376ea3..00000000 --- a/tests/Parallel.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include "Parallel.h" -#include -#include - -namespace MinimalSocket::test { -void parallel(const Task &master, const Task &slave) { -#pragma omp parallel num_threads(2) - { - if (0 == omp_get_thread_num()) { - master(); - } else { - slave(); - } - } -} - -void parallel(const std::vector &tasks) { - int threads = tasks.size(); - if (threads < 2) { - throw std::runtime_error{"invalid number of tasks for parallel region"}; - } - -#pragma omp parallel num_threads(threads) - { - const auto th_id = omp_get_thread_num(); - tasks[th_id](); - } -} -} // namespace MinimalSocket::test diff --git a/tests/Parallel.h b/tests/Parallel.h deleted file mode 100644 index a75ac5b8..00000000 --- a/tests/Parallel.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#pragma once - -#include -#include - -namespace MinimalSocket::test { -using Task = std::function; - -void parallel(const Task &master, const Task &slave); - -void parallel(const std::vector &tasks); -} // namespace MinimalSocket::test diff --git a/tests/ParallelSection.cpp b/tests/ParallelSection.cpp new file mode 100644 index 00000000..64002b86 --- /dev/null +++ b/tests/ParallelSection.cpp @@ -0,0 +1,35 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#include "ParallelSection.h" +#include + +namespace MinimalSocket::test { +namespace { +std::function make_thread(Barrier &br, const Task &task) { + return [&task = task, &br = br]() mutable { + br.arrive_and_wait(); + task(br); + }; +} +} // namespace + +void ParallelSection::run() { + if (tasks.size() < 2) { + throw std::runtime_error{"invalid number of tasks for parallel region"}; + } + 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())); + for (auto &sp : spinners) { + sp.join(); + } +} +} // namespace MinimalSocket::test diff --git a/tests/ParallelSection.h b/tests/ParallelSection.h new file mode 100644 index 00000000..69431782 --- /dev/null +++ b/tests/ParallelSection.h @@ -0,0 +1,51 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace MinimalSocket::test { +namespace detail { + struct NullCompletion { + void operator()() noexcept {} + }; +} +using Barrier = std::barrier; + +using Task = std::function; + +class ParallelSection { +public: + ParallelSection() = default; + + template ParallelSection &add(Pred &&pred) { + tasks.emplace_back(std::forward(pred)); + return *this; + } + + void run(); + + template + static void biSection(First &&first, Second &&second) { + ParallelSection temp; + temp.add(std::forward(first)) + .add(std::forward(second)) + .run(); + } + +private: + std::optional barrier; + + std::vector tasks; +}; + +} // namespace MinimalSocket::test diff --git a/tests/TestRobustness.cpp b/tests/TestRobustness.cpp index a08488a8..ab0b1713 100644 --- a/tests/TestRobustness.cpp +++ b/tests/TestRobustness.cpp @@ -3,12 +3,11 @@ #include -#include #include #include #include "ConnectionsUtils.h" -#include "Parallel.h" +#include "ParallelSection.h" #include "PortFactory.h" using namespace MinimalSocket; @@ -40,26 +39,22 @@ TEST_CASE("Thread safe d'tor tcp case", "[robustness]") { test::TcpPeers peers(port, family); SECTION("close client while receiving") { - parallel( - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&peers](auto &) { CHECK(peers.getClientSide().receive(500).empty()); }, - [&]() { -#pragma omp barrier + [&peers](auto &) { std::this_thread::sleep_for(std::chrono::milliseconds{50}); close(peers.getClientSide()); }); } SECTION("close server side while receiving") { - parallel( - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&peers](auto &) { CHECK(peers.getClientSide().receive(500).empty()); }, - [&]() { -#pragma omp barrier + [&peers](auto &) { std::this_thread::sleep_for(std::chrono::milliseconds{50}); close(peers.getServerSide()); }); @@ -69,13 +64,11 @@ TEST_CASE("Thread safe d'tor tcp case", "[robustness]") { SECTION("close while accepting client") { tcp::TcpServer server(port, family); REQUIRE(server.open()); - parallel( - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&server](auto &) { CHECK_THROWS_AS(server.acceptNewClient(), SocketError); }, - [&]() { -#pragma omp barrier + [&server](auto &) { std::this_thread::sleep_for(std::chrono::milliseconds{50}); close(server); }); @@ -91,16 +84,17 @@ TEST_CASE("Receive from multiple threads tcp case", "[robustness]") { auto &client_side = peers.getClientSide(); const std::size_t threads = 3; - std::vector tasks; - tasks.emplace_back( - [&]() { client_side.send(make_repeated_message(MESSAGE, threads)); }); + ParallelSection sections; + sections.add([&](auto &) { + client_side.send(make_repeated_message(MESSAGE, threads)); + }); for (std::size_t t = 0; t < threads; ++t) { - tasks.emplace_back([&]() { + sections.add([&](auto &) { const auto received_request = server_side.receive(MESSAGE.size()); CHECK(received_request == MESSAGE); }); } - parallel(tasks); + sections.run(); } TEST_CASE("Send from multiple threads tcp case", "[robustness]") { @@ -112,17 +106,17 @@ TEST_CASE("Send from multiple threads tcp case", "[robustness]") { auto &client_side = peers.getClientSide(); const std::size_t threads = 3; - std::vector tasks; + ParallelSection sections; for (std::size_t t = 0; t < threads; ++t) { - tasks.emplace_back([&]() { client_side.send(MESSAGE); }); + sections.add([&](auto &) { client_side.send(MESSAGE); }); } - tasks.emplace_back([&]() { + sections.add([&](auto &) { for (std::size_t t = 0; t < threads; ++t) { const auto received_request = server_side.receive(MESSAGE.size()); CHECK(received_request == MESSAGE); } }); - parallel(tasks); + sections.run(); } TEST_CASE("Thread safe d'tor udp case", "[robustness]") { @@ -130,13 +124,9 @@ TEST_CASE("Thread safe d'tor udp case", "[robustness]") { udp::UdpBinded connection(PortFactory::makePort()); - parallel( - [&]() { -#pragma omp barrier - CHECK_THROWS_AS(connection.receive(500), Error); - }, - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&](auto &) { CHECK_THROWS_AS(connection.receive(500), Error); }, + [&](auto &) { std::this_thread::sleep_for(std::chrono::milliseconds{50}); close(connection); }); @@ -150,22 +140,22 @@ TEST_CASE("Receive from multiple threads udp case", "[robustness]") { UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family) const std::size_t threads = 3; - std::vector tasks; - tasks.emplace_back([&]() { + ParallelSection sections; + sections.add([&](Barrier &br) { for (std::size_t t = 0; t < threads; ++t) { requester.sendTo(MESSAGE, responder_address); } -#pragma omp barrier + br.arrive_and_wait(); }); -#pragma omp barrier for (std::size_t t = 0; t < threads; ++t) { - tasks.emplace_back([&]() { + sections.add([&](Barrier &br) { + br.arrive_and_wait(); const auto received_request = responder.receive(MESSAGE.size()); CHECK(received_request); CHECK(received_request->received_message == MESSAGE); }); } - parallel(tasks); + sections.run(); } TEST_CASE("Send from multiple threads udp case", "[robustness]") { @@ -174,24 +164,23 @@ TEST_CASE("Send from multiple threads udp case", "[robustness]") { UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family) const std::size_t threads = 3; - std::vector tasks; + ParallelSection sections; for (std::size_t t = 0; t < threads; ++t) { - tasks.emplace_back([&]() { + sections.add([&](Barrier &br) { requester.sendTo(MESSAGE, responder_address); -#pragma omp barrier + br.arrive_and_wait(); }); } - tasks.emplace_back([&]() { -#pragma omp barrier + 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()); CHECK(received_request); CHECK(received_request->received_message == MESSAGE); } }); - parallel(tasks); + sections.run(); } - */ TEST_CASE("Use tcp socket before opening it", "[robustness]") { diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index 66be76ee..f09f7c0a 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -2,13 +2,12 @@ #include #include -#include #include #include #include "ConnectionsUtils.h" -#include "Parallel.h" +#include "ParallelSection.h" #include "PortFactory.h" #include "SlicedOps.h" @@ -34,15 +33,15 @@ void send_response(const SenderReceiver &requester, const SenderReceiver &responder) { std::size_t cycles = 5; - parallel( - [&]() { + ParallelSection::biSection( + [&](auto &) { for (std::size_t c = 0; c < cycles; ++c) { CHECK(requester.sender.send(request)); auto received_response = requester.receiver.receive(response.size()); CHECK(received_response == response); } }, - [&]() { + [&](auto &) { for (std::size_t c = 0; c < cycles; ++c) { auto received_request = responder.receiver.receive(request.size()); CHECK(received_request == request); @@ -95,14 +94,12 @@ TEST_CASE("Establish tcp connection", "[tcp]") { SECTION("expect success within timeout") { const auto wait = Timeout{250}; - parallel( - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&](auto &) { std::this_thread::sleep_for(wait); client_side.send(request); }, - [&]() { -#pragma omp barrier + [&](auto &) { auto received_request = server_side.receive(request.size(), timeout); CHECK(received_request == request); @@ -124,13 +121,13 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { SECTION("sequencial connnections") { std::list accepted_clients; std::list clients; - parallel( - [&]() { + ParallelSection::biSection( + [&](auto &) { for (std::size_t c = 0; c < clients_numb; ++c) { accepted_clients.emplace_back(server.acceptNewClient()); } }, - [&]() { + [&](auto &) { for (std::size_t c = 0; c < clients_numb; ++c) { auto &client = clients.emplace_back(Address(port, family)); CHECK(client.open()); @@ -139,20 +136,20 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { } SECTION("concurrent connnections") { - std::vector tasks; - tasks.emplace_back([&]() { + ParallelSection sections; + sections.add([&](auto &) { for (std::size_t c = 0; c < clients_numb; ++c) { auto accepted = server.acceptNewClient(); } }); - Task ask_connection = [&]() { + Task ask_connection = [&](auto &) { TcpClient client(Address(port, family)); CHECK(client.open()); }; for (std::size_t c = 0; c < clients_numb; ++c) { - tasks.push_back(ask_connection); + sections.add(ask_connection); } - parallel(tasks); + sections.run(); } } @@ -168,12 +165,12 @@ TEST_CASE("Open multiple times tcp clients", "[tcp]") { TcpClient client(Address(port, family)); for (std::size_t c = 0; c < cycles; ++c) { - parallel([&]() { server.acceptNewClient(); }, - [&]() { - CHECK(client.open()); - TcpClient{std::move(client)}; - CHECK_FALSE(client.wasOpened()); - }); + ParallelSection::biSection([&](auto &) { server.acceptNewClient(); }, + [&](auto &) { + CHECK(client.open()); + TcpClient{std::move(client)}; + CHECK_FALSE(client.wasOpened()); + }); } } @@ -201,16 +198,14 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { const auto wait = Timeout{250}; TcpServer server(port, family); REQUIRE(server.open()); - parallel( - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&](auto &) { std::this_thread::sleep_for(wait); TcpConnection conn = server.acceptNewClient(); auto received_request = conn.receive(request.size()); CHECK(received_request == request); }, - [&]() { -#pragma omp barrier + [&](auto &) { CHECK(client.open(timeout)); client.send(request); }); @@ -225,17 +220,17 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { const auto port = server.getPortToBind(); REQUIRE(port != 0); - parallel( - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&](Barrier &br) { + br.arrive_and_wait(); auto accepted = server.acceptNewClient(); auto received_request = accepted.receive(request.size()); CHECK(received_request == request); }, - [&]() { + [&](Barrier &br) { // client TcpClient client(Address(port, family)); -#pragma omp barrier + br.arrive_and_wait(); REQUIRE(client.open()); REQUIRE(client.wasOpened()); client.send(request); @@ -256,46 +251,40 @@ TEST_CASE("Accept client with timeout", "[tcp]") { // connect first client TcpClient client_first = TcpClient{server_address}; std::unique_ptr server_side_first; - parallel( - [&]() { -#pragma omp barrier - CHECK(client_first.open()); - }, - [&]() { -#pragma omp barrier - auto accepted = server.acceptNewClient(); - server_side_first = - std::make_unique(std::move(accepted)); - }); + ParallelSection::biSection([&](auto &) { CHECK(client_first.open()); }, + [&](auto &) { + auto accepted = server.acceptNewClient(); + server_side_first = + std::make_unique( + std::move(accepted)); + }); // expect second accept to fail CHECK_FALSE(server.acceptNewClient(timeout)); CHECK(server.wasOpened()); // check first accepted connection is still valid - parallel( - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&](auto &) { auto received_request = server_side_first->receive(request.size()); CHECK(received_request == request); }, - [&]() { - // client -#pragma omp barrier + [&](auto &) { + // client client_first.send(request); }); // connect second client after accept unsuccess and check they can exchange // messages - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { TcpClient client_second = TcpClient{server_address}; -#pragma omp barrier + br.arrive_and_wait(); CHECK(client_second.open()); client_second.send(request); }, - [&]() { -#pragma omp barrier + [&](Barrier &br) { + br.arrive_and_wait(); auto server_side_second = server.acceptNewClient(); auto received_request = server_side_second.receive(request.size()); CHECK(received_request == request); @@ -304,15 +293,15 @@ TEST_CASE("Accept client with timeout", "[tcp]") { SECTION("expect success within timeout") { const auto wait = Timeout{250}; - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { TcpClient client = TcpClient{server_address}; -#pragma omp barrier + br.arrive_and_wait(); std::this_thread::sleep_for(wait); CHECK(client.open()); }, - [&]() { -#pragma omp barrier + [&](Barrier &br) { + br.arrive_and_wait(); CHECK(server.acceptNewClient(timeout)); }); } @@ -332,22 +321,22 @@ TEST_CASE("Send Receive messages split into multiple pieces (tcp)", "[tcp]") { const std::size_t delta = 4; SECTION("split receive") { - parallel([&]() { client_side.send(request); }, - [&]() { - auto received_request = - sliced_receive(server_side, request.size(), 4); - CHECK(received_request == request); - }); + ParallelSection::biSection([&](auto &) { client_side.send(request); }, + [&](auto &) { + auto received_request = sliced_receive( + server_side, request.size(), 4); + CHECK(received_request == request); + }); } SECTION("split send") { - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { sliced_send(client_side, request, 4); -#pragma omp barrier + br.arrive_and_wait(); }, - [&]() { -#pragma omp barrier + [&](Barrier &br) { + br.arrive_and_wait(); auto received_request = server_side.receive(request.size()); CHECK(received_request == request); }); diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index f31c6ec6..c393ab43 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -1,11 +1,10 @@ #include #include -#include #include #include "ConnectionsUtils.h" -#include "Parallel.h" +#include "ParallelSection.h" #include "PortFactory.h" #include "SlicedOps.h" @@ -29,27 +28,27 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family); - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { CHECK(requester.sendTo(request, responder_address)); -#pragma omp barrier -#pragma omp barrier + br.arrive_and_wait(); + br.arrive_and_wait(); 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)); } }, - [&]() { + [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { -#pragma omp barrier + br.arrive_and_wait(); 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); -#pragma omp barrier + br.arrive_and_wait(); } }); @@ -63,14 +62,12 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { SECTION("expect success within timeout") { const auto wait = Timeout{250}; - parallel( - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&](auto &) { std::this_thread::sleep_for(wait); requester.sendTo(request, responder_address); }, - [&]() { -#pragma omp barrier + [&](auto &) { auto received_request = responder.receive(request.size(), timeout); REQUIRE(received_request); CHECK(received_request->received_message == request); @@ -96,23 +93,23 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { UdpConnected responder(requester_address, responder_port); REQUIRE(responder.open()); - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { CHECK(requester.send(request)); -#pragma omp barrier -#pragma omp barrier + br.arrive_and_wait(); + br.arrive_and_wait(); auto received_response = requester.receive(response.size()); CHECK(received_response == response); } }, - [&]() { + [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { -#pragma omp barrier + br.arrive_and_wait(); auto received_request = responder.receive(request.size()); CHECK(received_request == request); responder.send(response); -#pragma omp barrier + br.arrive_and_wait(); } }); @@ -126,14 +123,12 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { SECTION("expect success within timeout") { const auto wait = Timeout{250}; - parallel( - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&](auto &) { std::this_thread::sleep_for(wait); requester.send(request); }, - [&]() { -#pragma omp barrier + [&](auto &) { auto received_request = responder.receive(request.size(), timeout); CHECK(received_request == request); }); @@ -159,20 +154,20 @@ TEST_CASE( auto exchange_messages_before = GENERATE(true, false); if (exchange_messages_before) { - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { CHECK(requester.send(request)); -#pragma omp barrier -#pragma omp barrier + br.arrive_and_wait(); + br.arrive_and_wait(); auto received_response = requester.receive(response.size()); CHECK(received_response == response); }, - [&]() { -#pragma omp barrier + [&](Barrier &br) { + br.arrive_and_wait(); auto received_request = responder.receive(request.size()); CHECK(received_request == request); responder.send(response); -#pragma omp barrier + br.arrive_and_wait(); }); } @@ -180,14 +175,12 @@ TEST_CASE( REQUIRE(second_requester.open()); const auto timeout = Timeout{500}; const auto wait = Timeout{250}; - parallel( - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&](auto &) { std::this_thread::sleep_for(wait); second_requester.sendTo(request, Address(responder_port, family)); }, - [&]() { -#pragma omp barrier + [&](auto &) { auto received_request = responder.receive(request.size(), timeout); CHECK(received_request.empty()); }); @@ -213,13 +206,13 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { auto deduce_sender = GENERATE(true, false); std::unique_ptr requester_connected; if (deduce_sender) { - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { responder.sendTo("1", requester_address); -#pragma omp barrier + br.arrive_and_wait(); }, - [&]() { -#pragma omp barrier + [&](Barrier &br) { + br.arrive_and_wait(); auto socket_connected = requester_only_bind->connect(); CHECK(are_same(socket_connected.getRemoteAddress(), responder_address, family)); @@ -233,25 +226,25 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { REQUIRE(requester_connected->wasOpened()); // try message exchange - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { CHECK(requester_connected->send(request)); -#pragma omp barrier -#pragma omp barrier + br.arrive_and_wait(); + br.arrive_and_wait(); auto received_response = requester_connected->receive(response.size()); CHECK(received_response == response); } }, - [&]() { + [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { -#pragma omp barrier + 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); -#pragma omp barrier + br.arrive_and_wait(); } }); @@ -261,26 +254,26 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { REQUIRE(requester_only_bind->wasOpened()); // try message exchange - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { CHECK(requester_only_bind->sendTo(request, responder_address)); -#pragma omp barrier -#pragma omp barrier + 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) { -#pragma omp barrier + 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); -#pragma omp barrier + br.arrive_and_wait(); } }); } @@ -298,14 +291,12 @@ TEST_CASE("Open connection with timeout", "[udp]") { SECTION("expect success within timeout") { const auto wait = Timeout{250}; - parallel( - [&]() { -#pragma omp barrier + ParallelSection::biSection( + [&](auto &) { std::this_thread::sleep_for(wait); responder.sendTo("1", requester_address); }, - [&]() { -#pragma omp barrier + [&](auto &) { auto connected_result = requester.connect(timeout); REQUIRE(connected_result); CHECK(are_same(connected_result->getRemoteAddress(), @@ -329,28 +320,29 @@ TEST_CASE("Reserve random port for udp connection", "[udp]") { responder_port = responder.getPortToBind(); const Address responder_address = Address(responder_port, family); - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { CHECK(requester.sendTo(request, responder_address)); -#pragma omp barrier -#pragma omp barrier + br.arrive_and_wait(); + br.arrive_and_wait(); 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)); }, - [&]() { -#pragma omp barrier + [&](Barrier &br) { + br.arrive_and_wait(); 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); -#pragma omp barrier + br.arrive_and_wait(); }); } /* + TEST_CASE("Send Receive messages split into multiple pieces (udp)", "[udp][!mayfail]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); @@ -363,13 +355,13 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", SECTION("un connected") { SECTION("split receive") { - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { requester.sendTo(request, responder_address); -#pragma omp barrier + br.arrive_and_wait(); }, - [&]() { -#pragma omp barrier + [&](Barrier &br) { + br.arrive_and_wait(); auto received_request = sliced_receive(responder, request.size(), 4); CHECK(received_request == request); @@ -377,13 +369,13 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", } SECTION("split send") { - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { sliced_send(requester, request, responder_address, 4); -#pragma omp barrier + br.arrive_and_wait(); }, - [&]() { -#pragma omp barrier + [&](Barrier &br) { + br.arrive_and_wait(); auto received_request = responder.receive(request.size()); CHECK(received_request); CHECK(received_request->received_message == request); @@ -395,13 +387,13 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", auto requester_conn = requester.connect(responder_address); auto responder_conn = responder.connect(requester_address); SECTION("split receive") { - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { requester_conn.send(request); -#pragma omp barrier + br.arrive_and_wait(); }, - [&]() { -#pragma omp barrier + [&](Barrier &br) { + br.arrive_and_wait(); auto received_request = sliced_receive(responder_conn, request.size(), 4); CHECK(received_request == request); @@ -409,17 +401,18 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", } SECTION("split send") { - parallel( - [&]() { + ParallelSection::biSection( + [&](Barrier &br) { sliced_send(requester_conn, request, 4); -#pragma omp barrier + br.arrive_and_wait(); }, - [&]() { -#pragma omp barrier + [&](Barrier &br) { + br.arrive_and_wait(); auto received_request = responder_conn.receive(request.size()); CHECK(received_request == request); }); } } } - */ \ No newline at end of file + +*/ From 6c7870b84d3abc47dab8bf32323c6bf5ae53f6f1 Mon Sep 17 00:00:00 2001 From: Foo Date: Fri, 2 Jun 2023 22:54:13 +0200 Subject: [PATCH 218/228] Monitor.pt script samples as custom cmake tarfet triggering the script with proper options tested for tcp tested for udp samples documentation updated and improved --- .gitignore | 2 + samples/CMakeLists.txt | 3 +- samples/README.md | 3 + samples/cmake/MakaLauncher.cmake | 11 -- samples/cmake/MakeSample.cmake | 20 ++- samples/tcp/CMakeLists.txt | 16 +- samples/tcp/README.md | 72 +++++++-- samples/tcp/Sample01_server_client | 2 + samples/tcp/Sample02_server_2_clients | 3 + samples/tcp/Sample03_chain_with_2_repeaters | 4 + samples/tcp/TcpClient.cpp | 12 +- samples/tcp/TcpRepeater.cpp | 40 +++-- samples/tcp/TcpScriptsGenerator.cpp | 81 ---------- samples/tcp/TcpServer.cpp | 56 +++---- samples/udp/CMakeLists.txt | 14 +- samples/udp/README.md | 63 ++++++-- samples/udp/Sample01_asker_responder | 2 + samples/udp/Sample02_asker_connected_responer | 2 + samples/udp/Sample03_2_askers_responder | 3 + samples/udp/UdpAsker.cpp | 22 +-- samples/udp/UdpResponder.cpp | 11 +- samples/utils/Args.h | 16 +- samples/utils/Ask.h | 25 +-- samples/utils/Monitor.py | 153 ++++++++++++++++++ samples/utils/Names.h | 4 + samples/utils/Respond.h | 35 ++-- samples/utils/ScriptGenerator.cpp | 74 --------- samples/utils/ScriptGenerator.h | 35 ---- samples/utils/TimeOfDay.cpp | 28 ++++ samples/utils/TimeOfDay.h | 24 +++ 30 files changed, 502 insertions(+), 334 deletions(-) create mode 100644 samples/README.md delete mode 100644 samples/cmake/MakaLauncher.cmake create mode 100644 samples/tcp/Sample01_server_client create mode 100644 samples/tcp/Sample02_server_2_clients create mode 100644 samples/tcp/Sample03_chain_with_2_repeaters delete mode 100644 samples/tcp/TcpScriptsGenerator.cpp create mode 100644 samples/udp/Sample01_asker_responder create mode 100644 samples/udp/Sample02_asker_connected_responer create mode 100644 samples/udp/Sample03_2_askers_responder create mode 100644 samples/utils/Monitor.py delete mode 100644 samples/utils/ScriptGenerator.cpp delete mode 100644 samples/utils/ScriptGenerator.h create mode 100644 samples/utils/TimeOfDay.cpp create mode 100644 samples/utils/TimeOfDay.h diff --git a/.gitignore b/.gitignore index 880580d6..ffb44a60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ bin build .vscode +*.log +test.html diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index fc95a66d..811e8637 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -1,8 +1,9 @@ # add_executable(README README.cpp) # target_link_libraries(README PUBLIC MinimalSocket) +find_package (Python3 COMPONENTS Interpreter) + include(cmake/MakeSample.cmake) -include(cmake/MakaLauncher.cmake) add_subdirectory(utils) diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 00000000..7f2fbaa5 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,3 @@ +Refer to [README.md](./tcp/README.md) for tcp samples. + +Refer to [README.md](./udp/README.md) for udp samples. diff --git a/samples/cmake/MakaLauncher.cmake b/samples/cmake/MakaLauncher.cmake deleted file mode 100644 index 038880bb..00000000 --- a/samples/cmake/MakaLauncher.cmake +++ /dev/null @@ -1,11 +0,0 @@ -function(MakeLauncher NAME) - add_executable(${NAME} ${NAME}.cpp) - - target_link_libraries(${NAME} PUBLIC - Utils - ) - - set_target_properties(${NAME} PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=%PATH%;${CMAKE_INSTALL_PREFIX}/bin/") - - install(TARGETS ${NAME}) -endfunction() diff --git a/samples/cmake/MakeSample.cmake b/samples/cmake/MakeSample.cmake index d10422f3..344ba9fd 100644 --- a/samples/cmake/MakeSample.cmake +++ b/samples/cmake/MakeSample.cmake @@ -1,4 +1,4 @@ -function(MakeSample NAME) +function(MakeApp NAME) add_executable(${NAME} ${NAME}.cpp) target_link_libraries(${NAME} PUBLIC @@ -10,3 +10,21 @@ function(MakeSample NAME) install(TARGETS ${NAME}) endfunction() + +function(MakeSample INPUT PREFIX) + set(SCRIPT_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/../utils/Monitor.py) + set(TARGET_NAME "${PREFIX}${INPUT}") + + set(BIN_LOCATION ${CMAKE_CURRENT_BINARY_DIR}) + if (CMAKE_GENERATOR MATCHES "Visual Studio") + set(BIN_LOCATION ${BIN_LOCATION}/$) + endif() + + add_custom_target(${TARGET_NAME} ALL + COMMAND "${Python3_EXECUTABLE}" ${SCRIPT_LOCATION} + "--location" "${BIN_LOCATION}" + "--cmd" "${CMAKE_CURRENT_SOURCE_DIR}/${INPUT}" + "--dest" "${CMAKE_CURRENT_BINARY_DIR}/report.html" + "--sleep" "0.5" + ) +endfunction() diff --git a/samples/tcp/CMakeLists.txt b/samples/tcp/CMakeLists.txt index 47bcf210..f62c6531 100644 --- a/samples/tcp/CMakeLists.txt +++ b/samples/tcp/CMakeLists.txt @@ -1,6 +1,12 @@ -MakeSample(TcpClient) -MakeSample(TcpServer) -MakeSample(TcpRepeater) +MakeApp(TcpClient) +MakeApp(TcpServer) +MakeApp(TcpRepeater) -MakeLauncher(TcpScriptsGenerator) -add_dependencies(TcpScriptsGenerator TcpClient TcpServer TcpRepeater) +MakeSample(Sample01_server_client Tcp) +add_dependencies(TcpSample01_server_client TcpClient TcpServer) + +MakeSample(Sample02_server_2_clients Tcp) +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) diff --git a/samples/tcp/README.md b/samples/tcp/README.md index 1da67df6..fdb62112 100644 --- a/samples/tcp/README.md +++ b/samples/tcp/README.md @@ -1,27 +1,81 @@ ## TCP samples This folder stores some examples showing how to set up and use the objects inside **MinimalSocket** in order to use **tcp** connections. -Each sample requires to run multiple processes at the same time. This is automatically done by some shell scripts, which are NOT contained in this folder. Indeed, such scipts are generated in the bin folder, after running **TcpScriptsGenerator**, which generates all scripts without launching it. -Each process pertaining to a sample is run in a dedicated window. +Each sample requires to run in parallel multiple processes at the same time. +This is automatically done by for you by the [Monitor.py](../utils/Monitor.py) script. Which in essence: +- reads a configuration file with the list of processes to run together with the options to pass and run them concurrently +- wait for all processes to complete and gather their outputs +- generate an **hmtl report** with the outputs taken from all processes + +Indeed, **TcpSample01_server_client**, **TcpSample02_server_2_clients** and **TcpSample03_chain_with_2_repeaters** are special [**cmake custom targets**](https://cmake.org/cmake/help/latest/command/add_custom_target.html) that you can "compile" in order to run **Monitor.py** in the proper for executing and controlling the processes involved in the corresponfing sample. After the **hmtl report** is generated, you can inspect it with any browser. 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.) -After generating the scripts, you can run them to see what the samples do. -In particular, 3 scripts can be generated, representative of 3 classes of samples: +The above classes of samples can be described as follows: -- **tcp01_server_client**: +- **TcpSample01_server_client**: + - related config file is [Sample01_server_client](./Sample01_server_client) - runs **TcpServer**, creating a tcp server that binds and listen to a specified port - runs **TcpClient**, creating a tcp client that connections to the previous server, exchanging messages with it. + - the following sequence diagram summarizes this sample + ```mermaid + sequenceDiagram + TcpServer->>TcpServer: bind a port + TcpClient->>TcpServer: ask for connection + TcpServer->>TcpClient: connection done + TcpClient->>TcpServer: request 1 + TcpServer->>TcpClient: response 1 + TcpClient->>TcpServer: request 2 + TcpServer->>TcpClient: response 2 + ``` -- **tcp02_server_3_clients**: +- **TcpSample02_server_2_clients**: + - related config file is [Sample02_server_2_clients](./Sample02_server_2_clients) - runs **TcpServer**, 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 + TcpClient1->>TcpServer: request 1 + TcpServer->>TcpClient1: response 1 + TcpClient2->>TcpServer: request 1 + TcpServer->>TcpClient2: response 1 + TcpClient1->>TcpServer: request 2 + TcpServer->>TcpClient1: response 2 + ``` -- **tcp03_chain_size_4** has the aim of creating a chain of connected processes. More in detail: +- **TcpSample03_chain_with_2_repeaters** has the aim of creating a chain of connected tcps. More in detail: + - related config file is [Sample03_chain_with_2_repeaters](./Sample03_chain_with_2_repeaters) - runs **TcpServer**, creating a tcp server that binds and listen to a specified port - - runs a series of **TcpRepeater**, creating a tcp client that connects to the previous process in the chain and a tcp server waitning for the connection request from the next element in the chain + - runs a couple of **TcpRepeater**, creating a tcp client that connects to the previous process in the chain and a tcp server waitning for the connection request from the next element in the chain - runs **TcpClient**, connecting to the last spawned process of the chain The last client sends some requests, which are forward along the chain till the first server. This latter, sends a response that is backwarded along the chain till xoming back to the first client. + - the following sequence diagram summarizes this sample + ```mermaid + sequenceDiagram + TcpServer->>TcpServer: bind a port + TcpRepeater1->>TcpRepeater1: bind a port + TcpRepeater2->>TcpRepeater2: bind a port + TcpRepeater1->>TcpServer: ask for connection + TcpServer->>TcpRepeater1: connection done + TcpRepeater2->>TcpRepeater1: ask for connection + TcpRepeater1->>TcpRepeater2: connection done + TcpClient->>TcpRepeater2: ask for connection + TcpRepeater2->>TcpClient: connection done + + TcpClient->>TcpRepeater2: request 1 + TcpRepeater2->>TcpRepeater1: forward request 1 + TcpRepeater1->>TcpServer: forward request 1 + + TcpServer->>TcpRepeater1: response 1 + TcpRepeater1->>TcpRepeater2: forawrd response 1 + TcpRepeater2->>TcpClient: forawrd response 1 + ``` -**TcpServer** and **TcpClient** 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 **TcpScriptsGenerator**) for the syntax of the accepted arguments. +**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/Sample01_server_client b/samples/tcp/Sample01_server_client new file mode 100644 index 00000000..bafa71ad --- /dev/null +++ b/samples/tcp/Sample01_server_client @@ -0,0 +1,2 @@ +TcpServer --port 35998 --clients 1 +TcpClient --port 35998 \ No newline at end of file diff --git a/samples/tcp/Sample02_server_2_clients b/samples/tcp/Sample02_server_2_clients new file mode 100644 index 00000000..0ea66a8b --- /dev/null +++ b/samples/tcp/Sample02_server_2_clients @@ -0,0 +1,3 @@ +TcpServer --port 35998 --clients 2 +TcpClient --port 35998 +TcpClient --port 35998 --rate 800 \ No newline at end of file diff --git a/samples/tcp/Sample03_chain_with_2_repeaters b/samples/tcp/Sample03_chain_with_2_repeaters new file mode 100644 index 00000000..c085bdbd --- /dev/null +++ b/samples/tcp/Sample03_chain_with_2_repeaters @@ -0,0 +1,4 @@ +TcpServer --port 35998 --clients 1 +TcpRepeater --port 36008 --next_port 35998 +TcpRepeater --port 36018 --next_port 36008 +TcpClient --port 36018 \ No newline at end of file diff --git a/samples/tcp/TcpClient.cpp b/samples/tcp/TcpClient.cpp index 9e1ec79d..f9f5bde6 100644 --- a/samples/tcp/TcpClient.cpp +++ b/samples/tcp/TcpClient.cpp @@ -23,22 +23,22 @@ int main(const int argc, const char **argv) { PARSE_ARGS const auto server_host = options->getValue("host", "127.0.0.1"); - const auto server_port = static_cast( - std::atoi(options->getValue("port").c_str())); - const auto rate = std::chrono::milliseconds{ - std::atoi(options->getValue("rate", "250").c_str())}; + const auto server_port = + static_cast(options->getIntValue("port")); + const auto rate = + std::chrono::milliseconds{options->getIntValue<250>("rate")}; const MinimalSocket::Address server_address(server_host, server_port); MinimalSocket::tcp::TcpClient client(server_address); cout << "Connecting to " << MinimalSocket::to_string(server_address) << endl; if (!client.open()) { - cout << "Failed to open connection" << endl; + cerr << "Failed to open connection" << endl; return EXIT_FAILURE; } cout << "Connected" << endl; - MinimalSocket::samples::ask_forever(client, rate); + MinimalSocket::samples::ask(client, rate, options->getIntValue<5>("cycles")); // 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 07c727a5..912ea8e4 100644 --- a/samples/tcp/TcpRepeater.cpp +++ b/samples/tcp/TcpRepeater.cpp @@ -15,18 +15,30 @@ // just a bunch of utilities #include +#include +#include #include using namespace std; void repeat(MinimalSocket::tcp::TcpConnection &preceding, MinimalSocket::tcp::TcpClient &following) { - auto request = preceding.receive(500); - cout << "forwarding request: " << request << endl; - following.send(request); - auto response = following.receive(500); - cout << "backwarding response: " << response << endl; - preceding.send(response); + while (true) { + auto request = preceding.receive(500, std::chrono::seconds{5}); + if (request.empty()) { + break; + } + cout << MinimalSocket::samples::TimeOfDay{} + << "forwarding request: " << request << endl; + following.send(request); + auto response = following.receive(500, std::chrono::seconds{5}); + if (request.empty()) { + break; + } + cout << MinimalSocket::samples::TimeOfDay{} + << "backwarding response: " << response << endl; + preceding.send(response); + } } int main(const int argc, const char **argv) { @@ -34,18 +46,18 @@ int main(const int argc, const char **argv) { PARSE_ARGS const auto following_host = options->getValue("host", "127.0.0.1"); - const auto following_port = static_cast( - std::atoi(options->getValue("next_port").c_str())); + const auto following_port = + static_cast(options->getIntValue("next_port")); MinimalSocket::Address following_address(following_host, following_port); - const auto port_to_reserve = static_cast( - std::atoi(options->getValue("port").c_str())); + const auto port_to_reserve = + static_cast(options->getIntValue("port")); // reserve port MinimalSocket::tcp::TcpServer acceptor(port_to_reserve, following_address.getFamily()); if (!acceptor.open()) { - cout << "Failed to bind and listen to specified port" << endl; + cerr << "Failed to bind and listen to specified port" << endl; return EXIT_FAILURE; } cout << "Listening on port " << port_to_reserve << endl; @@ -55,7 +67,7 @@ int main(const int argc, const char **argv) { cout << "Connecting to next on chain at " << MinimalSocket::to_string(following_address) << endl; if (!connection_to_following.open()) { - cout << "Unable to connect to next on chain" << endl; + cerr << "Unable to connect to next on chain" << endl; return EXIT_FAILURE; } cout << "Connected to next on chain" << endl; @@ -65,9 +77,7 @@ int main(const int argc, const char **argv) { auto connection_to_previous = acceptor.acceptNewClient(); cout << "Connected to preceding on chain" << endl; - while (true) { - repeat(connection_to_previous, connection_to_following); - } + repeat(connection_to_previous, connection_to_following); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/samples/tcp/TcpScriptsGenerator.cpp b/samples/tcp/TcpScriptsGenerator.cpp deleted file mode 100644 index b75abaa8..00000000 --- a/samples/tcp/TcpScriptsGenerator.cpp +++ /dev/null @@ -1,81 +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 server 1 client - const std::string sample_name = "tcp01_server_client"; - MinimalSocket::samples::ScriptGenerator generator; - - const std::string port = "35998"; - - generator.add("TcpServer", {{"port", port}, {"clients", "1"}}); - - generator.add("TcpClient", {{"port", port}}); - - cout << "generating " << sample_name << endl; - generator.generate(sample_name); - } - - { - // 1 server many clients - const std::size_t clients = 3; - - const std::string sample_name = - "tcp02_server_" + std::to_string(clients) + "_clients"; - MinimalSocket::samples::ScriptGenerator generator; - - const std::string port = "35998"; - - generator.add("TcpServer", {{"port", port}}); - - generator.add("TcpClient", {{"port", port}}); - - for (std::size_t c = 1; c < clients; ++c) { - generator.add("TcpClient", {{"port", port}, {"rate", "800"}}); - } - - cout << "generating " << sample_name << endl; - generator.generate(sample_name); - } - - { - // repeaters - const std::size_t repeaters = 2; - const std::string sample_name = - "tcp03_chain_size_" + std::to_string(repeaters + 2); - MinimalSocket::samples::ScriptGenerator generator; - - std::size_t port = 35998; - - generator.add("TcpServer", - {{"port", std::to_string(port)}, {"clients", "1"}}); - - for (std::size_t r = 0; r < repeaters; ++r) { - auto new_port = port + 10; - generator.add("TcpRepeater", {{"port", std::to_string(new_port)}, - {"next_port", std::to_string(port)}}); - port = new_port; - } - - generator.add("TcpClient", {{"port", std::to_string(port)}}); - - cout << "generating " << sample_name << endl; - generator.generate(sample_name); - } - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/samples/tcp/TcpServer.cpp b/samples/tcp/TcpServer.cpp index bf7343d5..419c1d55 100644 --- a/samples/tcp/TcpServer.cpp +++ b/samples/tcp/TcpServer.cpp @@ -16,26 +16,15 @@ #include #include -#include - +#include using namespace std; -struct ActiveConnection { - MinimalSocket::tcp::TcpConnection channel; - std::future task; -}; -void accept_new_client(MinimalSocket::tcp::TcpServer &server, - std::list &active_connections) { +std::thread accept_new_client(MinimalSocket::tcp::TcpServer &server) { MinimalSocket::tcp::TcpConnection accepted_connection = server.acceptNewClient(); cout << "New client accepted" << endl; - - auto *channel = &active_connections - .emplace_back(ActiveConnection{ - std::move(accepted_connection), std::future{}}) - .channel; - active_connections.back().task = std::async([channel = channel]() { - MinimalSocket::samples::respond_forever(*channel); + return std::thread([connection = std::move(accepted_connection)]() mutable { + MinimalSocket::samples::respond(connection); }); } @@ -43,41 +32,34 @@ int main(const int argc, const char **argv) { cout << "----------------------- Server -----------------------" << endl; PARSE_ARGS - const auto server_port = static_cast( - std::atoi(options->getValue("port").c_str())); - const auto max_clients = std::atoi(options->getValue("clients", "0").c_str()); + 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")); MinimalSocket::tcp::TcpServer server(server_port, family); if (!server.open()) { - cout << "Failed to bind and listen to specified port" << endl; + cerr << "Failed to bind and listen to specified port" << endl; return EXIT_FAILURE; } cout << "Listening for new clients on port " << server_port << endl; - std::list active_connections; - auto accept_clients_task = std::async([&]() { - if (0 == max_clients) { - while (true) { - accept_new_client(server, active_connections); - } - - } else { - for (int c = 0; c < max_clients; ++c) { - accept_new_client(server, active_connections); - } + std::vector active; + if (0 == max_clients) { + while (true) { + active.emplace_back(accept_new_client(server)); } - }); - - accept_clients_task.get(); - for (auto &active_connection : active_connections) { - try { - active_connection.task.get(); - } catch (...) { + } else { + for (int c = 0; c < max_clients; ++c) { + active.emplace_back(accept_new_client(server)); } } + for (auto &th : active) { + th.join(); + } + return EXIT_SUCCESS; } diff --git a/samples/udp/CMakeLists.txt b/samples/udp/CMakeLists.txt index 19eff40c..0b3086f9 100644 --- a/samples/udp/CMakeLists.txt +++ b/samples/udp/CMakeLists.txt @@ -1,5 +1,11 @@ -MakeSample(UdpAsker) -MakeSample(UdpResponder) +MakeApp(UdpAsker) +MakeApp(UdpResponder) -MakeLauncher(UdpScriptsGenerator) -add_dependencies(UdpScriptsGenerator UdpAsker UdpResponder) +MakeSample(Sample01_asker_responder Udp) +add_dependencies(UdpSample01_asker_responder UdpAsker UdpResponder) + +MakeSample(Sample02_asker_connected_responer Udp) +add_dependencies(UdpSample02_asker_connected_responer UdpAsker UdpResponder) + +MakeSample(Sample03_2_askers_responder Udp) +add_dependencies(UdpSample03_2_askers_responder UdpAsker UdpResponder) diff --git a/samples/udp/README.md b/samples/udp/README.md index 283fcb71..111b8777 100644 --- a/samples/udp/README.md +++ b/samples/udp/README.md @@ -1,28 +1,69 @@ ## UDP samples This folder stores some examples showing how to set up and use the objects inside **MinimalSocket** in order to use **udp** connections. -Each sample requires to run multiple processes at the same time. This is automatically done by some shell scripts, which are NOT contained in this folder. Indeed, such scipts are generated in the bin folder, after running **UdpScriptsGenerator**, which generates all scripts without launching it. -Each process pertaining to a sample is run in a dedicated window. +Each sample requires to run in parallel multiple processes at the same time. +This is automatically done by for you by the [Monitor.py](../utils/Monitor.py) script. Which in essence: +- reads a configuration file with the list of processes to run together with the options to pass and run them concurrently +- wait for all processes to complete and gather their outputs +- generate an **hmtl report** with the outputs taken from all processes + +Indeed **UdpSample01_asker_responder**, **UdpSample02_asker_connected_responer** and **UdpSample03_2_askers_responder** are special [**cmake custom targets**](https://cmake.org/cmake/help/latest/command/add_custom_target.html) that you can "compile" in order to run **Monitor.py** in the proper for executing and controlling the processes involved in the corresponfing sample. After the **hmtl report** is generated, you can inspect it with any browser. 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.) -After generating the scripts, you can run them to see what the samples do. -In particular, 3 scripts can be generated, representative of 3 classes of samples: +The above classes of samples can be described as follows: -- **udp01_responder_asker**: +- **UdpSample01_asker_responder**: + - related config file is [Sample01_asker_responder](./Sample01_asker_responder) - runs **UdpResponder**, creating a udp socket that binds a specified port - runs **UdpAsker**, creating a udp socket that binds another port and exchanges messages with the first spawned udp socket. + - the following sequence diagram summarizes this sample + ```mermaid + sequenceDiagram + UdpResponder->>UdpResponder: bind a port + UdpAsker->>UdpAsker: bind a port + UdpAsker->>UdpResponder: request 1 + UdpResponder->>UdpAsker: response 1 + UdpAsker->>UdpResponder: request 2 + UdpResponder->>UdpAsker: response 2 + ``` -- **udp02_connecting_responder_asker**: - - runs **UdpResponder**, creating a udp socket that binds a specified port, then connects to the first udp socket reaching it. +- **UdpSample02_asker_connected_responer**: + - related config file is [Sample02_asker_connected_responer](./Sample02_asker_connected_responer) + - runs **UdpResponder**, creating a udp socket that binds a specified port, then "connects" (this is an udp so connect means only to filter out messages incoming from other not connected upds) to the first udp socket reaching it. - runs **UdpAsker**, creating a udp socket that binds another port and exchanges messages with the first spawned udp socket. After the connection, the first socket can't exchange messages with other peers. You can verify this by running a second **UdpAsker** sending requests to the port specified for the first **UdpResponder**: they will be always not satisfied. + - the following sequence diagram summarizes this sample + ```mermaid + sequenceDiagram + UdpResponder->>UdpResponder: bind a port + UdpAsker->>UdpAsker: bind a port + UdpAsker->>UdpResponder: request 1 + UdpResponder->>UdpResponder: "connect" address of UdpAsker + UdpResponder->>UdpAsker: response 1 + UdpAsker->>UdpResponder: request 2 + UdpResponder->>UdpAsker: response 2 + ``` -- **udp03_responder_2_askers**: +- **UdpSample03_2_askers_responder**: + - related config file is [Sample03_2_askers_responder](./Sample03_2_askers_responder) - runs **UdpResponder**, creating a udp socket that binds a specified port - - runs **UdpAsker**, creating a first udp socket that binds another port and exchanges messages with the first spawned udp socket. - - runs **UdpAsker**, creating a second udp socket that binds another port and exchanges messages with the first spawned udp socket. - The socket created in **UdpResponder** is able fufllfill the requests of both clients, as it is an un-connected udp socket. On the opposite, the one created in **udp02_connecting_responder_asker** is connecting to the first peer sending a message and that's why it would not be able to answer the requests of a third udp socket. + - runs **UdpAsker**, creating a second udp socket that binds another port and exchanges messages with the first spawned udp socket. + - runs **UdpAsker**, creating a third udp socket that binds another port and exchanges messages with the first spawned udp socket. + The socket created in **UdpResponder** is able fufll the requests of both clients, as it is an "un-connected" udp socket, i.e. accepts incoming messages from anyone. On the opposite, the one created in **UdpSample02_asker_connected_responer** connects to the first peer sending a message and that's why it would not be able to answer the requests of a third udp socket. + - the following sequence diagram summarizes this sample + ```mermaid + sequenceDiagram + UdpResponder->>UdpResponder: bind a port + UdpAsker1->>UdpAsker1: bind a port + UdpAsker2->>UdpAsker2: bind a port + UdpAsker1->>UdpResponder: request 1 + UdpResponder->>UdpAsker1: response 1 + UdpAsker2->>UdpResponder: request 1 + UdpResponder->>UdpAsker2: response 1 + UdpAsker1->>UdpResponder: request 2 + UdpResponder->>UdpAsker1: 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/Sample01_asker_responder b/samples/udp/Sample01_asker_responder new file mode 100644 index 00000000..3765e883 --- /dev/null +++ b/samples/udp/Sample01_asker_responder @@ -0,0 +1,2 @@ +UdpResponder --port_this 37005 +UdpAsker --port 37005 --port_this 36995 \ No newline at end of file diff --git a/samples/udp/Sample02_asker_connected_responer b/samples/udp/Sample02_asker_connected_responer new file mode 100644 index 00000000..1e57d298 --- /dev/null +++ b/samples/udp/Sample02_asker_connected_responer @@ -0,0 +1,2 @@ +UdpResponder --port_this 37005 --connect yes +UdpAsker --port 37005 --port_this 36995 \ No newline at end of file diff --git a/samples/udp/Sample03_2_askers_responder b/samples/udp/Sample03_2_askers_responder new file mode 100644 index 00000000..0e4b7c64 --- /dev/null +++ b/samples/udp/Sample03_2_askers_responder @@ -0,0 +1,3 @@ +UdpResponder --port_this 36995 +UdpAsker --port 36995 --port_this 37005 +UdpAsker --port 36995 --port_this 37015 --rate 800 \ No newline at end of file diff --git a/samples/udp/UdpAsker.cpp b/samples/udp/UdpAsker.cpp index ae43648e..7c79a2e0 100644 --- a/samples/udp/UdpAsker.cpp +++ b/samples/udp/UdpAsker.cpp @@ -23,25 +23,27 @@ int main(const int argc, const char **argv) { PARSE_ARGS const auto remote_host = options->getValue("host", "127.0.0.1"); - const auto remote_port = static_cast( - std::atoi(options->getValue("port").c_str())); - const auto port_this = static_cast( - std::atoi(options->getValue("port_this").c_str())); - const auto rate = std::chrono::milliseconds{ - std::atoi(options->getValue("rate", "250").c_str())}; + 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 MinimalSocket::Address remote_address(remote_host, remote_port); MinimalSocket::udp::UdpBinded asker(port_this, remote_address.getFamily()); - std::this_thread::sleep_for(std::chrono::seconds{ - 1}); // just to be sure the responder has already prepared the receive + std::this_thread::sleep_for( + std::chrono::seconds{1}); // just to be sure the responder has already + // prepared to receive the requests if (!asker.open()) { - cout << "Failed to reserve specified port" << endl; + cerr << "Failed to reserve specified port" << endl; return EXIT_FAILURE; } cout << "Port successfully reserved" << endl; - MinimalSocket::samples::ask_forever(asker, remote_address, rate); + MinimalSocket::samples::ask(asker, remote_address, rate, + options->getIntValue<5>("cycles")); return EXIT_SUCCESS; } diff --git a/samples/udp/UdpResponder.cpp b/samples/udp/UdpResponder.cpp index d8fd315e..926db3e6 100644 --- a/samples/udp/UdpResponder.cpp +++ b/samples/udp/UdpResponder.cpp @@ -15,7 +15,6 @@ // just a bunch of utilities #include #include - using namespace std; int main(const int argc, const char **argv) { @@ -23,8 +22,8 @@ int main(const int argc, const char **argv) { << endl; PARSE_ARGS - const auto port_this = static_cast( - std::atoi(options->getValue("port_this").c_str())); + 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"; @@ -32,7 +31,7 @@ int main(const int argc, const char **argv) { MinimalSocket::udp::UdpBinded responder(port_this, family); if (!responder.open()) { - cout << "Failed to reserve specified port" << endl; + cerr << "Failed to reserve specified port" << endl; return EXIT_FAILURE; } cout << "Port successfully reserved" << endl; @@ -52,10 +51,10 @@ int main(const int argc, const char **argv) { ->second; connected_responder.send(first_response); - MinimalSocket::samples::respond_forever(connected_responder); + MinimalSocket::samples::respond(connected_responder); } else { // use as un-connected udp - MinimalSocket::samples::respond_forever(responder); + MinimalSocket::samples::respond(responder); } return EXIT_SUCCESS; diff --git a/samples/utils/Args.h b/samples/utils/Args.h index 6af8a95d..22dc9dec 100644 --- a/samples/utils/Args.h +++ b/samples/utils/Args.h @@ -32,6 +32,20 @@ class Args { // throw if this option does not exists std::string getValue(const std::string &argument_name) const; + 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()); + } + + int getIntValue(const std::string &argument_name) const { + auto temp = getValue(argument_name); + return std::atoi(temp.c_str()); + } + private: Args(const int argc, const char **argv); @@ -43,7 +57,7 @@ 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) { \ - std::cout << "Invalid arguments" << std::endl; \ + std::cerr << "Invalid arguments" << std::endl; \ return EXIT_FAILURE; \ } } // namespace MinimalSocket::samples diff --git a/samples/utils/Ask.h b/samples/utils/Ask.h index b1b7d66e..ef0fcb37 100644 --- a/samples/utils/Ask.h +++ b/samples/utils/Ask.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -17,31 +18,35 @@ namespace MinimalSocket::samples { template -void ask_forever(SocketT &channel, const std::chrono::milliseconds &rate) { +void ask(SocketT &channel, const std::chrono::milliseconds &rate, + std::size_t cycles) { NamesCircularIterator iterator; - while (true) { + for (std::size_t k = 0; k < cycles * NamesCircularIterator::size(); ++k) { // send name of this person - std::cout << "Sending: " << iterator.current()->first; + std::cout << TimeOfDay{} << "Sending: " << iterator.current()->first + << std::endl; channel.send(iterator.current()->first); // expect to get back the corresponding surname auto response = channel.receive(500); - std::cout << " , got as response: " << response << std::endl; + std::cout << TimeOfDay{} << "Got response: " << response << std::endl; iterator.next(); std::this_thread::sleep_for(rate); } } -void ask_forever(MinimalSocket::udp::UdpBinded &channel, - const MinimalSocket::Address &target, - const std::chrono::milliseconds &rate) { +void ask(MinimalSocket::udp::UdpBinded &channel, + const MinimalSocket::Address &target, + const std::chrono::milliseconds &rate, std::size_t cycles) { NamesCircularIterator iterator; - while (true) { + for (std::size_t k = 0; k < cycles * NamesCircularIterator::size(); ++k) { // send name of this person - std::cout << "Sending: " << iterator.current()->first; + std::cout << TimeOfDay{} << "Sending: " << iterator.current()->first + << std::endl; channel.sendTo(iterator.current()->first, target); // expect to get back the corresponding surname auto response = channel.receive(500); - std::cout << "From " << MinimalSocket::to_string(response->sender) + std::cout << TimeOfDay{} << "From " + << MinimalSocket::to_string(response->sender) << " , got as response: " << response->received_message << std::endl; iterator.next(); diff --git a/samples/utils/Monitor.py b/samples/utils/Monitor.py new file mode 100644 index 00000000..1b5a8f37 --- /dev/null +++ b/samples/utils/Monitor.py @@ -0,0 +1,153 @@ +import subprocess +import threading +import tempfile +import os +import sys +from io import StringIO +from optparse import OptionParser +import time + +def stripEndl(line): + if len(line) > 0 and line[-1] == '\n': + return line[:-1] + return line + +class ProcessHandler: + def __init__(self, cmd_line, location, sleep_initial=None): + if not sleep_initial == None: + print('sleeping {} [s]'.format(sleep_initial)) + time.sleep(float(sleep_initial)) + self.cmd = cmd_line.strip().split() + if not location == None: + self.cmd[0] = os.path.join(location, self.cmd[0]) + print('running `{}`'.format( self.to_string(False) )) + self.thread = threading.Thread(target=self.run_) + self.thread.start() + self.stdout = '' + self.stderr = '' + + def to_string(self, prune): + if prune: + temp = list(self.cmd) + temp[0] = os.path.basename(temp[0]) + return ' '.join(temp) + return ' '.join(self.cmd) + + def run_(self): + hndlr = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + self.stdout, self.stderr = hndlr.communicate() + if not hndlr.returncode == 0: + msg = 'Something wrong from {}: {}'.format(self.cmd[0], self.stderr) + raise Exception(msg) + + def get(self): + if not self.thread == None: + self.thread.join() + self.thread = None + return self.stdout, self.stderr + + def fromFile(source, location=None, sleep_in_between=None): + processes = [] + with open(source, 'r') as stream: + first = True + for line in stream: + processes.append(ProcessHandler(line.strip(), location, None if first else sleep_in_between)) + first = False + return processes + +class Monitor: + LAYOUT_TEMPLATE=""" + + + + + +

+ + +""" + + SHELL_TEMPLATE = """ +
+
$CMD
+
+$LINES +
+""" + + def makeShell(process, w): + content = Monitor.SHELL_TEMPLATE + content = content.replace('$WIDTH', str(w)) + content = content.replace('$CMD', process.to_string(True)) + shell_content = '' + out, err = process.get() + for line in StringIO(out).readlines(): + shell_content += '
{}
\n'.format( stripEndl(line) ) + for line in StringIO(err).readlines(): + shell_content += '
{}
\n'.format( stripEndl(line) ) + content = content.replace('$LINES', shell_content) + return content + + def make(cmd_source, destination, location=None, sleep_in_between=None): + procs = ProcessHandler.fromFile(cmd_source, location, sleep_in_between) + monitor_page = Monitor.LAYOUT_TEMPLATE + w = int(round(100 / len(procs), 0)) + shells = '' + for proc in procs: + shells += '{}\n'.format( Monitor.makeShell(proc, w) ) + monitor_page = monitor_page.replace('$SHELLS', shells) + with open(destination, 'w') as stream: + stream.write(monitor_page) + +def test(): + tmpdirname = tempfile.TemporaryDirectory() + tempFile = os.path.join(tmpdirname.name, 'Commands') + with open(tempFile, 'w') as stream: + stream.write('ls\n') + stream.write('ls ../') + output = os.path.join( os.path.dirname(__file__) , 'test.html') + output = os.path.abspath(output) + Monitor.make(tempFile, output) + print('open in a browser {}'.format(output)) + +def monitor(options): + try: + Monitor.make(options.cmd, options.dest, options.location, options.sleep) + print('open in a browser {} to see the results sent by the run processes'.format(options.dest)) + except: + sys.exit(1) + +def main(): + parser = OptionParser() + parser.add_option("--location", default=None, help="prefix of the path storing the processes to run") + parser.add_option("--sleep", default=None, help="intra start of processes sleep in [s]") + + parser.add_option("--test", action="store_true", default=False) + + parser.add_option("--cmd", default=None, help="files with the list of commands") + parser.add_option("--dest", default=None, help="the location where to generate the hmtl page with the stdout of all processes") + + (options, args) = parser.parse_args() + + if options.test: + test() + elif options.cmd and options.dest: + monitor(options) + +if __name__ == '__main__': + main() diff --git a/samples/utils/Names.h b/samples/utils/Names.h index a96d47e1..6e795733 100644 --- a/samples/utils/Names.h +++ b/samples/utils/Names.h @@ -26,6 +26,10 @@ class NamesCircularIterator { void next(); + static std::size_t size() { + return NamesCircularIterator::NAMES_SURNAMES.size(); + } + private: NamesIterator current_; }; diff --git a/samples/utils/Respond.h b/samples/utils/Respond.h index 67af28c6..0d627861 100644 --- a/samples/utils/Respond.h +++ b/samples/utils/Respond.h @@ -12,26 +12,27 @@ #include namespace MinimalSocket::samples { -template void respond_forever(SocketT &channel) { +template void respond(SocketT &channel) { while (true) { // receive name to search - auto request = channel.receive(500); + auto request = channel.receive(500, std::chrono::seconds{5}); // respond with corresponding surname - const auto &response = - NamesCircularIterator::NAMES_SURNAMES.find(request)->second; - channel.send(response); - } -} - -void respond_forever(MinimalSocket::udp::UdpBinded &channel) { - while (true) { - // receive name to search - auto request = channel.receive(500); - // respond with corresponding surname - const auto &response = - NamesCircularIterator::NAMES_SURNAMES.find(request->received_message) - ->second; - channel.sendTo(response, request->sender); + if constexpr (std::is_same::value) { + 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); + } } } } // namespace MinimalSocket::samples diff --git a/samples/utils/ScriptGenerator.cpp b/samples/utils/ScriptGenerator.cpp deleted file mode 100644 index 97ca9895..00000000 --- a/samples/utils/ScriptGenerator.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -#include -#include -#include -#include - -namespace MinimalSocket::samples { -namespace { -#ifdef _WIN32 -static const std::string SCRIPT_EXTENSION = std::string{".bat"}; -#elif defined(__linux__) || defined(__APPLE__) -static const std::string SCRIPT_EXTENSION = std::string{".sh"}; -#endif - -std::string to_string(const ProcessAndArgs &subject) { - std::stringstream stream; -#ifdef _WIN32 - stream << '\"' << subject.process_name << '\"'; - for (const auto &[name, val] : subject.arguments) { - stream << " \"--" << name << "\" \"" << val << "\""; - } -#elif defined(__linux__) || defined(__APPLE__) - stream << "./" << subject.process_name; - for (const auto &[name, val] : subject.arguments) { - stream << " --" << name << ' ' << val; - } -#endif - return stream.str(); -} - -void add_process(std::ofstream &stream, const ProcessAndArgs &proc_and_args, - const bool new_terminal) { -#ifdef _WIN32 - if (new_terminal) { - stream << "start \"\" "; - } - stream << to_string(proc_and_args); -#elif defined(__linux__) || defined(__APPLE__) - if (new_terminal) { - stream << "gnome-terminal -x sh -c \"" << to_string(proc_and_args) - << " ; bash\""; - } else { - stream << to_string(proc_and_args); - } -#endif - stream << std::endl; -} -} // namespace - -void ScriptGenerator::generate(const std::string &file_name) { - const std::string recipient = file_name + SCRIPT_EXTENSION; - std::ofstream stream(recipient); - -#if defined(__linux__) || defined(__APPLE__) - stream << "#!/bin/sh" << std::endl; -#endif - - for (std::size_t k = 0; k < (processes.size() - 1); ++k) { - add_process(stream, processes[k], true); - } - add_process(stream, processes.back(), false); - - std::cout << "launcher script generated at: " - << std::filesystem::absolute(recipient) << std::endl; -} -} // namespace MinimalSocket::samples diff --git a/samples/utils/ScriptGenerator.h b/samples/utils/ScriptGenerator.h deleted file mode 100644 index 41e345d5..00000000 --- a/samples/utils/ScriptGenerator.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#pragma once - -#include -#include -#include - -namespace MinimalSocket::samples { -using ProcessArgs = std::unordered_map; - -struct ProcessAndArgs { - std::string process_name; - ProcessArgs arguments; -}; - -class ScriptGenerator { -public: - ScriptGenerator() = default; - - void add(const std::string &process_name, const ProcessArgs &args) { - processes.push_back(ProcessAndArgs{process_name, args}); - }; - - void generate(const std::string &file_name); - -private: - std::vector processes; -}; -} // namespace MinimalSocket::samples diff --git a/samples/utils/TimeOfDay.cpp b/samples/utils/TimeOfDay.cpp new file mode 100644 index 00000000..afd3b967 --- /dev/null +++ b/samples/utils/TimeOfDay.cpp @@ -0,0 +1,28 @@ +#include + +#include + +namespace MinimalSocket::samples { +TimeOfDay::TimeOfDay(const time_t &t) { + struct tm *tmp = gmtime(&t); + hours = tmp->tm_hour; + minutes = tmp->tm_min; + seconds = tmp->tm_sec; +} + +std::ostream &operator<<(std::ostream &the_stream, const TimeOfDay &subject) { + auto print = [&the_stream](int val) { + if (val < 10) { + the_stream << '0'; + } + the_stream << val; + }; + + print(subject.hours); + the_stream << ':'; + print(subject.minutes); + the_stream << ':'; + print(subject.seconds); + return the_stream << ": "; +} +} // namespace MinimalSocket::samples diff --git a/samples/utils/TimeOfDay.h b/samples/utils/TimeOfDay.h new file mode 100644 index 00000000..be24a93b --- /dev/null +++ b/samples/utils/TimeOfDay.h @@ -0,0 +1,24 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +#include +#include + +namespace MinimalSocket::samples { +struct TimeOfDay { + TimeOfDay() : TimeOfDay{time(NULL)} {} + TimeOfDay(const time_t &t); + + int hours; + int minutes; + int seconds; +}; + +std::ostream &operator<<(std::ostream &the_stream, const TimeOfDay &subject); +} // namespace MinimalSocket::samples From 4de1b4b4f78b2b0ba708bfd55484da9228a591f1 Mon Sep 17 00:00:00 2001 From: Foo Date: Mon, 12 Jun 2023 21:11:47 +0200 Subject: [PATCH 219/228] avoid to fetch samples in readme sample --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0aa3c6bb..4c6c8487 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,7 @@ To consume this library you can rely on [CMake](https://cmake.org). More precisely, You can fetch this package and link to the **MinimalSocket** library: ```cmake include(FetchContent) +set(BUILD_MinimalCppSocket_SAMPLES OFF CACHE BOOL "" FORCE) # you don't want the samples in this case FetchContent_Declare( min_sock GIT_REPOSITORY https://github.com/andreacasalino/Minimal-Socket From bddb6c9c89b207462701c989a62e459a78714a3d Mon Sep 17 00:00:00 2001 From: Foo Date: Sat, 23 Dec 2023 13:53:50 +0100 Subject: [PATCH 220/228] use clang-17 for ubuntu-clang format as workaround to github action bug use Ninja for ubuntu flows --- .github/workflows/installArtifacts.yml | 66 ------------------------- .github/workflows/runTests.yml | 25 ++++++++-- README.md | 1 - src/header/MinimalSocket/core/Address.h | 1 + tests/CMakeLists.txt | 2 +- 5 files changed, 23 insertions(+), 72 deletions(-) delete mode 100644 .github/workflows/installArtifacts.yml diff --git a/.github/workflows/installArtifacts.yml b/.github/workflows/installArtifacts.yml deleted file mode 100644 index 78b8466d..00000000 --- a/.github/workflows/installArtifacts.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Binaries Compilation - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - binCompile: - strategy: - # max-parallel: 4 - matrix: - name: [ubuntu-gcc-static, ubuntu-clang-static, windows-VS-static,ubuntu-gcc-shared, ubuntu-clang-shared, windows-VS-shared] - include: - - name: ubuntu-gcc-static - os: ubuntu-latest - compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -G \"Unix Makefiles\"" - lib_opt: "" - - name: ubuntu-clang-static - os: ubuntu-latest - compiler_opt: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -G \"Unix Makefiles\"" - lib_opt: "" - - name: windows-VS-static - os: windows-latest - compiler_opt: "" - lib_opt: "" - - name: macOS-static - os: macos-latest - compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++" - lib_opt: "" - - name: ubuntu-gcc-shared - os: ubuntu-latest - compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -G \"Unix Makefiles\"" - lib_opt: "-DLIB_OPT=ON" - - name: ubuntu-clang-shared - os: ubuntu-latest - compiler_opt: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -G \"Unix Makefiles\"" - lib_opt: "-DLIB_OPT=ON" - - name: windows-VS-shared - os: windows-latest - compiler_opt: "" - lib_opt: "-DLIB_OPT=ON" - - name: macOS-shared - os: macos-latest - compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++" - lib_opt: "-DLIB_OPT=ON" - - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Checkout submodules - run: git submodule update --init --recursive - - name: CMake configure - run: cmake -B./build -DCMAKE_INSTALL_PREFIX:STRING=./artifacts/ -DBUILD_MinimalCppSocket_SAMPLES=OFF -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_BUILD_TYPE:STRING=Release ${{ matrix.compiler_opt }} ${{ matrix.lib_opt }} - - name: build release - run: cmake --build ./build --config Release - - name: Install artifacts - run: cmake --install ./build --config Release - - uses: actions/upload-artifact@v2 - with: - path: artifacts - name: ${{ matrix.name }} diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index 55842a7e..5e4a75d6 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -16,25 +16,42 @@ jobs: include: - name: ubuntu-gcc os: ubuntu-latest - compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -G \"Unix Makefiles\"" + compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++" + build_system: '-G Ninja' - name: ubuntu-clang os: ubuntu-latest - compiler_opt: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -G \"Unix Makefiles\"" + compiler_opt: "-DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17" + build_system: '-G Ninja' - name: windows-VS os: windows-latest compiler_opt: "" + build_system: "" - name: macOS os: macos-latest compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++" - + build_system: "" + runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v2.0.0 - name: Checkout submodules run: git submodule update --init --recursive + - name: Install Ninja + if: matrix.build_system == '-G Ninja' + uses: seanmiddleditch/gha-setup-ninja@master + # needed as this project requires c++ 20. Due to this github action bug: + # https://github.com/actions/runner-images/issues/8659 + # This actually implements the workaround described here: + # https://github.com/wheremyfoodat/Panda3DS/blob/master/.github/workflows/Linux_Build.yml + - name: Install newer Clang + if: ${{ matrix.name == 'ubuntu-clang' }} + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x ./llvm.sh + sudo ./llvm.sh 17 - name: CMake configure - run: cmake -B./build -DCMAKE_INSTALL_PREFIX:STRING=./artifacts/ -DBUILD_MinimalCppSocket_SAMPLES=OFF -DBUILD_MinimalCppSocket_TESTS=ON -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_BUILD_TYPE:STRING=Release ${{ matrix.compiler_opt }} + run: cmake -B./build -DCMAKE_INSTALL_PREFIX:STRING=./artifacts/ -DBUILD_MinimalCppSocket_SAMPLES=OFF -DBUILD_MinimalCppSocket_TESTS=ON -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_BUILD_TYPE:STRING=Release ${{ matrix.compiler_opt }} ${{ matrix.build_system }} - name: Build run: cmake --build ./build --config Release - name: Install diff --git a/README.md b/README.md index 4c6c8487..42d62e14 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -![binaries_compilation](https://github.com/andreacasalino/Cross-Platform-Socket/actions/workflows/installArtifacts.yml/badge.svg) ![binaries_compilation](https://github.com/andreacasalino/Cross-Platform-Socket/actions/workflows/runTests.yml/badge.svg) - [What is this library about](#intro) diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index ef5f4e66..e6ca3aca 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 02c7924a..734b7833 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,7 +2,7 @@ include(FetchContent) FetchContent_Declare( catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG 4ff8b27bb6bed4b8b309e56cd269b4f1fbc24e89 + GIT_TAG v3.5.0 ) FetchContent_MakeAvailable(catch2) From 7d6ae74273ac7e3ac085bdc7fd6c930665412393 Mon Sep 17 00:00:00 2001 From: Foo Date: Sat, 23 Dec 2023 17:09:43 +0100 Subject: [PATCH 221/228] auto detect tests labels in runTests CI action --- .github/workflows/runTests.py | 63 ++++++++++++++++++++++++++++++++++ .github/workflows/runTests.yml | 14 ++++---- 2 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/runTests.py diff --git a/.github/workflows/runTests.py b/.github/workflows/runTests.py new file mode 100644 index 00000000..069b80ad --- /dev/null +++ b/.github/workflows/runTests.py @@ -0,0 +1,63 @@ +import re, os, subprocess, sys, argparse + +class TestsRunner: + FOLDER = os.path.dirname(__file__) + FOLDER = os.path.join(FOLDER, '..', '..', 'tests') + + def gatherLabels_(self): + res = set() + for filename in filter(lambda f: os.path.splitext(f)[1] == '.cpp' , os.listdir(TestsRunner.FOLDER)): + with open(os.path.join(TestsRunner.FOLDER, filename), 'r') as stream: + for m in re.finditer("""TEST_CASE\(\"(.*?)\", \"\[(.*?)\]\"""", stream.read()): + res.add(m.group(2)) + self.labels = [r for r in res] + + def sortLabels_(self, first): + sorted = [] + for label in first: + if not label in self.labels: + msg = '[{}] is not a valid label'.format(label) + raise Exception(msg) + sorted.append(label) + self.labels.remove(label) + for label in self.labels: + sorted.append(label) + self.labels = sorted + + def __init__(self, first=None): + self.gatherLabels_() + self.sortLabels_([] if first == None else first) + + def run(self, binLocation, fltr): + for label in filter(lambda label: not label in fltr, self.labels): + print('============================================') + print('running [{}]'.format(label)) + print('============================================') + hndlr = subprocess.Popen([binLocation, '[{}]'.format(label)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + out, err = hndlr.communicate() + print('{}\n{}'.format(out, err)) + if not hndlr.returncode == 0: + sys.exit(1) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--bin', default=None) + parser.add_argument('--priority', default=None) + parser.add_argument('--filter', default=None) + args = parser.parse_args() + + if args.priority == None: + args.priority = [] + else: + args.priority = args.priority.split() + if args.filter == None: + args.filter = [] + else: + args.filter = args.filter.split() + + if args.bin == None: + print('labels detected at {}'.format(TestsRunner.FOLDER)) + print('\n'.join(TestsRunner().labels)) + else: + runner = TestsRunner(args.priority) + runner.run(args.bin, args.filter) diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index 5e4a75d6..6bdd50b7 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -18,18 +18,22 @@ jobs: os: ubuntu-latest compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++" build_system: '-G Ninja' + filter: "" - name: ubuntu-clang os: ubuntu-latest compiler_opt: "-DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17" build_system: '-G Ninja' + filter: "" - name: windows-VS os: windows-latest compiler_opt: "" build_system: "" + filter: "robustness" - name: macOS os: macos-latest compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++" build_system: "" + filter: "robustness" runs-on: ${{ matrix.os }} steps: @@ -56,14 +60,8 @@ jobs: run: cmake --build ./build --config Release - name: Install run: cmake --install ./build --config Release - - name: Test-open - run: ./artifacts/bin/MinimalSocket-tests "[open]" - - name: Test-address - run: ./artifacts/bin/MinimalSocket-tests "[address]" - - name: Test-tcp - run: ./artifacts/bin/MinimalSocket-tests "[tcp]" - - name: Test-udp - run: ./artifacts/bin/MinimalSocket-tests "[udp]" + - name: Tests + run: python .github/workflows/runTests.py --bin "./artifacts/bin/MinimalSocket-tests" --priority "open address" --filter "${{ matrix.filter }}" - uses: actions/upload-artifact@v2 with: path: artifacts From 023700b21ef53c0a615109866d4efbddb267d277 Mon Sep 17 00:00:00 2001 From: LostbBlizzard <106630000+LostbBlizzard@users.noreply.github.com> Date: Wed, 20 Dec 2023 14:47:23 -0800 Subject: [PATCH 222/228] fixed errors when using wasm emscripten. --- src/src/SocketId.h | 2 +- src/src/core/Receiver.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/src/SocketId.h b/src/src/SocketId.h index 41e7be4f..fe8a458f 100644 --- a/src/src/SocketId.h +++ b/src/src/SocketId.h @@ -16,7 +16,7 @@ #include #include #include -#elif defined(__linux__) || defined(__APPLE__) +#elif defined(__unix__) || defined(__APPLE__) #include #include #include //memset diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 7be1e71f..7cffbd1c 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -9,7 +9,9 @@ #include #include "../SocketAddress.h" - +#ifndef _WIN32 +#include +#endif namespace MinimalSocket { std::unique_ptr> ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { From 813d6daea60b931807a8b94b64ad913e06c29ebc Mon Sep 17 00:00:00 2001 From: Foo Date: Thu, 8 Feb 2024 18:39:15 +0100 Subject: [PATCH 223/228] GNU LICENSE added --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..4842d817 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From f74355ef7140676436eb468f8423ae64b01e5541 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 19 Feb 2024 22:05:23 +0000 Subject: [PATCH 224/228] avoid custom target command in VisualStudio in favour of a Popen C++ wrapper --- samples/CMakeLists.txt | 3 + samples/Launcher.cpp | 106 +++++++++++++++++++++++++++++++++ samples/cmake/MakeSample.cmake | 14 +++++ samples/utils/Monitor.py | 13 +++- 4 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 samples/Launcher.cpp diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 811e8637..daa81338 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -7,5 +7,8 @@ include(cmake/MakeSample.cmake) add_subdirectory(utils) +set(LauncherSource ${CMAKE_CURRENT_SOURCE_DIR}/Launcher.cpp) +option(BUILD_MinimalCppSocket_AVOID_CUSTOM_TARGET "" OFF) + add_subdirectory(tcp) add_subdirectory(udp) diff --git a/samples/Launcher.cpp b/samples/Launcher.cpp new file mode 100644 index 00000000..7d1167ae --- /dev/null +++ b/samples/Launcher.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include +#include +#include +#if _WIN64 || _WIN32 +#include +#endif + +class Popen { +public: + Popen(const std::string &line) { + fp = +#if _WIN64 || _WIN32 + _popen(line.c_str(), "r") +#elif __linux__ + popen(line.c_str(), "r") +#endif + ; + if (fp == NULL) { + throw std::runtime_error{"Unable to start the child process"}; + } + } + + struct CompletedProcess { + int returnCode; + std::string out; + }; + CompletedProcess communicate() { + if (!done) { + while (poll()) { + } + done = true; + } + return CompletedProcess{returnCode_, out_.str()}; + } + +protected: + bool poll() { + std::string temp; + temp.resize(250); + if (fgets(temp.data(), static_cast(temp.size()), fp) == nullptr) { +#if _WIN64 || _WIN32 + feof(fp); +#endif + + returnCode_ = +#if _WIN64 || _WIN32 + _pclose(fp) +#elif __linux__ + pclose(fp) +#endif + ; + + fp = nullptr; + return false; + } + std::size_t last_pos = temp.find('\n'); + if (last_pos != std::string::npos) { + temp.resize(last_pos + 1); + } + out_ << temp; + return true; + } + +private: + bool done = false; + FILE *fp = nullptr; + std::string buffer_; + int returnCode_ = -1; + std::stringstream out_; +}; + +#include +#include + +const std::string cmd{CMD}; +std::vector getCommands() { + std::vector res; + std::size_t pos = 0; + while (pos < cmd.size()) { + auto next = cmd.find('|', pos); + if (next == std::string::npos) { + res.emplace_back(cmd.data() + pos); + break; + } + res.emplace_back(cmd.data() + pos, next - pos); + pos = next + 1; + } + return res; +} + +int main() { + for (const auto &proc : getCommands()) { + Popen process(std::string{proc.data(), proc.size()}); + auto &&[retCode, out] = process.communicate(); + std::cout << out; + if (retCode != 0) { + throw std::runtime_error{"Child process didn't complete well"}; + } + } + + return EXIT_SUCCESS; +} diff --git a/samples/cmake/MakeSample.cmake b/samples/cmake/MakeSample.cmake index 344ba9fd..b57e3bd4 100644 --- a/samples/cmake/MakeSample.cmake +++ b/samples/cmake/MakeSample.cmake @@ -20,6 +20,17 @@ function(MakeSample INPUT PREFIX) set(BIN_LOCATION ${BIN_LOCATION}/$) endif() +if (CMAKE_GENERATOR MATCHES "Visual Studio" OR BUILD_MinimalCppSocket_AVOID_CUSTOM_TARGET) + # add_custom_target is not supported by visual studio + add_executable(${TARGET_NAME} ${LauncherSource}) + target_compile_definitions(${TARGET_NAME} PUBLIC + -D CMD="${Python3_EXECUTABLE} ${SCRIPT_LOCATION} --cmd ${CMAKE_CURRENT_SOURCE_DIR}/${INPUT}|${Python3_EXECUTABLE} ${SCRIPT_LOCATION} --location ${BIN_LOCATION} --cmd ${CMAKE_CURRENT_SOURCE_DIR}/${INPUT} --dest ${CMAKE_CURRENT_BINARY_DIR}/report.html --sleep 0.5" + ) +else() + add_custom_target(${TARGET_NAME}-preamble ALL + COMMAND "${Python3_EXECUTABLE}" ${SCRIPT_LOCATION} + "--cmd" "${CMAKE_CURRENT_SOURCE_DIR}/${INPUT}" + ) add_custom_target(${TARGET_NAME} ALL COMMAND "${Python3_EXECUTABLE}" ${SCRIPT_LOCATION} "--location" "${BIN_LOCATION}" @@ -27,4 +38,7 @@ function(MakeSample INPUT PREFIX) "--dest" "${CMAKE_CURRENT_BINARY_DIR}/report.html" "--sleep" "0.5" ) + add_dependencies(${TARGET_NAME} ${TARGET_NAME}-preamble) +endif() + endfunction() diff --git a/samples/utils/Monitor.py b/samples/utils/Monitor.py index 1b5a8f37..ce1ff26f 100644 --- a/samples/utils/Monitor.py +++ b/samples/utils/Monitor.py @@ -15,12 +15,11 @@ def stripEndl(line): class ProcessHandler: def __init__(self, cmd_line, location, sleep_initial=None): if not sleep_initial == None: - print('sleeping {} [s]'.format(sleep_initial)) + # print('sleeping {} [s]'.format(sleep_initial)) time.sleep(float(sleep_initial)) self.cmd = cmd_line.strip().split() if not location == None: self.cmd[0] = os.path.join(location, self.cmd[0]) - print('running `{}`'.format( self.to_string(False) )) self.thread = threading.Thread(target=self.run_) self.thread.start() self.stdout = '' @@ -128,6 +127,7 @@ def test(): def monitor(options): try: Monitor.make(options.cmd, options.dest, options.location, options.sleep) + print() print('open in a browser {} to see the results sent by the run processes'.format(options.dest)) except: sys.exit(1) @@ -145,9 +145,16 @@ def main(): (options, args) = parser.parse_args() if options.test: - test() + test() elif options.cmd and options.dest: monitor(options) + elif options.cmd: + # just show the commands + print() + with open(options.cmd, 'r') as stream: + print(''.join(['Running {}'.format(line) for line in stream.readlines()])) + print('\n\nwaiting for all the spawned processes to complete ...\n\n') + if __name__ == '__main__': main() From 81386a964fab078d409f4cb2448a131f30e2d319 Mon Sep 17 00:00:00 2001 From: Foo Date: Tue, 20 Feb 2024 22:44:09 +0100 Subject: [PATCH 225/228] cosmetic changes to Monitor.py --- samples/utils/Monitor.py | 68 ++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/samples/utils/Monitor.py b/samples/utils/Monitor.py index ce1ff26f..8436364d 100644 --- a/samples/utils/Monitor.py +++ b/samples/utils/Monitor.py @@ -7,17 +7,18 @@ from optparse import OptionParser import time -def stripEndl(line): - if len(line) > 0 and line[-1] == '\n': - return line[:-1] - return line +def onlyOnceTrue(iterable): + firstTime = True + for element in iterable: + yield firstTime, element + firstTime = False class ProcessHandler: def __init__(self, cmd_line, location, sleep_initial=None): if not sleep_initial == None: # print('sleeping {} [s]'.format(sleep_initial)) time.sleep(float(sleep_initial)) - self.cmd = cmd_line.strip().split() + self.cmd = cmd_line.split() if not location == None: self.cmd[0] = os.path.join(location, self.cmd[0]) self.thread = threading.Thread(target=self.run_) @@ -25,12 +26,8 @@ def __init__(self, cmd_line, location, sleep_initial=None): self.stdout = '' self.stderr = '' - def to_string(self, prune): - if prune: - temp = list(self.cmd) - temp[0] = os.path.basename(temp[0]) - return ' '.join(temp) - return ' '.join(self.cmd) + def __str__(self): + return ' '.join([os.path.basename(piece) if isFirst else piece for isFirst, piece in onlyOnceTrue(self.cmd)]) def run_(self): hndlr = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) @@ -46,13 +43,8 @@ def get(self): return self.stdout, self.stderr def fromFile(source, location=None, sleep_in_between=None): - processes = [] with open(source, 'r') as stream: - first = True - for line in stream: - processes.append(ProcessHandler(line.strip(), location, None if first else sleep_in_between)) - first = False - return processes + return [ProcessHandler(line.strip(), location, None if firstTime else sleep_in_between) for firstTime, line in onlyOnceTrue(stream.readlines())] class Monitor: LAYOUT_TEMPLATE=""" @@ -82,36 +74,29 @@ class Monitor: """ SHELL_TEMPLATE = """ -
-
$CMD
+
+
{CMD}

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