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  NET_ISocketListener(ctx.get_executor())
46  {}
47  NET_ISocketListener(const ASIO_IOContext::executor_type& executor)
48  : myExecutor(executor), mySocketGroup(nullptr)
49  {
50  }
51 
52  /// Called when something failed with the socket listener.
53  void fail(const hboost::system::error_code& ec, const char* what);
54 
56  ASIO_IOContext::executor_type myExecutor;
58 };
59 
60 template <typename EndpointT, typename Enabled = void>
61 struct endpoint_has_port : std::false_type
62 {
63 };
64 
65 template <typename EndpointT>
66 struct endpoint_has_port<EndpointT, decltype(std::declval<EndpointT>().port(0))>
67  : std::true_type
68 {
69 };
70 
71 template <typename SocketT>
73 {
74  using endpoint_type = typename SocketT::endpoint_type;
75  static constexpr bool uses_port
77 };
78 
79 template <
80  typename AcceptorT,
81  typename SocketT
82  = hboost::asio::basic_stream_socket<typename AcceptorT::protocol_type>>
84 {
85 public:
86  using acceptor_type = AcceptorT;
87  using socket_type = SocketT;
88  using endpoint_type = typename AcceptorT::endpoint_type;
89 
90  template <
91  typename S = SocketT,
92  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
93  void start(
94  int initial_port,
95  int max_port_range = -1,
96  bool use_system_port = false,
97  const ASIO_IPAddress& address = ASIO_IPAddressV4())
98  {
99  myPortInfo.myPort = initial_port;
100  myPortInfo.myMaxPort = max_port_range;
101  myPortInfo.myUseSystemPort = use_system_port;
102  myPortInfo.myBindAddress = address;
103 
104  start();
105  }
106  template <
107  typename S = SocketT,
108  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
109  void start();
110  void start(const ASIO_TCPEndpoint& ep)
111  {
112  if (isOpen())
113  {
114  return;
115  }
116 
117  try
118  {
119  myAcceptor.open(ep.protocol());
120  myAcceptor.set_option(typename AcceptorT::reuse_address(true));
121  myAcceptor.bind(ep);
122  myAcceptor.listen();
123  }
124  catch (std::exception& e)
125  {
126  NETaddFatal(NET_ERR_NET_MGR, "Server start error: {}", e.what());
127  myAcceptor.close();
128  return;
129  }
130 
131  if (isOpen())
132  {
133  init_();
134  startAccept_();
135  }
136  }
137  void close(bool force) override
138  {
140 
141  hboost::asio::dispatch(
142  myAcceptor.get_executor(),
143  [this, self = shared_from_this(), force]()
144  {
145  // Regardless of the closure type always try to close the
146  // acceptor so that no new connections come in.
147  if (myAcceptor.is_open())
148  {
149  hboost::system::error_code ec;
150  myAcceptor.close(ec);
151  if (ec)
152  fail(ec, "acceptor close");
153  }
154 
155  // Let the individual handlers define what a forced closure
156  // means to that handler.
158  });
159  }
160  /// Returns the port the socket listener is bound to. It does not return
161  /// the port that was requested to bind to.
162  template <
163  typename S = SocketT,
164  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
165  int port() const
166  {
167  return myAcceptor.local_endpoint().port();
168  }
169  template <
170  typename S = SocketT,
171  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
172  void setPort(int port)
173  {
174  myPortInfo.myPort = port;
175  }
176  template <
177  typename S = SocketT,
178  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
179  void setMaxPort(int port)
180  {
181  myPortInfo.myMaxPort = port;
182  }
183  template <
184  typename S = SocketT,
185  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
186  void setUseSystemPort(bool use_sys)
187  {
188  myPortInfo.myUseSystemPort = use_sys;
189  }
190  template <
191  typename S = SocketT,
192  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
193  void setUseIPv6(bool use_v6)
194  {
195  myPortInfo.myUseIPv6 = use_v6;
196  }
197  template <
198  typename S = SocketT,
199  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
200  bool useIPv6() const
201  {
202  return myPortInfo.myUseIPv6;
203  }
204  template <
205  typename S = SocketT,
206  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
207  void setBindAddress(const ASIO_IPAddress& bind_address)
208  {
209  myPortInfo.myBindAddress = bind_address;
210  }
211  template <
212  typename S = SocketT,
213  typename = typename std::enable_if_t<socket_info<S>::uses_port>>
215  {
216  return myPortInfo.myBindAddress;
217  }
218 
219  endpoint_type endpoint() const { return myAcceptor.local_endpoint(); }
220 
221  bool isOpen() const override { return myAcceptor.is_open(); }
222 
223 protected:
225  NET_SocketListener(ctx.get_executor())
226  {}
227  NET_SocketListener(const ASIO_IOContext::executor_type& executor)
228  : NET_ISocketListener(executor)
229  , myAcceptor(hboost::asio::make_strand(executor))
230  {
231  }
232 
233  /// Do any initialization after the socket listener has been setup but
234  /// before we actually start accepting requests.
235  virtual void init_() {}
236  /// Called each time a new socket is accepted. Make sure to add the
237  /// connection handler to the connection manager so that we can easily
238  /// stop all requests that came from this listening socket when shutting
239  /// down.
240  virtual void onAccept_(
241  const hboost::system::error_code& ec,
242  socket_type socket)
243  = 0;
244 
245  /// Start an async accept. If the accept was a success onAccept_ is called.
246  void startAccept_();
247 
248  AcceptorT myAcceptor;
249 
250  struct Empty {};
251 
252  struct PortInfo
253  {
255  int myPort = -1;
256  int myMaxPort = -1;
257  bool myUseSystemPort = false;
258  bool myUseIPv6 = false;
259  };
260 
261  std::conditional_t<socket_info<socket_type>::uses_port, PortInfo, Empty>
263 };
264 
265 template <typename AcceptorT, typename SocketT>
266 template <typename S, typename Detected>
267 void
269 {
270  if (isOpen())
271  {
272  UT_ASSERT(!"Trying to start the same connection twice.");
273  return;
274  }
275  endpoint_type ep(myPortInfo.myBindAddress, myPortInfo.myPort);
276 
277  start(ep);
278  if (!isOpen() && myPortInfo.myPort != 0)
279  {
280  // First try the port range if possible.
281  if (myPortInfo.myMaxPort > 0 && myPortInfo.myMaxPort > myPortInfo.myPort)
282  {
283  for (int port = myPortInfo.myPort + 1;
284  port <= myPortInfo.myMaxPort && !isOpen(); port++)
285  {
286  ep.port(port);
287  start(ep);
288  }
289  }
290 
291  // Second let the OS pick a port.
292  if (!isOpen() && myPortInfo.myUseSystemPort)
293  {
294  ep.port(0);
295  start(ep);
296  }
297  }
298 }
299 
300 template <typename AcceptorT, typename SocketT>
301 void
303 {
305 
306  myAcceptor.async_accept(
307  hboost::asio::make_strand(myExecutor),
308  [this, self = shared_from_this()](
309  hboost::system::error_code ec, socket_type socket) {
311  if (!myAcceptor.is_open())
312  return;
313 
314  if (!ec)
315  {
316  if (socket.is_open())
317  {
318  onAccept_(ec, std::move(socket));
319  }
320  }
321  else
322  {
323  // If the operation was cancelled it does not need to be
324  // logged and the operation is finished so there is no
325  // need to re-add an accept task.
326  if (ec == hboost::asio::error::operation_aborted)
327  return;
328  fail(ec, "accept");
329  }
330 
331  startAccept_();
332  });
333 }
334 
335 #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())
NET_SocketListener(ASIO_IOContext &ctx)
void setBindAddress(const ASIO_IPAddress &bind_address)
GLuint start
Definition: glcorearb.h:475
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
ASIO_IOContext::executor_type myExecutor
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_ISocketListener(const ASIO_IOContext::executor_type &executor)
NET_SocketListener(const ASIO_IOContext::executor_type &executor)
NET_ISocketListener(ASIO_IOContext &ctx)
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