OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
tcp_socket.hpp
1#pragma once
2#include <ossia/network/context.hpp>
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/tcp.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#include <nano_signal_slot.hpp>
15
16namespace ossia::net
17{
18class tcp_listener
19{
20public:
21 using proto = boost::asio::ip::tcp;
22 using socket = typename proto::socket;
23
24 tcp_listener() = delete;
25 tcp_listener(const tcp_listener&) = delete;
26 tcp_listener& operator=(const tcp_listener&) = delete;
27 tcp_listener(tcp_listener&&) = default;
28 tcp_listener& operator=(tcp_listener&&) = default;
29 explicit tcp_listener(proto::socket sock)
30 : m_socket{std::move(sock)}
31 {
32 }
33
34 void close()
35 {
36 // FIXME async?
37 try
38 {
39 m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
40 }
41 catch(...)
42 {
43 }
44 m_socket.close();
45 }
46
47 void write(const boost::asio::const_buffer& buf)
48 {
49 boost::system::error_code ec;
50 boost::asio::write(m_socket, buf, ec);
51 }
52
53 void on_close() { }
54
55 void on_fail() { }
56
57 proto::socket m_socket;
58};
59
60class tcp_server
61{
62public:
63 using proto = boost::asio::ip::tcp;
64 using socket = typename proto::socket;
65 using listener = tcp_listener;
66
67 tcp_server(const inbound_socket_configuration& conf, boost::asio::io_context& ctx)
68 : m_context{ctx}
69#if defined(__EMSCRIPTEN__)
70 // A browser has no way to bind a port.
71 , m_acceptor{boost::asio::make_strand(ctx)}
72#else
73 , m_acceptor{
74 boost::asio::make_strand(ctx),
75 proto::endpoint{boost::asio::ip::make_address(conf.bind), conf.port}}
76#endif
77 {
78 }
79
80 tcp_server(
81 const inbound_socket_configuration& conf, ossia::net::network_context_ptr ctx)
82 : tcp_server{conf, ctx->context}
83 {
84 }
85
86 boost::asio::io_context& m_context;
87 proto::acceptor m_acceptor;
88};
89
90class tcp_client
91{
92public:
93 using proto = boost::asio::ip::tcp;
94 using socket = typename proto::socket;
95
96 tcp_client(const outbound_socket_configuration& conf, boost::asio::io_context& ctx)
97 : m_context{ctx}
98 , m_endpoint{boost::asio::ip::make_address(conf.host), conf.port}
99 , m_socket{boost::asio::make_strand(ctx)}
100 {
101 }
102
103 void connect()
104 {
105 boost::system::error_code ec;
106 m_socket.set_option(boost::asio::ip::tcp::no_delay{true}, ec);
107 m_socket.set_option(boost::asio::socket_base::reuse_address{true}, ec);
108
109 m_socket.async_connect(
110 m_endpoint, [this, alive = m_lifetime.watch()](
111 const boost::system::error_code& ec, auto&&...) {
112 // The client may have been destroyed while the connect was in flight;
113 // see lifetime_token.
114 if(alive.expired())
115 return;
116
117 if(m_socket.is_open() && !ec)
118 {
119 m_connected = true;
120 on_open();
121 }
122 else
123 {
124 m_connected = false;
125 puts(ec.message().c_str());
126 on_fail();
127 }
128 });
129 }
130
131 bool connected() const { return m_connected; }
132
133 void close()
134 {
135 boost::asio::post(m_context, [this, alive = m_lifetime.watch()] {
136 if(alive.expired())
137 return;
138
139 try
140 {
141 m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
142 }
143 catch(...)
144 {
145 }
146 m_socket.close();
147 on_close();
148 });
149 }
150
151 void write(const char* data, std::size_t sz)
152 {
153 boost::system::error_code ec;
154 boost::asio::write(m_socket, boost::asio::const_buffer(data, sz), ec);
155 }
156
157 Nano::Signal<void()> on_open;
158 Nano::Signal<void()> on_close;
159 Nano::Signal<void()> on_fail;
160
161 boost::asio::io_context& m_context;
162 proto::endpoint m_endpoint;
163 proto::socket m_socket;
164 bool m_connected{false};
165
166 // Guards the handlers of connect() and close(); the pending reads of a framed
167 // client are guarded by the decoder's own token. See lifetime_token.
168 lifetime_token m_lifetime;
169};
170}