From 33d8277dc39f2541b6b5e6f83a668b46443d9201 Mon Sep 17 00:00:00 2001 From: ethanglide Date: Fri, 5 Jul 2024 15:37:02 -0400 Subject: [PATCH] Start RPC library --- CMakeLists.txt | 3 +- cpp-console-game/Board.cpp | 1 - cpp-console-game/CMakeLists.txt | 1 + cpp-console-game/main.cpp | 47 ++++++++++++--- eRPC/CMakeLists.txt | 26 ++++++++ eRPC/Client.cpp | 74 +++++++++++++++++++++++ eRPC/Client.hpp | 24 ++++++++ eRPC/Request.cpp | 65 ++++++++++++++++++++ eRPC/Request.hpp | 38 ++++++++++++ eRPC/Response.cpp | 61 +++++++++++++++++++ eRPC/Response.hpp | 39 ++++++++++++ eRPC/Server.cpp | 101 ++++++++++++++++++++++++++++++++ eRPC/Server.hpp | 45 ++++++++++++++ 13 files changed, 516 insertions(+), 9 deletions(-) create mode 100644 eRPC/CMakeLists.txt create mode 100644 eRPC/Client.cpp create mode 100644 eRPC/Client.hpp create mode 100644 eRPC/Request.cpp create mode 100644 eRPC/Request.hpp create mode 100644 eRPC/Response.cpp create mode 100644 eRPC/Response.hpp create mode 100644 eRPC/Server.cpp create mode 100644 eRPC/Server.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 775c834..e742ccc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(CppConsoleGame VERSION 1.0.0) add_subdirectory(conio) add_subdirectory(random-utils) +add_subdirectory(eRPC) add_subdirectory(cpp-console-game) \ No newline at end of file diff --git a/cpp-console-game/Board.cpp b/cpp-console-game/Board.cpp index f190224..3a1ff3e 100644 --- a/cpp-console-game/Board.cpp +++ b/cpp-console-game/Board.cpp @@ -16,7 +16,6 @@ namespace ConsoleGame void Board::draw() { std::cout << "\033[H"; // Move cursor to the top left corner - std::cout << "\033[2J"; // Clear the screen for (auto row : cells) { diff --git a/cpp-console-game/CMakeLists.txt b/cpp-console-game/CMakeLists.txt index d336365..42b8e15 100644 --- a/cpp-console-game/CMakeLists.txt +++ b/cpp-console-game/CMakeLists.txt @@ -25,6 +25,7 @@ target_link_libraries( cpp-console-game PRIVATE GameState conio + eRPC ) target_link_libraries( diff --git a/cpp-console-game/main.cpp b/cpp-console-game/main.cpp index 812260d..7d13850 100644 --- a/cpp-console-game/main.cpp +++ b/cpp-console-game/main.cpp @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include ConsoleGame::GameState gameState(20, 10); @@ -39,6 +42,8 @@ void inputLoop(int playerId) void renderLoop() { + std::cout << "\033[2J"; // Clear the screen + while (gameState.isRunning()) { gameState.draw(); @@ -46,15 +51,43 @@ void renderLoop() } } -int main() +int main(int argc, char **argv) { - int playerId = gameState.addPlayer(); + if (argc < 4) + { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return EXIT_FAILURE; + } - std::thread inputThread(inputLoop, playerId); - std::thread renderThread(renderLoop); + std::string mode = argv[1]; + int port = std::stoi(argv[2]); + std::string host = argv[3]; - inputThread.join(); - renderThread.join(); + if (mode == "server") + { + eRPC::Server server(port); + server.start(); + } + else if (mode == "client") + { + eRPC::Client client(host, port); + client.openConnection(); + client.call("Hello from client"); + client.closeConnection(); + } + else + { + std::cerr << "Invalid mode" << std::endl; + return EXIT_FAILURE; + } - return 0; + // int playerId = gameState.addPlayer(); + + // std::thread inputThread(inputLoop, playerId); + // std::thread renderThread(renderLoop); + + // inputThread.join(); + // renderThread.join(); + + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/eRPC/CMakeLists.txt b/eRPC/CMakeLists.txt new file mode 100644 index 0000000..c17651b --- /dev/null +++ b/eRPC/CMakeLists.txt @@ -0,0 +1,26 @@ +add_library( + eRPC + Client.hpp + Client.cpp + Server.hpp + Server.cpp + ) + +add_library( + eRPC_helpers + Request.hpp + Request.cpp + Response.hpp + Response.cpp + ) + +target_link_libraries( + eRPC PRIVATE + eRPC_helpers + ) + +target_include_directories( + eRPC + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ) \ No newline at end of file diff --git a/eRPC/Client.cpp b/eRPC/Client.cpp new file mode 100644 index 0000000..294e22a --- /dev/null +++ b/eRPC/Client.cpp @@ -0,0 +1,74 @@ +#include "Client.hpp" +#include "Request.hpp" +#include "Response.hpp" + +#include +#include +#include +#include +#include + +namespace eRPC +{ + Client::Client(std::string host, int port) : sockfd(-1), serv_addr() + { + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + throw std::runtime_error("Failed to create socket"); + } + + sockaddr_in serv_addr; + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(port); + + if (inet_pton(AF_INET, host.c_str(), &serv_addr.sin_addr) <= 0) + { + throw std::runtime_error("Invalid address"); + } + + this->serv_addr = serv_addr; + } + + Client::~Client() + { + closeConnection(); + } + + void Client::openConnection() + { + if (sockfd < 0) + { + throw std::runtime_error("Socket not created"); + } + + if (connect(sockfd, (sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) + { + throw std::runtime_error("Failed to connect to server"); + } + + std::cout << "Connected to server" << std::endl; + } + + void Client::closeConnection() + { + close(sockfd); + } + + void Client::call(std::string method) + { + if (sockfd < 0) + { + throw std::runtime_error("Socket not created"); + } + + Request request(0, method, {"First argument", "Another argument"}); + auto serialized = request.serialize(); + write(sockfd, serialized.c_str(), serialized.size() + 1); + + char buffer[1024] = {0}; + read(sockfd, buffer, 1024); + + Response response(buffer); + std::cout << "Received response:\n" << response.serialize() << std::endl; + } +} \ No newline at end of file diff --git a/eRPC/Client.hpp b/eRPC/Client.hpp new file mode 100644 index 0000000..6b9bd7c --- /dev/null +++ b/eRPC/Client.hpp @@ -0,0 +1,24 @@ +#ifndef ERPC_CLIENT_HPP +#define ERPC_CLIENT_HPP + +#include +#include + +namespace eRPC +{ + class Client + { + public: + Client(std::string host, int port); + ~Client(); + void openConnection(); + void closeConnection(); + void call(std::string method); + + private: + int sockfd; + sockaddr_in serv_addr; + }; +} + +#endif // ERPC_CLIENT_HPP \ No newline at end of file diff --git a/eRPC/Request.cpp b/eRPC/Request.cpp new file mode 100644 index 0000000..75fdd76 --- /dev/null +++ b/eRPC/Request.cpp @@ -0,0 +1,65 @@ +#include "Request.hpp" + +#include +#include + +namespace eRPC +{ + Request::Request(int msgid, std::string method, std::vector params) + : msgid(msgid), method(method), params(params) + { + } + + Request::Request(std::string msg) : msgid(-1), method(), params() + { + std::string line; + std::istringstream iss(msg); + + std::getline(iss, line); + if (line != "eRPC 1.0") + { + throw std::runtime_error("Invalid message"); + } + + std::getline(iss, line); + msgid = std::stoi(line); + + std::getline(iss, method); + + while (std::getline(iss, line)) + { + params.push_back(line); + } + } + + std::string Request::serialize() + { + std::string msg = "eRPC 1.0\n"; + + msg += std::to_string(msgid) + "\n"; + msg += method + "\n"; + + for (std::string param : params) + { + msg += param + "\n"; + } + msg.pop_back(); + + return msg; + } + + int Request::getMsgid() + { + return msgid; + } + + std::string Request::getMethod() + { + return method; + } + + std::vector Request::getParams() + { + return params; + } +} \ No newline at end of file diff --git a/eRPC/Request.hpp b/eRPC/Request.hpp new file mode 100644 index 0000000..f17e96a --- /dev/null +++ b/eRPC/Request.hpp @@ -0,0 +1,38 @@ +#ifndef ERPC_REQUEST_HPP +#define ERPC_REQUEST_HPP + +#include +#include + +namespace eRPC +{ + class Request + { + public: + /** + * Construct a request with the given message ID, method, and parameters. + */ + Request(int msgid, std::string method, std::vector params); + + /** + * Construct a request by parsing the given message. + */ + Request(std::string msg); + + /** + * Serialize the request to a string. + */ + std::string serialize(); + + int getMsgid(); + std::string getMethod(); + std::vector getParams(); + + private: + int msgid; + std::string method; + std::vector params; + }; +} + +#endif // ERPC_REQUEST_HPP \ No newline at end of file diff --git a/eRPC/Response.cpp b/eRPC/Response.cpp new file mode 100644 index 0000000..152cc4e --- /dev/null +++ b/eRPC/Response.cpp @@ -0,0 +1,61 @@ +#include "Response.hpp" + +#include + +namespace eRPC +{ + Response::Response() : msgid(-1), ok(false), result() + { + } + + Response::Response(int msgid, bool ok, std::string result) + : msgid(msgid), ok(ok), result(result) + { + } + + Response::Response(std::string msg) : msgid(-1), ok(false), result() + { + std::string line; + std::istringstream iss(msg); + + std::getline(iss, line); + if (line != "eRPC 1.0") + { + throw std::runtime_error("Invalid message"); + } + + std::getline(iss, line); + msgid = std::stoi(line); + + std::getline(iss, line); + ok = (line == "OK"); + + std::getline(iss, result); + } + + std::string Response::serialize() + { + std::string msg = "eRPC 1.0\n"; + + msg += std::to_string(msgid) + "\n"; + msg += ok ? "OK\n" : "ERROR\n"; + msg += result; + + return msg; + } + + int Response::getMsgid() + { + return msgid; + } + + bool Response::isOk() + { + return ok; + } + + std::string Response::getResult() + { + return result; + } +} \ No newline at end of file diff --git a/eRPC/Response.hpp b/eRPC/Response.hpp new file mode 100644 index 0000000..55304a0 --- /dev/null +++ b/eRPC/Response.hpp @@ -0,0 +1,39 @@ +#ifndef ERPC_RESPONSE_HPP +#define ERPC_RESPONSE_HPP + +#include + +namespace eRPC +{ + class Response + { + public: + Response(); + + /** + * Construct a response with the given message ID, status, and result. + */ + Response(int msgid, bool ok, std::string result); + + /** + * Construct a response by parsing the given message. + */ + Response(std::string msg); + + /** + * Serialize the response to a string. + */ + std::string serialize(); + + int getMsgid(); + bool isOk(); + std::string getResult(); + + private: + int msgid; + bool ok; + std::string result; + }; +} + +#endif // ERPC_RESPONSE_HPP \ No newline at end of file diff --git a/eRPC/Server.cpp b/eRPC/Server.cpp new file mode 100644 index 0000000..cd286b2 --- /dev/null +++ b/eRPC/Server.cpp @@ -0,0 +1,101 @@ +#include "Server.hpp" +#include "Request.hpp" +#include "Response.hpp" + +#include +#include +#include +#include +#include + +namespace eRPC +{ + Server::Server(int port) : sockfd(-1), running(false) + { + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + throw std::runtime_error("Failed to create socket"); + } + + sockaddr_in serv_addr; + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = INADDR_ANY; + serv_addr.sin_port = htons(port); + + if (bind(sockfd, (sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) + { + throw std::runtime_error("Failed to bind socket"); + } + + if (listen(sockfd, 5) < 0) + { + throw std::runtime_error("Failed to listen on socket"); + } + + std::cout << "Server listening on port " << port << std::endl; + } + + Server::~Server() + { + stop(); + } + + void Server::start() + { + if (running) + { + return; + } + + running = true; + + while (running) + { + std::cout << "Waiting for connection" << std::endl; + + int connfd = accept(sockfd, (sockaddr *)NULL, NULL); + + char buffer[1024] = {0}; + read(connfd, buffer, 1024); + Request request(buffer); + std::cout << "Received request:\n" + << request.serialize() << std::endl; + + Response response; + if (methods.find(request.getMethod()) != methods.end()) + { + std::function method = methods[request.getMethod()]; + method(); + + response = Response( + request.getMsgid(), + true, + "RESULTS HERE"); + } + else + { + response = Response( + request.getMsgid(), + false, + "Method \"" + request.getMethod() + "\" not found"); + } + + std::string serialized = response.serialize(); + write(connfd, serialized.c_str(), serialized.size() + 1); + + close(connfd); + } + + close(sockfd); + } + + void Server::stop() + { + running = false; + } + + void Server::bindMethod(std::string method, std::function callback) + { + methods[method] = callback; + } +} \ No newline at end of file diff --git a/eRPC/Server.hpp b/eRPC/Server.hpp new file mode 100644 index 0000000..10d82ca --- /dev/null +++ b/eRPC/Server.hpp @@ -0,0 +1,45 @@ +#ifndef ERPC_SERVER_HPP +#define ERPC_SERVER_HPP + +#include +#include +#include + +namespace eRPC +{ + class Server + { + public: + /** + * Construct a server on the given port. + */ + Server(int port); + + /** + * Destroy the server. + */ + ~Server(); + + /** + * Start the server loop. + */ + void start(); + + /** + * Stop the server loop. + */ + void stop(); + + /** + * Bind a method to a callback. + */ + void bindMethod(std::string method, std::function callback); + + private: + int sockfd; + bool running; + std::unordered_map> methods; + }; +} + +#endif // ERPC_SERVER_HPP \ No newline at end of file