Skip to content
This repository was archived by the owner on Mar 29, 2026. It is now read-only.

Commit edea9e5

Browse files
committed
feat: start of sending heartbeats to clients, along with other improvements
1 parent 112a738 commit edea9e5

3 files changed

Lines changed: 92 additions & 55 deletions

File tree

include/rconpp/server.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ class RCONPP_EXPORT rcon_server {
4848

4949
std::thread accept_connections_runner;
5050

51+
// time_t is time since epoch in seconds (last time we ran).
52+
std::unordered_map<int, time_t> client_socket_to_last_heartbeat;
53+
5154
public:
5255
bool online{false};
5356

@@ -110,8 +113,17 @@ class RCONPP_EXPORT rcon_server {
110113

111114
/**
112115
* @brief Gathers all the packet's content (based on the length returned by `read_packet_length`)
116+
*
117+
* @param client Client to read packet from.
113118
*/
114119
void read_packet(rconpp::connected_client client);
120+
121+
/**
122+
* @brief Sends a heartbeat to a client.
123+
*
124+
* @param client Client to send a heartbeat to.
125+
*/
126+
void send_heartbeat(rconpp::connected_client client);
115127
};
116128

117129
} // namespace rconpp

include/rconpp/utilities.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ constexpr int DEFAULT_TIMEOUT = 4; // In Seconds.
1414
constexpr int MIN_PACKET_SIZE = 10;
1515
constexpr int MIN_PACKET_LENGTH = 14;
1616
constexpr int MAX_RETRIES_TO_RECEIVE_INFO = 500;
17+
constexpr int HEARTBEAT_TIME = 30;
18+
1719

1820
enum data_type {
1921
/**

src/rconpp/server.cpp

Lines changed: 78 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ rconpp::rcon_server::~rcon_server() {
2626
if (accept_connections_runner.joinable()) {
2727
accept_connections_runner.join();
2828
}
29+
30+
if (heartbeat_runner.joinable()) {
31+
heartbeat_runner.join();
32+
}
2933
}
3034

3135
bool rconpp::rcon_server::startup_server() {
@@ -98,71 +102,70 @@ void rconpp::rcon_server::disconnect_client(const int client_socket) {
98102
}
99103

100104
void rconpp::rcon_server::read_packet(rconpp::connected_client client) {
101-
while (client.connected) {
102-
const int packet_size = read_packet_size(static_cast<int>(sock), on_log);
105+
const int packet_size = read_packet_size(static_cast<int>(sock), on_log);
103106

104-
if (packet_size <= MIN_PACKET_SIZE) {
105-
continue;
106-
}
107+
if (packet_size <= MIN_PACKET_SIZE) {
108+
return;
109+
}
107110

108-
std::vector<char> buffer{};
109-
buffer.resize(packet_size);
111+
std::vector<char> buffer{};
112+
buffer.resize(packet_size);
110113

111-
if (recv(client.socket, buffer.data(), packet_size, 0) == -1) {
112-
on_log("Failed to get a packet from client.");
113-
report_error();
114-
}
114+
if (recv(client.socket, buffer.data(), packet_size, 0) == -1) {
115+
on_log("Failed to get a packet from client.");
116+
report_error();
117+
return;
118+
}
115119

116-
std::string packet_data(&buffer[8], &buffer[buffer.size()-2]);
117-
int id = bit32_to_int(buffer);
118-
int type = type_to_int(buffer);
120+
std::string packet_data(&buffer[8], &buffer[buffer.size()-2]);
121+
int id = bit32_to_int(buffer);
122+
int type = type_to_int(buffer);
119123

120-
rconpp::packet packet_to_send{};
124+
rconpp::packet packet_to_send{};
121125

122-
if (!client.authenticated) {
123-
if (packet_data == password) {
124-
packet_to_send = form_packet("", id, rconpp::data_type::SERVERDATA_AUTH_RESPONSE);
125-
client.authenticated = true;
126-
} else {
127-
packet_to_send = form_packet("", -1, rconpp::data_type::SERVERDATA_AUTH_RESPONSE);
128-
}
126+
if (!client.authenticated) {
127+
if (packet_data == password) {
128+
packet_to_send = form_packet("", id, rconpp::data_type::SERVERDATA_AUTH_RESPONSE);
129+
client.authenticated = true;
129130
} else {
130-
if (type != rconpp::data_type::SERVERDATA_EXECCOMMAND) {
131-
packet_to_send = form_packet("Invalid packet type (" + std::to_string(type) + "). Double check your packets.", id, rconpp::data_type::SERVERDATA_RESPONSE_VALUE);
132-
on_log("Invalid packet type (" + std::to_string(type) + ") sent by [" + inet_ntoa(client.sock_info.sin_addr) + ":" + std::to_string(ntohs(client.sock_info.sin_port)) + "]. Double check your packets.");
131+
packet_to_send = form_packet("", -1, rconpp::data_type::SERVERDATA_AUTH_RESPONSE);
132+
}
133+
} else {
134+
if (type != rconpp::data_type::SERVERDATA_EXECCOMMAND) {
135+
packet_to_send = form_packet("Invalid packet type (" + std::to_string(type) + "). Double check your packets.", id, rconpp::data_type::SERVERDATA_RESPONSE_VALUE);
136+
on_log("Invalid packet type (" + std::to_string(type) + ") sent by [" + inet_ntoa(client.sock_info.sin_addr) + ":" + std::to_string(ntohs(client.sock_info.sin_port)) + "]. Double check your packets.");
137+
} else {
138+
on_log("Client [" + std::string(inet_ntoa(client.sock_info.sin_addr)) + ":" + std::to_string(ntohs(client.sock_info.sin_port)) + "] has asked to execute the command: \"" + packet_data + "\"");
139+
if (!on_command) {
140+
on_log("You have not set any response for on_command! The server will default to a blank response.");
141+
142+
/*
143+
* Whilst sending information about the server not responding would be nice,
144+
* we would end up with the possibility of clients thinking that is the response.
145+
* It's better to just send no information and let clients assume that meant
146+
* the server didn't like the command.
147+
*/
148+
packet_to_send = form_packet("", id, rconpp::data_type::SERVERDATA_RESPONSE_VALUE);
133149
} else {
134-
on_log("Client [" + std::string(inet_ntoa(client.sock_info.sin_addr)) + ":" + std::to_string(ntohs(client.sock_info.sin_port)) + "] has asked to execute the command: \"" + packet_data + "\"");
135-
if (!on_command) {
136-
on_log("You have not set any response for on_command! The server will default to a blank response.");
137-
138-
/*
139-
* Whilst sending information about the server not responding would be nice,
140-
* we would end up with the possibility of clients thinking that is the response.
141-
* It's better to just send no information and let clients assume that meant
142-
* the server didn't like the command.
143-
*/
144-
packet_to_send = form_packet("", id, rconpp::data_type::SERVERDATA_RESPONSE_VALUE);
145-
} else {
146-
client_command command{};
147-
command.command = packet_data;
148-
command.client = client;
149-
150-
std::string text_to_send = on_command(command);
151-
152-
on_log("Sending reply \"" + text_to_send + "\" to client [" + std::string(inet_ntoa(client.sock_info.sin_addr)) + ":" + std::to_string(ntohs(client.sock_info.sin_port)) + "].");
153-
154-
packet_to_send = form_packet(text_to_send, id, rconpp::data_type::SERVERDATA_RESPONSE_VALUE);
155-
}
150+
client_command command{};
151+
command.command = packet_data;
152+
command.client = client;
153+
154+
std::string text_to_send = on_command(command);
155+
156+
on_log("Sending reply \"" + text_to_send + "\" to client [" + std::string(inet_ntoa(client.sock_info.sin_addr)) + ":" + std::to_string(ntohs(client.sock_info.sin_port)) + "].");
157+
158+
packet_to_send = form_packet(text_to_send, id, rconpp::data_type::SERVERDATA_RESPONSE_VALUE);
156159
}
157160
}
161+
}
158162

