connect_cp.hpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. /*
  2. * Copyright (c) 2017-2023 zhllxt
  3. *
  4. * author : zhllxt
  5. * email : 37792738@qq.com
  6. *
  7. * Distributed under the Boost Software License, Version 1.0. (See accompanying
  8. * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  9. */
  10. #ifndef __ASIO2_CONNECT_COMPONENT_HPP__
  11. #define __ASIO2_CONNECT_COMPONENT_HPP__
  12. #if defined(_MSC_VER) && (_MSC_VER >= 1200)
  13. #pragma once
  14. #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
  15. #include <memory>
  16. #include <future>
  17. #include <utility>
  18. #include <string_view>
  19. #include <asio2/base/iopool.hpp>
  20. #include <asio2/base/listener.hpp>
  21. #include <asio2/base/detail/ecs.hpp>
  22. #include <asio2/base/detail/keepalive_options.hpp>
  23. #include <asio2/base/impl/event_queue_cp.hpp>
  24. namespace asio2::detail
  25. {
  26. template<class SocketT>
  27. class run_connect_op : public asio::coroutine
  28. {
  29. public:
  30. using socket_t = SocketT;
  31. using decay_socket_t = typename std::remove_cv_t<std::remove_reference_t<socket_t>>;
  32. using lowest_layer_t = typename decay_socket_t::lowest_layer_type;
  33. using resolver_type = typename asio::ip::basic_resolver<typename lowest_layer_t::protocol_type>;
  34. using endpoints_type = typename resolver_type::results_type;
  35. using endpoints_iterator = typename endpoints_type::iterator;
  36. std::string host_{}, port_{};
  37. SocketT& socket_;
  38. std::unique_ptr<resolver_type> resolver_ptr;
  39. std::unique_ptr<endpoints_type> endpoints_ptr;
  40. endpoints_iterator iter;
  41. template<class SKT>
  42. run_connect_op(std::string host, std::string port, SKT& skt)
  43. : host_ (std::move(host))
  44. , port_ (std::move(port))
  45. , socket_ (skt)
  46. {
  47. resolver_ptr = std::make_unique<resolver_type>(socket_.get_executor());
  48. }
  49. template <typename Self>
  50. void operator()(Self& self, error_code ec = {}, endpoints_type endpoints = {})
  51. {
  52. detail::ignore_unused(ec, endpoints);
  53. ASIO_CORO_REENTER(*this)
  54. {
  55. ASIO_CORO_YIELD
  56. resolver_ptr->async_resolve(host_, port_, std::move(self));
  57. if (ec)
  58. goto end;
  59. endpoints_ptr = std::make_unique<endpoints_type>(std::move(endpoints));
  60. iter = endpoints_ptr->begin();
  61. loop:
  62. ASIO_CORO_YIELD
  63. socket_.async_connect(iter->endpoint(), std::move(self));
  64. if (!ec)
  65. goto end;
  66. iter++;
  67. if (iter == endpoints_ptr->end())
  68. {
  69. ec = asio::error::host_unreachable;
  70. goto end;
  71. }
  72. else
  73. {
  74. goto loop;
  75. }
  76. end:
  77. self.complete(ec);
  78. }
  79. }
  80. };
  81. // C++17 class template argument deduction guides
  82. template<class SKT>
  83. run_connect_op(std::string, std::string, SKT&) -> run_connect_op<SKT>;
  84. }
  85. namespace asio2
  86. {
  87. /**
  88. * @brief Perform the socks5 handshake asynchronously in the client role.
  89. * @param host - The target server ip.
  90. * @param port - The target server port.
  91. * @param socket - The asio::ip::tcp::socket object reference.
  92. * @param token - The completion handler to invoke when the operation completes.
  93. * The implementation takes ownership of the handler by performing a decay-copy.
  94. * The equivalent function signature of the handler must be:
  95. * @code
  96. * void handler(
  97. * error_code const& ec // Result of operation
  98. * );
  99. */
  100. template <typename SocketT, typename CompletionToken>
  101. auto async_connect(
  102. std::string host, std::string port, SocketT& socket, CompletionToken&& token)
  103. -> decltype(asio::async_compose<CompletionToken, void(asio::error_code)>(
  104. std::declval<detail::run_connect_op<SocketT>>(), token, socket))
  105. {
  106. return asio::async_compose<CompletionToken, void(asio::error_code)>(
  107. detail::run_connect_op<SocketT>{
  108. std::move(host), std::move(port), socket},
  109. token, socket);
  110. }
  111. }
  112. namespace asio2::detail
  113. {
  114. template<class derived_t, class args_t, bool IsSession>
  115. class connect_cp_member_variables;
  116. template<class derived_t, class args_t>
  117. class connect_cp_member_variables<derived_t, args_t, true>
  118. {
  119. };
  120. template<class derived_t, class args_t>
  121. class connect_cp_member_variables<derived_t, args_t, false>
  122. {
  123. public:
  124. /**
  125. * @brief Set the host of the server.
  126. * If connect failed, and you want to use a different ip when reconnect,
  127. * then you can do it like this:
  128. * client.bind_connect([&client]()
  129. * {
  130. * if (asio2::get_last_error()) // has some error, means connect failed
  131. * client.set_host("192.168.0.99");
  132. * });
  133. * when reconnecting, the ip "192.168.0.99" will be used as the server's ip.
  134. */
  135. template<typename String>
  136. inline derived_t& set_host(String&& host)
  137. {
  138. this->host_ = detail::to_string(std::forward<String>(host));
  139. return (static_cast<derived_t&>(*this));
  140. }
  141. /**
  142. * @brief Set the port of the server.
  143. */
  144. template<typename StrOrInt>
  145. inline derived_t& set_port(StrOrInt&& port)
  146. {
  147. this->port_ = detail::to_string(std::forward<StrOrInt>(port));
  148. return (static_cast<derived_t&>(*this));
  149. }
  150. /**
  151. * @brief Get the host of the connected server.
  152. */
  153. inline const std::string& get_host() noexcept
  154. {
  155. return this->host_;
  156. }
  157. /**
  158. * @brief Get the host of the connected server.
  159. */
  160. inline const std::string& get_host() const noexcept
  161. {
  162. return this->host_;
  163. }
  164. /**
  165. * @brief Get the port of the connected server.
  166. */
  167. inline const std::string& get_port() noexcept
  168. {
  169. return this->port_;
  170. }
  171. /**
  172. * @brief Get the port of the connected server.
  173. */
  174. inline const std::string& get_port() const noexcept
  175. {
  176. return this->port_;
  177. }
  178. protected:
  179. /// Save the host and port of the server
  180. std::string host_, port_;
  181. };
  182. /*
  183. * can't use "derived_t::is_session()" as the third template parameter of connect_cp_member_variables,
  184. * must use "args_t::is_session", beacuse "tcp_session" is derived from "connect_cp", when the
  185. * "connect_cp" is contructed, the "tcp_session" has't contructed yet, then it will can't find the
  186. * "derived_t::is_session()" function, and compile failure.
  187. */
  188. template<class derived_t, class args_t>
  189. class connect_cp : public connect_cp_member_variables<derived_t, args_t, args_t::is_session>
  190. {
  191. public:
  192. using socket_t = typename args_t::socket_t;
  193. using decay_socket_t = typename std::remove_cv_t<std::remove_reference_t<socket_t>>;
  194. using lowest_layer_t = typename decay_socket_t::lowest_layer_type;
  195. using resolver_type = typename asio::ip::basic_resolver<typename lowest_layer_t::protocol_type>;
  196. using endpoints_type = typename resolver_type::results_type;
  197. using endpoints_iterator = typename endpoints_type::iterator;
  198. using self = connect_cp<derived_t, args_t>;
  199. public:
  200. /**
  201. * @brief constructor
  202. */
  203. connect_cp() noexcept {}
  204. /**
  205. * @destructor
  206. */
  207. ~connect_cp() = default;
  208. protected:
  209. template<bool IsAsync, typename C, typename DeferEvent, bool IsSession = args_t::is_session>
  210. inline typename std::enable_if_t<!IsSession, void>
  211. _start_connect(std::shared_ptr<derived_t> this_ptr, std::shared_ptr<ecs_t<C>> ecs, DeferEvent chain)
  212. {
  213. derived_t& derive = static_cast<derived_t&>(*this);
  214. ASIO2_ASSERT(derive.io_->running_in_this_thread());
  215. clear_last_error();
  216. #if defined(_DEBUG) || defined(DEBUG)
  217. derive.is_stop_reconnect_timer_called_ = false;
  218. derive.is_stop_connect_timeout_timer_called_ = false;
  219. derive.is_disconnect_called_ = false;
  220. #endif
  221. state_t expected = state_t::starting;
  222. if (!derive.state_.compare_exchange_strong(expected, state_t::starting))
  223. {
  224. ASIO2_ASSERT(false);
  225. derive._handle_connect(asio::error::operation_aborted,
  226. std::move(this_ptr), std::move(ecs), std::move(chain));
  227. return;
  228. }
  229. derive._make_reconnect_timer(this_ptr, ecs);
  230. // start the timeout timer
  231. derive._make_connect_timeout_timer(this_ptr, derive.get_connect_timeout());
  232. derive._post_resolve(std::move(this_ptr), std::move(ecs), std::move(chain));
  233. }
  234. template<typename C>
  235. std::string_view _get_real_host(std::shared_ptr<derived_t>&, std::shared_ptr<ecs_t<C>>& ecs)
  236. {
  237. if constexpr (ecs_helper::has_socks5<C>())
  238. {
  239. auto sock5 = ecs->get_component().socks5_option(std::in_place);
  240. return sock5->host();
  241. }
  242. else
  243. {
  244. return this->host_;
  245. }
  246. }
  247. template<typename C>
  248. std::string_view _get_real_port(std::shared_ptr<derived_t>&, std::shared_ptr<ecs_t<C>>& ecs)
  249. {
  250. if constexpr (ecs_helper::has_socks5<C>())
  251. {
  252. auto sock5 = ecs->get_component().socks5_option(std::in_place);
  253. return sock5->port();
  254. }
  255. else
  256. {
  257. return this->port_;
  258. }
  259. }
  260. template<typename C, typename DeferEvent, bool IsSession = args_t::is_session>
  261. inline typename std::enable_if_t<!IsSession, void>
  262. _post_resolve(std::shared_ptr<derived_t> this_ptr, std::shared_ptr<ecs_t<C>> ecs, DeferEvent chain)
  263. {
  264. derived_t& derive = static_cast<derived_t&>(*this);
  265. std::string_view h = derive._get_real_host(this_ptr, ecs);
  266. std::string_view p = derive._get_real_port(this_ptr, ecs);
  267. // resolve the server address.
  268. std::unique_ptr<resolver_type> resolver_ptr = std::make_unique<resolver_type>(
  269. derive.io_->context());
  270. resolver_type* resolver_rptr = resolver_ptr.get();
  271. // Before async_resolve execution is complete, we must hold the resolver object.
  272. // so we captured the resolver_ptr into the lambda callback function.
  273. resolver_rptr->async_resolve(h, p,
  274. [&derive, this_ptr = std::move(this_ptr), ecs = std::move(ecs),
  275. resolver_ptr = std::move(resolver_ptr), chain = std::move(chain)]
  276. (error_code ec, endpoints_type endpoints) mutable
  277. {
  278. // if the connect timeout timer is timedout and called already, it means
  279. // connect timed out already.
  280. if (!derive.connect_timeout_timer_)
  281. {
  282. ec = asio::error::timed_out;
  283. }
  284. std::unique_ptr<endpoints_type> eps = std::make_unique<endpoints_type>(std::move(endpoints));
  285. endpoints_type* p = eps.get();
  286. if (ec)
  287. derive._handle_connect(ec,
  288. std::move(this_ptr), std::move(ecs), std::move(chain));
  289. else
  290. derive._post_connect(ec, std::move(eps), p->begin(),
  291. std::move(this_ptr), std::move(ecs), std::move(chain));
  292. });
  293. }
  294. template<typename C, typename DeferEvent, bool IsSession = args_t::is_session>
  295. typename std::enable_if_t<!IsSession, void>
  296. inline _post_connect(
  297. error_code ec, std::unique_ptr<endpoints_type> eps, endpoints_iterator iter,
  298. std::shared_ptr<derived_t> this_ptr, std::shared_ptr<ecs_t<C>> ecs, DeferEvent chain)
  299. {
  300. derived_t& derive = static_cast<derived_t&>(*this);
  301. ASIO2_ASSERT(derive.io_->running_in_this_thread());
  302. state_t expected = state_t::starting;
  303. if (!derive.state_.compare_exchange_strong(expected, state_t::starting))
  304. {
  305. // There are no more endpoints. Shut down the client.
  306. derive._handle_connect(asio::error::operation_aborted,
  307. std::move(this_ptr), std::move(ecs), std::move(chain));
  308. return;
  309. }
  310. if (iter == eps->end())
  311. {
  312. // There are no more endpoints. Shut down the client.
  313. derive._handle_connect(ec ? ec : asio::error::host_unreachable,
  314. std::move(this_ptr), std::move(ecs), std::move(chain));
  315. return;
  316. }
  317. auto& socket = derive.socket();
  318. // the socket.open(...) must after async_resolve, beacuse only after async_resolve,
  319. // we can get the remote endpoint is ipv4 or ipv6, then we can open the socket
  320. // with ipv4 or ipv6 correctly.
  321. error_code ec_ignore{};
  322. // if the socket's binded endpoint is ipv4, and the finded endpoint is ipv6, then
  323. // the async_connect will be failed, so we need reopen the socket with ipv6.
  324. if (socket.is_open())
  325. {
  326. auto oldep = socket.local_endpoint(ec_ignore);
  327. if (ec_ignore || oldep.protocol() != iter->endpoint().protocol())
  328. {
  329. socket.cancel(ec_ignore);
  330. socket.close(ec_ignore);
  331. }
  332. }
  333. if (!socket.is_open())
  334. {
  335. socket.open(iter->endpoint().protocol(), ec);
  336. if (ec)
  337. {
  338. derive._handle_connect(ec, std::move(this_ptr), std::move(ecs), std::move(chain));
  339. return;
  340. }
  341. // set port reuse
  342. socket.set_option(asio::socket_base::reuse_address(true), ec_ignore);
  343. // open succeeded. set the keeplive values
  344. detail::set_keepalive_options(socket);
  345. // We don't call the bind function, beacuse it will be called internally in asio
  346. //socket.bind(endpoint);
  347. // And you can call the bind function youself in the bind_init() callback.
  348. // eg:
  349. //
  350. // asio::ip::tcp::endpoint ep(asio::ip::tcp::v4(), 1234);
  351. // asio::ip::udp::endpoint ep(asio::ip::udp::v6(), 9876);
  352. // asio::ip::tcp::endpoint ep(asio::ip::make_address("0.0.0.0"), 1234);
  353. //
  354. // client.socket().bind(ep);
  355. // maybe there has multi endpoints, and connect the first endpoint failed, then the
  356. // next endpoint is connected, at this time, the ec and get_last_error maybe not 0,
  357. // so we need clear the last error, otherwise if use call get_last_error in the
  358. // fire init function, the get_last_error will be not 0.
  359. clear_last_error();
  360. // every time the socket is recreated, we should call the _do_init function, then
  361. // the ssl stream will recreated too, otherwise when client disconnected and
  362. // reconnect to the ssl server, this will happen: ssl handshake will failed, and
  363. // next time reconnect again, ssl handshake will successed.
  364. derive._do_init(ecs);
  365. // call the user callback which setted by bind_init
  366. derive._fire_init();
  367. }
  368. else
  369. {
  370. ASIO2_LOG_ERROR("The client socket is opened already.");
  371. }
  372. // Start the asynchronous connect operation.
  373. socket.async_connect(iter->endpoint(), make_allocator(derive.rallocator(),
  374. [&derive, this_ptr = std::move(this_ptr), ecs = std::move(ecs), chain = std::move(chain),
  375. eps = std::move(eps), iter]
  376. (const error_code & ec) mutable
  377. {
  378. if (ec && ec != asio::error::operation_aborted)
  379. derive._post_connect(ec, std::move(eps), ++iter,
  380. std::move(this_ptr), std::move(ecs), std::move(chain));
  381. else
  382. derive._post_proxy(ec,
  383. std::move(this_ptr), std::move(ecs), std::move(chain));
  384. }));
  385. }
  386. template<typename C, typename DeferEvent>
  387. inline void _post_proxy(
  388. const error_code& ec,
  389. std::shared_ptr<derived_t> this_ptr, std::shared_ptr<ecs_t<C>> ecs, DeferEvent chain)
  390. {
  391. set_last_error(ec);
  392. derived_t& derive = static_cast<derived_t&>(*this);
  393. ASIO2_ASSERT(derive.io_->running_in_this_thread());
  394. error_code ec_ignore{};
  395. auto ep = derive.socket_->lowest_layer().remote_endpoint(ec_ignore);
  396. if (!ec_ignore)
  397. {
  398. derive.remote_endpoint_ = std::move(ep);
  399. }
  400. state_t expected = state_t::starting;
  401. if (!derive.state_.compare_exchange_strong(expected, state_t::starting))
  402. {
  403. derive._handle_connect(asio::error::operation_aborted,
  404. std::move(this_ptr), std::move(ecs), std::move(chain));
  405. return;
  406. }
  407. if constexpr (std::is_base_of_v<component_tag, detail::remove_cvref_t<C>>)
  408. {
  409. if (ec)
  410. {
  411. return derive._handle_proxy(ec, std::move(this_ptr), std::move(ecs), std::move(chain));
  412. }
  413. //// Traverse each component in order, and if it is a proxy component, start it
  414. //detail::for_each_tuple(ecs->get_component().values(), [&](auto& component) mutable
  415. //{
  416. // using type = detail::remove_cvref_t<decltype(component)>;
  417. // if constexpr (std::is_base_of_v<asio2::socks5::option_base, type>)
  418. // derive._socks5_start(std::move(this_ptr), std::move(ecs));
  419. // else
  420. // {
  421. // std::ignore = true;
  422. // }
  423. //});
  424. if constexpr (C::has_socks5())
  425. derive._socks5_start(std::move(this_ptr), std::move(ecs), std::move(chain));
  426. else
  427. derive._handle_proxy(ec, std::move(this_ptr), std::move(ecs), std::move(chain));
  428. }
  429. else
  430. {
  431. derive._handle_proxy(ec, std::move(this_ptr), std::move(ecs), std::move(chain));
  432. }
  433. }
  434. template<typename C, typename DeferEvent>
  435. inline void _handle_proxy(
  436. const error_code& ec,
  437. std::shared_ptr<derived_t> this_ptr, std::shared_ptr<ecs_t<C>> ecs, DeferEvent chain)
  438. {
  439. set_last_error(ec);
  440. derived_t& derive = static_cast<derived_t&>(*this);
  441. derive._handle_connect(ec, std::move(this_ptr), std::move(ecs), std::move(chain));
  442. }
  443. template<typename C, typename DeferEvent>
  444. inline void _handle_connect(
  445. const error_code& ec,
  446. std::shared_ptr<derived_t> this_ptr, std::shared_ptr<ecs_t<C>> ecs, DeferEvent chain)
  447. {
  448. set_last_error(ec);
  449. derived_t& derive = static_cast<derived_t&>(*this);
  450. if constexpr (args_t::is_session)
  451. {
  452. ASIO2_ASSERT(derive.sessions_.io_->running_in_this_thread());
  453. }
  454. else
  455. {
  456. ASIO2_ASSERT(derive.io_->running_in_this_thread());
  457. }
  458. derive._done_connect(ec, std::move(this_ptr), std::move(ecs), std::move(chain));
  459. }
  460. template<typename C, typename DeferEvent>
  461. inline void _done_connect(
  462. error_code ec, std::shared_ptr<derived_t> this_ptr, std::shared_ptr<ecs_t<C>> ecs, DeferEvent chain)
  463. {
  464. derived_t& derive = static_cast<derived_t&>(*this);
  465. // code run to here, the state must not be stopped( stopping is possible but stopped is impossible )
  466. //ASIO2_ASSERT(derive.state_ != state_t::stopped);
  467. if constexpr (args_t::is_session)
  468. {
  469. ASIO2_ASSERT(derive.sessions_.io_->running_in_this_thread());
  470. // if socket is invalid, it means that the connect is timeout and the socket has
  471. // been closed by the connect timeout timer, so reset the error to timed_out.
  472. if (!derive.socket().is_open())
  473. {
  474. ec = asio::error::timed_out;
  475. }
  476. }
  477. else
  478. {
  479. ASIO2_ASSERT(derive.io_->running_in_this_thread());
  480. // if connect_timeout_timer_ is empty, it means that the connect timeout timer is
  481. // timeout and the callback has called already, so reset the error to timed_out.
  482. // note : when the async_resolve is failed, the socket is invalid to.
  483. if (!derive.connect_timeout_timer_)
  484. {
  485. ec = asio::error::timed_out;
  486. }
  487. }
  488. state_t expected;
  489. // Set the state to started before fire_connect because the user may send data in
  490. // fire_connect and fail if the state is not set to started.
  491. if (!ec)
  492. {
  493. expected = state_t::starting;
  494. if (!derive.state_.compare_exchange_strong(expected, state_t::started))
  495. ec = asio::error::operation_aborted;
  496. }
  497. // set last error before fire connect
  498. set_last_error(ec);
  499. // Is session : Only call fire_connect notification when the connection is succeed.
  500. if constexpr (args_t::is_session)
  501. {
  502. ASIO2_ASSERT(derive.sessions_.io_->running_in_this_thread());
  503. if (!ec)
  504. {
  505. expected = state_t::started;
  506. if (derive.state_.compare_exchange_strong(expected, state_t::started))
  507. {
  508. derive._fire_connect(this_ptr, ecs);
  509. }
  510. }
  511. }
  512. // Is client : Whether the connection succeeds or fails, always call fire_connect notification
  513. else
  514. {
  515. ASIO2_ASSERT(derive.io_->running_in_this_thread());
  516. // if state is not stopped, call _fire_connect
  517. expected = state_t::stopped;
  518. if (!derive.state_.compare_exchange_strong(expected, state_t::stopped))
  519. {
  520. derive._fire_connect(this_ptr, ecs);
  521. }
  522. }
  523. if (!ec)
  524. {
  525. expected = state_t::started;
  526. if (!derive.state_.compare_exchange_strong(expected, state_t::started))
  527. ec = asio::error::operation_aborted;
  528. }
  529. // Whatever of connection success or failure or timeout, cancel the timeout timer.
  530. derive._stop_connect_timeout_timer();
  531. // must set last error again, beacuse in the _fire_connect some errors maybe occured.
  532. set_last_error(ec);
  533. if (ec)
  534. {
  535. // The connect process has finished, call the callback at here directly,
  536. // otherwise the callback maybe passed to other event queue chain, and the
  537. // last error maybe changed by other event.
  538. // can't pass the whole chain to the client _do_disconnect, it will cause
  539. // the auto reconnect has no effect.
  540. {
  541. [[maybe_unused]] detail::defer_event t{ chain.move_event() };
  542. }
  543. derive._do_disconnect(ec, std::move(this_ptr), defer_event(chain.move_guard()));
  544. return;
  545. }
  546. derive._do_start(std::move(this_ptr), std::move(ecs), std::move(chain));
  547. }
  548. };
  549. }
  550. #endif // !__ASIO2_CONNECT_COMPONENT_HPP__