HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
NET_SocketListener.h
Go to the documentation of this file.
1 /*
2  * PROPRIETARY INFORMATION. This software is proprietary to
3  * Side Effects Software Inc., and is not to be reproduced,
4  * transmitted, or disclosed in any way without written permission.
5  *
6  * NAME: NET_SocketListener.h
7  *
8  * COMMENTS:
9  *
10  */
11 
12 #ifndef __NET_SOCKETLISTENER_H__
13 #define __NET_SOCKETLISTENER_H__
14 
15 #include "NET_API.h"
16 
17 #include "NET_ConnectionHandler.h"
18 #include "NET_Error.h"
19 #include "NET_InfoCallback.h"
20 #include "NET_SocketGroup.h"
21 
22 #include <UT/UT_BoostAsio.h>
23 #include <UT/UT_Debug.h>
24 #include <UT/UT_IntrusivePtr.h>
25 #include <UT/UT_UniquePtr.h>
27 
28 #include <SYS/SYS_Compiler.h>
29 
30 #include <type_traits>
31 
32 class NET_WebServer;
33 
35  : public UTenable_shared_from_this<NET_ISocketListener>
36 {
37 public:
38  virtual ~NET_ISocketListener() = default;
39 
40  virtual void close(bool force) = 0;
41  virtual bool isOpen() const = 0;
42 
43 protected:
45  : myIOContext(context), mySocketGroup(nullptr)
46  {
47  }
48 
49  /// Called when something failed with the socket listener.
50  void fail(const hboost::system::error_code& ec, const char* what);
51 
55 };
56 
57 template <typename EndpointT, typename Enabled = void>
58 struct endpoint_has_port : std::false_type
59 {
60 };
61 
62 template <typename EndpointT>
63 struct endpoint_has_port<EndpointT, decltype(std::declval<EndpointT>().port(0))>
64  : std::true_type
65 {
66 };
67 
68 template <typename SocketT>
70 {
71  using endpoint_type = typename SocketT::endpoint_type;
72  static constexpr bool uses_port
74 };
75 
76 template <
77  typename AcceptorT,
78  typename SocketT
79  = hboost::asio::basic_stream_socket<typename AcceptorT::protocol_type>>
81 {
82 public:
83  using acceptor_type = AcceptorT;
84  using socket_type = SocketT;
85  using endpoint_type = typename AcceptorT::endpoint_type;
86 
87  template <
88  typename S = SocketT,
89  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
90  void start(
91  int initial_port,
92  int max_port_range = -1,
93  bool use_system_port = false,
94  const ASIO_IPAddress& address = ASIO_IPAddressV4())
95  {
96  myPortInfo.myPort = initial_port;
97  myPortInfo.myMaxPort = max_port_range;
98  myPortInfo.myUseSystemPort = use_system_port;
99  myPortInfo.myBindAddress = address;
100 
101  start();
102  }
103  template <
104  typename S = SocketT,
105  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
106  void start();
107  void start(const ASIO_TCPEndpoint& ep)
108  {
109  if (isOpen())
110  {
111  return;
112  }
113 
114  try
115  {
116  myAcceptor.open(ep.protocol());
117  myAcceptor.set_option(typename AcceptorT::reuse_address(true));
118  myAcceptor.bind(ep);
119  myAcceptor.listen();
120  }
121  catch (std::exception& e)
122  {
123  NETaddFatal(NET_ERR_NET_MGR, "Server start error: {}", e.what());
124  myAcceptor.close();
125  return;
126  }
127 
128  if (isOpen())
129  {
130  init_();
131  startAccept_();
132  }
133  }
134  void close(bool force) override
135  {
137 
138  hboost::asio::dispatch(
139  myAcceptor.get_executor(),
140  [this, self = shared_from_this(), force]()
141  {
142  // Regardless of the closure type always try to close the
143  // acceptor so that no new connections come in.
144  if (myAcceptor.is_open())
145  {
146  hboost::system::error_code ec;
147  myAcceptor.close(ec);
148  if (ec)
149  fail(ec, "acceptor close");
150  }
151 
152  // Let the individual handlers define what a forced closure
153  // means to that handler.
155  });
156  }
157  /// Returns the port the socket listener is bound to. It does not return
158  /// the port that was requested to bind to.
159  template <
160  typename S = SocketT,
161  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
162  int port() const
163  {
164  return myAcceptor.local_endpoint().port();
165  }
166  template <
167  typename S = SocketT,
168  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
169  void setPort(int port)
170  {
171  myPortInfo.myPort = port;
172  }
173  template <
174  typename S = SocketT,
175  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
176  void setMaxPort(int port)
177  {
178  myPortInfo.myMaxPort = port;
179  }
180  template <
181  typename S = SocketT,
182  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
183  void setUseSystemPort(bool use_sys)
184  {
185  myPortInfo.myUseSystemPort = use_sys;
186  }
187  template <
188  typename S = SocketT,
189  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
190  void setUseIPv6(bool use_v6)
191  {
192  myPortInfo.myUseIPv6 = use_v6;
193  }
194  template <
195  typename S = SocketT,
196  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
197  bool useIPv6() const
198  {
199  return myPortInfo.myUseIPv6;
200  }
201  template <
202  typename S = SocketT,
203  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
204  void setBindAddress(const ASIO_IPAddress& bind_address)
205  {
206  myPortInfo.myBindAddress = bind_address;
207  }
208  template <
209  typename S = SocketT,
210  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
212  {
213  return myPortInfo.myBindAddress;
214  }
215 
216  endpoint_type endpoint() const { return myAcceptor.local_endpoint(); }
217 
218  bool isOpen() const override { return myAcceptor.is_open(); }
219 
220 protected:
222  : NET_ISocketListener(context)
223  , myAcceptor(hboost::asio::make_strand(context))
224  {
225  }
226 
227  /// Do any initialization after the socket listener has been setup but
228  /// before we actually start accepting requests.
229  virtual void init_() {}
230  /// Called each time a new socket is accepted. Make sure to add the
231  /// connection handler to the connection manager so that we can easily
232  /// stop all requests that came from this listening socket when shutting
233  /// down.
234  virtual void onAccept_(
235  const hboost::system::error_code& ec,
236  socket_type socket)
237  = 0;
238 
239  /// Start an async accept. If the accept was a success onAccept_ is called.
240  void startAccept_();
241 
242  AcceptorT myAcceptor;
243 
244  struct Empty {};
245 
246  struct PortInfo
247  {
249  int myPort = -1;
250  int myMaxPort = -1;
251  bool myUseSystemPort = false;
252  bool myUseIPv6 = false;
253  };
254 
255  std::conditional_t<socket_info<socket_type>::uses_port, PortInfo, Empty>
257 };
258 
259 template <typename AcceptorT, typename SocketT>
260 template <typename S, typename Detected>
261 void
263 {
264  if (isOpen())
265  {
266  UT_ASSERT(!"Trying to start the same connection twice.");
267  return;
268  }
269  endpoint_type ep(myPortInfo.myBindAddress, myPortInfo.myPort);
270 
271  start(ep);
272  if (!isOpen() && myPortInfo.myPort != 0)
273  {
274  // First try the port range if possible.
275  if (myPortInfo.myMaxPort > 0 && myPortInfo.myMaxPort > myPortInfo.myPort)
276  {
277  for (int port = myPortInfo.myPort + 1;
278  port <= myPortInfo.myMaxPort && !isOpen(); port++)
279  {
280  ep.port(port);
281  start(ep);
282  }
283  }
284 
285  // Second let the OS pick a port.
286  if (!isOpen() && myPortInfo.myUseSystemPort)
287  {
288  ep.port(0);
289  start(ep);
290  }
291  }
292 }
293 
294 template <typename AcceptorT, typename SocketT>
295 void
297 {
299 
300  myAcceptor.async_accept(
301  hboost::asio::make_strand(myIOContext),
302  [this, self = shared_from_this()](
303  hboost::system::error_code ec, socket_type socket) {
305  if (!myAcceptor.is_open())
306  return;
307 
308  if (!ec)
309  {
310  if (socket.is_open())
311  {
312  onAccept_(ec, std::move(socket));
313  }
314  }
315  else
316  {
317  // If the operation was cancelled it does not need to be
318  // logged and the operation is finished so there is no
319  // need to re-add an accept task.
320  if (ec == hboost::asio::error::operation_aborted)
321  return;
322  fail(ec, "accept");
323  }
324 
325  startAccept_();
326  });
327 }
328 
329 #endif // __NET_SOCKETLISTENER_H__
virtual void onAccept_(const hboost::system::error_code &ec, socket_type socket)=0
void start(const ASIO_TCPEndpoint &ep)
void setUseSystemPort(bool use_sys)
void start(int initial_port, int max_port_range=-1, bool use_system_port=false, const ASIO_IPAddress &address=ASIO_IPAddressV4())
void setBindAddress(const ASIO_IPAddress &bind_address)
GLuint start
Definition: glcorearb.h:475
NET_ISocketListener(ASIO_IOContext &context)
ASIO_IOContext & myIOContext
static constexpr bool uses_port
hboost::asio::ip::address_v4 ASIO_IPAddressV4
Definition: UT_BoostAsio.h:56
void close() override
void close(bool force) override
NET_ConnectionManager myConnectionManager
typename ASIO_TCPAcceptor::endpoint_type endpoint_type
#define NET_API
Definition: NET_API.h:9
std::enable_shared_from_this< T > UTenable_shared_from_this
Definition: UT_SharedPtr.h:39
typename SocketT::endpoint_type endpoint_type
hboost::asio::basic_stream_socket< typename ASIO_TCPAcceptor::protocol_type > socket_type
endpoint_type endpoint() const
#define ASIO_HANDLER_LOCATION
Definition: UT_BoostAsio.h:40
NET_SocketListener(ASIO_IOContext &context)
void startAccept_()
Start an async accept. If the accept was a success onAccept_ is called.
hboost::asio::io_context ASIO_IOContext
Definition: UT_BoostAsio.h:74
hboost::asio::ip::tcp::endpoint ASIO_TCPEndpoint
Definition: UT_BoostAsio.h:45
bool isOpen() const override
void setPort(int port)
void NETaddFatal(NET_LogManager &mgr, int code, const char *fmt, Args &&...args)
Definition: NET_Error.h:99
SIM_API const UT_StringHolder force
void setMaxPort(int port)
void fail(const hboost::system::error_code &ec, const char *what)
Called when something failed with the socket listener.
void setUseIPv6(bool use_v6)
#define UT_ASSERT(ZZ)
Definition: UT_Assert.h:156
std::conditional_t< socket_info< socket_type >::uses_port, PortInfo, Empty > myPortInfo
NET_ISocketGroup * mySocketGroup
hboost::asio::ip::address ASIO_IPAddress
Definition: UT_BoostAsio.h:55
const ASIO_IPAddress & bindAddress() const