diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e8ede7c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.17) +project(Project_1) + +set(CMAKE_CXX_STANDARD 17) + +add_executable(Project_1 main.cpp sockets.cpp switch_expression.h) \ No newline at end of file diff --git a/client.cpp b/client.cpp new file mode 100755 index 0000000..6f25323 --- /dev/null +++ b/client.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include +#include +#include +#define max_buffer 1024 +#define PORT 666 +#define SOCKET int + + std::queue message_queue_{}; + auto ufnk(const SOCKET socket)->std::string + { + if (!message_queue_.empty()) + { + auto message = message_queue_.front(); + message_queue_.pop(); + return message; + } + std::string data; + char message_buffer[max_buffer] = { 0 }; + auto length = max_buffer; + while (length == max_buffer) + { + length = ::recv(socket, message_buffer, max_buffer, 0); + for (auto i = 0; i < length; ++i) + if(message_buffer[i]) /*append until null.*/ + { + data += message_buffer[i]; + } + else + { + message_queue_.push(data); + data.clear(); + } + } + if (message_queue_.empty()) + return ""; + return ufnk(socket); //recurse. + } + + +int main(int argc, char const *argv[]) +{ + int sock = 0, valread = 1; + struct sockaddr_in serv_addr; + char *hello = "4"; + char buffer[1024] = {0}; + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + printf("\n Socket creation error \n"); + return -1; + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(PORT); + + // Convert IPv4 and IPv6 addresses from text to binary form + if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) + { + printf("\nInvalid address/ Address not supported \n"); + return -1; + } + + if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) + { + printf("\nConnection Failed \n"); + return -1; + } + send(sock , hello , strlen(hello)+1 , 0 ); + printf("sent date command\n"); + + + while(true) + { + auto text = ufnk(sock); + if (text == "") + break; + printf("%s\n", text.c_str()); + } + return 0; +} diff --git a/cmake-build-debug/Project_1 b/cmake-build-debug/Project_1 new file mode 100755 index 0000000..be1f747 Binary files /dev/null and b/cmake-build-debug/Project_1 differ diff --git a/cmake-build-debug/Project_1.cbp b/cmake-build-debug/Project_1.cbp new file mode 100644 index 0000000..27468ca --- /dev/null +++ b/cmake-build-debug/Project_1.cbp @@ -0,0 +1,97 @@ + + + + + + diff --git a/event.h b/event.h new file mode 100755 index 0000000..a5036c1 --- /dev/null +++ b/event.h @@ -0,0 +1,24 @@ +#pragma once +#include + +template +class event +{ + std::vector > callbacks; +public: + auto operator+=(std::function callback) -> event& + { + callbacks.emplace_back(callback); + return *this; + } + auto reset() -> void + { + callbacks.clear(); + } + auto execute(T... data) -> void + { + for (auto& callback : callbacks) + if (callback(data...)) /* true = handled... */ + break; + } +}; diff --git a/linux.h b/linux.h new file mode 100755 index 0000000..058ecf2 --- /dev/null +++ b/linux.h @@ -0,0 +1,8 @@ +#pragma once +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#define INVALID_SOCKET 0 +typedef int SOCKET; \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..05385c1 --- /dev/null +++ b/main.cpp @@ -0,0 +1,207 @@ +#if WIN32 +#define NOMINMAX +#endif +#include "sockets.h" +#include "user_input.h" +#include +#include "server.h" +#include "osyncstream.h" +#include "switch_expression.h" + +/* + * + auto server(const addrinfo_up& address) noexcept -> void +{ + + const auto socket = net::socket(AF_INET, SOCK_STREAM, 0); + net::bind(socket, address); + net::listen(socket); + std::cout << "[ Server Started ]" << std::endl; + + //below needs update. + do + { + auto const client = accept(socket, nullptr, nullptr); + if (!client) + continue; + printf("Got a client.\n"); + + + auto operation = net::recv(client); + try + { + switch (std::stoi(operation)) + { + case 1: + net::send(client, net::system("date /t")); + break; + case 4: + net::send(client, net::system("netstat -ant")); + break; + default: + net::send(client, "INVALID INPUT!"); + break; + } + } + catch (std::exception&) + { + net::send(client, "INVALID INPUT!"); + } + net::send(client, "<=> T123 <=>"); + net::closesocket(client); + printf("Closed Client\n"); + + } while (true); +} + +auto client(const addrinfo_up& address) noexcept -> void +{ + while(true) + { + const auto operation = get_input("Select Command:\n\t1. Date", ""); + const auto total_clients = get_input("Input Number of Clients: ", ""); + + std::vector threads; + + for(auto i = 0u; i < total_clients; ++i) + { + const auto prefix = "[client " + std::to_string(i) + "]: "; + try + { + threads.emplace_back(std::thread([=, &address]() + { + sscout << prefix << "started" << std::endl; + const auto socket = net::socket(AF_INET, SOCK_STREAM, 0); + net::connect(socket, address); + net::send(socket, std::to_string(operation)); + sscout << prefix << "sent command" << std::endl; + const auto& response = net::recv(socket); + sscout << prefix << "got response\n" << prefix << response << std::endl; + net::closesocket(socket); + sscout << prefix << "closed" << std::endl; + })); + } + catch (std::exception&) { sscout << prefix << " failed to init " << std::endl; } + } + for (auto& thread : threads) + thread.join(); + } +} + + + * + */ + +bool onClientConnected(networked& client) +{ + sscout << "Accepted Client on Server.\n"; + + using namespace std::chrono_literals; + auto start = std::chrono::high_resolution_clock::now(); + //std::this_thread::sleep_for(2000ms); + + return true; +} +auto onClientMessaged(networked& client, std::string& message) +{ + sscout << "message was " << message << "\n"; + + client.send_message("thanks for the message!"); + client.disconnect(); + return true; +} + +inline auto resolve_selection(const int selection) -> std::string +{ + if (selection == 1) + return "Server"; + else if (selection == 2) + return "Client"; + return ""; +} + +int main() +{ + net::prologue(); + const auto mode = get_input("Select Mode\n\t1. Server\n\t2. Client\n>:","Invalid Choice!",math::is_between<0,3>); + while (true) + { + std::string ipaddress{}; + std::uint16_t port{}; + if (mode == 1) + ipaddress = "0.0.0.0"; + else + ipaddress = get_input("Enter IP: "); + port = get_input("Enter Port:"); + sscout << resolve_selection(mode) << " selected, ip/port is " << ipaddress << "/" << port << ".\n"; + if (mode == 2) + { + client net_client{}; + net_client.message_received += [](std::string& what){ + sscout << "GOT " << what << ".\n"; + return true; + }; + if(net_client.connect(ipaddress, port)) + { + net_client.send_message("!Hello!\n"); + net_client.send_message("Second Message\n"); + net_client.send_message("Final Message!\n"); + while(net_client.is_valid()){} + } + else + { + sscout << "NO NET\n"; + } + net_client.disconnect(); + } + } + + + auto listen_server = []() + { + server srv{}; + srv.client_connected += onClientConnected; + srv.message_received += onClientMessaged; + auto thread = srv.listen("0.0.0.0", 80); + if(thread.joinable()) + { + thread.join(); + }else{ + sscout << "Thread is bad!\n"; + } + }; + + net::epilogue(); + return 0; +/* + * + const auto selection = get_input("Select Mode\n\t1. Server\n\t2. Client\n>:", "Invalid Choice!", [](const auto x) {return x > 0 && x < 3; }); + + auto address = std::string("0.0.0.0"); + auto port = 0_p; + addrinfo_up address_binding{}; + while(!address_binding.get()) + { + if (selection == 2) + address = get_input("Enter Remote IP (aaa.bbb.ccc.ddd): ", ""); + port = get_input("Enter Port: ", ""); + address_binding = net::getaddrinfo(address, std::to_string(port)); + } + + try + { + std::cout << "You have selected " << (selection == 1 ? "Server" : "Client") << std::endl; + std::cout << "Address: " << address << ":" << port << std::endl; + }catch(std::exception&){} + + + if (selection == 2) + client(address_binding); + else + listen_server(address_binding); + * + */ + net::epilogue(); +} + + diff --git a/osyncstream.h b/osyncstream.h new file mode 100755 index 0000000..e7c5454 --- /dev/null +++ b/osyncstream.h @@ -0,0 +1,64 @@ +/* + * osyncstream - A wrapper around ostream that properly locks while consuming stream operators + * Copyright Riley Strickland 2021 + */ +#pragma once +#include +#include +#include + +class osyncstream_locked final +{ + std::unique_lock lock_; + std::ostream& stream_; +public: + osyncstream_locked(std::recursive_mutex& mtx, std::ostream& stream) : lock_(mtx), stream_(stream) {} //construct and lock the mutex from osyncstream. + osyncstream_locked(const osyncstream_locked& o) = delete; + /* A recursive mutex isn't great here (reference forwarding would be better except...only a recursive mutex can assure lock-before-release on ownership transfer */ + template + friend auto operator<<(osyncstream_locked&& os, const T& d) -> osyncstream_locked&& + { + os.stream_ << d; + return std::move(os); /* move semantics :( ... thanks Clang/GCC */ + } + friend auto operator<<(osyncstream_locked&& os, std::ostream& (*func)(std::ostream&)) -> std::ostream& + { + return os.stream_ << func; + } + osyncstream_locked(osyncstream_locked&& o) noexcept : osyncstream_locked(*o.lock_.mutex(), o.stream_) {} //could be better... max depth 2, usual depth 1. + osyncstream_locked& operator=(osyncstream_locked&) = delete; //disallow + osyncstream_locked& operator=(osyncstream_locked&&) = delete; //disallow + ~osyncstream_locked() = default; +}; + +class osyncstream final +{ + std::recursive_mutex mutex_{}; + std::ostream& stream_; +public: + explicit osyncstream(std::ostream& stream) : stream_(stream) {} + template + friend auto operator<<(osyncstream& os, const T& d) -> osyncstream_locked + { + std::unique_lock lock(os.mutex_); + os.stream_ << d; + return osyncstream_locked { os.mutex_, os.stream_ }; + } + friend auto operator<<(osyncstream& os, std::ostream& (*func)(std::ostream&)) -> osyncstream_locked + { + std::unique_lock lock(os.mutex_); + os.stream_ << func; + return osyncstream_locked{ os.mutex_, os.stream_ }; + } +}; + +struct streams +{ + static std::ostream& cout; + static std::ostream& cerr; +}; + +inline std::ostream& streams::cout = std::cout; +inline std::ostream& streams::cerr = std::cerr; +inline osyncstream sscout(streams::cout); +inline osyncstream sscerr(streams::cerr); \ No newline at end of file diff --git a/server.h b/server.h new file mode 100755 index 0000000..0fda599 --- /dev/null +++ b/server.h @@ -0,0 +1,112 @@ +#pragma once +#include "sockets.h" +#include "event.h" +#include + +#include "osyncstream.h" + +class networked +{ +protected: + SOCKET socket; +public: + explicit networked(SOCKET existing) + { + socket = existing; + } + auto disconnect() -> void + { + if (!is_valid()) return; //socket is closed. + net::closesocket(socket); + socket = INVALID_SOCKET; + } + [[nodiscard]] auto receive_message() const -> std::string { if (!is_valid()) return ""; return net::recv(socket); } + auto send_message(const std::string& message) const -> void { if (!is_valid()) return; net::send(socket, message);} + [[nodiscard]] auto is_valid() const -> bool { return socket != INVALID_SOCKET; } +}; + + +class client : public networked +{ + auto accept_message() -> void //add const later + { + while(is_valid()) /* Accept all messages */ + { + auto message = receive_message(); + if (message.empty() && is_valid()) + { + sscout << "Server Connection Lost\n"; + disconnect(); + break; + } + message_received.execute(message); + } + } +public: + event message_received {}; + explicit client() : networked(net::socket(AF_INET, SOCK_STREAM, 0)){} + auto connect(const std::string& ip, const port port) -> bool + { + const auto address = net::getaddrinfo(ip, std::to_string(port)); + auto remote_address = net::getaddrinfo(ip, std::to_string(port)); + if (net::connect(socket, remote_address) < 0) + return false; + std::thread(&client::accept_message, this).detach(); + return true; + } +}; + +class server : networked +{ + auto accept_clients() -> void + { + while(is_valid()) + { + auto client = networked(accept(socket, nullptr, nullptr)); + client_connected.execute(client); +#ifdef THREADED + std::thread(&server::accept_client_message, this, client).detach(); +#else + std::thread(&server::accept_client_message, this, client).join(); +#endif + } + } + + auto accept_client_message(networked client) -> void //add const later + { + using namespace std::chrono_literals; + std::this_thread::sleep_for(2000ms); + while(client.is_valid()) /* Accept all messages */ + { + auto message = client.receive_message(); + if (message.empty() && client.is_valid()) //Client could have disconnected after check but PRIOR to recv. + { + sscout << "Client Connection Lost\n"; + client.disconnect(); + break; + } + message_received.execute(client, message); + } + } + +public: + event client_connected {}; + event message_received {}; + auto listen(const std::string& ip, const port port) -> std::thread + { + const auto address = net::getaddrinfo(ip, std::to_string(port)); + if (net::bind(socket, address) < 0) + { + sscout << "Error binding.\n"; + return std::thread(); + } + if (net::listen(socket) < 0) + { + sscout << "Error listening.\n"; + return std::thread(); + } + sscout << "Listening ... \n"; + return std::thread(&server::accept_clients, this); + } + explicit server() : networked(net::socket(AF_INET, SOCK_STREAM, 0)){} +}; diff --git a/sockets.cpp b/sockets.cpp new file mode 100755 index 0000000..e0eebcb --- /dev/null +++ b/sockets.cpp @@ -0,0 +1,112 @@ +#include "sockets.h" +#include "osyncstream.h" +#include +#include +#include +#include + + +constexpr auto max_buffer = 1024; +decltype(net::message_queue) net::message_queue{}; + + +auto net::recv(const SOCKET socket) noexcept -> std::string +{ + static std::recursive_mutex mutex{}; //allows for multiple thread access to our recv. + std::unique_lock lock(mutex); + try + { + auto& my_queue = message_queue[socket]; + if (!my_queue.empty()) + { + std::string message = my_queue.front(); // ReSharper disable All Just shut up resharper. I would use auto here normally, but the linter wants to make message a reference.....even though it won't be valid next line. + my_queue.pop(); + lock.unlock(); + return message; + } + lock.unlock(); //release lock so as to not deadlock on recv. + std::string data; + auto length = max_buffer; + while (length == max_buffer) /* queue all future messages from the stream */ + { + char message_buffer[max_buffer] = { 0 }; + + length = ::recv(socket, message_buffer, max_buffer, 0); + if (length < 0) + throw std::runtime_error("Closed Socket"); + for (auto i = 0; i < length; ++i) + if (message_buffer[i]) /*append until null. [free message framing ;)]*/ + { + data += message_buffer[i]; + } + else + { + lock.lock(); + my_queue.push(data); + data.clear(); + lock.unlock(); + } + } + + if(!data.empty()) //improperly terminated message, don't drop it! + { + lock.lock(); + my_queue.push(data); + data.clear(); + lock.unlock(); + } + if (my_queue.empty()) /* empty response */ + { + return ""; + } + return recv(socket); //recurse. + }catch(std::exception& err) /* shouldn't happen. */ + { + return {}; //empty + } +} +auto net::system(const std::string& command) noexcept -> std::string +{ + try + { + std::array output{ 0 }; //size is arbitrary + std::string data{}; + const popen_up pipe(popen(command.c_str(), "r")); + if (!pipe) + return ""; + while (fgets(output.data(), output.size(), pipe.get()) != nullptr) + data += output.data(); + return data; + }catch(std::exception&) + { + return ""; + } +} + +#if WIN32 +auto net::closesocket(const SOCKET socket) noexcept -> unsigned +{ + message_queue.erase(socket); + return ::closesocket(socket); +} +auto net::prologue() noexcept -> void +{ + WSADATA wsa_data; + const auto result = WSAStartup(win_sock_ver, &wsa_data); + if (result) + { + std::cout << "FATAL ERROR IN PROLOGUE " << result << std::endl; + exit(-1); + } +} +auto net::epilogue() noexcept -> void { WSACleanup(); } + +#else +auto net::closesocket(const SOCKET socket) noexcept -> unsigned +{ + message_queue.erase(socket); + return close(socket); +} +auto net::prologue() noexcept -> void {} //not required on Linux. +auto net::epilogue() noexcept -> void {} //not required on Linux. +#endif \ No newline at end of file diff --git a/sockets.h b/sockets.h new file mode 100755 index 0000000..7e9443a --- /dev/null +++ b/sockets.h @@ -0,0 +1,63 @@ +#pragma once +#if WIN32 +#include "windows.h" +#else +#include "linux.h" +#endif +#include +#include +#include +#include +#include +#include +#include + +typedef std::uint16_t port; +inline port operator "" _p(const unsigned long long value) { return static_cast(value); } + +struct addrinfo_up_deleter { void operator()(addrinfo* ptr) const { ::freeaddrinfo(ptr); } }; +typedef std::unique_ptr addrinfo_up; /* Modern C++ shared pointer for addrinfo. */ + +struct popen_up_deleter { void operator()(FILE* ptr) const { pclose(ptr); } }; +typedef std::unique_ptr popen_up; /* Modern C++ shared pointer for popen. */ + +class net +{ + // ReSharper disable once CppInconsistentNaming + static std::unordered_map> message_queue; +public: + /* inlined functions*/ + static auto bind(SOCKET socket, const addrinfo_up& address) noexcept -> int + { + return ::bind(socket, address->ai_addr, address->ai_addrlen); + } + static auto connect(SOCKET socket, const addrinfo_up& address) noexcept -> int + { + return ::connect(socket, address->ai_addr, address->ai_addrlen); + } + static auto getaddrinfo(const std::string& address, const std::string& port) noexcept -> addrinfo_up + { + addrinfo* result{}; + addrinfo hints{}; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE; + ::getaddrinfo(address.c_str(), port.c_str(), &hints, &result); + return addrinfo_up(result); + } + static auto listen(const SOCKET socket) noexcept -> int { return ::listen(socket, SOMAXCONN); } + static auto send(SOCKET socket, const std::string& message) noexcept -> unsigned + { + return ::send(socket, message.c_str(), static_cast(message.length() + 1u), MSG_NOSIGNAL); //make sure to include null terminator, this is our delim. + } + static auto socket(int af, int type, int protocol) noexcept -> SOCKET { return ::socket(af, type, protocol); } + /* extern functions */ + static void prologue() noexcept; + static void epilogue() noexcept; + static auto closesocket(const SOCKET socket) noexcept -> unsigned; + static auto recv(SOCKET socket) noexcept -> std::string; + static auto system(const std::string& command) noexcept -> std::string; + +}; diff --git a/switch_expression.h b/switch_expression.h new file mode 100644 index 0000000..4a3193d --- /dev/null +++ b/switch_expression.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +template +void on(T what, std::tuple> match) +{ + auto [a,b] = match; + if (what == a) + b; +} + +template +void on(T what, std::tuple> match, std::tuple> others) +{ + auto [a,b] = match; + if (what == a) + b; + else + on(what, others); +} \ No newline at end of file diff --git a/user_input.h b/user_input.h new file mode 100755 index 0000000..c32b685 --- /dev/null +++ b/user_input.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include +#include +#include +#include + + +constexpr auto all = std::numeric_limits::max(); + +namespace math +{ + template::value, bool> = true> + constexpr auto is_between(typeof(lb) x) -> bool { return (ub > x) && (x > lb); } +} + +template +T get_input(const std::string& prompt, const std::string& on_error, std::function validator) noexcept +{ + T user_input{}; + try + { + while (true) + { + printf("%s", prompt.c_str()); + std::cin >> user_input; + if (std::cin.fail() || !validator(user_input)) + { + std::cout << on_error << std::endl; + std::cin.clear(); + std::cin.ignore(all, '\n'); + continue; + } + break; + } + return user_input; + } + catch (std::exception&) { return user_input; } /* This should not happen ..... cin/cout are not configured to throw...*/ +} + +template +T get_input(const std::string& prompt) { return get_input(prompt, "", [](auto) {return true; }); } \ No newline at end of file diff --git a/windows.h b/windows.h new file mode 100755 index 0000000..b08920d --- /dev/null +++ b/windows.h @@ -0,0 +1,12 @@ +#pragma once +// ReSharper disable CppInconsistentNaming, cppcoreguidelines-macro-usage +#define popen _popen +#define pclose _pclose +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +// ReSharper restore CppInconsistentNaming, cppcoreguidelines-macro-usage +#include +#include +#include +#pragma comment(lib, "Ws2_32.lib") +constexpr auto win_sock_ver = 514; //2.2 \ No newline at end of file