Start RPC library

This commit is contained in:
ethanglide 2024-07-05 15:37:02 -04:00
parent 716ae24600
commit 33d8277dc3
13 changed files with 516 additions and 9 deletions

View File

@ -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)

View File

@ -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)
{

View File

@ -25,6 +25,7 @@ target_link_libraries(
cpp-console-game PRIVATE
GameState
conio
eRPC
)
target_link_libraries(

View File

@ -3,6 +3,9 @@
#include <unistd.h>
#include <thread>
#include <conio.hpp>
#include <Server.hpp>
#include <Client.hpp>
#include <iostream>
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();
std::thread inputThread(inputLoop, playerId);
std::thread renderThread(renderLoop);
inputThread.join();
renderThread.join();
return 0;
if (argc < 4)
{
std::cerr << "Usage: " << argv[0] << "<client/server> <port> <host>" << std::endl;
return EXIT_FAILURE;
}
std::string mode = argv[1];
int port = std::stoi(argv[2]);
std::string host = argv[3];
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;
}
// int playerId = gameState.addPlayer();
// std::thread inputThread(inputLoop, playerId);
// std::thread renderThread(renderLoop);
// inputThread.join();
// renderThread.join();
return EXIT_SUCCESS;
}

26
eRPC/CMakeLists.txt Normal file
View File

@ -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}
)

74
eRPC/Client.cpp Normal file
View File

@ -0,0 +1,74 @@
#include "Client.hpp"
#include "Request.hpp"
#include "Response.hpp"
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdexcept>
#include <unistd.h>
#include <iostream>
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;
}
}

24
eRPC/Client.hpp Normal file
View File

@ -0,0 +1,24 @@
#ifndef ERPC_CLIENT_HPP
#define ERPC_CLIENT_HPP
#include <string>
#include <netinet/in.h>
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

65
eRPC/Request.cpp Normal file
View File

@ -0,0 +1,65 @@
#include "Request.hpp"
#include <sstream>
#include <stdexcept>
namespace eRPC
{
Request::Request(int msgid, std::string method, std::vector<std::string> 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<std::string> Request::getParams()
{
return params;
}
}

38
eRPC/Request.hpp Normal file
View File

@ -0,0 +1,38 @@
#ifndef ERPC_REQUEST_HPP
#define ERPC_REQUEST_HPP
#include <string>
#include <vector>
namespace eRPC
{
class Request
{
public:
/**
* Construct a request with the given message ID, method, and parameters.
*/
Request(int msgid, std::string method, std::vector<std::string> 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<std::string> getParams();
private:
int msgid;
std::string method;
std::vector<std::string> params;
};
}
#endif // ERPC_REQUEST_HPP

61
eRPC/Response.cpp Normal file
View File

@ -0,0 +1,61 @@
#include "Response.hpp"
#include <sstream>
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;
}
}

39
eRPC/Response.hpp Normal file
View File

@ -0,0 +1,39 @@
#ifndef ERPC_RESPONSE_HPP
#define ERPC_RESPONSE_HPP
#include <string>
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

101
eRPC/Server.cpp Normal file
View File

@ -0,0 +1,101 @@
#include "Server.hpp"
#include "Request.hpp"
#include "Response.hpp"
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdexcept>
#include <unistd.h>
#include <iostream>
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<void *()> 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<void *()> callback)
{
methods[method] = callback;
}
}

45
eRPC/Server.hpp Normal file
View File

@ -0,0 +1,45 @@
#ifndef ERPC_SERVER_HPP
#define ERPC_SERVER_HPP
#include <string>
#include <functional>
#include <unordered_map>
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<void *()> callback);
private:
int sockfd;
bool running;
std::unordered_map<std::string, std::function<void *()>> methods;
};
}
#endif // ERPC_SERVER_HPP