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 m_socket.open(boost::asio::ip::udp::v4());
49 if(!m_multicast_group.empty())
50 {
51 m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
52#if defined(SO_REUSEPORT)
53 // macOS / *BSD require SO_REUSEPORT in addition to SO_REUSEADDR for
54 // multiple processes to share a multicast port. On Linux it's harmless
55 // (kernel allows it for multicast). Windows has no SO_REUSEPORT.
56 using reuse_port
57 = boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT>;
58 boost::system::error_code ec;
59 m_socket.set_option(reuse_port(true), ec);
60#endif
61 }
62 m_socket.bind(m_endpoint);
63 if(!m_multicast_group.empty())
64 {
65 const auto group = boost::asio::ip::make_address_v4(m_multicast_group);
66 const auto iface = boost::asio::ip::make_address_v4(m_multicast_interface);
67 m_socket.set_option(boost::asio::ip::multicast::join_group(group, iface));
68 }
69 }
70
71 void close()
72 {
73 // Posts a lambda that can run after the socket is gone: the usual shutdown
74 // is close() followed by dropping the owner.
75 if(m_socket.is_open())
76 {
77 boost::asio::post(m_context, [this, alive = m_lifetime.watch()] {
78 if(alive.expired())
79 return;
80
81 try
82 {
83 m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
84 }
85 catch(...)
86 {
87 }
88
89 m_socket.close();
90 on_close();
91 });
92 }
93 }
94
99 template <typename F>
100 void receive(F f)
101 {
102 m_socket.async_receive_from(
103 boost::asio::mutable_buffer(&m_data[0], std::size(m_data)), m_endpoint,
104 [this, f](auto ec, std::size_t sz) {
105 if(ec == boost::asio::error::operation_aborted)
106 return;
107
108 if(!ec && sz > 0)
109 {
110 try
111 {
112 f(m_data, sz);
113 }
114 catch(const std::exception& e)
115 {
116 ossia::logger().error("[udp_socket::receive]: {}", e.what());
117 }
118 catch(...)
119 {
120 ossia::logger().error("[udp_socket::receive]: unknown error");
121 }
122 }
123
124 this->receive(f);
125 });
126 }
127
128 Nano::Signal<void()> on_close;
129
130 boost::asio::io_context& m_context;
131 proto::endpoint m_endpoint;
132 proto::socket m_socket;
133 std::string m_multicast_group;
134 std::string m_multicast_interface;
135 alignas(16) char m_data[65535];
136 lifetime_token m_lifetime;
137};
138
139class udp_send_socket
140{
141 using proto = boost::asio::ip::udp;
142
143public:
144 udp_send_socket(const outbound_socket_configuration& conf, boost::asio::io_context& ctx)
145 : m_context{ctx}
146 , m_endpoint{boost::asio::ip::make_address(conf.host), conf.port}
147 , m_socket{boost::asio::make_strand(ctx)}
148 , m_broadcast{conf.broadcast}
149 , m_multicast_ttl{conf.multicast_ttl}
150 , m_multicast_interface{conf.multicast_interface}
151 , m_multicast_loopback{conf.multicast_loopback}
152 {
153 }
154
155 udp_send_socket(
156 const boost::asio::ip::address& host, const uint16_t port,
157 boost::asio::io_context& ctx)
158 : m_context{ctx}
159 , m_endpoint{host, port}
160 , m_socket{boost::asio::make_strand(ctx)}
161 {
162 }
163
164 void connect()
165 {
166 m_socket.open(boost::asio::ip::udp::v4());
167
168 m_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
169
170 if(m_broadcast)
171 m_socket.set_option(boost::asio::socket_base::broadcast(true));
172
173 if(m_endpoint.address().is_multicast())
174 {
175 if(m_multicast_ttl)
176 m_socket.set_option(boost::asio::ip::multicast::hops(*m_multicast_ttl));
177 if(!m_multicast_interface.empty())
178 {
179 m_socket.set_option(boost::asio::ip::multicast::outbound_interface(
180 boost::asio::ip::make_address_v4(m_multicast_interface)));
181 }
182 if(m_multicast_loopback)
183 {
184 m_socket.set_option(
185 boost::asio::ip::multicast::enable_loopback(*m_multicast_loopback));
186 }
187 }
188 }
189
190 void close()
191 {
192 // Same as udp_receive_socket::close(): the posted lambda outlives us.
193 if(m_socket.is_open())
194 {
195 boost::asio::post(m_context, [this, alive = m_lifetime.watch()] {
196 if(alive.expired())
197 return;
198
199 try
200 {
201 m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
202 }
203 catch(...)
204 {
205 }
206 m_socket.close();
207 on_close();
208 });
209 }
210 }
211
212 void write(const proto::endpoint& ep, const char* data, std::size_t sz)
213 {
214 boost::system::error_code ec;
215 m_socket.send_to(boost::asio::const_buffer(data, sz), ep, 0, ec);
216 }
217
218 void write(const char* data, std::size_t sz) { write(m_endpoint, data, sz); }
219
220 Nano::Signal<void()> on_close;
221
222 boost::asio::io_context& m_context;
223 proto::endpoint m_endpoint;
224 proto::socket m_socket;
225 bool m_broadcast{};
226 std::optional<int> m_multicast_ttl;
227 std::string m_multicast_interface;
228 std::optional<bool> m_multicast_loopback;
229 lifetime_token m_lifetime;
230};
231
232}
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition context.cpp:120