Air-Trap 1.0.0
A multiplayer R-Type clone game engine built with C++23 and ECS architecture
Loading...
Searching...
No Matches
ClientNetwork.cpp
Go to the documentation of this file.
1
9#include "RType/Logger.hpp"
10
11namespace rtp::client {
12
14 // Public API
16
17 ClientNetwork::ClientNetwork(const std::string& serverIp, uint16_t serverPort)
18 : _serverPort(serverPort),
19 _serverIp(serverIp),
20 _ioContext(),
21 _tcpSocket(_ioContext),
22 _udpSocket(_ioContext),
23 _serverEndpoint(),
24 _ioThread(),
25 _eventQueueMutex(),
26 _eventQueue()
27 {
28 }
29
31 {
32 this->stop();
33 }
34
36 {
37 try {
38 _ioThread = std::thread([this]() {
39 auto workGuard = asio::make_work_guard(_ioContext);
40 _ioContext.run();
41 });
42
43 asio::error_code ec;
44 asio::ip::tcp::endpoint endpoint(asio::ip::make_address(_serverIp, ec), _serverPort);
45 _tcpSocket.connect(endpoint, ec);
46
47 if (ec) {
48 log::error("Error connecting to server: {}", ec.message());
49 return this->stop();
50 }
51
53 log::info("Connected to server {}:{}", _serverIp, _serverPort);
54
55 _udpSocket.open(asio::ip::udp::v4());
56 _udpSocket.bind(asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
57 _serverEndpoint = asio::ip::udp::endpoint(asio::ip::make_address(_serverIp), _serverPort);
58
60 readUdp();
61
62 } catch (const std::exception& e) {
63 log::error("Client Network Start Error: {}", e.what());
64 this->stop();
65 }
66 }
67
69 {
70 _ioContext.stop();
71
72 if (std::this_thread::get_id() != _ioThread.get_id()) {
73 if (_ioThread.joinable()) {
74 _ioThread.join();
75 log::info("ASIO I/O thread stopped.");
76 }
77 }
78
79 if (_tcpSocket.is_open()) _tcpSocket.close();
80 if (_udpSocket.is_open()) _udpSocket.close();
81 };
82
84 {
85 asio::error_code ec;
86 if (mode == net::NetworkMode::TCP) {
87 asio::write(_tcpSocket, packet.getBufferSequence(), ec);
88 if (ec) {
89 log::error("Error sending TCP packet: {}", ec.message());
90 }
91 } else if (mode == net::NetworkMode::UDP) {
92 if (_serverEndpoint.port() == 0) {
93 asio::ip::udp::resolver resolver(_ioContext);
94 _serverEndpoint = *resolver.resolve(asio::ip::udp::v4(), _serverIp, std::to_string(_serverPort)).begin();
95 }
96 _udpSocket.send_to(packet.getBufferSequence(), _serverEndpoint, 0, ec);
97 if (ec) {
98 log::error("Error sending UDP packet: {}", ec.message());
99 }
100 }
101 }
102
103 std::optional<net::NetworkEvent> ClientNetwork::pollEvent(void)
104 {
105 std::lock_guard<std::mutex> lock(_eventQueueMutex);
106
107 if (!_eventQueue.empty()) {
108 net::NetworkEvent event = std::move(_eventQueue.front());
109 _eventQueue.pop_front();
110 return event;
111 }
112
113 return std::nullopt;
114 }
115
117 {
118 std::lock_guard<std::mutex> lock(_eventQueueMutex);
119 _eventQueue.push_back(std::move(event));
120 }
121
122 std::vector<net::Packet> ClientNetwork::hasPendingPackets(void)
123 {
124 std::lock_guard<std::mutex> lock(_eventQueueMutex);
125 std::vector<net::Packet> packets;
126 for (const auto& event : _eventQueue) {
127 packets.push_back(event.packet);
128 }
129 return packets;
130 }
131
133 {
134 std::lock_guard<std::mutex> lock(_eventQueueMutex);
135 if (_eventQueue.empty()) {
136 throw std::runtime_error("No packets available to pop.");
137 }
138 net::Packet packet = std::move(_eventQueue.front().packet);
139 _eventQueue.pop_front();
140 return packet;
141 }
142
144 {
145 asio::async_read(
147 asio::buffer(&_tcpHeader, sizeof(net::Header)),
148 [this](const asio::error_code& error, std::size_t)
149 {
150 if (error) {
151 log::error("TCP Read Header Error: {}", error.message());
152 stop();
153 return;
154 }
155
160
161 log::info(
162 "Received TCP Header - OpCode: {}, BodySize: {}",
163 static_cast<uint8_t>(_tcpHeader.opCode),
165 );
166
168 log::error("Invalid Magic Number from Server");
170 return;
171 }
172
173 if (_tcpHeader.bodySize > 0) {
175 readTcpBody();
176 } else {
177 net::Packet packet;
178 packet.header = _tcpHeader;
179
181 log::error("Welcome packet without sessionId body");
182 }
183
184 publishEvent({0, packet});
186 }
187 }
188 );
189 }
190
191
193 {
194 asio::async_read(
196 asio::buffer(_tcpBody.data(), _tcpBody.size()),
197 [this](const asio::error_code& error, std::size_t)
198 {
199 if (error) {
200 log::error("TCP Read Body Error: {}", error.message());
201 stop();
202 return;
203 }
204
205 net::Packet packet;
206 packet.header = _tcpHeader;
207 packet.body = _tcpBody;
208
210 uint32_t sessionId = 0;
211 packet >> sessionId;
212
213 _sessionId = sessionId;
214
215 log::info("Session ID reçu: {}", _sessionId);
216
217 this->sendUdpHandshake();
218 }
219
220 publishEvent({0, packet});
222 }
223 );
224 }
225
226
227 void ClientNetwork::readUdp(void)
228 {
229 _udpSocket.async_receive_from(
230 asio::buffer(_udpBuffer),
231 _udpSenderEndpoint,
232 [this](const asio::error_code& error, std::size_t bytesReceived)
233 {
234 if (error) {
235 if (error != asio::error::operation_aborted)
236 log::error("UDP receive error: {}", error.message());
237 return;
238 }
239
240 if (bytesReceived < sizeof(net::Header)) {
241 readUdp();
242 return;
243 }
244
245 net::Header header;
246 std::memcpy(&header, _udpBuffer.data(), sizeof(header));
247
248 header.magic = net::Packet::from_network(header.magic);
251 header.ackId = net::Packet::from_network(header.ackId);
253
254 if (header.magic != net::MAGIC_NUMBER) {
255 readUdp();
256 return;
257 }
258
259 if (bytesReceived != sizeof(header) + header.bodySize) {
260 log::error("Malformed UDP packet");
261 readUdp();
262 return;
263 }
264
265 net::Packet packet;
266 packet.header = header;
267 packet.body.resize(header.bodySize);
268
269 std::memcpy(
270 packet.body.data(),
271 _udpBuffer.data() + sizeof(header),
272 header.bodySize
273 );
274
275 publishEvent({ header.sessionId, packet });
276 readUdp();
277 }
278 );
279 }
280
281 void ClientNetwork::sendUdpHandshake(void)
282 {
283 log::info("sendUdpHandshake() called: bound={}, sessionId={}", _udpBound, _sessionId);
284
285 if (_udpBound || _sessionId == 0)
286 return;
287
289 p.header.sessionId = _sessionId;
290 sendPacket(p, net::NetworkMode::UDP);
291
292 _udpBound = true;
293 log::info("UDP handshake envoyé (session {})", _sessionId);
294 }
295
296 bool ClientNetwork::isUdpReady(void) const
297 {
298 return _udpBound;
299 }
300
301
302} // namespace rtp::client
Logger declaration with support for multiple log levels.
std::vector< net::Packet > hasPendingPackets(void)
Check if there are pending packets in the event queue.
void readUdp(void)
Read UDP packets asynchronously.
asio::ip::tcp::socket _tcpSocket
TCP socket for communication.
void sendPacket(const net::Packet &packet, net::NetworkMode mode)
Send a packet to the server.
ClientNetwork(const std::string &serverIp, uint16_t serverPort)
Constructor for Client Network.
std::string _serverIp
Server IP address.
void sendUdpHandshake(void)
Send UDP handshake to the server.
~ClientNetwork() override
Destructor for Client Network.
std::mutex _eventQueueMutex
Mutex for event queue synchronization.
net::Packet popPacket(void)
Pop a packet from the event queue.
std::deque< net::NetworkEvent > _eventQueue
Queue of network events.
uint32_t _sessionId
Client session ID.
void stop(void) override
Stop the network client.
net::Header _tcpHeader
TCP packet header.
std::thread _ioThread
Thread for running the I/O context.
uint16_t _serverPort
Server port number.
std::vector< uint8_t > _tcpBody
TCP packet body.
asio::ip::udp::endpoint _serverEndpoint
Server UDP endpoint.
void readTcpBody(void)
Read TCP packet body asynchronously.
void start(void) override
Start the network client.
asio::io_context _ioContext
ASIO I/O context.
asio::ip::udp::socket _udpSocket
UDP socket for communication.
void readTcpHeader(void)
Read TCP packet header asynchronously.
void publishEvent(net::NetworkEvent event)
Publish a network event.
std::optional< net::NetworkEvent > pollEvent(void) override
Poll for a network event.
Network packet with header and serializable body.
Definition Packet.hpp:471
BufferSequence getBufferSequence(void) const
Get buffer sequence for network transmission.
Definition Packet.cpp:36
std::vector< uint8_t > body
Packet body/payload.
Definition Packet.hpp:474
Header header
Packet header.
Definition Packet.hpp:473
static T from_network(T value)
Converts a primitive type (integer, float) from Big-Endian (network) to machine endianness.
Definition Packet.hpp:532
R-Type client namespace.
void error(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an error message.
void info(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an informational message.
constexpr uint16_t MAGIC_NUMBER
Magic number for packet validation.
Definition Packet.hpp:52
@ Welcome
Server welcome response.
@ Hello
Client hello packet.
NetworkMode
Enum representing network transmission modes.
Definition INetwork.hpp:25
Packet header with sequencing and acknowledgment support.
Definition Packet.hpp:130
uint16_t ackId
Last acknowledged packet.
Definition Packet.hpp:134
uint16_t magic
Magic number for validation.
Definition Packet.hpp:131
uint16_t sequenceId
Packet sequence number.
Definition Packet.hpp:132
uint32_t sessionId
Session identifier.
Definition Packet.hpp:137
uint32_t bodySize
Size of the packet body.
Definition Packet.hpp:133
OpCode opCode
Operation code.
Definition Packet.hpp:135
Represents a network event containing session ID and packet data.
Definition INetwork.hpp:34