From 78758f35bf63bef5f4279a465ea7dd726b2a6ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com> Date: Mon, 31 Jan 2022 12:06:27 +0100 Subject: [PATCH] Begin socket handling: core functionalities are available On provides a way to create - server socket (listening to a given port) - accept connections - client socket that can connect to an host:port these three functionalities are the only ones accessible from pugs' language On the other hand, reading and writing are provided by low level calls. On can exchange values or arrays of values (pointers+size). It remains unclear if we should allow transfer of higher level objects, since socket exchanges should be generally used between different codes? --- src/language/modules/CMakeLists.txt | 1 + src/language/modules/ModuleRepository.cpp | 2 + src/language/modules/SocketModule.cpp | 43 ++++++ src/language/modules/SocketModule.hpp | 28 ++++ src/utils/CMakeLists.txt | 3 +- src/utils/Socket.cpp | 177 ++++++++++++++++++++++ src/utils/Socket.hpp | 79 ++++++++++ 7 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 src/language/modules/SocketModule.cpp create mode 100644 src/language/modules/SocketModule.hpp create mode 100644 src/utils/Socket.cpp create mode 100644 src/utils/Socket.hpp diff --git a/src/language/modules/CMakeLists.txt b/src/language/modules/CMakeLists.txt index f7f1baeaf..c4edd2505 100644 --- a/src/language/modules/CMakeLists.txt +++ b/src/language/modules/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(PugsLanguageModules MeshModule.cpp ModuleRepository.cpp SchemeModule.cpp + SocketModule.cpp UnaryOperatorRegisterForVh.cpp UtilsModule.cpp WriterModule.cpp diff --git a/src/language/modules/ModuleRepository.cpp b/src/language/modules/ModuleRepository.cpp index 71a1368b9..85810cbfc 100644 --- a/src/language/modules/ModuleRepository.cpp +++ b/src/language/modules/ModuleRepository.cpp @@ -6,6 +6,7 @@ #include <language/modules/MathModule.hpp> #include <language/modules/MeshModule.hpp> #include <language/modules/SchemeModule.hpp> +#include <language/modules/SocketModule.hpp> #include <language/modules/UtilsModule.hpp> #include <language/modules/WriterModule.hpp> #include <language/utils/BasicAffectationRegistrerFor.hpp> @@ -56,6 +57,7 @@ ModuleRepository::ModuleRepository() this->_subscribe(std::make_unique<MathModule>()); this->_subscribe(std::make_unique<MeshModule>()); this->_subscribe(std::make_unique<SchemeModule>()); + this->_subscribe(std::make_unique<SocketModule>()); this->_subscribe(std::make_unique<UtilsModule>()); this->_subscribe(std::make_unique<WriterModule>()); } diff --git a/src/language/modules/SocketModule.cpp b/src/language/modules/SocketModule.cpp new file mode 100644 index 000000000..99eb7a702 --- /dev/null +++ b/src/language/modules/SocketModule.cpp @@ -0,0 +1,43 @@ +#include <language/modules/SocketModule.hpp> + +#include <language/utils/BuiltinFunctionEmbedder.hpp> +#include <utils/Socket.hpp> + +SocketModule::SocketModule() +{ + this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const Socket>>); + + this->_addBuiltinFunction("createSocketServer", + std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const Socket>(const uint64_t&)>>( + + [](const uint64_t& port_number) -> std::shared_ptr<const Socket> { + return std::make_shared<const Socket>(createSocketServer(port_number)); + } + + )); + + this->_addBuiltinFunction("acceptSocketClient", + std::make_shared< + BuiltinFunctionEmbedder<std::shared_ptr<const Socket>(std::shared_ptr<const Socket>)>>( + + [](std::shared_ptr<const Socket> server_socket) -> std::shared_ptr<const Socket> { + return std::make_shared<const Socket>(acceptSocketClient(*server_socket)); + } + + )); + + this->_addBuiltinFunction("connectSocketServer", + std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const Socket>(const std::string&, + const uint64_t&)>>( + + [](const std::string& hostname, + const uint64_t& port_number) -> std::shared_ptr<const Socket> { + return std::make_shared<const Socket>(connectSocketServer(hostname, port_number)); + } + + )); +} + +void +SocketModule::registerOperators() const +{} diff --git a/src/language/modules/SocketModule.hpp b/src/language/modules/SocketModule.hpp new file mode 100644 index 000000000..0b32b64fa --- /dev/null +++ b/src/language/modules/SocketModule.hpp @@ -0,0 +1,28 @@ +#ifndef SOCKET_MODULE_HPP +#define SOCKET_MODULE_HPP + +#include <language/modules/BuiltinModule.hpp> +#include <language/utils/ASTNodeDataTypeTraits.hpp> + +class Socket; + +template <> +inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const Socket>> = + ASTNodeDataType::build<ASTNodeDataType::type_id_t>("socket"); + +class SocketModule : public BuiltinModule +{ + public: + std::string_view + name() const final + { + return "socket"; + } + + void registerOperators() const final; + + SocketModule(); + ~SocketModule() = default; +}; + +#endif // SOCKET_MODULE_HPP diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 19feb273c..741b0d510 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -15,7 +15,8 @@ add_library( RandomEngine.cpp RevisionInfo.cpp SignalManager.cpp - SLEPcWrapper.cpp) + SLEPcWrapper.cpp + Socket.cpp) target_link_libraries( PugsUtils diff --git a/src/utils/Socket.cpp b/src/utils/Socket.cpp new file mode 100644 index 000000000..d8ece713d --- /dev/null +++ b/src/utils/Socket.cpp @@ -0,0 +1,177 @@ +#include <utils/Exceptions.hpp> +#include <utils/Socket.hpp> + +#include <arpa/inet.h> +#include <cstring> +#include <iostream> +#include <netdb.h> +#include <netinet/in.h> +#include <stdexcept> +#include <sys/socket.h> +#include <unistd.h> + +class Socket::Internals +{ + private: + int m_socket_fd; + sockaddr_in m_address; + + public: + friend Socket createSocketServer(int port_number); + friend Socket acceptSocketClient(const Socket& server); + friend Socket connectSocketServer(const std::string& server_name, int port_number); + + friend std::ostream& + operator<<(std::ostream& os, const Socket::Internals& internals) + { + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + if (::getnameinfo(reinterpret_cast<const sockaddr*>(&internals.m_address), sizeof(internals.m_address), hbuf, + sizeof(hbuf), sbuf, sizeof(sbuf), NI_NAMEREQD) == 0) { + os << hbuf << ':' << sbuf; + } else if (::getnameinfo(reinterpret_cast<const sockaddr*>(&internals.m_address), sizeof(internals.m_address), hbuf, + sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST) == 0) { + if (std ::string{hbuf} == "0.0.0.0") { + if (::gethostname(hbuf, NI_MAXHOST) == 0) { + os << hbuf << ':' << sbuf; + } else { + os << "localhost:" << sbuf; + } + } else { + os << hbuf << ':' << sbuf; + } + } else { + os << "<unknown host>"; + } + return os; + } + + int + fileDescriptor() const + { + return m_socket_fd; + } + + Internals(const Internals&) = delete; + Internals(Internals&&) = delete; + + Internals() = default; + + ~Internals() + { + close(m_socket_fd); + } +}; + +std::ostream& +operator<<(std::ostream& os, const Socket& socket) +{ + return os << *socket.m_internals; +} + +Socket +createSocketServer(int port_number) +{ + auto p_socket_internals = std::make_shared<Socket::Internals>(); + Socket::Internals& socket_internals = *p_socket_internals; + + socket_internals.m_socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (socket_internals.m_socket_fd < 0) { + throw NormalError(strerror(errno)); + } + + socket_internals.m_address.sin_family = AF_INET; + socket_internals.m_address.sin_addr.s_addr = INADDR_ANY; + socket_internals.m_address.sin_port = htons(port_number); + + int on = 1; + ::setsockopt(socket_internals.m_socket_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + if (::bind(socket_internals.m_socket_fd, reinterpret_cast<sockaddr*>(&socket_internals.m_address), + sizeof(socket_internals.m_address)) < 0) { + throw NormalError(strerror(errno)); + } + + ::listen(socket_internals.m_socket_fd, 1); + + return Socket{p_socket_internals}; +} + +Socket +acceptSocketClient(const Socket& server) +{ + auto p_socket_internals = std::make_shared<Socket::Internals>(); + Socket::Internals& socket_internals = *p_socket_internals; + + std::cout << "Waiting for connection on " << server << '\n'; + + socklen_t address_lenght = sizeof(socket_internals.m_address); + socket_internals.m_socket_fd = ::accept(server.m_internals->m_socket_fd, + reinterpret_cast<sockaddr*>(&socket_internals.m_address), &address_lenght); + + if (socket_internals.m_socket_fd < 0) { + throw NormalError(strerror(errno)); + } + + Socket client(p_socket_internals); + std::cout << "Connected from " << client << '\n'; + + return client; +} + +Socket +connectSocketServer(const std::string& server_name, int port_number) +{ + std::cout << "Trying to establish connection to " << server_name << ':' << port_number << '\n'; + + auto p_socket_internals = std::make_shared<Socket::Internals>(); + Socket::Internals& socket_internals = *p_socket_internals; + + socket_internals.m_socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (socket_internals.m_socket_fd < 0) { + throw NormalError(strerror(errno)); + } + + hostent* server = ::gethostbyname(server_name.c_str()); + if (server == NULL) { + throw NormalError(strerror(errno)); + } + + sockaddr_in& serv_addr = socket_internals.m_address; + ::memset(reinterpret_cast<char*>(&serv_addr), 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + + ::memcpy(reinterpret_cast<char*>(&serv_addr.sin_addr.s_addr), reinterpret_cast<char*>(server->h_addr), + server->h_length); + + serv_addr.sin_port = ::htons(port_number); + + if (::connect(socket_internals.m_socket_fd, reinterpret_cast<sockaddr*>(&serv_addr), sizeof(serv_addr))) { + throw NormalError(strerror(errno)); + } + + Socket server_socket{p_socket_internals}; + std::cout << "Connected to " << server_socket << '\n'; + + return server_socket; +} + +void +Socket::_write(const char* data, const size_t lenght) const +{ + if (::write(this->m_internals->fileDescriptor(), data, lenght) < 0) { + throw NormalError(strerror(errno)); + } +} + +void +Socket::_read(char* data, const size_t length) const +{ + size_t received = 0; + do { + int n = ::read(this->m_internals->fileDescriptor(), reinterpret_cast<char*>(data) + received, length - received); + if (n <= 0) { + throw NormalError(strerror(errno)); + } + received += n; + } while (received < length); +} diff --git a/src/utils/Socket.hpp b/src/utils/Socket.hpp new file mode 100644 index 000000000..89bc63b9f --- /dev/null +++ b/src/utils/Socket.hpp @@ -0,0 +1,79 @@ +#ifndef SOCKET_HPP +#define SOCKET_HPP + +#include <memory> +#include <string> +#include <type_traits> + +class Socket +{ + private: + class Internals; + + std::shared_ptr<Internals> m_internals; + + Socket(std::shared_ptr<Internals> internals) : m_internals{internals} {} + + void _write(const char* data, const size_t lenght) const; + void _read(char* data, const size_t lenght) const; + + public: + friend std::ostream& operator<<(std::ostream& os, const Socket& s); + + friend Socket createSocketServer(int port_number); + friend Socket acceptSocketClient(const Socket& server); + friend Socket connectSocketServer(const std::string& server_name, int port_number); + + template <typename T> + friend void write(const Socket& socket, const T& value); + + template <typename T> + friend void read(const Socket& socket, T& value); + + template <template <typename T, typename... R> typename ArrayT, typename T, typename... R> + friend void write(const Socket& socket, const ArrayT<T, R...>& array); + + template <template <typename T, typename... R> typename ArrayT, typename T, typename... R> + friend void read(const Socket& socket, ArrayT<T, R...>& array); + + Socket(Socket&&) = default; + Socket(const Socket&) = default; + + ~Socket() = default; +}; + +Socket createSocketServer(int port_number); +Socket acceptSocketClient(const Socket& server); +Socket connectSocketServer(const std::string& server_name, int port_number); + +template <typename T> +inline void +write(const Socket& socket, const T& value) +{ + socket._write(reinterpret_cast<const char*>(&value), sizeof(T) / sizeof(char)); +} + +template <template <typename T, typename... R> typename ArrayT, typename T, typename... R> +void +write(const Socket& socket, const ArrayT<T, R...>& array) +{ + socket._write(reinterpret_cast<const char*>(&array[0]), array.size() * sizeof(T) / sizeof(char)); +} + +template <typename T> +inline void +read(const Socket& socket, T& value) +{ + static_assert(not std::is_const_v<T>, "cannot read values into const data"); + socket._read(reinterpret_cast<char*>(&value), sizeof(T) / sizeof(char)); +} + +template <template <typename T, typename... R> typename ArrayT, typename T, typename... R> +void +read(const Socket& socket, ArrayT<T, R...>& array) +{ + static_assert(not std::is_const_v<T>, "cannot read values into const data"); + socket._read(reinterpret_cast<char*>(&array[0]), array.size() * sizeof(T) / sizeof(char)); +} + +#endif // SOCKET_HPP -- GitLab