OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
udp_socket.hpp
1#pragma once
3#include <ossia/network/sockets/configuration.hpp>
4#include <ossia/network/sockets/writers.hpp>
5
6#include <boost/asio/io_context.hpp>
7#include <boost/asio/ip/multicast.hpp>
8#include <boost/asio/ip/udp.hpp>
9#include <boost/asio/local/datagram_protocol.hpp>
10#include <boost/asio/placeholders.hpp>
11#include <boost/asio/strand.hpp>
12#include <boost/asio/write.hpp>
13
14#if !defined(_WIN32)
15#include <sys/socket.h>
16#endif
17
18#include <nano_signal_slot.hpp>
19
20namespace ossia::net
21{
22
23class udp_receive_socket
24{
25 using proto = boost::asio::ip::udp;
26
27public:
28 udp_receive_socket(boost::asio::io_context& ctx)
29 : m_context{ctx}
30 , m_socket{boost::asio::make_strand(ctx)}
31 {
32 }
33
34 udp_receive_socket(const inbound_socket_configuration& conf, boost::asio::io_context& ctx)
35 : m_context{ctx}
36 , m_endpoint{boost::asio::ip::make_address(conf.bind), conf.port}
37 , m_socket{boost::asio::make_strand(ctx)}
38 , m_multicast_group{conf.multicast_group}
39 , m_multicast_interface{conf.multicast_interface}
40 {
41 }
42
43 ~udp_receive_socket() = default;
44
45 void assign(int sock) { m_socket.assign(boost::asio::ip::udp::v4(), sock); }
46 void open()
47 {
48#if defined(__EMSCRIPTEN__)
49 // A browser has no way to bind a port.
50 return;
51#else
52 m_socket.open(boost::asio::ip::udp::v4());
53 if(!m_multicast_group.empty())
54 {
55 m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
56#if defined(SO_REUSEPORT)
57 // macOS / *BSD require SO_REUSEPORT in addition to SO_REUSEADDR for
58 // multiple processes to share a multicast port. On Linux it's harmless
59 // (kernel allows it for multicast). Windows has no SO_REUSEPORT.
60 using reuse_port
61 = boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT>;
62 boost::system::error_code ec;
63 m_socket.set_option(reuse_port(true), ec);
64#endif
65 }
66 m_socket.bind(m_endpoint);
67 if(!m_multicast_group.empty())
68 {
69 const auto group = boost::asio::ip::make_address_v4(m_multicast_group);
70 const auto iface = boost::asio::ip::make_address_v4(m_multicast_interface);
71 m_socket.set_option(boost::asio::ip::multicast::join_group(group, iface));
72 }
73#endif
74 }
75
76 void close()
77 {
78 // Posts a lambda that can run after the socket is gone: the usual shutdown
79 // is close() followed by dropping the owner.
80 if(m_socket.is_open())
81 {
82 boost::asio::post(m_context, [this, alive = m_lifetime.watch()] {
83 if(alive.expired())
84 return;
85
86 try
87 {
88 m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
89 }
90 catch(...)
91 {
92 }
93
94 m_socket.close();
95 on_close();
96 });
97 }
98 }
99
104 template <typename F>
105 void receive(F f)
106 {
107#if defined(__EMSCRIPTEN__)
108 (void)f;
109 return;
110#else
111 m_socket.async_receive_from(
112 boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,
113 [this, f](auto ec, std::size_t sz) {
114 if(ec == boost::asio::error::operation_aborted)
115 return;
116
117 if(!ec && sz > 0)
118 {
119 try
120 {
121 f(m_data, sz);
122 }
123 catch(const std::exception& e)
124 {
125 ossia::logger().error("[udp_socket::receive]: {}", e.what());
126 }
127 catch(...)
128 {
129 ossia::logger().error("[udp_socket::receive]: unknown error");
130 }
131 }
132
133 this->receive(f);
134 });
135#endif
136 }
137
138 Nano::Signal<void()> on_close;
139
140 boost::asio::io_context& m_context;
141 proto::endpoint m_endpoint;
142 proto::socket m_socket;
143 std::string m_multicast_group;
144 std::string m_multicast_interface;
145 alignas(16) char m_data[65535];
146 lifetime_token m_lifetime;
147};
148
149class udp_send_socket
150{
151 using proto = boost::asio::ip::udp;
152
153public:
154 udp_send_socket(const outbound_socket_configuration& conf, boost::asio::io_context& ctx)
155 : m_context{ctx}
156 , m_endpoint{boost::asio::ip::make_address(conf.host), conf.port}
157 , m_socket{boost::asio::make_strand(ctx)}
158 , m_broadcast{conf.broadcast}
159 , m_multicast_ttl{conf.multicast_ttl}
160 , m_multicast_interface{conf.multicast_interface}
161 , m_multicast_loopback{conf.multicast_loopback}
162 {
163 }
164
165 udp_send_socket(
166 const boost::asio::ip::address& host, const uint16_t port,
167 boost::asio::io_context& ctx)
168 : m_context{ctx}
169 , m_endpoint{host, port}
170 , m_socket{boost::asio::make_strand(ctx)}
171 {
172 }
173
174 void connect()
175 {
176#if defined(__EMSCRIPTEN__)
177 return;
178#else
179 m_socket.open(boost::asio::ip::udp::v4());
180
181 m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
182
183 if(m_broadcast)
184 m_socket.set_option(boost::asio::socket_base::broadcast(true));
185
186 if(m_endpoint.address().is_multicast())
187 {
188 if(m_multicast_ttl)
189 m_socket.set_option(boost::asio::ip::multicast::hops(*m_multicast_ttl));
190 if(!m_multicast_interface.empty())
191 {
192 m_socket.set_option(boost::asio::ip::multicast::outbound_interface(
193 boost::asio::ip::make_address_v4(m_multicast_interface)));
194 }
195 if(m_multicast_loopback)
196 {
197 m_socket.set_option(
198 boost::asio::ip::multicast::enable_loopback(*m_multicast_loopback));
199 }
200 }
201#endif
202 }
203
204 void close()
205 {
206 // Same as udp_receive_socket::close(): the posted lambda outlives us.
207 if(m_socket.is_open())
208 {
209 boost::asio::post(m_context, [this, alive = m_lifetime.watch()] {
210 if(alive.expired())
211 return;
212
213 try
214 {
215 m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
216 }
217 catch(...)
218 {
219 }
220 m_socket.close();
221 on_close();
222 });
223 }
224 }
225
226 void write(const proto::endpoint& ep, const char* data, std::size_t sz)
227 {
228 boost::system::error_code ec;
229 m_socket.send_to(boost::asio::const_buffer(data, sz), ep, 0, ec);
230 }
231
232 void write(const char* data, std::size_t sz) { write(m_endpoint, data, sz); }
233
234 Nano::Signal<void()> on_close;
235
236 boost::asio::io_context& m_context;
237 proto::endpoint m_endpoint;
238 proto::socket m_socket;
239 bool m_broadcast{};
240 std::optional<int> m_multicast_ttl;
241 std::string m_multicast_interface;
242 std::optional<bool> m_multicast_loopback;
243 lifetime_token m_lifetime;
244};
245
246}
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition context.cpp:120