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 , m_acceptor{
70 boost::asio::make_strand(ctx),
71 proto::endpoint{boost::asio::ip::make_address(conf.bind), conf.port}}
72 {
73 }
74
75 tcp_server(
76 const inbound_socket_configuration& conf, ossia::net::network_context_ptr ctx)
77 : tcp_server{conf, ctx->context}
78 {
79 }
80
81 boost::asio::io_context& m_context;
82 proto::acceptor m_acceptor;
83};
84
85class tcp_client
86{
87public:
88 using proto = boost::asio::ip::tcp;
89 using socket = typename proto::socket;
90
91 tcp_client(const outbound_socket_configuration& conf, boost::asio::io_context& ctx)
92 : m_context{ctx}
93 , m_endpoint{boost::asio::ip::make_address(conf.host), conf.port}
94 , m_socket{boost::asio::make_strand(ctx)}
95 {
96 }
97
98 void connect()
99 {
100 boost::system::error_code ec;
101 m_socket.set_option(boost::asio::ip::tcp::no_delay{true}, ec);
102 m_socket.set_option(boost::asio::socket_base::reuse_address{true}, ec);
103
104 m_socket.async_connect(
105 m_endpoint, [this, alive = m_lifetime.watch()](
106 const boost::system::error_code& ec, auto&&...) {
107 // The client may have been destroyed while the connect was in flight;
108 // see lifetime_token.
109 if(alive.expired())
110 return;
111
112 if(m_socket.is_open() && !ec)
113 {
114 m_connected = true;
115 on_open();
116 }
117 else
118 {
119 m_connected = false;
120 puts(ec.message().c_str());
121 on_fail();
122 }
123 });
124 }
125
126 bool connected() const { return m_connected; }
127
128 void close()
129 {
130 boost::asio::post(m_context, [this, alive = m_lifetime.watch()] {
131 if(alive.expired())
132 return;
133
134 try
135 {
136 m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
137 }
138 catch(...)
139 {
140 }
141 m_socket.close();
142 on_close();
143 });
144 }
145
146 void write(const char* data, std::size_t sz)
147 {
148 boost::system::error_code ec;
149 boost::asio::write(m_socket, boost::asio::const_buffer(data, sz), ec);
150 }
151
152 Nano::Signal<void()> on_open;
153 Nano::Signal<void()> on_close;
154 Nano::Signal<void()> on_fail;
155
156 boost::asio::io_context& m_context;
157 proto::endpoint m_endpoint;
158 proto::socket m_socket;
159 bool m_connected{false};
160
161 // Guards the handlers of connect() and close(); the pending reads of a framed
162 // client are guarded by the decoder's own token. See lifetime_token.
163 lifetime_token m_lifetime;
164};
165}