Skip to content

Commit

Permalink
feat: added custom logging, changed how servers/clients start (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaskowicz1 committed Apr 5, 2024
1 parent 5e18ca7 commit 763a6af
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 134 deletions.
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();
}
};

0 comments on commit 763a6af

Please sign in to comment.