Skip to content

Commit

Permalink
Merge pull request andreacasalino#39 from andreacasalino/Refactoring
Browse files Browse the repository at this point in the history
Refactoring
  • Loading branch information
andreacasalino authored Feb 24, 2024
2 parents bbd3f4b + cca045c commit e692da0
Show file tree
Hide file tree
Showing 34 changed files with 436 additions and 443 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ build
.vscode
*.log
test.html
TODO
48 changes: 25 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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

Expand All @@ -48,7 +48,7 @@ To create a **tcp** server you just need to build a **tcp::TcpServer** object:
```cpp
#include <MinimalSocket/tcp/TcpServer.h>

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);
```
Expand All @@ -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
```

Expand Down Expand Up @@ -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:
Expand All @@ -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();
```

Expand Down Expand Up @@ -167,15 +169,15 @@ 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.)

## CMAKE SUPPORT

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)
Expand All @@ -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**.
17 changes: 10 additions & 7 deletions samples/README.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
// tcp server
#include <MinimalSocket/tcp/TcpServer.h>
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
Expand All @@ -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");
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions samples/tcp/TcpServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <Args.h>
#include <Respond.h>

#include <thread>
#include <vector>
using namespace std;

Expand Down
38 changes: 19 additions & 19 deletions src/header/MinimalSocket/Error.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,46 @@ class Error : public std::runtime_error {
public:
Error(const std::string &what) : std::runtime_error(what){};

template <typename... Args> Error(Args... args) : Error(merge(args...)) {}
template <typename... Args>
Error(const Args &...args) : Error(merge(args...)) {}

protected:
template <typename... Args> static std::string merge(Args... args) {
template <typename... Args> static std::string merge(const Args &...args) {
std::stringstream stream;
merge(stream, args...);
(merge_(stream, args), ...);
return stream.str();
};

template <typename T, typename... Args>
static void merge(std::stringstream &stream, const T &current,
Args... remaining) {
stream << current;
merge(stream, remaining...);
};

template <typename T, typename... Args>
static void merge(std::stringstream &stream, const T &back) {
stream << back;
template <typename T>
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 <typename... Args>
SocketError(const Args &...args) : SocketError{merge(args...)} {};
};

class TimeOutError : public Error {
public:
TimeOutError() : Error("Timeout"){};
};
} // namespace MinimalSocket
19 changes: 19 additions & 0 deletions src/header/MinimalSocket/NonCopiable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Author: Andrea Casalino
* Created: 01.28.2020
*
* report any bug to [email protected].
**/

#pragma once

namespace MinimalSocket {
class NonCopiable {
public:
NonCopiable(const NonCopiable &) = delete;
NonCopiable &operator=(const NonCopiable &) = delete;

protected:
NonCopiable() = default;
};
} // namespace MinimalSocket
15 changes: 9 additions & 6 deletions src/header/MinimalSocket/core/Address.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);

Expand Down
14 changes: 7 additions & 7 deletions src/header/MinimalSocket/core/Definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,34 @@
#include <string>

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;
};

/**
* @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);
BufferViewConst makeBufferViewConst(const std::string &subject);

enum class SocketType { UDP, TCP };

Expand Down
16 changes: 11 additions & 5 deletions src/header/MinimalSocket/core/Receiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@
namespace MinimalSocket {
class ReceiverBase : public virtual Socket {
protected:
std::unique_ptr<std::scoped_lock<std::mutex>>
lazyUpdateReceiveTimeout(const Timeout &timeout);
template <typename Pred>
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;
};
Expand All @@ -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);

/**
Expand All @@ -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.
Expand All @@ -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<ReceiveResult> receive(const Buffer &message,
std::optional<ReceiveResult> receive(BufferView message,
const Timeout &timeout = NULL_TIMEOUT);

struct ReceiveStringResult {
Expand Down
Loading

0 comments on commit e692da0

Please sign in to comment.