Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added custom logging, changed how servers/clients start #10

Merged
merged 5 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Rcon++ is a modern Source RCON library for C++, allowing people to easily use RC

This library is used in:
- [Factorio-Discord-Relay Revamped](https://github.com/Jaskowicz1/fdr-remake)
- RCON-UE

If you're using this library, feel free to message me and show me, you might just get your project shown here!

Expand All @@ -31,18 +32,26 @@ We do not test support for MinGW, nor do we want to actively try and support it.

# Getting Started

rcon++ can be installed from the .deb file in the recent actions (soon to be released!).
rcon++ can be installed from the releases section!

We're aiming to start rolling out to package managers soon!

# Quick Example

### Client
```c++
#include <iostream>
#include <rconpp/rcon.h>

int main() {
rconpp::rcon_client client("127.0.0.1", 27015, "changeme");

client.on_log = [](const std::string_view& log) {
std::cout << log << "\n";
};

client.start(true);

client.send_data("Hello!", 3, rconpp::data_type::SERVERDATA_EXECCOMMAND, [](const rconpp::response& response) {
std::cout << "response: " << response.data << "\n";
});
Expand All @@ -51,6 +60,32 @@ int main() {
}
```

### Server
```c++
#include <iostream>
#include <rconpp/rcon.h>

int main() {
rconpp::rcon_server server("0.0.0.0", 27015, "testing");

server.on_log = [](const std::string_view log) {
std::cout << log << "\n";
};

server.on_command = [](const rconpp::client_command& command) {
if (command.command == "/test") {
return "This is a test!";
} else {
return "Hello!";
}
};

server.start(false);

return 0;
}
```

# Contributing

If you want to help out, simply make a fork and submit your PR!
Expand Down
9 changes: 8 additions & 1 deletion include/rconpp/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <iostream>
#include <cstring>
#include <thread>
#include <condition_variable>
#include "utilities.h"

namespace rconpp {
Expand Down Expand Up @@ -45,8 +46,12 @@ class RCONPP_EXPORT rcon_client {
public:
bool connected{false};

std::function<void(const std::string_view& log)> on_log;

std::condition_variable terminating;

/**
* @brief rcon constuctor. Initiates a connection to an RCON server with the parameters given.
* @brief rcon_client constuctor.
*
* @param addr The IP Address (NOT domain) to connect to.
* @param _port The port to connect to.
Expand All @@ -59,6 +64,8 @@ class RCONPP_EXPORT rcon_client {

~rcon_client();

void start(bool return_after);

/**
* @brief Send data to the connected RCON server. Requests from this function are added to a queue (`requests_queued`) and are handled by a different thread.
*
Expand Down
2 changes: 1 addition & 1 deletion include/rconpp/rcon.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#ifdef _WIN32
#pragma warning( disable : 4251 ); // 4251 warns when we export classes or structures with stl member variables
#pragma warning( disable : 4251 ) // 4251 warns when we export classes or structures with stl member variables
#endif

#include "export.h"
Expand Down
19 changes: 11 additions & 8 deletions include/rconpp/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <iostream>
#include <cstring>
#include <thread>
#include <condition_variable>
#include "utilities.h"

namespace rconpp {
Expand All @@ -29,19 +30,15 @@ struct connected_client {
bool authenticated{false};
};

struct server_info {
std::string address{};
int port{0};
std::string password{};
};

struct client_command {
connected_client client;
std::string command{};
};

class RCONPP_EXPORT rcon_server {
server_info serv_info{};
std::string address{};
int port{0};
std::string password{};

#ifdef _WIN32
SOCKET sock{INVALID_SOCKET};
Expand All @@ -56,6 +53,10 @@ class RCONPP_EXPORT rcon_server {

std::function<std::string(const client_command& command)> on_command;

std::function<void(const std::string_view log)> on_log = {};

std::condition_variable terminating;

/**
* @brief A map of connected clients. The key is their socket to talk to.
*/
Expand All @@ -73,10 +74,12 @@ class RCONPP_EXPORT rcon_server {
* @note This is a blocking call (done on purpose). It needs to wait to connect to the RCON server before anything else happens.
* It will timeout after 4 seconds if it can't connect.
*/
rcon_server(const std::string_view addr, const int port, const std::string_view pass);
rcon_server(const std::string_view addr, const int _port, const std::string_view pass);

~rcon_server();

void start(bool return_after);

/**
* @brief Disconnect a client from the server.
*
Expand Down
122 changes: 71 additions & 51 deletions src/rconpp/client.cpp
Original file line number Diff line number Diff line change
@@ -1,59 +1,16 @@
#include <mutex>
#include "client.h"
#include "utilities.h"

rconpp::rcon_client::rcon_client(const std::string_view addr, const int _port, const std::string_view pass) : address(addr), port(_port), password(pass) {

if(_port > 65535) {
std::cout << "Invalid port! The port can't exceed 65535!" << "\n";
return;
}

std::cout << "Attempting connection to RCON server..." << "\n";

if (!connect_to_server()) {
std::cout << "RCON is aborting as it failed to initiate client." << "\n";
return;
}

std::cout << "Connected successfully! Sending login data..." << "\n";

// The server will send SERVERDATA_AUTH_RESPONSE once it's happy. If it's not -1, the server will have accepted us!
response response = send_data_sync(pass, 1, data_type::SERVERDATA_AUTH, true);

if (!response.server_responded) {
std::cout << "Login data was incorrect. RCON will now abort." << "\n";
return;
}

std::cout << "Sent login data." << "\n";

connected = true;

queue_runner = std::thread([this]() {
while (connected) {
if (requests_queued.empty()) {
continue;
}

for (const queued_request& request : requests_queued) {
// Send data to callback if it's been set.
if (request.callback)
request.callback(send_data_sync(request.data, request.id, request.type));
else
send_data_sync(request.data, request.id, request.type, false);
}

requests_queued.clear();
}
});

queue_runner.detach();
}

rconpp::rcon_client::~rcon_client() {
// Set connected to false, meaning no requests can be attempted during shutdown.
connected = false;

terminating.notify_all();

#ifdef _WIN32
closesocket(sock);
WSACleanup();
Expand All @@ -68,14 +25,14 @@ rconpp::rcon_client::~rcon_client() {

rconpp::response rconpp::rcon_client::send_data_sync(const std::string_view data, const int32_t id, rconpp::data_type type, bool feedback) {
if (!connected && type != data_type::SERVERDATA_AUTH) {
std::cout << "Cannot send data when not connected." << "\n";
on_log("Cannot send data when not connected.");
return { "", false };
}

packet formed_packet = form_packet(data, id, type);

if (send(sock, formed_packet.data.data(), formed_packet.length, 0) < 0) {
std::cout << "Sending failed!" << "\n";
on_log("Sending failed!");
report_error();
return { "", false };
}
Expand All @@ -95,7 +52,7 @@ bool rconpp::rcon_client::connect_to_server() {
WSADATA wsa_data;
int result = WSAStartup(MAKEWORD(2, 2), &wsa_data);
if (result != 0) {
std::cout << "WSAStartup failed. Error: " << result << std::endl;
on_log("WSAStartup failed. Error: " + std::to_string(result));
return false;
}
#endif
Expand All @@ -108,7 +65,7 @@ bool rconpp::rcon_client::connect_to_server() {
#else
if (sock == -1) {
#endif
std::cout << "Failed to open socket." << "\n";
on_log("Failed to open socket.");
report_error();
return false;
}
Expand Down Expand Up @@ -235,10 +192,73 @@ int rconpp::rcon_client::read_packet_size() {
* We simply just want to read that and then return it.
*/
if (recv(sock, buffer.data(), 4, 0) == -1) {
std::cout << "Did not receive a packet in time. Did the server send a response?" << "\n";
on_log("Did not receive a packet in time. Did the server send a response?");
report_error();
return -1;
}

return bit32_to_int(buffer);
}

void rconpp::rcon_client::start(bool return_after) {

auto block_calling_thread = [this]() {
std::mutex thread_mutex;
std::unique_lock thread_lock(thread_mutex);
this->terminating.wait(thread_lock);
};

if(port > 65535) {
on_log("Invalid port! The port can't exceed 65535!");
return;
}

on_log("Attempting connection to RCON server...");

if (!connect_to_server()) {
on_log("RCON is aborting as it failed to initiate client.");
return;
}

on_log("Connected successfully! Sending login data...");

// The server will send SERVERDATA_AUTH_RESPONSE once it's happy. If it's not -1, the server will have accepted us!
response response = send_data_sync(password, 1, data_type::SERVERDATA_AUTH, true);

if (!response.server_responded) {
on_log("Login data was incorrect. RCON will now abort.");
return;
}

on_log("Sent login data.");

connected = true;

queue_runner = std::thread([this]() {
while (connected) {
if (requests_queued.empty()) {
continue;
}

for (const queued_request& request : requests_queued) {
// If we're closing the connection down, we need to back out.
if(!connected)
return;

// Send data to callback if it's been set.
if (request.callback)
request.callback(send_data_sync(request.data, request.id, request.type));
else
send_data_sync(request.data, request.id, request.type, false);
}

requests_queued.clear();
}
});

queue_runner.detach();

if(!return_after) {
block_calling_thread();
}
};
Loading
Loading