159-
on_log("Sending...");
163+
on_log("Sending packet (of size: " + std::to_string(packet_to_send.length) + ") to client [" + std::string(inet_ntoa(client.sock_info.sin_addr)) + ":" + std::to_string(ntohs(client.sock_info.sin_port)) + "]");
160164

161-
if (send(client.socket, packet_to_send.data.data(), packet_to_send.length, 0) < 0) {
162-
on_log("Sending failed!");
163-
report_error();
164-
continue;
165-
}
165+
if (send(client.socket, packet_to_send.data.data(), packet_to_send.length, 0) < 0) {
166+
on_log("Sending failed!");
167+
report_error();
168+
return;
166169
}
167170
}
168171

@@ -209,14 +212,34 @@ void rconpp::rcon_server::start(bool return_after) {
209212
client.connected = true;
210213

211214
std::thread client_thread([this, client]{
212-
read_packet(client);
215+
while (client.connected) {
216+
read_packet(client);
217+
218+
time_t current_time = time(nullptr);
219+
if (client_socket_to_last_heartbeat.find(client.socket) == client_socket_to_last_heartbeat.end()) {
220+
client_socket_to_last_heartbeat.insert({ client.socket, current_time });
221+
} else {
222+
time_t last_time = client_socket_to_last_heartbeat.at(client.socket);
223+
224+
if (current_time - last_time >= HEARTBEAT_TIME)
225+
{
226+
send_heartbeat(client);
227+
228+
// We should check if the heartbeat actually got anything, if it does then insert back into the map.
229+
// if it failed, we bin that client off and shut this thread down.
230+
}
231+
}
232+
233+
// No need to let the server keep running this causing 100% usage on a thread, we can wait a bit between requests.
234+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
235+
}
213236
});
214237

215238
request_handlers.insert({ client_socket, std::move(client_thread) });
216239

217240
request_handlers.at(client_socket).detach();
218241

219-
connected_clients.insert({});
242+
connected_clients.insert({ client_socket, client });
220243
}
221244
});
222245

0 commit comments

Comments
 (0)