added net module

This commit is contained in:
2025-06-27 15:19:25 +02:00
parent acd30afe6f
commit cd61ac7f00
39 changed files with 4599 additions and 12 deletions
+174
View File
@@ -0,0 +1,174 @@
#ifndef MATADOR_ACCEPTOR_HPP
#define MATADOR_ACCEPTOR_HPP
#include "matador/net/export.hpp"
#include "matador/net/handler.hpp"
#include "matador/net/handler_creator.hpp"
#include "matador/net/ip.hpp"
#include "matador/logger/logger.hpp"
#include <functional>
namespace matador {
/**
* The acceptor class is used to accept new connection
* within the reactor dispatcher.
*
* Once a new connection was accepted by the acceptor a
* new handler is created and registered within the reactor
* to handle the established connection
*/
class OOS_NET_API acceptor : public handler, public handler_creator
{
public:
typedef std::function<std::shared_ptr<handler>(tcp::socket sock, tcp::peer endpoint, acceptor *accptr)> t_accept_handler; /**< Shortcut to a function creating a handler on successfully accepted a new connection */
/**
* Default constructor
*/
acceptor();
/**
* Creates an acceptor with the given endpoint. The endpoint
* represents the address on which the acceptor listens for new
* connections
*
* @param endpoint Endpoint to listen for new connections
*/
explicit acceptor(tcp::peer endpoint);
/**
* Creates an acceptor with the given endpoint. The endpoint
* represents the address on which the acceptor listens for new
* connections. The given function is called when a new
* connection was accepted and returns a new handler for
* the new connection.
*
* @param endpoint Endpoint to listen for new connections
* @param on_new_connection Function creating a new handler for each accepted new connection
*/
acceptor(tcp::peer endpoint, t_accept_handler on_new_connection);
/**
* Destructor
*/
~acceptor() override;
/**
* When a new connection is accepted the given function
* is called to create a new handler for the connection
*
* @param on_new_connection Function creating a new handler for the new connection
*/
void accecpt(t_accept_handler on_new_connection);
/**
* Accepts new connection at the given endpoint.
* When a new connection is accepted the given function
* is called to create a new handler for the connection.
*
* @param endpoint Endpoint to listen for new connections
* @param on_new_connection Function creating a new handler for the new connection
*/
void accecpt(const tcp::peer& endpoint, t_accept_handler on_new_connection);
/**
* Opens the acceptor means the socket address of the
* endpoint is bound to the created listing socket
* Then the socket is used for listening for new
* connections.
*/
void open() override;
/**
* Returns the current listening socket fd
*
* @return Listening socket fd
*/
socket_type handle() const override;
/**
* Is called when a new connection wants to
* execute to the endpoint. Once the connection was accepted
* a new connection handler is created and the socket is
* passed to the handler. The handler is then registered
* to the reactor to disptach its read and write events.
*/
void on_input() override;
/**
* Does actually nothing
*/
void on_output() override {}
/**
* Does actually nothing
*/
void on_except() override {}
/**
* Does actually nothing
*/
void on_timeout() override {}
/**
* Does actually nothing
*/
void on_close() override {}
/**
* Closes the listen fd of the acceptor
*/
void close() override;
/**
* Returns always false because new connections
* are indicated as read events.
*
* @return Always false
*/
bool is_ready_write() const override;
/**
* Returns true if the acceptor was opened
* and a listen fd was created.
*
* @return True If a listen socket was created
*/
bool is_ready_read() const override;
/**
* Notifies the acceptor that
* this handler was closed.
*
* @param hndlr Closed handler.
*/
void notify_close(handler *hndlr) override;
std::string name() const override;
public:
/**
* Returns the current endpoint accepting new connection.
*
* @return Current listening endpoint
*/
const tcp::peer& endpoint() const;
private:
tcp::acceptor acceptor_;
tcp::peer endpoint_;
std::string name_ { "acceptor" };
t_accept_handler accept_handler_;
logger log_;
tcp::peer create_client_endpoint() const;
};
}
#endif //MATADOR_ACCEPTOR_HPP
+453
View File
@@ -0,0 +1,453 @@
#ifndef MATADOR_ADDRESS_HPP
#define MATADOR_ADDRESS_HPP
#include "matador/utils/os.hpp"
#include "matador/net/export.hpp"
#include "matador/net/os.hpp"
#include "matador/net/error.hpp"
#include <string>
#include <cstring>
#include <stdexcept>
#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#endif
namespace matador {
/**
* Enum representing the protocol
* family IPv4 and IPv6
*/
enum protocol_family {
V4, /**< IPv4 enum value */
V6 /**< IPv6 enum value */
};
/// @cond MATADOR_DEV
template < protocol_family PF >
class address_router;
/// @endcond
/**
* The address class represents a IPv4 or
* IPv6 address.
*/
class OOS_NET_API address
{
public:
/**
* Default constructor
*/
address() = default;
/**
* Constructs an address from the given
* addr representing a IPv4 socket address
* structure
*
* @param addr Initial IPv4 Socket address
*/
explicit address(const sockaddr_in &addr);
/**
* Constructs an address from the given
* addr representing a IPv6 socket address
* structure
*
* @param addr Initial IPv6 Socket address
*/
explicit address(const sockaddr_in6 &addr);
/**
* Copy constructs an address from the
* given address x
*
* @param x Address to copy from
*/
address(const address &x) = default;
/**
* Copy assign an address from the
* given address x
*
* @param x Address to assign from
* @return The assigned address
*/
address& operator=(const address &x);
/**
* Move copy constructs an address from the
* given address x
*
* @param x Address to move copy from
*/
address(address &&x) noexcept;
/**
* Move assign an address from the
* given address x
*
* @param x Address to move assign from
* @return The moved address
*/
address& operator=(address &&x) noexcept;
/**
* Destructs the address
*/
~address();
/**
* Returns the address as unsigned long value
*
* @return The address as unsigned long value
*/
unsigned int to_ulong() const;
/**
* Return the address in string format either
* in IPv4 dotted format or in IPv6 colon based
* format
*
* @return The address as string
*/
std::string to_string() const;
/**
* Sets the port number
*
* @param pn Port number to set
*/
void port(unsigned short pn);
/**
* Returns the current port number of the
* address.
*
* @return The current port number
*/
unsigned short port() const;
/**
* Returns true if address is IPv4
*
* @return True if IPv4 address
*/
bool is_v4() const;
/**
* Returns true if address is IPv4
*
* @return True if IPv4 address
*/
bool is_v6() const;
/**
* Returns the raw sockaddr structure
*
* @return The raw sockaddr structure
*/
sockaddr* addr();
/**
* Returns the raw sockaddr structure
*
* @return The raw sockaddr structure
*/
const sockaddr* addr() const;
/**
* Returns the IPv4 sockaddr_in structure
*
* @return The IPv4 sockaddr_in structure
*/
sockaddr_in* addr_v4();
/**
* Returns the IPv4 sockaddr_in structure
*
* @return The IPv4 sockaddr_in structure
*/
const sockaddr_in* addr_v4() const;
/**
* Returns the IPv6 sockaddr_in6 structure
*
* @return The IPv6 sockaddr_in6 structure
*/
sockaddr_in6* addr_v6();
/**
* Returns the IPv6 sockaddr_in6 structure
*
* @return The IPv6 sockaddr_in6 structure
*/
const sockaddr_in6* addr_v6() const;
/**
* Returns the size of the underlying
* address structure.
*
* @return Size of the underlying address structure
*/
socklen_t size() const;
typedef address_router<V4> v4; /**< Shortcut to the internal IPv4 address router */
typedef address_router<V6> v6; /**< Shortcut to the internal IPv6 address router */
private:
void clear();
private:
template < protocol_family PF >
friend class address_router;
union socket_address {
sockaddr sa_raw;
sockaddr_in sa_in;
sockaddr_in6 sa_in6;
};
socket_address socket_address_ {};
socklen_t size_ = 0;
};
/// @cond MATADOR_DEV
template <>
class address_router<V4>
{
public:
address_router() = delete;
address_router& operator=(const address_router&) = delete;
address_router(const address_router&) = delete;
address_router& operator=(address_router&&) = delete;
address_router(address_router&&) = delete;
static address empty() { return mk_address(INADDR_ANY); };
static address any() { return mk_address(INADDR_ANY); }
static address loopback() { return mk_address(INADDR_LOOPBACK); }
static address broadcast() {return mk_address(INADDR_BROADCAST); }
static address from_ip(const std::string &str) { return from_ip(str.c_str()); }
static address from_ip(const char *str)
{
// now fill in the address info struct
// create and fill the hints struct
if (str == nullptr) {
return address();
}
// get address from string
sockaddr_in addr{};
int ret = os::inet_pton(PF_INET, str, &(addr.sin_addr));
if (ret == 1) {
addr.sin_family = PF_INET;
return address(addr);
} else if (ret == 0) {
detail::throw_logic_error("invalid ip address");
} else {
detail::throw_logic_error_with_errno("invalid ip address: %s", errno);
}
return address();
}
static address from_hostname(const std::string &str) { return from_hostname(str.c_str()); }
static address from_hostname(const char *str)
{
// now fill in the address info struct
// create and fill the hints struct
if (str == nullptr) {
return address();
}
// get address from string
sockaddr_in addr{};
int ret;
if ((ret = os::inet_pton(AF_INET, str, &addr.sin_addr)) == 1) {
addr.sin_family = PF_INET;
} else if (ret == -1) {
detail::throw_logic_error_with_errno("invalid address: %s", errno);
} else { /* 0 == try name */
struct addrinfo hints{};
struct addrinfo *result = nullptr;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* Stream socket */
hints.ai_flags = AI_PASSIVE; /* Numeric or net network hostname */
hints.ai_protocol = 0; /* Any protocol */
hints.ai_canonname = nullptr;
hints.ai_addr = nullptr;
hints.ai_next = nullptr;
int s = getaddrinfo(str, nullptr, &hints, &result);
if (s != 0) {
detail::throw_logic_error_with_gai_errno("invalid ip address (getaddrinfo): %s", s);
}
/* getaddrinfo() returns a list of address structures.
Try each address until we successfully bind(2).
If socket(2) (or bind(2)) fails, we (close the socket
and) try the next address. */
// take first address
memcpy(&addr, result->ai_addr, sizeof(&result->ai_addr));
freeaddrinfo(result); /* No longer needed */
}
addr.sin_family = PF_INET;
memset(addr.sin_zero, '\0', sizeof(addr.sin_zero));
return address(addr);
}
private:
static address mk_address(unsigned int inaddr)
{
sockaddr_in addr{};
memset(&addr, 0, sizeof(addr));
addr.sin_family = PF_INET;
addr.sin_addr.s_addr = htonl(inaddr);
return address(addr);
}
};
template <>
class address_router<V6>
{
public:
address_router() = delete;
address_router& operator=(const address_router&) = delete;
address_router(const address_router&) = delete;
address_router& operator=(address_router&&) = delete;
address_router(address_router&&) = delete;
static address empty() { return mk_address(in6addr_any); }
static address any() { return mk_address(in6addr_any); }
static address loopback() { return mk_address(in6addr_loopback); }
static address broadcast() {return mk_multicast_address(); }
static address from_ip(const std::string &str)
{
return from_ip(str.c_str());
}
static address from_ip(const char *str)
{
// now fill in the address info struct
// create and fill the hints struct
if (str == nullptr) {
return address();
}
// get address from string
sockaddr_in6 addr{};
int ret = os::inet_pton(PF_INET6, str, &(addr.sin6_addr));
if (ret == 1) {
addr.sin6_family = PF_INET6;
return address(addr);
} else if (ret == 0) {
char message_buffer[1024];
os::sprintf(message_buffer, 1024, "INET_PTON (ip): invalid ip address [%s]", str);
detail::throw_logic_error(message_buffer);
} else {
char message_buffer[1024];
int err = errno;
os::sprintf(message_buffer, 1024, "INET_PTON (ip): invalid ip address [%s]: %%s", str);
detail::throw_logic_error_with_errno(message_buffer, err);
}
return address();
}
static address from_hostname(const std::string &str)
{
return from_hostname(str.c_str());
}
static address from_hostname(const char *str)
{
// now fill in the address info struct
// create and fill the hints struct
if (str == nullptr) {
return address();
}
// get address from string
sockaddr_in6 addr{};
int ret = os::inet_pton(PF_INET6, str, &addr.sin6_addr);
if (ret == 1) {
addr.sin6_family = PF_INET6;
} else if (ret == -1) {
char message_buffer[1024];
int err = errno;
os::sprintf(message_buffer, 1024, "INET_PTON (host): invalid ip address [%s] (errno: %d): %%s", str, err);
detail::throw_logic_error_with_errno(message_buffer, err);
} else { /* 0 == try name */
struct addrinfo hints{};
struct addrinfo *result = nullptr;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET6; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* Stream socket */
hints.ai_flags = AI_PASSIVE; /* Numeric or net network hostname */
hints.ai_protocol = 0; /* Any protocol */
hints.ai_canonname = nullptr;
hints.ai_addr = nullptr;
hints.ai_next = nullptr;
int s = getaddrinfo(str, nullptr, &hints, &result);
if (s != 0) {
char message_buffer[1024];
os::sprintf(message_buffer, 1024, "GETADDRINFO (host): invalid ip address [%s] (errno: %d): %%s", str, s);
detail::throw_logic_error_with_errno(message_buffer, s);
}
/* getaddrinfo() returns a list of address structures.
Try each address until we successfully bind(2).
If socket(2) (or bind(2)) fails, we (close the socket
and) try the next address. */
// take first address
memcpy(&addr, result->ai_addr, result->ai_addrlen);
freeaddrinfo(result); /* No longer needed */
}
addr.sin6_family = PF_INET6;
return address(addr);
}
private:
static OOS_NET_API const char *IP6ADDR_MULTICAST_ALLNODES;
static address mk_address(in6_addr in6addr)
{
sockaddr_in6 addr{};
memset(&addr, 0, sizeof(addr));
addr.sin6_family = PF_INET6;
addr.sin6_addr = in6addr;
return address(addr);
}
static address mk_multicast_address()
{
sockaddr_in6 addr{};
memset(&addr, 0, sizeof(addr));
addr.sin6_family = PF_INET6;
os::inet_pton(AF_INET6, IP6ADDR_MULTICAST_ALLNODES, &addr.sin6_addr);
return address(addr);
}
};
/// @endcond
}
#endif //MATADOR_ADDRESS_HPP
+115
View File
@@ -0,0 +1,115 @@
#ifndef MATADOR_ADDRESS_RESOLVER_HPP
#define MATADOR_ADDRESS_RESOLVER_HPP
#include "matador/net/export.hpp"
#include "matador/net/peer.hpp"
#include "matador/net/error.hpp"
#include <vector>
namespace matador {
/// @cond MATADOR_DEV
class tcp;
class udp;
namespace detail {
template < class P >
int determine_socktype();
template <>
OOS_NET_API int determine_socktype<tcp>();
template <>
OOS_NET_API int determine_socktype<udp>();
template < class P >
void initialize_hints(struct addrinfo &hints, int flags) {
memset(&hints,0,sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = determine_socktype<P>();
hints.ai_protocol = 0;
hints.ai_flags = flags;
}
/// @endcond
}
/**
* The address resolver resolves a given host and port
* to a peer object representing the given address
*
* @tparam P Type of protocol
*/
template < class P >
class address_resolver
{
public:
typedef typename P::peer peer; /**< Shortcut to peer type */
/**
* Default constructor
*/
address_resolver() = default;
/**
* Resolves the given host and port to a list
* of valid peers representing the ip addresses
* of the host either in IPv4 or IPv6 format
*
* @param hostname Hostname to resolve
* @param port Port to resolve
* @return A list of peers representing the host and port
*/
std::vector<peer> resolve(const std::string &hostname, const std::string &port);
/**
* Resolves the given host and port to a list
* of valid peers representing the ip addresses
* of the host either in IPv4 or IPv6 format
*
* @param hostname Hostname to resolve
* @param port Port to resolve
* @return A list of peers representing the host and port
*/
std::vector<peer> resolve(const char *hostname, const char *port);
};
/// @cond MATADOR_DEV
template<class P>
std::vector<typename address_resolver<P>::peer> address_resolver<P>::resolve(const std::string &hostname, const std::string &port)
{
return resolve(hostname.c_str(), port.c_str());
}
template < class P >
std::vector<typename address_resolver<P>::peer> address_resolver<P>::resolve(const char *hostname, const char *port)
{
struct addrinfo hints = {};
detail::initialize_hints<P>(hints, AI_PASSIVE);
struct addrinfo* res = nullptr;
struct addrinfo* head = nullptr;
int err = getaddrinfo(hostname, port, &hints, &res);
if (err != 0) {
detail::throw_logic_error_with_gai_errno("error on getaddrinfo: %s", err);
}
head = res;
std::vector<peer> peers;
do {
if (res->ai_family == PF_INET) {
peers.push_back(peer(address(*(struct sockaddr_in*)res->ai_addr)));
} else if (res->ai_family == PF_INET6) {
peers.push_back(peer(address(*(struct sockaddr_in6*)res->ai_addr)));
} // else -> not supported
} while ( (res = res->ai_next) != nullptr);
freeaddrinfo(head);
return peers;
}
/// @endcond
}
#endif //MATADOR_ADDRESS_RESOLVER_HPP
+146
View File
@@ -0,0 +1,146 @@
#ifndef MATADOR_CONNECTOR_HPP
#define MATADOR_CONNECTOR_HPP
#include "matador/net/export.hpp"
#include "matador/net/handler.hpp"
#include "matador/net/handler_creator.hpp"
#include "matador/net/ip.hpp"
#include "matador/logger/logger.hpp"
#include <functional>
namespace matador {
/**
* Connector which initiates a reactor based connection
* to a remote network host identified by a list
* of endpoints.
*
* Once a connection is established a handler is created
* with the given create handler function. The socket
* is passed to the created handler.
*/
class OOS_NET_API connector : public handler, public handler_creator
{
public:
typedef std::function<std::shared_ptr<handler>(tcp::socket sock, tcp::peer endpoint, connector *cnnctr)> t_connect_handler; /**< Shortcut to a function creating a handler on successfully execute to a host */
/**
* Default constructor
*/
connector();
/**
* Creates a new connector with the given
* create handler function
*
* @param on_new_connection Function which creates a handler on new connection
*/
explicit connector(t_connect_handler on_new_connection);
/**
* Initiates a execute to one of the given endpoints within the
* given reactor. Once a connection is established a new handler
* for this connection is created. The new connection is dispatched
* by the reactor.
*
* @param r Reactor to handle the connector and the created connection
* @param endpoints List of endpoints to
*/
void connect(reactor &r, const std::vector<tcp::peer> &endpoints);
/**
* Initiates a execute to one of the given endpoints within the
* given reactor. Once a connection is established a new handler
* for this connection is created with the given function. The new
* connection is dispatched by the reactor.
*
* @param r Reactor to handle the connector and the created connection
* @param endpoints List of endpoints to
* @param on_new_connection Function creating a new handler on new connection
*/
void connect(reactor &r, const std::vector<tcp::peer> &endpoints, t_connect_handler on_new_connection);
/**
* Opens the connector. Actually this does
* nothing at all.
*/
void open() override {}
/**
* Returns the handle of the connector. The result will
* always be zero, because the connector doesn't has a socket
*
* @return Always zero
*/
socket_type handle() const override;
/**
* Does nothing
*/
void on_input() override {}
/**
* Does nothing
*/
void on_output() override {}
/**
* Does nothing
*/
void on_except() override {}
/**
* The timeout call is used to establish the connection.
* Once the connection could be established within this call
* the timeout is canceled otherwise it will be tried again
* after three seconds
*/
void on_timeout() override;
/**
* Does nothing
*/
void on_close() override {}
/**
* Does nothing
*/
void close() override {}
/**
* Always false because connection is established via timeout
*
* @return Always false
*/
bool is_ready_write() const override;
/**
* Always false because connection is established via timeout
*
* @return Always false
*/
bool is_ready_read() const override;
/**
* Notifies the connector that
* this handler was closed.
*
* @param hndlr Closed handler.
*/
void notify_close(handler *hndlr) override;
std::string name() const override;
private:
t_connect_handler connect_handler_;
logger log_;
std::vector<tcp::peer> endpoints_;
};
}
#endif //MATADOR_CONNECTOR_HPP
+127
View File
@@ -0,0 +1,127 @@
#ifndef MATADOR_EVENT_TYPE_HPP
#define MATADOR_EVENT_TYPE_HPP
namespace matador::net {
/**
* Enum representing an event processing mask
* for the reactor io dispatcher. The mask values
* can be bitwise combined and are used to
* check if a handler (@see handler.hpp) is able
* to read, write or accept.
*/
enum class event_type : unsigned {
NONE_MASK = 0, /**< Enum value for no event */
READ_MASK = 1 << 0, /**< Enum value for read mask */
WRITE_MASK = 1 << 1, /**< Enum value for write mask */
EXCEPT_MASK = 1 << 2, /**< Enum value for except mask */
TIMEOUT_MASK = 1 << 3, /**< Enum value for timeout mask */
ACCEPT_MASK = READ_MASK, /**< Enum value for accept mask */
READ_WRITE_MASK = READ_MASK | WRITE_MASK, /**< Enum value for read write mask */
ALL_MASK = READ_MASK | WRITE_MASK | EXCEPT_MASK | ACCEPT_MASK /**< Enum value for all events mask */
};
/**
* event_type operator to concat to event types
* with or operator
*
* @param a Left event_type
* @param b Right event_type
* @return The combined result
*/
inline event_type operator|(event_type a, event_type b)
{
return static_cast<event_type>(static_cast<unsigned int>(a) | static_cast<unsigned int>(b));
}
/**
* event_type operator to concat to event types
* with and operator
*
* @param a Left event_type
* @param b Right event_type
* @return The combined result
*/
inline event_type operator&(event_type a, event_type b)
{
return static_cast<event_type>(static_cast<unsigned int>(a) & static_cast<unsigned int>(b));
}
/**
* event_type not operator
*
* @param a Left event_type
* @return The combined result
*/
inline event_type operator~ (event_type a)
{
return (event_type)~(int)a;
}
/**
* event_type operator to concat to event types
* with xor operator
*
* @param a Left event_type
* @param b Right event_type
* @return The combined result
*/
inline event_type operator^ (event_type a, event_type b)
{
return (event_type)((int)a ^ (int)b);
}
/**
* event_type operator to assign event type
* with or operator
*
* @param a Left event_type
* @param b Right event_type
* @return The combined result
*/
inline event_type& operator|= (event_type& a, event_type b)
{
return (event_type&)((int&)a |= (int)b);
}
/**
* event_type operator to assign event type
* with and operator
*
* @param a Left event_type
* @param b Right event_type
* @return The combined result
*/
inline event_type& operator&= (event_type& a, event_type b)
{
return (event_type&)((int&)a &= (int)b);
}
/**
* event_type operator to assign event type
* with xor operator
*
* @param a Left event_type
* @param b Right event_type
* @return The combined result
*/
inline event_type& operator^= (event_type& a, event_type b)
{
return (event_type&)((int&)a ^= (int)b);
}
/**
* Checks if a specific event_type is set in
* a given event type mask. If type is set
* true is returned.
*
* @param source Event type mask to check.
* @param needle Requested event type
* @return True if event type is set
*/
inline bool is_event_type_set(event_type source, event_type needle)
{
return static_cast<int>(source & needle) > 0;
}
}
#endif //MATADOR_EVENT_TYPE_HPP
+111
View File
@@ -0,0 +1,111 @@
#ifndef MATADOR_FDSET_HPP
#define MATADOR_FDSET_HPP
#include "matador/net/export.hpp"
#include "matador/net/os.hpp"
#include <set>
#include <functional>
#include <cstddef>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <sys/select.h>
#endif
namespace matador {
/**
* This class represents a fd set
* used by the reactor class. There it
* is used in combination with a call
* to select.
*/
class OOS_NET_API fdset
{
public:
fdset(const fdset&) = delete;
fdset& operator=(const fdset&) = delete;
/**
* Default constructor creates
* an empty fd set
*/
fdset();
/**
* Destroys the fd set
*/
~fdset() = default;
fdset(fdset&& x) noexcept;
fdset& operator=(fdset&& x) noexcept;
/**
* Reset all bits to zero
*/
void reset();
/**
* Checks if the given fd is set
* in the fd set.
*
* @param fd Requested fd
* @return True if fd is set
*/
bool is_set(socket_type fd) const;
/**
* Clears the giveb fd from the set.
*
* @param fd fd to clear
*/
void clear(socket_type fd);
/**
* Sets the given fd in the fd set.
*
* @param fd fd to set
*/
void set(socket_type fd);
/**
* Returns the highest fd plus one.
* This is needed for the call to select
*
* @return Highest fd plus one
*/
socket_type maxp1() const;
/**
* Returns the current number of fd in the set
*
* @return Number of fd in set
*/
size_t count() const;
/**
* Checks if the set is empty
*
* @return True if the fd set is empty
*/
bool empty() const;
/**
* Returns a pointer to the underlying fd_set structure
*
* @return Pointer to the underlying fd_set structure
*/
fd_set* get();
private:
typedef std::set<socket_type, std::greater<> > int_set_t;
int_set_t max_fd_set_;
fd_set fd_set_ = {};
};
}
#endif //MATADOR_FDSET_HPP
+138
View File
@@ -0,0 +1,138 @@
#ifndef MATADOR_HANDLER_HPP
#define MATADOR_HANDLER_HPP
#include "matador/net/os.hpp"
#include <memory>
#include <string>
namespace matador::net {
class reactor;
/**
* Base class for all handlers used
* with the reactor. The handler must implement its
* interface to handle input, output, exceptional and
* timeout data.
*/
class handler : public std::enable_shared_from_this<handler>
{
public:
/**
* Virtual destructor
*/
virtual ~handler() = default;
/**
* Interface to open a handler. This
* is called when a handler is registered
* within the reactor
*/
virtual void open() = 0;
/**
* Interface to returns the socket handle
* of the concrete handler implementation.
*
* @return The socket fd
*/
virtual socket_type handle() const = 0;
/**
* Interface handling incoming data
*/
virtual void on_input() = 0;
/**
* Interface handling outgoing data
*/
virtual void on_output() = 0;
/**
* Interface handling exceptional data
*/
virtual void on_except() = 0;
/**
* Interface handling timout data
*/
virtual void on_timeout() = 0;
/**
* Interface called when the handler is closed
*/
virtual void on_close() = 0;
/**
* Interface implementation should close
* the handle gracefully
*/
virtual void close() = 0;
/**
* Interface should return true if there
* is outgoing data
*
* @return True if there is outgoing data
*/
virtual bool is_ready_write() const = 0;
/**
* Interface should return true if there
* is incoming data
*
* @return True if there is incoming data
*/
virtual bool is_ready_read() const = 0;
/**
* Returns the next timeout scheduled
* in the reactor
*
* @return Next timeout
*/
time_t next_timeout() const;
/**
* Returns the timeout interval in
* seconds
*
* @return Timeout interval
*/
time_t interval() const;
/**
* Get the name of the handler.
* The name don't need to be unique.
* It has only an informational purpose.
*
* @return The name of the handler
*/
virtual std::string name() const = 0;
protected:
/**
* Gets the underlying reactor
*
* @return The underlying reactor
*/
reactor* get_reactor() const;
private:
friend class reactor;
void register_reactor(reactor *r);
void schedule(time_t offset, time_t interval);
void cancel_timer();
void calculate_next_timeout(time_t now);
reactor *reactor_ = nullptr;
time_t next_timeout_ = 0;
time_t interval_ = 0;
};
}
#endif //MATADOR_HANDLER_HPP
+23
View File
@@ -0,0 +1,23 @@
#ifndef MATADOR_HANDLER_CREATOR_HPP
#define MATADOR_HANDLER_CREATOR_HPP
#include "matador/net/export.hpp"
namespace matador {
/// @cond MATADOR_DEV
class handler;
class OOS_NET_API handler_creator
{
public:
virtual ~handler_creator() = default;
virtual void notify_close(handler *hndlr) = 0;
};
/// @endcond
}
#endif //MATADOR_HANDLER_CREATOR_HPP
+128
View File
@@ -0,0 +1,128 @@
#ifndef MATADOR_IO_SERVICE_HPP
#define MATADOR_IO_SERVICE_HPP
#include "matador/utils/buffer.hpp"
#include "matador/net/export.hpp"
#include "matador/net/reactor.hpp"
#include "matador/net/acceptor.hpp"
#include "matador/net/connector.hpp"
#include "matador/net/stream_handler.hpp"
namespace matador {
/**
* IO Service is used to encapsulate the an instance
* of the reactor class.
*/
class OOS_NET_API io_service
{
public:
/**
* Creates a io_service
*/
io_service();
~io_service();
/**
* Starts the io_service with the underlying reactor
*/
void run();
/**
* Returns true if the io service is
* running
*
* @return True if service is running
*/
bool is_running() const;
/**
* Shuts down a running service
*/
void shutdown();
/**
* Adds the given acceptor for the
* given peer endpoint and callback.
*
* The callback is called, when a new connection
* was accepted.
*
* @tparam AcceptCallback Type of callback
* @param ac Acceptor used to accept connections
* @param ep Endpoint on which the acceptor will listen
* @param accept_callback Callback when connection was accepted
*/
template < typename AcceptCallback >
void accept(const std::shared_ptr<acceptor>& ac, const tcp::peer &ep, AcceptCallback accept_callback);
/**
* Adds the given acceptor for the
* given callback.
*
* The callback is called, when a new connection
* was accepted.
*
* @tparam AcceptCallback Type of callback
* @param ac Acceptor used to accept connections
* @param accept_callback Callback when connection was accepted
*/
template < typename AcceptCallback >
void accept(const std::shared_ptr<acceptor>& ac, AcceptCallback accept_callback);
/**
* Add the given connector for the given port
* and execute callback.
*
* Once a connection is established the callback
* is called.
*
* @tparam ConnectCallback Type of the callback
* @param co Connector Used to establish the connection
* @param port Port to execute to
* @param connect_callback Callback when connection was established
*/
template < typename ConnectCallback >
void connect(const std::shared_ptr<connector>& co, const std::string &port, ConnectCallback connect_callback);
private:
logger log_;
reactor reactor_;
};
template<typename AcceptCallback>
void io_service::accept(const std::shared_ptr<acceptor>& ac, const tcp::peer &ep, AcceptCallback accept_callback)
{
log_.info("registering acceptor for %s", ep.to_string().c_str());
ac->accecpt(ep, [accept_callback, this](tcp::socket sock, tcp::peer p, acceptor *accptr) {
return std::make_shared<stream_handler>(sock, p, accptr, accept_callback);
});
reactor_.register_handler(ac, event_type::ACCEPT_MASK);
}
template<typename AcceptCallback>
void io_service::accept(const std::shared_ptr<acceptor>& ac, AcceptCallback accept_callback)
{
log_.info("registering acceptor for %s", ac->endpoint().to_string().c_str());
ac->accecpt([accept_callback](tcp::socket sock, tcp::peer p, acceptor *accptr) {
return std::make_shared<stream_handler>(sock, p, accptr, accept_callback);
});
reactor_.register_handler(ac, event_type::ACCEPT_MASK);
}
template<typename ConnectCallback>
void io_service::connect(const std::shared_ptr<connector> &co, const std::string &port, ConnectCallback connect_callback)
{
log_.info("registering connector for localhost:%s", port.c_str());
tcp::resolver resolver;
auto endpoints = resolver.resolve("localhost", port);
co->connect(reactor_, endpoints, [connect_callback](const tcp::socket& sock, const tcp::peer &p, connector *cnnctr) {
return std::make_shared<stream_handler>(sock, p, cnnctr, connect_callback);
});
}
}
#endif //MATADOR_IO_SERVICE_HPP
+67
View File
@@ -0,0 +1,67 @@
#ifndef MATADOR_IO_STREAM_HPP
#define MATADOR_IO_STREAM_HPP
#include <functional>
#include <list>
#include "matador/net/export.hpp"
#include "matador/net/ip.hpp"
namespace matador {
class buffer;
class buffer_view;
/**
* The io stream class is proposed
* to be used with the io_service class
* and provides therefore an interface
* which is used by the io_service
*/
class OOS_NET_API io_stream
{
public:
typedef std::function<void(int ec, long nread)> t_read_handler; /**< Short for function to process read data */
typedef std::function<void(int ec, long nwrite)> t_write_handler; /**< Short for function to prepare data to write */
/**
* This interface is called when data should
* be read from a socket. Once the date was read
* the given read handler is called.
*
* @param buf Buffer to read the data in
* @param read_handler Handler to be called when data was read
*/
virtual void read(buffer_view buf, t_read_handler read_handler) = 0;
/**
* This interface is called when data should be written
* to a socket. Once the data was written the given
* write handler is called.
*
* @param buffers List of buffers containing the data to write
* @param write_handler Handler to be called when the data was written
*/
virtual void write(std::list<buffer_view> buffers, t_write_handler write_handler) = 0;
/**
* Closes the stream
*/
virtual void close_stream() = 0;
/**
* Returns the underlying stream socket
* @return
*/
virtual tcp::socket& stream() = 0;
/**
* Returns a name for the io stream.
*
* @return Name of the io stream
*/
virtual std::string name() const = 0;
};
}
#endif //MATADOR_IO_STREAM_HPP
+130
View File
@@ -0,0 +1,130 @@
#ifndef MATADOR_IP_HPP
#define MATADOR_IP_HPP
#include "matador/net/peer.hpp"
#include "matador/net/address_resolver.hpp"
#include "matador/net/socket.hpp"
#include "matador/net/socket_stream.hpp"
#include "matador/net/socket_acceptor.hpp"
namespace matador::net {
/**
* The tcp class represents all
* settings to handle tcp socket
* connections
*/
class tcp {
public:
typedef peer_base<tcp> peer; /**< Shortcut to a tcp based peer */
typedef socket_stream<tcp> socket; /**< Shortcut to a tcp based socket */
typedef socket_acceptor<tcp> acceptor; /**< Shortcut to a tcp based acceptor */
typedef address_resolver<tcp> resolver; /**< Shortcut to a tcp based address resolver */
/**
* Returns the type of the socket
*
* @return Type of the socket
*/
int type() const { return SOCK_STREAM; }
/**
* Returns the socket protocol
*
* @return Socket protocol
*/
int protocol() const { return IPPROTO_TCP; }
/**
* Returns the socket family
*
* @return Socket family
*/
int family() const { return family_; }
/**
* Creates an instance of tcp representing
* an IPv4 socket
*
* @return IPv4 tcp object
*/
static tcp v4() { return tcp(PF_INET); }
/**
* Creates an instance of tcp representing
* an IPv6 socket
*
* @return IPv6 tcp object
*/
static tcp v6() { return tcp(PF_INET6); }
private:
explicit tcp(int family)
: family_(family)
{}
int family_;
};
/**
* The udp class represents all
* settings to handle udp socket
* connections
*/
class OOS_NET_API udp
{
public:
typedef peer_base<udp> peer; /**< Shortcut to a udp based peer */
typedef socket_stream<udp> socket; /**< Shortcut to a udp based socket */
typedef socket_acceptor<udp> acceptor; /**< Shortcut to a udp based acceptor */
typedef address_resolver<udp> resolver; /**< Shortcut to a udp based address resolver */
/**
* Returns the type of the socket
*
* @return Type of the socket
*/
int type() const { return SOCK_DGRAM; }
/**
* Returns the socket protocol
*
* @return Socket protocol
*/
int protocol() const { return IPPROTO_UDP; }
/**
* Returns the socket family
*
* @return Socket family
*/
int family() const { return family_; }
/**
* Creates an instance of udp representing
* an IPv4 socket
*
* @return IPv4 udp object
*/
static udp v4() { return udp(PF_INET); }
/**
* Creates an instance of udp representing
* an IPv6 socket
*
* @return IPv6 udp object
*/
static udp v6() { return udp(PF_INET6); }
private:
explicit udp(int family)
: family_(family)
{}
int family_;
};
}
#endif //MATADOR_IP_HPP
+45
View File
@@ -0,0 +1,45 @@
#ifndef MATADOR_NET_OS_HPP
#define MATADOR_NET_OS_HPP
#include "matador/net/export.hpp"
#include <cstdio>
#if _WIN32
#include <WinSock2.h>
#endif
#ifdef _WIN32
using socket_type = SOCKET;
#else
using socket_type = int;
#endif
namespace matador {
namespace net {
OOS_NET_API void init();
OOS_NET_API void cleanup();
}
bool is_valid_socket(socket_type fd);
namespace os {
OOS_NET_API int inet_pton(int af, const char *src, void *dst);
OOS_NET_API const char* inet_ntop(int af, const void* src, char* dst, size_t size);
enum class shutdown_type
{
READ = 0,
WRITE = 1,
READ_WRITE = 2
};
OOS_NET_API int shutdown(socket_type fd, shutdown_type type);
OOS_NET_API int close(socket_type fd);
}
}
#endif //MATADOR_NET_OS_HPP
+203
View File
@@ -0,0 +1,203 @@
#ifndef MATADOR_PEER_HPP
#define MATADOR_PEER_HPP
#include "matador/net/address.hpp"
#ifdef _WIN32
//#include <ws2tcpip.h>
#else
#include <netinet/in.h>
#endif
#include <cstring>
namespace matador {
/**
* The peer_base class acts like the holder
* of network endpoint information like socket
* address and port. The template argument
* sets the protocol type either TCP or UDP.
*
* @tparam P Protocol type
*/
template < class P >
class peer_base
{
public:
typedef P protocol_type; /**< Short to protocol type */
/**
* Default constructor
*/
peer_base() = default;
/**
* Creates a peer from a given address. Port is set
* to zero.
*
* @param addr Address to create the peer from
*/
explicit peer_base(address addr)
: addr_(std::move(addr))
{}
/**
* Creates a peer from a given address and port
*
* @param addr Address to create the peer from
* @param port Port of the endpoint
*/
peer_base(address addr, unsigned short port)
: addr_(std::move(addr))
{
addr_.port(port);
}
/**
* Copy creates a peer from the given peer
*
* @param x Peer to copy from
*/
peer_base(const peer_base &x)
: addr_(x.addr_)
{}
/**
* Move creates a peer from a given peer
*
* @param x Peer to move from
*/
peer_base(peer_base &&x) noexcept
: addr_(std::move(x.addr_))
{}
/**
* Copy assigns a given peer to this peer
*
* @param x Peer to assign
* @return The assigned peer
*/
peer_base& operator=(const peer_base &x)
{
addr_ = x.addr_;
return *this;
}
/**
* Assign moves the given peer to this peer
*
* @param x The peer to move assign
* @return The moved peer
*/
peer_base& operator=(peer_base &&x) noexcept
{
addr_ = std::move(x.addr_);
return *this;
}
/**
* Destructor
*/
~peer_base() = default;
/**
* Returns the current port of the peer.
*
* @return The current port
*/
int port() const { return addr_.port(); }
/**
* Returns the current IP protocol of the peer
* address which is either IPv4 or IPv6
*
* @return The current IP protocol
*/
protocol_type protocol() const
{
if (addr_.is_v4()) {
return protocol_type::v4();
} else {
return protocol_type::v6();
}
}
/**
* Returns the raw pointer to the sockaddr structure
*
* @return The raw pointer to the sockaddr structure
*/
sockaddr* data()
{
return addr_.addr();
}
/**
* Returns the raw pointer to the sockaddr structure
*
* @return The raw pointer to the sockaddr structure
*/
const sockaddr* data() const
{
return addr_.addr();
}
/**
* Returns the size of the underlying sockaddr structure
*
* @return The size of the underlying sockaddr structure
*/
size_t size() const
{
return addr_.size();
}
/**
* Returns a reference to the address
*
* @return A reference to the address
*/
address& addr()
{
return addr_;
}
/**
* Returns a reference to the address
*
* @return A reference to the address
*/
const address& addr() const
{
return addr_;
}
/**
* Converts the peer endpoint to a string in
* the format [ip]:[port]
*
* @return Returns a string representation of the peer
*/
std::string to_string() const
{
char addstr[INET6_ADDRSTRLEN + 8];
const char *name;
if (addr().is_v4()) {
name = os::inet_ntop(addr_.addr()->sa_family, &addr_.addr_v4()->sin_addr, addstr, INET6_ADDRSTRLEN);
} else {
name = os::inet_ntop(addr_.addr()->sa_family, &addr_.addr_v6()->sin6_addr, addstr, INET6_ADDRSTRLEN);
}
size_t pos = strlen(name);
snprintf(addstr+pos, INET6_ADDRSTRLEN+8-pos, ":%d", addr_.port());
return addstr;
}
private:
address addr_;
};
}
#endif //MATADOR_PEER_HPP
+117
View File
@@ -0,0 +1,117 @@
#ifndef REACTOR_HPP
#define REACTOR_HPP
#include "matador/net/event_type.hpp"
#include "matador/net/select_fd_sets.hpp"
#include "matador/net/socket_interrupter.hpp"
#include "matador/utils/leader_follower_thread_pool.hpp"
#include <unordered_map>
#include <memory>
#include <atomic>
#include <mutex>
#include <chrono>
#include <queue>
#include <list>
namespace matador::net {
class handler;
class reactor {
public:
using handler_ptr = std::shared_ptr<handler>;
using handler_weak_ptr = std::weak_ptr<handler>;
struct Statistics {
std::atomic<size_t> total_events_handled_{0};
std::atomic<size_t> read_events_{0};
std::atomic<size_t> write_events_{0};
std::atomic<size_t> timer_events_{0};
std::atomic<size_t> errors_{0};
};
struct HandlerEntry {
explicit HandlerEntry(handler_ptr h, event_type et)
: handler(std::move(h)), events(et) {}
handler_ptr handler;
event_type events;
time_t next_timeout{0};
time_t interval{0};
bool marked_for_deletion{false};
};
reactor();
~reactor();
reactor(const reactor&) = delete;
reactor& operator=(const reactor&) = delete;
void register_handler(const handler_ptr& h, event_type type);
void unregister_handler(const handler_ptr& h, event_type type);
void schedule_timer(const handler_ptr& h, time_t offset, time_t interval);
void cancel_timer(const handler_ptr& h);
void run();
void handle_events();
void shutdown();
bool is_running() const { return running_; }
select_fdsets fdsets() const;
void mark_handler_for_delete(const handler_ptr& h);
void activate_handler(const handler_ptr& h, event_type ev);
void deactivate_handler(const handler_ptr& h, event_type ev);
const Statistics& get_statistics() const { return stats_; }
private:
struct TimerEvent {
time_t timeout;
handler_weak_ptr handler;
bool operator>(const TimerEvent& other) const {
return timeout > other.timeout;
}
};
using handler_map = std::unordered_map<handler*, std::unique_ptr<HandlerEntry>>;
using timer_queue = std::priority_queue<TimerEvent, std::vector<TimerEvent>, std::greater<>>;
void prepare_select_bits(time_t& timeout, select_fdsets& fd_sets) const;
bool process_events(const select_fdsets& fd_sets, time_t now);
void process_timers(time_t now);
void cleanup_deleted_handlers();
void handle_error(const std::string& operation, const handler_ptr& h);
int perform_select(struct timeval* timeout, select_fdsets& fd_sets);
bool check_interruption(const select_fdsets& fd_sets);
void safe_handler_operation(const handler_ptr& h, const std::string& op_name,
const std::function<void()>& operation);
HandlerEntry* find_handler_entry(const handler_ptr& h);
void remove_handler_entry(const handler_ptr& h);
void update_handler_events(HandlerEntry* entry, event_type ev, bool activate);
private:
handler_map handlers_;
timer_queue timers_;
std::atomic<bool> running_{false};
std::atomic<bool> shutdown_requested_{false};
mutable std::shared_mutex handlers_mutex_;
std::mutex timers_mutex_;
std::condition_variable shutdown_cv_;
logger log_;
Statistics stats_;
leader_follower_thread_pool thread_pool_;
socket_interrupter interrupter_;
static constexpr std::chrono::seconds CLEANUP_INTERVAL{60};
time_t last_cleanup_{0};
};
} // namespace matador
#endif //REACTOR_HPP
+132
View File
@@ -0,0 +1,132 @@
#ifndef MATADOR_SELECT_FDSETS_HPP
#define MATADOR_SELECT_FDSETS_HPP
#include "matador/net/fdset.hpp"
namespace matador {
/**
* This class represents three fd sets
* needed for the system call to select.
*/
class select_fdsets
{
public:
/**
* These enum values are used
* to identify and access the three
* different fd sets
*/
typedef enum {
read_type = 0, /**< Enum value for the read fd set */
write_type = 1, /**< Enum value for the write fd set */
except_type /**< Enum value for the exceptional fd set */
} fdset_type;
/**
* Returns the highest fd value over
* all three fd sets plus one. This is used
* by the system call to select.
*
* @return Highest fd value over all sets
*/
socket_type maxp1() const;
/**
* Returns the fdset identified by the given type
*
* @param type Requested fdset type
* @return The requested fd set
*/
fdset& fd_set(fdset_type type);
/**
* Returns the read fd set.
*
* @return The read fd set.
*/
fdset& read_set();
/**
* Returns the read fd set.
*
* @return The read fd set.
*/
const fdset& read_set() const;
/**
* Returns the write fd set.
*
* @return The write fd set.
*/
fdset& write_set();
/**
* Returns the write fd set.
*
* @return The write fd set.
*/
const fdset& write_set() const;
/**
* Returns the exceptional fd set.
*
* @return The exceptional fd set.
*/
fdset& except_set();
/**
* Returns the exceptional fd set.
*
* @return The exceptional fd set.
*/
const fdset& except_set() const;
/**
* Reset all bits in all three sets to zero
*/
void reset();
/**
* Resets the fd set identified by the
* given type
*
* @param type Type of set to reset
*/
void reset(fdset_type type);
/**
* Checks if the given fd is set in the fdset
* identified by the given type. If fd
* is set true returned.
*
* @param type Requested fd set type
* @param fd fd to check
* @return True if fd is set in fdset
*/
bool is_set(fdset_type type, int fd) const;
/**
* Clears the given fd is set in the fdset
* identified by the given type.
*
* @param type Requested fd set type
* @param fd fd to clear
*/
void clear(fdset_type type, int fd);
/**
* Sets the bit for the given fd is set
* in the fdset identified by the given type.
*
* @param type Requested fd set type
* @param fd fd to set
*/
void set(fdset_type type, int fd);
private:
fdset fdsets_[3];
};
}
#endif //MATADOR_SELECT_FDSETS_HPP
+168
View File
@@ -0,0 +1,168 @@
#ifndef MATADOR_SOCKET_HPP
#define MATADOR_SOCKET_HPP
#include <stdexcept>
#include <sstream>
#ifdef _WIN32
#else
#include <sys/socket.h>
#include <fcntl.h>
#include <netdb.h>
#include <arpa/inet.h>
#endif
#include <cstring>
namespace matador {
/**
* Base class for several kind of socket
* classes (acceptor, stream) representing a
* socket. The protocol is selected via the
* template parameter (/sa tcp and udp classes)
*
* @tparam P Protocol type
*/
template < class P >
class socket_base
{
public:
typedef P protocol_type; /**< Shortcut to the protocol type */
typedef typename P::peer peer_type; /**< Shortcut to the peer type */
/**
* Creates a socket for a specific given
* protocol
*
* @param protocol Initial protocol
*/
explicit socket_base(const protocol_type &protocol);
/**
* Creates a socket with the given peer
*
* @param peer Peer used to initialize the socket
*/
explicit socket_base(const peer_type &peer);
/**
* Opens a socket. On success a positive socket id (fd)
* is returned. In case of error -1 is returned and
* errno is set.
*
* @param protocol Protocol for which a socket is created
* @return The socket fd or -1 on error
*/
socket_type open(const protocol_type &protocol);
/**
* Closes the open socket
*/
void close();
/**
* Returns true if the socket is open (created)
*
* @return True on open socket
*/
bool is_open() const;
/**
* Releases the socket fd and sets
* the internal socket to zero (0).
*
* After the socket is released the user
* is in charge to take of the socket
*
* @return The released socket fd
*/
socket_type release();
/**
* Connect to the given peer. If the connection
* could be established true is returned,
* otherwise false is returned and errno is set.
*
* @param p Peer to execute to
* @return True on successful connection
*/
bool connect(const typename protocol_type::peer &p);
/**
* Sets the socket either blocking (false) or
* non blocking (true).
*
* @param nb True sets the socket non blocking false blocking
*/
void non_blocking(bool nb);
/**
* Returns true if the socket is non blocking
* otherwise returns false
* @return True if socket is non blocking
*/
bool non_blocking() const;
/**
* Set or unset the cose on exec flag
* for the socket
*
* @param nb Flag to set or unset cloexec option
*/
void cloexec(bool nb);
/**
* Returns true if close on exec option is set
*
* @return True on set cloexec option
*/
bool cloexec() const;
/**
* Sets a socket option represented by name. If option
* was successfully set true is returned. Otherwise false
* and errno ist set.
*
* @param name Option name
* @param value Flag to set or unset the option
* @return True on success
*/
bool options(int name, bool value);
/**
* Returns the underlying socket fd
*
* @return Underlying socket fd
*/
socket_type id() const;
/**
* Assigns the given socket fd to this
* socket. If the socket is already opened
* an exception is thrown.
*
* @param sock The socket fd to assign
*/
void assign(socket_type sock);
protected:
/// @cond MATADOR_DEV
socket_base() = default;
~socket_base() = default;
socket_type open(int family, int type, int protocol);
socket_type sock_ = 0;
std::string name_;
#ifdef _WIN32
bool is_nonblocking_ = false;
#endif
/// @endcond
};
}
#include "matador/net/socket.tpp"
#endif //MATADOR_SOCKET_HPP
+257
View File
@@ -0,0 +1,257 @@
#include "matador/net/socket.hpp"
#include "matador/net/socket_stream.hpp"
#include "matador/net/os.hpp"
#include "matador/net/error.hpp"
#include "matador/net/ip.hpp"
#include <cstring>
namespace matador {
#define throw_logic_error(msg) \
do { \
std::stringstream str; \
str << msg; \
throw std::logic_error(str.str()); \
} while(false);
template < class P >
socket_base<P>::socket_base(const protocol_type &protocol)
{
open(protocol);
}
template < class P >
socket_base<P>::socket_base(const peer_type &peer)
{
open(peer.protocol());
}
template < class P >
socket_type socket_base<P>::open(const protocol_type &protocol)
{
return open(protocol.family(), protocol.type(), protocol.protocol());
}
template < class P >
void socket_base<P>::close()
{
if (sock_ <= 0) {
return;
}
//os::shutdown(sock_, os::shutdown_type::READ_WRITE);
os::close(sock_);
sock_ = 0;
}
template < class P >
bool socket_base<P>::is_open() const
{
return sock_ > 0;
}
template < class P >
socket_type socket_base<P>::release()
{
auto tmp_fd = sock_;
sock_ = 0;
return tmp_fd;
}
template < class P >
bool socket_base<P>::connect(const typename protocol_type::peer &p)
{
return ::connect(sock_, p.data(), static_cast<int>(p.size())) == 0;
}
template < class P >
void socket_base<P>::non_blocking(bool nb)
{
#ifdef WIN32
unsigned long nonblock = nb ? 0 : 1;
// fcntl doesn't do the right thing, but the similar ioctl does
// warning: is that still true? and does it the right thing for
// set blocking as well?
if (ioctlsocket(sock_, FIONBIO, &nonblock) != 0) {
throw std::logic_error("ioctlsocket: couldn't set flags");
}
is_nonblocking_ = nb;
#else
int val = fcntl(sock_, F_GETFL, 0);
if (val < 0) {
throw std::logic_error("fcntl: couldn't get flags");
}
int flag = (nb ? O_NONBLOCK : 0);
if (fcntl(sock_, F_SETFL, val | flag) < 0) {
std::string err(strerror(errno));
throw std::logic_error("fcntl: couldn't set flags (" + err + ")");
}
#endif
}
template < class P >
bool socket_base<P>::non_blocking() const
{
#ifdef _WIN32
return is_nonblocking_;
#else
int val = fcntl(sock_, F_GETFL, 0);
if (val < 0) {
std::string err(strerror(errno));
throw std::logic_error("fcntl: couldn't get flags (" + err + ")");
}
return (val & O_NONBLOCK) > 0;
#endif
}
template < class P >
void socket_base<P>::cloexec(bool nb)
{
#ifdef WIN32
unsigned long cloexec = 1;
// fcntl doesn't do the right thing, but the simular ioctl does
// warning: is that still true? and does it the right thing for
// set blocking as well?
if (ioctlsocket(sock_, FIONBIO, &cloexec) != 0) {
throw std::logic_error("ioctlsocket: couldn't set flags");
}
#else
int val = fcntl(sock_, F_GETFL, 0);
if (val < 0) {
throw std::logic_error("fcntl: couldn't get flags");
}
int flag = (nb ? FD_CLOEXEC : 0);
if (fcntl(sock_, F_SETFL, val | flag) < 0) {
std::string err(strerror(errno));
throw std::logic_error("fcntl: couldn't set flags (" + err + ")");
}
#endif
}
template < class P >
bool socket_base<P>::cloexec() const
{
int val = fcntl(sock_, F_GETFL, 0);
if (val < 0) {
std::string err(strerror(errno));
throw std::logic_error("fcntl: couldn't get flags (" + err + ")");
}
return (val & FD_CLOEXEC) > 0;
}
template < class P >
bool socket_base<P>::options(int name, bool value)
{
const char flag = static_cast<char>(value ? 1 : 0);
return setsockopt(sock_, IPPROTO_TCP, name, &flag, sizeof(flag)) == 0;
}
template < class P >
socket_type socket_base<P>::id() const
{
return sock_;
}
template < class P >
void socket_base<P>::assign(socket_type sock)
{
if (is_open()) {
throw std::logic_error("couldn't assign: socket already opened");
}
struct sockaddr_in addr{};
socklen_t addr_size = sizeof(struct sockaddr_in);
if (getpeername(sock, (struct sockaddr *)&addr, &addr_size) == 0) {
//char *clientip = new char[20];
char s[INET6_ADDRSTRLEN];
os::inet_ntop(addr.sin_family, &addr.sin_addr, s, sizeof s);
#ifdef _WIN32
// strcpy_s(clientip, 20, s);
#else
// strcpy(clientip, s);
#endif
}
sock_ = sock;
}
template < class P >
socket_type socket_base<P>::open(int family, int type, int protocol)
{
sock_ = ::socket(family, type, protocol);
return sock_;
}
template < class P >
socket_type connect(socket_stream<P> &stream, const char* hostname, unsigned short port)
{
char portstr[6];
sprintf(portstr, "%d", port);
// const char* portname = "daytime";
struct addrinfo hints = {};
memset(&hints,0,sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo* res = nullptr;
struct addrinfo* head = nullptr;
int err = getaddrinfo(hostname, portstr, &hints, &res);
if (err != 0) {
detail::throw_logic_error_with_gai_errno("failed to resolve local socket address: %s", err);
}
head = res;
socket_type conn_fd = 0;
socket_type ret = 0;
do {
conn_fd = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (!is_valid_socket(conn_fd)) {
// error, try next one
continue;
}
ret = ::connect(conn_fd, res->ai_addr, res->ai_addrlen);
if (ret == 0) {
// success
stream.assign(conn_fd);
break;
// } else {
// throw_logic_error("couldn't execute: " << strerror(errno));
}
// bind error, close and try next one
os::shutdown(conn_fd, os::shutdown_type::READ_WRITE);
} while ( (res = res->ai_next) != nullptr);
if (res == nullptr) {
throw_logic_error("couldn't execute to " << hostname << ":" << port);
}
freeaddrinfo(head);
return ret;
}
template < class P >
int connect(socket_stream<P> &stream, peer_base<P> endpoint)
{
auto pt = endpoint.protocol();
auto fd = ::socket(pt.family(), pt.type(), pt.protocol());
if (!is_valid_socket(fd)) {
return static_cast<int>(fd);
}
auto ret = ::connect(fd, endpoint.data(), static_cast<int>(endpoint.size()));
if (ret == 0) {
stream.assign(fd);
} else {
os::shutdown(fd, os::shutdown_type::READ_WRITE);
}
return ret;
}
}
+357
View File
@@ -0,0 +1,357 @@
#ifndef MATADOR_SOCKET_ACCEPTOR_HPP
#define MATADOR_SOCKET_ACCEPTOR_HPP
#include "matador/net/socket.hpp"
namespace matador {
/**
* The socket acceptor class provides the
* base functionality of the socket base class
* plus acceptor specific functions like
* listen, bind or accept
*
* @tparam P Socket protocol type
*/
template < class P >
class socket_acceptor : public socket_base<P>
{
public:
typedef socket_base<P> base; /**< Shortcut to base socket type */
typedef socket_stream<P> stream_type; /**< Shortcut to socket stream type */
typedef typename base::protocol_type protocol_type; /**< Shortcut to protocol type */
typedef typename base::peer_type peer_type; /**< Shortcut to peer type */
/**
* Default constructor
*/
socket_acceptor() = default;
/**
* Constructs a socket acceptor for
* the given peer
*
* @param peer Peer to construct an acceptor from
*/
explicit socket_acceptor(peer_type &peer);
/**
* Constructs an acceptor for given hostname and port
*
* @param hostname Hostname of the accepting endpoint
* @param port Portnumber of the accepting endpoint
*/
socket_acceptor(const char* hostname, unsigned short port);
/**
* Creates a listening socket and binds
* the given hostname and port to it
*
* Returns zero (0) on success and -1 on error
* with errno set
*
* @param hostname Hostname to bind
* @param port Portnumber to bind
* @return Returns zero (0) on success.
*/
int bind(const char* hostname, unsigned short port);
/**
* Creates a listening socket and binds
* the given peer endpoint to it
*
* Returns zero (0) on success and -1 on error
* with errno set
*
* @param peer Binds the given peer endpoint to the socket
* @return Returns zero (0) on success.
*/
int bind(peer_type &peer);
/**
* Start listening to the bound endpoint using
* the internally created socket.
*
* Returns zero (0) on success and -1 on error
* with errno set
*
* @param backlog Number of backlog
* @return Returns zero (0) on success.
*/
int listen(int backlog);
/**
* Returns a pointer to the underlying
* concrete internet address of the given
* socket address structure
*
* @param sa Socket address
* @return Pointer to the internet address
*/
void* get_in_addr(struct sockaddr *sa);
/**
* Returns the port number of the given
* socket address structure
*
* @param sa Socket address
* @return The port number
*/
unsigned short get_port(struct sockaddr *sa);
/**
* Get the remote address and port as string
* representation.
*
* @param remote_addr Remote socket address structure
* @return String representation of the remote address
*/
std::string get_remote_address(struct sockaddr_storage &remote_addr);
/**
* Accept a connection and assign the socket descriptor
* to the given socket stream.
*
* Once the descriptor is assigned to the stream it
* can be used to read and write data to it.
*
* @param stream Stream to use after connection was accepted
* @return The fd of the new connection
*/
int accept(stream_type &stream);
/**
* Accept a connection and assign the socket descriptor
* to the given socket stream.
*
* Once the descriptor is assigned to the stream it
* can be used to read and write data to it.
*
* The given peer endpoint is filled with the
* address information of the remote endpoint.
*
* @param stream Stream to use after connection was accepted
* @param endpoint Will be filled with the remote endpoint information
* @return The fd of the new connection
*/
int accept(stream_type &stream, peer_type &endpoint);
/**
* Sets or clears the reuse address flag. If the
* given value is true the flag is set otherwise the
* flag is cleared.
*
* @param reuse Indicates if the reuse address flag should be set
* @return 0 if setting was successful, -1 on error
*/
int reuse_address(bool reuse);
/**
* Returns true if the flag is set otherwise
* false is returned.
*
* @return True if flag is set
*/
bool reuse_address() const;
};
/// @cond MATADOR_DEV
template < class P >
socket_acceptor<P>::socket_acceptor(peer_type &peer)
: socket_base<P>(peer)
{
bind(peer);
}
template < class P >
socket_acceptor<P>::socket_acceptor(const char* hostname, unsigned short port)
{
bind(hostname, port);
}
template < class P >
int socket_acceptor<P>::bind(const char* hostname, unsigned short port)
{
char portstr[6];
sprintf(portstr, "%d", port);
// const char* portname = "daytime";
struct addrinfo hints = {};
memset(&hints,0,sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
struct addrinfo* res = nullptr;
struct addrinfo* head = nullptr;
int err = getaddrinfo(hostname, portstr, &hints, &res);
if (err != 0) {
throw_logic_error("failed to resolve local socket address (error: " << err << ")");
}
head = res;
int listenfd = 0;
int ret = 0;
const int on = 1;
do {
listenfd = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (listenfd < 0) {
// error, try next one
continue;
}
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
throw std::logic_error(strerror(errno));
}
ret = ::bind(listenfd, res->ai_addr, res->ai_addrlen);
if (ret == 0) {
// success
this->assign(listenfd);
break;
} else {
throw_logic_error("couldn't bind to " << hostname << ":" << port << ": " << strerror(errno));
}
// bind error, close and try next one
this->close();
} while ( (res = res->ai_next) != nullptr);
if (res == nullptr) {
throw_logic_error("couldn't bind to " << hostname << ":" << port);
}
freeaddrinfo(head);
return ret;
}
template < class P >
int socket_acceptor<P>::bind(peer_type &peer)
{
socket_type listen_fd = ::socket(peer.protocol().family(), peer.protocol().type(), peer.protocol().protocol());
if (!is_valid_socket(listen_fd)) {
// error, try next one
return static_cast<int>(listen_fd);
}
#ifdef _WIN32
const char on = 1;
#else
const int on = 1;
#endif
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
detail::throw_logic_error_with_errno("setsockopt error: %s", errno);
}
int ret = ::bind(listen_fd, peer.data(), static_cast<int>(peer.size()));
if (ret == 0) {
// success
this->assign(listen_fd);
} else {
detail::throw_logic_error_with_errno("couldn't bind fd: %s", errno);
}
size_t s = peer.size();
ret = getsockname(this->id(), peer.data(), (socklen_t*)&s);
return ret;
}
template < class P >
int socket_acceptor<P>::listen(int backlog)
{
return ::listen(this->id(), backlog);
}
template < class P >
void* socket_acceptor<P>::get_in_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
} else {
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
}
template < class P >
unsigned short socket_acceptor<P>::get_port(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET) {
return ntohs(((struct sockaddr_in*)sa)->sin_port);
} else {
return ntohs(((struct sockaddr_in6*)sa)->sin6_port);
}
}
template < class P >
std::string socket_acceptor<P>::get_remote_address(struct sockaddr_storage &remote_addr)
{
char s[INET6_ADDRSTRLEN];
os::inet_ntop(remote_addr.ss_family,
get_in_addr((struct sockaddr *)&remote_addr),
s, sizeof s);
std::stringstream ra;
ra << s << ":" << get_port((struct sockaddr *)&remote_addr);
return ra.str();
}
template < class P >
int socket_acceptor<P>::accept(stream_type &stream)
{
struct sockaddr_storage remote_addr = {};
// address_type remote_addr;
socklen_t addrlen = sizeof(remote_addr);
auto fd = ::accept(this->id(), (struct sockaddr *)&remote_addr, &addrlen);
if (is_valid_socket(fd)) {
stream.assign(fd);
stream.non_blocking(true);
stream.cloexec(true);
// } else {
// detail::throw_logic_error_with_errno("accept failed: %s", errno);
}
return static_cast<int>(fd);
}
template<class P>
int socket_acceptor<P>::accept(stream_type &stream, peer_type &endpoint)
{
auto addr_len = static_cast<socklen_t>(endpoint.size());
auto fd = ::accept(this->id(), endpoint.data(), &addr_len);
if (is_valid_socket(fd)) {
stream.assign(fd);
stream.non_blocking(true);
stream.cloexec(true);
// } else {
// detail::throw_logic_error_with_errno("accept failed: %s", errno);
}
return static_cast<int>(fd);
}
template < class P >
int socket_acceptor<P>::reuse_address(bool reuse)
{
const int option(reuse ? 1 : 0);
return setsockopt(this->id(), SOL_SOCKET, SO_REUSEADDR, (char*)&option, sizeof(option));
}
template < class P >
bool socket_acceptor<P>::reuse_address() const
{
size_t option {};
socklen_t i;
i = sizeof(option);
getsockopt(this->id(), SOL_SOCKET, SO_REUSEADDR, (char*)&option, &i);
return option > 0;
}
/// @endcond
}
#endif //MATADOR_SOCKET_ACCEPTOR_HPP
@@ -0,0 +1,38 @@
#ifndef MATADOR_SOCKET_INTERRUPTER_HPP
#define MATADOR_SOCKET_INTERRUPTER_HPP
#include "matador/net/ip.hpp"
// #include "matador/logger/logger.hpp"
#include <array>
namespace matador::net {
/// @cond MATADOR_DEV
class OOS_NET_API socket_interrupter
{
public:
socket_interrupter();
~socket_interrupter();
socket_type socket_id() const;
void interrupt();
bool reset();
private:
matador::tcp::socket server_;
matador::tcp::socket client_;
// matador::logger log_;
std::array<char, 1> indicator_ = { { 0 } };
std::array<char, 1> consumer_ = {};
};
/// @endcond
}
#endif //MATADOR_SOCKET_INTERRUPTER_HPP
+95
View File
@@ -0,0 +1,95 @@
#ifndef MATADOR_SOCKET_STREAM_HPP
#define MATADOR_SOCKET_STREAM_HPP
#ifdef _WIN64
#define ssize_t __int64
#elif _WIN32
#define ssize_t long
#endif
#include "matador/net/socket.hpp"
namespace matador {
/**
* The class represents a read write socket. It is
* independent to the protocol type (UDP or TCP).
*
* It provides to methods receive and send to read
* or write data to an open socket.
*
* @tparam P Protocol type of the socket
*/
template < class P >
class socket_stream : public socket_base<P>
{
public:
typedef socket_base<P> base; /**< Shortcut to socket base class */
typedef typename base::protocol_type protocol_type; /**< Shortcut to protocol type */
typedef typename base::peer_type peer_type; /**< Shortcut to the peer type */
/**
* Creates an uninitialized socket stream
*/
socket_stream() = default;
/**
* Creates a socket stream for the given
* protocol type.
*
* @param protocol Type of the protocol
*/
explicit socket_stream(const protocol_type &protocol);
/**
* Receives data from the underlying open socket.
* All received data is stored in the given buffer.
* The limit of the data to receive is the size of
* the buffer
*
* Number of bytes is returned. If returned value is zero (0)
* the socket was closed. If the return value is -1 an error
* occurred or the socket was blocked (in case of nonblocking)
* The concrete error code and message can be retrieved with
* errno.
*
* @tparam Buffer type of the buffer object.
* @param buffer The buffer object.
* @return The number of bytes received, -1 on error or 0 on close
*/
template < class Buffer >
ssize_t receive(Buffer &buffer);
/**
*
* @tparam Buffer type of the buffer object.
* @param buffer The buffer object.
* @return The number of bytes sent, -1 on error or 0 on close
*/
template < class Buffer >
ssize_t send(const Buffer &buffer);
};
/// @cond MATADOR_DEV
template < class P >
socket_stream<P>::socket_stream(const protocol_type &protocol)
: base(protocol)
{}
template < class P >
template < class Buffer >
ssize_t socket_stream<P>::receive(Buffer &buffer)
{
return ::recv(this->id(), buffer.data(), static_cast<int>(buffer.capacity()), 0);
}
template < class P >
template < class Buffer >
ssize_t socket_stream<P>::send(const Buffer &buffer)
{
return ::send(this->id(), buffer.data(), static_cast<int>(buffer.size()), 0);
}
/// @endcond
}
#endif //MATADOR_SOCKET_STREAM_HPP
+96
View File
@@ -0,0 +1,96 @@
#ifndef MATADOR_STREAM_HANDLER_HPP
#define MATADOR_STREAM_HANDLER_HPP
#include "matador/utils/buffer_view.hpp"
#include "matador/logger/logger.hpp"
#include "matador/net/export.hpp"
#include "matador/net/handler.hpp"
#include "matador/net/ip.hpp"
#include "matador/net/io_stream.hpp"
#include <atomic>
#include <mutex>
namespace matador {
class handler_creator;
/**
* The stream_handler class implements the
* handler and io_stream interface and is
* used with the io_service to handle socket
* connections more easily in comparison
* with the plain reactor boilerplate.
*
* Instances of the class are used internally
* within the io_service hiding the read and write
* wiring. The user just use the interface
* provided by the io_service to setup
* a server.
*/
class OOS_NET_API stream_handler : public handler, public io_stream
{
public:
typedef std::function<void(tcp::peer, io_stream &)> t_init_handler; /**< Shortcut to the initialize function */
public:
/**
* Creates a new stream_handler for the given socket.
* The acceptor is the link to the creation source where
* the given init function is called when the handler
* is initialized
*
* @param sock Socket to read and write on
* @param endpoint Endpoint of the connection
* @param creator Pointer to the creating handler class
* @param init_handler Initialize function
*/
stream_handler(tcp::socket sock, tcp::peer endpoint, handler_creator *creator, t_init_handler init_handler);
void open() override;
socket_type handle() const override;
void on_input() override;
void on_output() override;
void on_except() override {}
void on_timeout() override {}
void on_close() override;
void close() override;
bool is_ready_write() const override;
bool is_ready_read() const override;
void read(buffer_view buf, t_read_handler read_handler) override;
void write(std::list<buffer_view> buffers, t_write_handler write_handler) override;
void close_stream() override;
tcp::socket &stream() override;
std::string name() const override;
private:
logger log_;
tcp::socket stream_;
tcp::peer endpoint_;
std::string name_;
buffer_view read_buffer_;
std::list<buffer_view> write_buffers_;
handler_creator *creator_ = nullptr;
t_init_handler init_handler_;
t_read_handler on_read_;
t_write_handler on_write_;
std::atomic_bool is_ready_to_read_ { false };
std::atomic_bool is_ready_to_write_ { false };
mutable std::mutex mutex_;
};
}
#endif //MATADOR_STREAM_HANDLER_HPP