basic_socket_streambuf.hpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. //
  2. // basic_socket_streambuf.hpp
  3. // ~~~~~~~~~~~~~~~~~~~~~~~~~~
  4. //
  5. // Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot 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 BOOST_ASIO_BASIC_SOCKET_STREAMBUF_HPP
  11. #define BOOST_ASIO_BASIC_SOCKET_STREAMBUF_HPP
  12. #if defined(_MSC_VER) && (_MSC_VER >= 1200)
  13. # pragma once
  14. #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
  15. #include <boost/asio/detail/config.hpp>
  16. #if !defined(BOOST_ASIO_NO_IOSTREAM)
  17. #include <streambuf>
  18. #include <vector>
  19. #include <boost/asio/basic_socket.hpp>
  20. #include <boost/asio/basic_stream_socket.hpp>
  21. #include <boost/asio/detail/buffer_sequence_adapter.hpp>
  22. #include <boost/asio/detail/memory.hpp>
  23. #include <boost/asio/detail/throw_error.hpp>
  24. #include <boost/asio/io_context.hpp>
  25. #include <boost/asio/steady_timer.hpp>
  26. #include <boost/asio/detail/push_options.hpp>
  27. namespace boost {
  28. namespace asio {
  29. namespace detail {
  30. // A separate base class is used to ensure that the io_context member is
  31. // initialised prior to the basic_socket_streambuf's basic_socket base class.
  32. class socket_streambuf_io_context
  33. {
  34. protected:
  35. socket_streambuf_io_context(io_context* ctx)
  36. : default_io_context_(ctx)
  37. {
  38. }
  39. shared_ptr<io_context> default_io_context_;
  40. };
  41. // A separate base class is used to ensure that the dynamically allocated
  42. // buffers are constructed prior to the basic_socket_streambuf's basic_socket
  43. // base class. This makes moving the socket is the last potentially throwing
  44. // step in the streambuf's move constructor, giving the constructor a strong
  45. // exception safety guarantee.
  46. class socket_streambuf_buffers
  47. {
  48. protected:
  49. socket_streambuf_buffers()
  50. : get_buffer_(buffer_size),
  51. put_buffer_(buffer_size)
  52. {
  53. }
  54. enum { buffer_size = 512 };
  55. std::vector<char> get_buffer_;
  56. std::vector<char> put_buffer_;
  57. };
  58. } // namespace detail
  59. #if !defined(BOOST_ASIO_BASIC_SOCKET_STREAMBUF_FWD_DECL)
  60. #define BOOST_ASIO_BASIC_SOCKET_STREAMBUF_FWD_DECL
  61. // Forward declaration with defaulted arguments.
  62. template <typename Protocol,
  63. typename Clock = chrono::steady_clock,
  64. typename WaitTraits = wait_traits<Clock>>
  65. class basic_socket_streambuf;
  66. #endif // !defined(BOOST_ASIO_BASIC_SOCKET_STREAMBUF_FWD_DECL)
  67. /// Iostream streambuf for a socket.
  68. #if defined(GENERATING_DOCUMENTATION)
  69. template <typename Protocol,
  70. typename Clock = chrono::steady_clock,
  71. typename WaitTraits = wait_traits<Clock>>
  72. #else // defined(GENERATING_DOCUMENTATION)
  73. template <typename Protocol, typename Clock, typename WaitTraits>
  74. #endif // defined(GENERATING_DOCUMENTATION)
  75. class basic_socket_streambuf
  76. : public std::streambuf,
  77. private detail::socket_streambuf_io_context,
  78. private detail::socket_streambuf_buffers,
  79. private basic_socket<Protocol>
  80. {
  81. private:
  82. typedef detail::chrono_time_traits<Clock, WaitTraits> traits_helper;
  83. public:
  84. /// The protocol type.
  85. typedef Protocol protocol_type;
  86. /// The endpoint type.
  87. typedef typename Protocol::endpoint endpoint_type;
  88. /// The clock type.
  89. typedef Clock clock_type;
  90. #if defined(GENERATING_DOCUMENTATION)
  91. /// The time type.
  92. typedef typename WaitTraits::time_point time_point;
  93. /// The duration type.
  94. typedef typename WaitTraits::duration duration;
  95. #else
  96. typedef typename traits_helper::time_type time_point;
  97. typedef typename traits_helper::duration_type duration;
  98. #endif
  99. /// Construct a basic_socket_streambuf without establishing a connection.
  100. basic_socket_streambuf()
  101. : detail::socket_streambuf_io_context(new io_context),
  102. basic_socket<Protocol>(*default_io_context_),
  103. expiry_time_(max_expiry_time())
  104. {
  105. init_buffers();
  106. }
  107. /// Construct a basic_socket_streambuf from the supplied socket.
  108. explicit basic_socket_streambuf(basic_stream_socket<protocol_type> s)
  109. : detail::socket_streambuf_io_context(0),
  110. basic_socket<Protocol>(std::move(s)),
  111. expiry_time_(max_expiry_time())
  112. {
  113. init_buffers();
  114. }
  115. /// Move-construct a basic_socket_streambuf from another.
  116. basic_socket_streambuf(basic_socket_streambuf&& other)
  117. : detail::socket_streambuf_io_context(other),
  118. basic_socket<Protocol>(std::move(other.socket())),
  119. ec_(other.ec_),
  120. expiry_time_(other.expiry_time_)
  121. {
  122. get_buffer_.swap(other.get_buffer_);
  123. put_buffer_.swap(other.put_buffer_);
  124. setg(other.eback(), other.gptr(), other.egptr());
  125. setp(other.pptr(), other.epptr());
  126. other.ec_ = boost::system::error_code();
  127. other.expiry_time_ = max_expiry_time();
  128. other.init_buffers();
  129. }
  130. /// Move-assign a basic_socket_streambuf from another.
  131. basic_socket_streambuf& operator=(basic_socket_streambuf&& other)
  132. {
  133. this->close();
  134. socket() = std::move(other.socket());
  135. detail::socket_streambuf_io_context::operator=(other);
  136. ec_ = other.ec_;
  137. expiry_time_ = other.expiry_time_;
  138. get_buffer_.swap(other.get_buffer_);
  139. put_buffer_.swap(other.put_buffer_);
  140. setg(other.eback(), other.gptr(), other.egptr());
  141. setp(other.pptr(), other.epptr());
  142. other.ec_ = boost::system::error_code();
  143. other.expiry_time_ = max_expiry_time();
  144. other.put_buffer_.resize(buffer_size);
  145. other.init_buffers();
  146. return *this;
  147. }
  148. /// Destructor flushes buffered data.
  149. virtual ~basic_socket_streambuf()
  150. {
  151. if (pptr() != pbase())
  152. overflow(traits_type::eof());
  153. }
  154. /// Establish a connection.
  155. /**
  156. * This function establishes a connection to the specified endpoint.
  157. *
  158. * @return \c this if a connection was successfully established, a null
  159. * pointer otherwise.
  160. */
  161. basic_socket_streambuf* connect(const endpoint_type& endpoint)
  162. {
  163. init_buffers();
  164. ec_ = boost::system::error_code();
  165. this->connect_to_endpoints(&endpoint, &endpoint + 1);
  166. return !ec_ ? this : 0;
  167. }
  168. /// Establish a connection.
  169. /**
  170. * This function automatically establishes a connection based on the supplied
  171. * resolver query parameters. The arguments are used to construct a resolver
  172. * query object.
  173. *
  174. * @return \c this if a connection was successfully established, a null
  175. * pointer otherwise.
  176. */
  177. template <typename... T>
  178. basic_socket_streambuf* connect(T... x)
  179. {
  180. init_buffers();
  181. typedef typename Protocol::resolver resolver_type;
  182. resolver_type resolver(socket().get_executor());
  183. connect_to_endpoints(resolver.resolve(x..., ec_));
  184. return !ec_ ? this : 0;
  185. }
  186. /// Close the connection.
  187. /**
  188. * @return \c this if a connection was successfully established, a null
  189. * pointer otherwise.
  190. */
  191. basic_socket_streambuf* close()
  192. {
  193. sync();
  194. socket().close(ec_);
  195. if (!ec_)
  196. init_buffers();
  197. return !ec_ ? this : 0;
  198. }
  199. /// Get a reference to the underlying socket.
  200. basic_socket<Protocol>& socket()
  201. {
  202. return *this;
  203. }
  204. /// Get the last error associated with the stream buffer.
  205. /**
  206. * @return An \c error_code corresponding to the last error from the stream
  207. * buffer.
  208. */
  209. const boost::system::error_code& error() const
  210. {
  211. return ec_;
  212. }
  213. /// Get the stream buffer's expiry time as an absolute time.
  214. /**
  215. * @return An absolute time value representing the stream buffer's expiry
  216. * time.
  217. */
  218. time_point expiry() const
  219. {
  220. return expiry_time_;
  221. }
  222. /// Set the stream buffer's expiry time as an absolute time.
  223. /**
  224. * This function sets the expiry time associated with the stream. Stream
  225. * operations performed after this time (where the operations cannot be
  226. * completed using the internal buffers) will fail with the error
  227. * boost::asio::error::operation_aborted.
  228. *
  229. * @param expiry_time The expiry time to be used for the stream.
  230. */
  231. void expires_at(const time_point& expiry_time)
  232. {
  233. expiry_time_ = expiry_time;
  234. }
  235. /// Set the stream buffer's expiry time relative to now.
  236. /**
  237. * This function sets the expiry time associated with the stream. Stream
  238. * operations performed after this time (where the operations cannot be
  239. * completed using the internal buffers) will fail with the error
  240. * boost::asio::error::operation_aborted.
  241. *
  242. * @param expiry_time The expiry time to be used for the timer.
  243. */
  244. void expires_after(const duration& expiry_time)
  245. {
  246. expiry_time_ = traits_helper::add(traits_helper::now(), expiry_time);
  247. }
  248. protected:
  249. int_type underflow()
  250. {
  251. #if defined(BOOST_ASIO_WINDOWS_RUNTIME)
  252. ec_ = boost::asio::error::operation_not_supported;
  253. return traits_type::eof();
  254. #else // defined(BOOST_ASIO_WINDOWS_RUNTIME)
  255. if (gptr() != egptr())
  256. return traits_type::eof();
  257. for (;;)
  258. {
  259. // Check if we are past the expiry time.
  260. if (traits_helper::less_than(expiry_time_, traits_helper::now()))
  261. {
  262. ec_ = boost::asio::error::timed_out;
  263. return traits_type::eof();
  264. }
  265. // Try to complete the operation without blocking.
  266. if (!socket().native_non_blocking())
  267. socket().native_non_blocking(true, ec_);
  268. detail::buffer_sequence_adapter<mutable_buffer, mutable_buffer>
  269. bufs(boost::asio::buffer(get_buffer_) + putback_max);
  270. detail::signed_size_type bytes = detail::socket_ops::recv(
  271. socket().native_handle(), bufs.buffers(), bufs.count(), 0, ec_);
  272. // Check if operation succeeded.
  273. if (bytes > 0)
  274. {
  275. setg(&get_buffer_[0], &get_buffer_[0] + putback_max,
  276. &get_buffer_[0] + putback_max + bytes);
  277. return traits_type::to_int_type(*gptr());
  278. }
  279. // Check for EOF.
  280. if (bytes == 0)
  281. {
  282. ec_ = boost::asio::error::eof;
  283. return traits_type::eof();
  284. }
  285. // Operation failed.
  286. if (ec_ != boost::asio::error::would_block
  287. && ec_ != boost::asio::error::try_again)
  288. return traits_type::eof();
  289. // Wait for socket to become ready.
  290. if (detail::socket_ops::poll_read(
  291. socket().native_handle(), 0, timeout(), ec_) < 0)
  292. return traits_type::eof();
  293. }
  294. #endif // defined(BOOST_ASIO_WINDOWS_RUNTIME)
  295. }
  296. int_type overflow(int_type c)
  297. {
  298. #if defined(BOOST_ASIO_WINDOWS_RUNTIME)
  299. ec_ = boost::asio::error::operation_not_supported;
  300. return traits_type::eof();
  301. #else // defined(BOOST_ASIO_WINDOWS_RUNTIME)
  302. char_type ch = traits_type::to_char_type(c);
  303. // Determine what needs to be sent.
  304. const_buffer output_buffer;
  305. if (put_buffer_.empty())
  306. {
  307. if (traits_type::eq_int_type(c, traits_type::eof()))
  308. return traits_type::not_eof(c); // Nothing to do.
  309. output_buffer = boost::asio::buffer(&ch, sizeof(char_type));
  310. }
  311. else
  312. {
  313. output_buffer = boost::asio::buffer(pbase(),
  314. (pptr() - pbase()) * sizeof(char_type));
  315. }
  316. while (output_buffer.size() > 0)
  317. {
  318. // Check if we are past the expiry time.
  319. if (traits_helper::less_than(expiry_time_, traits_helper::now()))
  320. {
  321. ec_ = boost::asio::error::timed_out;
  322. return traits_type::eof();
  323. }
  324. // Try to complete the operation without blocking.
  325. if (!socket().native_non_blocking())
  326. socket().native_non_blocking(true, ec_);
  327. detail::buffer_sequence_adapter<
  328. const_buffer, const_buffer> bufs(output_buffer);
  329. detail::signed_size_type bytes = detail::socket_ops::send(
  330. socket().native_handle(), bufs.buffers(), bufs.count(), 0, ec_);
  331. // Check if operation succeeded.
  332. if (bytes > 0)
  333. {
  334. output_buffer += static_cast<std::size_t>(bytes);
  335. continue;
  336. }
  337. // Operation failed.
  338. if (ec_ != boost::asio::error::would_block
  339. && ec_ != boost::asio::error::try_again)
  340. return traits_type::eof();
  341. // Wait for socket to become ready.
  342. if (detail::socket_ops::poll_write(
  343. socket().native_handle(), 0, timeout(), ec_) < 0)
  344. return traits_type::eof();
  345. }
  346. if (!put_buffer_.empty())
  347. {
  348. setp(&put_buffer_[0], &put_buffer_[0] + put_buffer_.size());
  349. // If the new character is eof then our work here is done.
  350. if (traits_type::eq_int_type(c, traits_type::eof()))
  351. return traits_type::not_eof(c);
  352. // Add the new character to the output buffer.
  353. *pptr() = ch;
  354. pbump(1);
  355. }
  356. return c;
  357. #endif // defined(BOOST_ASIO_WINDOWS_RUNTIME)
  358. }
  359. int sync()
  360. {
  361. return overflow(traits_type::eof());
  362. }
  363. std::streambuf* setbuf(char_type* s, std::streamsize n)
  364. {
  365. if (pptr() == pbase() && s == 0 && n == 0)
  366. {
  367. put_buffer_.clear();
  368. setp(0, 0);
  369. sync();
  370. return this;
  371. }
  372. return 0;
  373. }
  374. private:
  375. // Disallow copying and assignment.
  376. basic_socket_streambuf(const basic_socket_streambuf&) = delete;
  377. basic_socket_streambuf& operator=(
  378. const basic_socket_streambuf&) = delete;
  379. void init_buffers()
  380. {
  381. setg(&get_buffer_[0],
  382. &get_buffer_[0] + putback_max,
  383. &get_buffer_[0] + putback_max);
  384. if (put_buffer_.empty())
  385. setp(0, 0);
  386. else
  387. setp(&put_buffer_[0], &put_buffer_[0] + put_buffer_.size());
  388. }
  389. int timeout() const
  390. {
  391. int64_t msec = traits_helper::to_posix_duration(
  392. traits_helper::subtract(expiry_time_,
  393. traits_helper::now())).total_milliseconds();
  394. if (msec > (std::numeric_limits<int>::max)())
  395. msec = (std::numeric_limits<int>::max)();
  396. else if (msec < 0)
  397. msec = 0;
  398. return static_cast<int>(msec);
  399. }
  400. template <typename EndpointSequence>
  401. void connect_to_endpoints(const EndpointSequence& endpoints)
  402. {
  403. this->connect_to_endpoints(endpoints.begin(), endpoints.end());
  404. }
  405. template <typename EndpointIterator>
  406. void connect_to_endpoints(EndpointIterator begin, EndpointIterator end)
  407. {
  408. #if defined(BOOST_ASIO_WINDOWS_RUNTIME)
  409. ec_ = boost::asio::error::operation_not_supported;
  410. #else // defined(BOOST_ASIO_WINDOWS_RUNTIME)
  411. if (ec_)
  412. return;
  413. ec_ = boost::asio::error::not_found;
  414. for (EndpointIterator i = begin; i != end; ++i)
  415. {
  416. // Check if we are past the expiry time.
  417. if (traits_helper::less_than(expiry_time_, traits_helper::now()))
  418. {
  419. ec_ = boost::asio::error::timed_out;
  420. return;
  421. }
  422. // Close and reopen the socket.
  423. typename Protocol::endpoint ep(*i);
  424. socket().close(ec_);
  425. socket().open(ep.protocol(), ec_);
  426. if (ec_)
  427. continue;
  428. // Try to complete the operation without blocking.
  429. if (!socket().native_non_blocking())
  430. socket().native_non_blocking(true, ec_);
  431. detail::socket_ops::connect(socket().native_handle(),
  432. ep.data(), ep.size(), ec_);
  433. // Check if operation succeeded.
  434. if (!ec_)
  435. return;
  436. // Operation failed.
  437. if (ec_ != boost::asio::error::in_progress
  438. && ec_ != boost::asio::error::would_block)
  439. continue;
  440. // Wait for socket to become ready.
  441. if (detail::socket_ops::poll_connect(
  442. socket().native_handle(), timeout(), ec_) < 0)
  443. continue;
  444. // Get the error code from the connect operation.
  445. int connect_error = 0;
  446. size_t connect_error_len = sizeof(connect_error);
  447. if (detail::socket_ops::getsockopt(socket().native_handle(), 0,
  448. SOL_SOCKET, SO_ERROR, &connect_error, &connect_error_len, ec_)
  449. == detail::socket_error_retval)
  450. return;
  451. // Check the result of the connect operation.
  452. ec_ = boost::system::error_code(connect_error,
  453. boost::asio::error::get_system_category());
  454. if (!ec_)
  455. return;
  456. }
  457. #endif // defined(BOOST_ASIO_WINDOWS_RUNTIME)
  458. }
  459. // Helper function to get the maximum expiry time.
  460. static time_point max_expiry_time()
  461. {
  462. return (time_point::max)();
  463. }
  464. enum { putback_max = 8 };
  465. boost::system::error_code ec_;
  466. time_point expiry_time_;
  467. };
  468. } // namespace asio
  469. } // namespace boost
  470. #include <boost/asio/detail/pop_options.hpp>
  471. #endif // !defined(BOOST_ASIO_NO_IOSTREAM)
  472. #endif // BOOST_ASIO_BASIC_SOCKET_STREAMBUF_HPP