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

Commit 2ce4620

Browse files
authored
feat, fix: Improved logging, improved handling of sockets, fixed crashing issues on Linux/Unix. (#24)
* fix: send/recv now uses MSG_NOSIGNAL, improved server logging, changed disconnect logic from heartbeat * fix: compile issue * fix: added missing header * feat, fix: logging improvements, improved handling of sockets on all platforms, improved disconnection logic * refactor, fix: change of disconnect logic again, compile fix * feat, fix: more rewrites and cleanup * refactor: more cleanup * fix: fixed potential race condition in server * refactor: improved logging for server connections * fix: No more deadlocking, unit tests now test multiple client connections to a server * refactor: added socket to disconnect message
1 parent 5dbab0f commit 2ce4620

7 files changed

Lines changed: 257 additions & 126 deletions

File tree

include/rconpp/client.h

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,7 @@ class RCONPP_EXPORT rcon_client {
3434
const std::string address{};
3535
const int port{0};
3636
const std::string password{};
37-
#ifdef _WIN32
38-
SOCKET sock{INVALID_SOCKET};
39-
#else
40-
int sock{0};
41-
#endif
37+
SOCKET_TYPE sock{INVALID_SOCKET};
4238

4339
std::vector<queued_request> requests_queued{};
4440

@@ -47,7 +43,7 @@ class RCONPP_EXPORT rcon_client {
4743
public:
4844
std::atomic<bool> connected{false};
4945

50-
std::function<void(const std::string_view& log)> on_log;
46+
std::function<void(const std::string_view& log)> on_log{};
5147

5248
std::condition_variable terminating;
5349

@@ -61,7 +57,7 @@ class RCONPP_EXPORT rcon_client {
6157
* @note This is a blocking call (done on purpose). It needs to wait to connect to the RCON server before anything else happens.
6258
* It will timeout after 4 seconds if it can't connect.
6359
*/
64-
rcon_client(const std::string_view addr, const int _port, const std::string_view pass);
60+
rcon_client(std::string_view addr, int _port, std::string_view pass);
6561

6662
~rcon_client();
6763

@@ -93,7 +89,7 @@ class RCONPP_EXPORT rcon_client {
9389
*
9490
* @returns Data given by the server from the request.
9591
*/
96-
response send_data_sync(const std::string_view data, const int32_t id, data_type type, bool feedback = true);
92+
response send_data_sync(std::string_view data, int32_t id, data_type type, bool feedback = true);
9793

9894
private:
9995

include/rconpp/server.h

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace rconpp {
2424

2525
struct connected_client {
2626
sockaddr_in sock_info{};
27-
int socket{0};
27+
SOCKET_TYPE socket{0};
2828
bool connected{false};
2929

3030
bool authenticated{false};
@@ -42,14 +42,12 @@ class RCONPP_EXPORT rcon_server {
4242
int port{0};
4343
std::string password{};
4444

45-
#ifdef _WIN32
46-
SOCKET sock{INVALID_SOCKET};
47-
#else
48-
int sock{0};
49-
#endif
45+
SOCKET_TYPE sock{INVALID_SOCKET};
5046

5147
std::thread accept_connections_runner;
48+
5249
std::mutex connected_clients_mutex;
50+
std::mutex request_handlers_mutex;
5351

5452
public:
5553
bool online{false};
@@ -61,11 +59,14 @@ class RCONPP_EXPORT rcon_server {
6159
std::condition_variable terminating;
6260

6361
/**
64-
* @brief A map of connected clients. The key is their socket to talk to.
62+
* @brief A map of connected clients. The key is their socket.
6563
*/
66-
std::unordered_map<int, connected_client> connected_clients{};
64+
std::unordered_map<SOCKET_TYPE, connected_client> connected_clients{};
6765

68-
std::unordered_map<int, std::thread> request_handlers{};
66+
/**
67+
* @brief A map of request_handlers (the thread for a client that handles all requests a client will send). The key is their socket.
68+
*/
69+
std::unordered_map<SOCKET_TYPE, std::thread> request_handlers{};
6970

7071
/**
7172
* @brief rcon_server constuctor. Initiates a connection to an RCON server with the parameters given.
@@ -77,7 +78,7 @@ class RCONPP_EXPORT rcon_server {
7778
* @note This is a blocking call (done on purpose). It needs to wait to connect to the RCON server before anything else happens.
7879
* It will timeout after 4 seconds if it can't connect.
7980
*/
80-
rcon_server(const std::string_view addr, const int _port, const std::string_view pass);
81+
rcon_server(std::string_view addr, int _port, std::string_view pass);
8182

8283
~rcon_server();
8384

@@ -89,7 +90,7 @@ class RCONPP_EXPORT rcon_server {
8990
* @param client_socket The socket of the client to disconnect.
9091
* @param remove_after Should remove client from connected_clients after?
9192
*/
92-
void disconnect_client(int client_socket, bool remove_after = true);
93+
void disconnect_client(SOCKET_TYPE client_socket, bool remove_after = true);
9394

9495
private:
9596

@@ -117,6 +118,28 @@ class RCONPP_EXPORT rcon_server {
117118
* @returns bool, true is heartbeat was sent, otherwise false.
118119
*/
119120
bool send_heartbeat(connected_client& client);
121+
122+
void client_process_loop(connected_client& client);
123+
124+
void add_client(const SOCKET_TYPE client_socket, connected_client& client) {
125+
while (!connected_clients_mutex.try_lock()) {
126+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
127+
}
128+
129+
connected_clients.insert({ client_socket, client });
130+
131+
connected_clients_mutex.unlock();
132+
}
133+
134+
void remove_client(const SOCKET_TYPE client_socket) {
135+
while (!connected_clients_mutex.try_lock()) {
136+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
137+
}
138+
139+
connected_clients.erase(client_socket);
140+
141+
connected_clients_mutex.unlock();
142+
};
120143
};
121144

122145
} // namespace rconpp

include/rconpp/utilities.h

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,58 @@
11
#pragma once
22

3-
#include <functional>
3+
#ifdef _WIN32
4+
#include <winsock2.h>
5+
#else
6+
#include <sys/types.h>
7+
#include <sys/socket.h>
8+
#endif
9+
10+
11+
#include <optional>
412

513
#include "export.h"
614
#include <string>
715
#include <string_view>
816
#include <vector>
9-
#include <iostream>
1017

1118
namespace rconpp {
1219

20+
// Connection constants.
1321
constexpr int DEFAULT_TIMEOUT = 4; // In Seconds.
22+
constexpr int MAX_RETRIES_TO_RECEIVE_INFO = 5;
23+
constexpr int HEARTBEAT_TIME = 30;
24+
25+
// Packet constants.
1426
constexpr int MIN_PACKET_SIZE = 10;
1527
constexpr int MIN_PACKET_LENGTH = 14;
16-
constexpr int MAX_RETRIES_TO_RECEIVE_INFO = 500;
17-
constexpr int HEARTBEAT_TIME = 30;
28+
constexpr int MAX_PACKET_SIZE = 4096;
29+
constexpr int PACKET_SIZE_BYTES = 4; // The first x bytes of the packet to read for the packet size (usually the first 4 bytes)
30+
31+
// Used for send/recv calls, as `signal(SIGPIPE, SIG_IGN);` seems to be ignored.
32+
#ifndef MSG_NOSIGNAL
33+
#define MSG_NOSIGNAL 0
34+
#endif
35+
36+
// INVALID_SOCKET doesn't exist on Linux/Unix (both platforms just return -1 on error), add this to avoid ifdef spam.
37+
#ifndef INVALID_SOCKET
38+
#define INVALID_SOCKET -1
39+
#endif
40+
41+
// Same as above.
42+
#ifndef SOCKET_ERROR
43+
#define SOCKET_ERROR -1
44+
#endif
45+
46+
// Windows uses uint64_t for sockets, whereas Linux/Unix uses int,
47+
// having this reduces the warnings and whatnot on Windows caused by converting them to int.
48+
#ifndef SOCKET_TYPE
49+
#ifdef _WIN32
50+
#define SOCKET_TYPE SOCKET
51+
#else
52+
#define SOCKET_TYPE int
53+
#endif
54+
#endif
55+
1856

1957

2058
enum data_type {
@@ -62,6 +100,17 @@ struct response {
62100
bool server_responded{false};
63101
};
64102

103+
enum error_type {
104+
DISCONNECTED = 0,
105+
BAD_FD = 1,
106+
SHUTTING_DOWN = 2,
107+
};
108+
109+
struct last_error {
110+
error_type type_of_error{error_type::DISCONNECTED};
111+
int error_code{0};
112+
};
113+
65114
/**
66115
* @brief Form a valid RCON packet.
67116
*
@@ -71,7 +120,7 @@ struct response {
71120
*
72121
* @returns The packet data (as an array of chars) to send to a server.
73122
*/
74-
RCONPP_EXPORT packet form_packet(const std::string_view data, int32_t id, int32_t type);
123+
RCONPP_EXPORT packet form_packet(std::string_view data, int32_t id, int32_t type);
75124

76125
/**
77126
* @brief Turn the first 4 bytes of a buffer (which ideally a 32 bit int) into an integer.
@@ -92,15 +141,15 @@ RCONPP_EXPORT int bit32_to_int(const std::vector<char>& buffer);
92141
RCONPP_EXPORT int type_to_int(const std::vector<char>& buffer);
93142

94143
/**
95-
* @brief Reports the recent socket error.
144+
* @brief Converts the last error into a prev_error struct.
96145
*/
97-
RCONPP_EXPORT void report_error();
146+
RCONPP_EXPORT last_error get_last_error();
98147

99148
/**
100149
* @brief Reads the first 4 bytes of a packet to get the packet size (not to be mistaken with length).
101150
*
102151
* @return The size (not length) of the packet.
103152
*/
104-
RCONPP_EXPORT int read_packet_size(int socket);
153+
RCONPP_EXPORT int read_packet_size(SOCKET_TYPE socket);
105154

106155
} // namespace rconpp

src/rconpp/client.cpp

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ rconpp::rcon_client::~rcon_client() {
99
if (on_log) {
1010
on_log("RCON client is shutting down.");
1111
}
12+
1213
// Set connected to false, meaning no requests can be attempted during shutdown.
1314
connected = false;
1415

@@ -20,6 +21,7 @@ rconpp::rcon_client::~rcon_client() {
2021
#else
2122
close(sock);
2223
#endif
24+
2325
// Join the queue runner (if allowed), meaning we await its end before killing this object, preventing any corruption.
2426
if (queue_runner.joinable()) {
2527
queue_runner.join();
@@ -34,9 +36,9 @@ rconpp::response rconpp::rcon_client::send_data_sync(const std::string_view data
3436

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

37-
if (send(sock, formed_packet.data.data(), formed_packet.length, 0) < 0) {
38-
on_log("Sending failed!");
39-
report_error();
39+
if (send(sock, formed_packet.data.data(), formed_packet.length, MSG_NOSIGNAL) < 0) {
40+
const last_error err = get_last_error();
41+
on_log("Sending failed [Error code: " + std::to_string(err.error_code) + "]!");
4042
return { "", false };
4143
}
4244

@@ -53,7 +55,7 @@ bool rconpp::rcon_client::connect_to_server() {
5355
#ifdef _WIN32
5456
// Initialize Winsock
5557
WSADATA wsa_data;
56-
int result = WSAStartup(MAKEWORD(2, 2), &wsa_data);
58+
const int result = WSAStartup(MAKEWORD(2, 2), &wsa_data);
5759
if (result != 0) {
5860
on_log("WSAStartup failed. Error: " + std::to_string(result));
5961
return false;
@@ -63,13 +65,9 @@ bool rconpp::rcon_client::connect_to_server() {
6365
// Create new TCP socket.
6466
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
6567

66-
#ifdef _WIN32
6768
if (sock == INVALID_SOCKET) {
68-
#else
69-
if (sock == -1) {
70-
#endif
71-
on_log("Failed to open socket.");
72-
report_error();
69+
const last_error err = get_last_error();
70+
on_log("Failed to open socket [Error code: " + std::to_string(err.error_code) + "]!");
7371
return false;
7472
}
7573

@@ -103,8 +101,7 @@ bool rconpp::rcon_client::connect_to_server() {
103101
// Connect to the socket and set the status of the connection.
104102
int status = connect(sock, (struct sockaddr*)&server, sizeof(server));
105103

106-
if (status == -1) {
107-
report_error();
104+
if (status == SOCKET_ERROR) {
108105
return false;
109106
}
110107

@@ -158,7 +155,7 @@ rconpp::packet rconpp::rcon_client::read_packet() {
158155
}
159156

160157
packet temp_packet{};
161-
temp_packet.length = packet_size + 4;
158+
temp_packet.length = packet_size + PACKET_SIZE_BYTES;
162159

163160
if (packet_size > 0) {
164161
temp_packet.size = packet_size;
@@ -178,7 +175,7 @@ rconpp::packet rconpp::rcon_client::read_packet() {
178175
* Receiving by the length of the packet will give us 4 extra bytes, so, we do by size here.
179176
* This is because read_packet_size() reads the first 4 bytes and discards them.
180177
*/
181-
recv(sock, buffer.data(), temp_packet.size, 0);
178+
recv(sock, buffer.data(), temp_packet.size, MSG_NOSIGNAL);
182179

183180
temp_packet.data = buffer;
184181

0 commit comments

Comments
 (0)