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