PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
udpSocket.cpp
Go to the documentation of this file.
1
7
9
10#include <algorithm>
11#include <asio.hpp>
12#include <chrono>
13#include <cstdint>
14#include <memory>
15#include <string>
16#include <system_error>
17#include <utility>
18#include <vector>
19
20#include "profinet/exceptions.h"
21
22namespace profinet
23{
24namespace
25{
26
34[[noreturn]] void ThrowSocketError(
35 const std::string& operation,
36 const asio::error_code& error)
37{
38 if (error == asio::error::access_denied)
39 {
40 throw PermissionDeniedError(operation + ": " + error.message());
41 }
42
43 throw SocketError(operation + ": " + error.message());
44}
45
46} // namespace
47
49 const std::string& host,
50 std::uint16_t port,
51 std::chrono::duration<double> timeout)
52 : ioContext(std::make_unique<asio::io_context>()),
53 socket(std::make_unique<asio::ip::udp::socket>(*ioContext))
54{
55 asio::ip::udp::resolver resolver(*ioContext);
56 asio::error_code ec;
57
58 const auto endpoints = resolver.resolve(
59 asio::ip::udp::v4(),
60 host,
61 std::to_string(port),
62 ec);
63
64 if (ec)
65 {
66 ThrowSocketError(
67 "Failed to resolve UDP endpoint " + host + ":" +
68 std::to_string(port),
69 ec);
70 }
71
72 socket->open(asio::ip::udp::v4(), ec);
73 if (ec)
74 {
75 ThrowSocketError("Failed to create UDP socket", ec);
76 }
77
78 asio::connect(*socket, endpoints, ec);
79 if (ec)
80 {
81 ThrowSocketError(
82 "Failed to connect UDP socket to " + host + ":" +
83 std::to_string(port),
84 ec);
85 }
86
87 SetTimeout(std::chrono::duration_cast<std::chrono::milliseconds>(timeout));
88}
89
91 std::uint16_t localPort,
92 std::chrono::duration<double> timeout)
93 : ioContext(std::make_unique<asio::io_context>()),
94 socket(std::make_unique<asio::ip::udp::socket>(*ioContext))
95{
96 asio::error_code ec;
97
98 socket->open(asio::ip::udp::v4(), ec);
99 if (ec)
100 {
101 ThrowSocketError("Failed to create UDP socket", ec);
102 }
103
104 socket->set_option(
105 asio::ip::udp::socket::reuse_address(true),
106 ec);
107
108 if (ec)
109 {
110 ThrowSocketError("Failed to configure UDP socket reuse", ec);
111 }
112
113 const asio::ip::udp::endpoint endpoint(
114 asio::ip::udp::v4(),
115 localPort);
116
117 socket->bind(endpoint, ec);
118
119 if (ec)
120 {
121 ThrowSocketError(
122 "Failed to bind UDP socket to port " +
123 std::to_string(localPort),
124 ec);
125 }
126
127 SetTimeout(std::chrono::duration_cast<std::chrono::milliseconds>(timeout));
128}
129
131{
132 if (socket != nullptr)
133 {
134 asio::error_code ec;
135 socket->cancel(ec);
136 socket->close(ec);
137 }
138}
139
141 : ioContext(std::move(other.ioContext)),
142 socket(std::move(other.socket)),
143 receiveTimeout(other.receiveTimeout)
144{
145}
146
148{
149 if (this != &other)
150 {
151 if (socket != nullptr)
152 {
153 asio::error_code ec;
154 socket->cancel(ec);
155 socket->close(ec);
156 }
157
158 ioContext = std::move(other.ioContext);
159 socket = std::move(other.socket);
160 receiveTimeout = other.receiveTimeout;
161 }
162
163 return *this;
164}
165
166void UdpSocket::Send(const std::vector<std::uint8_t>& data) const
167{
168 if (socket == nullptr || !socket->is_open())
169 {
170 throw SocketError("UDP socket is not open");
171 }
172
173 asio::error_code ec;
174
175 const std::size_t sent = socket->send(
176 asio::buffer(data),
177 0,
178 ec);
179
180 if (ec)
181 {
182 ThrowSocketError("Failed to send UDP datagram", ec);
183 }
184
185 if (sent != data.size())
186 {
187 throw SocketError("UDP datagram was only partially sent");
188 }
189}
190
191std::vector<std::uint8_t> UdpSocket::Recv(std::size_t maxLen) const
192{
193 if (socket == nullptr || !socket->is_open())
194 {
195 throw SocketError("UDP socket is not open");
196 }
197
198 if (maxLen == 0)
199 {
200 return {};
201 }
202
203 std::vector<std::uint8_t> buffer(maxLen);
204
205 asio::error_code receiveError;
206 std::size_t receivedBytes = 0;
207 bool timedOut = false;
208
209 asio::steady_timer timer(*ioContext);
210 timer.expires_after(receiveTimeout);
211
212 socket->async_receive(
213 asio::buffer(buffer),
214 [&](const asio::error_code& ec, std::size_t bytesReceived)
215 {
216 receiveError = ec;
217 receivedBytes = bytesReceived;
218
219 asio::error_code timerError;
220 timer.cancel(timerError);
221 });
222
223 timer.async_wait(
224 [&](const asio::error_code& ec)
225 {
226 if (ec)
227 {
228 return;
229 }
230
231 timedOut = true;
232
233 asio::error_code cancelError;
234 socket->cancel(cancelError);
235 });
236
237 ioContext->restart();
238 ioContext->run();
239
240 if (timedOut)
241 {
242 return {};
243 }
244
245 if (receiveError)
246 {
247 ThrowSocketError("Socket error during UDP receive", receiveError);
248 }
249
250 buffer.resize(receivedBytes);
251 return buffer;
252}
253
255{
256 if (socket == nullptr || !socket->is_open())
257 {
258 throw SocketError("UDP socket is not open");
259 }
260
261 if (maxLen == 0)
262 {
263 return {};
264 }
265
266 UdpDatagram datagram;
267 datagram.data.resize(maxLen);
268
269 asio::ip::udp::endpoint sourceEndpoint;
270 asio::error_code receiveError;
271 std::size_t receivedBytes = 0;
272 bool timedOut = false;
273
274 asio::steady_timer timer(*ioContext);
275 timer.expires_after(receiveTimeout);
276
277 socket->async_receive_from(
278 asio::buffer(datagram.data),
279 sourceEndpoint,
280 [&](const asio::error_code& ec, std::size_t bytesReceived)
281 {
282 receiveError = ec;
283 receivedBytes = bytesReceived;
284
285 asio::error_code timerError;
286 timer.cancel(timerError);
287 });
288
289 timer.async_wait(
290 [&](const asio::error_code& ec)
291 {
292 if (ec)
293 {
294 return;
295 }
296
297 timedOut = true;
298
299 asio::error_code cancelError;
300 socket->cancel(cancelError);
301 });
302
303 ioContext->restart();
304 ioContext->run();
305
306 if (timedOut)
307 {
308 return {};
309 }
310
311 if (receiveError)
312 {
313 ThrowSocketError(
314 "Socket error during UDP receive",
315 receiveError);
316 }
317
318 datagram.data.resize(receivedBytes);
319 datagram.source.address = sourceEndpoint.address().to_string();
320 datagram.source.port = sourceEndpoint.port();
321
322 return datagram;
323}
324
326 const std::vector<std::uint8_t>& data,
327 const UdpEndpoint& destination)
328{
329 if (socket == nullptr || !socket->is_open())
330 {
331 throw SocketError("UDP socket is not open");
332 }
333
334 asio::error_code ec;
335
336 const auto address =
337 asio::ip::make_address_v4(destination.address, ec);
338
339 if (ec)
340 {
341 throw SocketError(
342 "Invalid IPv4 destination address '" +
343 destination.address + "': " + ec.message());
344 }
345
346 const asio::ip::udp::endpoint endpoint(
347 address,
348 destination.port);
349
350 const std::size_t sent = socket->send_to(
351 asio::buffer(data),
352 endpoint,
353 0,
354 ec);
355
356 if (ec)
357 {
358 ThrowSocketError("Failed to send UDP datagram", ec);
359 }
360
361 if (sent != data.size())
362 {
363 throw SocketError("UDP datagram was only partially sent");
364 }
365}
366
367void UdpSocket::SetTimeout(std::chrono::milliseconds timeout)
368{
369 receiveTimeout = std::max(timeout, std::chrono::milliseconds::zero());
370}
371
373{
374 if (socket == nullptr || !socket->is_open())
375 {
376 throw SocketError("UDP socket is not open");
377 }
378
379 asio::error_code ec;
380 const auto endpoint = socket->local_endpoint(ec);
381
382 if (ec)
383 {
384 ThrowSocketError(
385 "Failed to query local UDP endpoint",
386 ec);
387 }
388
389 return {
390 endpoint.address().to_string(),
391 endpoint.port()};
392}
393
394} // namespace profinet
Socket operation error.
Definition exceptions.h:663
RAII wrapper around a portable IPv4 UDP socket.
Definition udpSocket.h:40
std::vector< std::uint8_t > Recv(std::size_t maxLen=defaultReceiveBufferSize) const
Receive a UDP datagram from the connected peer.
void SendTo(const std::vector< std::uint8_t > &data, const UdpEndpoint &) override
Send a datagram to a specific IPv4 endpoint.
std::chrono::milliseconds receiveTimeout
Receive timeout applied to synchronous receive operations.
Definition udpSocket.h:142
UdpSocket(const std::string &host, std::uint16_t port, std::chrono::duration< double > timeout=std::chrono::duration< double >(defaultTimeout))
Resolve a host and connect a UDP socket to it.
Definition udpSocket.cpp:48
void Send(const std::vector< std::uint8_t > &data) const
Send a UDP datagram to the connected peer.
std::unique_ptr< asio::io_context > ioContext
Asio execution context owned by this socket.
Definition udpSocket.h:136
UdpDatagram RecvFrom(std::size_t maxLen) override
Receive a datagram together with its source endpoint.
std::unique_ptr< asio::ip::udp::socket > socket
Underlying portable Asio UDP socket.
Definition udpSocket.h:139
UdpSocket & operator=(const UdpSocket &)=delete
Not copyable.
UdpEndpoint LocalEndpoint() const
Return the local IPv4 endpoint assigned to the socket.
~UdpSocket() override
Destroy the socket and release all owned resources.
void SetTimeout(std::chrono::milliseconds timeout) override
Set the receive timeout used by subsequent receive operations.
Declares exceptions and error types used by the PROFINET IO controller stack.
asio::error_code error
Bytes buffer
std::atomic_bool timedOut
asio::steady_timer timer
std::size_t receivedBytes
A UDP datagram together with the IPv4 address of its sender.
Definition IUdpSocket.h:65
std::vector< std::uint8_t > data
Datagram payload.
Definition IUdpSocket.h:67
UdpEndpoint source
IPv4 address and UDP port of the datagram sender.
Definition IUdpSocket.h:70
Portable IPv4 UDP endpoint.
Definition IUdpSocket.h:42
std::string address
Numeric IPv4 address in dotted-decimal notation.
Definition IUdpSocket.h:44
std::uint16_t port
UDP port in host byte order.
Definition IUdpSocket.h:47
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
Declares the portable IPv4 UDP socket implementation.