1 // 2 // basic_socket_streambuf.hpp 3 // ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 // 5 // Copyright (c) 2003-2011 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 11 #ifndef BOOST_ASIO_BASIC_SOCKET_STREAMBUF_HPP 12 #define BOOST_ASIO_BASIC_SOCKET_STREAMBUF_HPP 13 14 #if defined(_MSC_VER) && (_MSC_VER >= 1200) 15 # pragma once 16 #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) 17 18 #include <boost/asio/detail/config.hpp> 19 20 #if !defined(BOOST_NO_IOSTREAM) 21 22 #include <streambuf> 23 #include <boost/utility/base_from_member.hpp> 24 #include <boost/asio/basic_socket.hpp> 25 #include <boost/asio/deadline_timer_service.hpp> 26 #include <boost/asio/detail/array.hpp> 27 #include <boost/asio/detail/throw_error.hpp> 28 #include <boost/asio/io_service.hpp> 29 #include <boost/asio/stream_socket_service.hpp> 30 #include <boost/asio/time_traits.hpp> 31 32 #include <boost/asio/detail/push_options.hpp> 33 #include <boost/date_time/posix_time/posix_time_types.hpp> 34 #include <boost/asio/detail/pop_options.hpp> 35 36 #if !defined(BOOST_ASIO_HAS_VARIADIC_TEMPLATES) 37 38 # include <boost/preprocessor/arithmetic/inc.hpp> 39 # include <boost/preprocessor/repetition/enum_binary_params.hpp> 40 # include <boost/preprocessor/repetition/enum_params.hpp> 41 # include <boost/preprocessor/repetition/repeat_from_to.hpp> 42 43 # if !defined(BOOST_ASIO_SOCKET_STREAMBUF_MAX_ARITY) 44 # define BOOST_ASIO_SOCKET_STREAMBUF_MAX_ARITY 5 45 # endif // !defined(BOOST_ASIO_SOCKET_STREAMBUF_MAX_ARITY) 46 47 // A macro that should expand to: 48 // template <typename T1, ..., typename Tn> 49 // basic_socket_streambuf<Protocol, StreamSocketService, 50 // Time, TimeTraits, TimerService>* connect( 51 // T1 x1, ..., Tn xn) 52 // { 53 // init_buffers(); 54 // this->basic_socket<Protocol, StreamSocketService>::close(ec_); 55 // typedef typename Protocol::resolver resolver_type; 56 // typedef typename resolver_type::query resolver_query; 57 // resolver_query query(x1, ..., xn); 58 // resolve_and_connect(query); 59 // return !ec_ ? this : 0; 60 // } 61 // This macro should only persist within this file. 62 63 # define BOOST_ASIO_PRIVATE_CONNECT_DEF( z, n, data ) \ 64 template <BOOST_PP_ENUM_PARAMS(n, typename T)> \ 65 basic_socket_streambuf<Protocol, StreamSocketService, \ 66 Time, TimeTraits, TimerService>* connect( \ 67 BOOST_PP_ENUM_BINARY_PARAMS(n, T, x)) \ 68 { \ 69 init_buffers(); \ 70 this->basic_socket<Protocol, StreamSocketService>::close(ec_); \ 71 typedef typename Protocol::resolver resolver_type; \ 72 typedef typename resolver_type::query resolver_query; \ 73 resolver_query query(BOOST_PP_ENUM_PARAMS(n, x)); \ 74 resolve_and_connect(query); \ 75 return !ec_ ? this : 0; \ 76 } \ 77 /**/ 78 79 #endif // !defined(BOOST_ASIO_HAS_VARIADIC_TEMPLATES) 80 81 #include <boost/asio/detail/push_options.hpp> 82 83 namespace boost { 84 namespace asio { 85 86 /// Iostream streambuf for a socket. 87 template <typename Protocol, 88 typename StreamSocketService = stream_socket_service<Protocol>, 89 typename Time = boost::posix_time::ptime, 90 typename TimeTraits = boost::asio::time_traits<Time>, 91 typename TimerService = deadline_timer_service<Time, TimeTraits> > 92 class basic_socket_streambuf 93 : public std::streambuf, 94 private boost::base_from_member<io_service>, 95 public basic_socket<Protocol, StreamSocketService> 96 { 97 public: 98 /// The endpoint type. 99 typedef typename Protocol::endpoint endpoint_type; 100 101 /// The time type. 102 typedef typename TimeTraits::time_type time_type; 103 104 /// The duration type. 105 typedef typename TimeTraits::duration_type duration_type; 106 107 /// Construct a basic_socket_streambuf without establishing a connection. basic_socket_streambuf()108 basic_socket_streambuf() 109 : basic_socket<Protocol, StreamSocketService>( 110 boost::base_from_member<boost::asio::io_service>::member), 111 unbuffered_(false), 112 timer_service_(0), 113 timer_state_(no_timer) 114 { 115 init_buffers(); 116 } 117 118 /// Destructor flushes buffered data. ~basic_socket_streambuf()119 virtual ~basic_socket_streambuf() 120 { 121 if (pptr() != pbase()) 122 overflow(traits_type::eof()); 123 124 destroy_timer(); 125 } 126 127 /// Establish a connection. 128 /** 129 * This function establishes a connection to the specified endpoint. 130 * 131 * @return \c this if a connection was successfully established, a null 132 * pointer otherwise. 133 */ 134 basic_socket_streambuf<Protocol, StreamSocketService, connect(const endpoint_type & endpoint)135 Time, TimeTraits, TimerService>* connect( 136 const endpoint_type& endpoint) 137 { 138 init_buffers(); 139 140 this->basic_socket<Protocol, StreamSocketService>::close(ec_); 141 142 if (timer_state_ == timer_has_expired) 143 { 144 ec_ = boost::asio::error::operation_aborted; 145 return 0; 146 } 147 148 io_handler handler = { this }; 149 this->basic_socket<Protocol, StreamSocketService>::async_connect( 150 endpoint, handler); 151 152 ec_ = boost::asio::error::would_block; 153 this->get_service().get_io_service().reset(); 154 do this->get_service().get_io_service().run_one(); 155 while (ec_ == boost::asio::error::would_block); 156 157 return !ec_ ? this : 0; 158 } 159 160 #if defined(GENERATING_DOCUMENTATION) 161 /// Establish a connection. 162 /** 163 * This function automatically establishes a connection based on the supplied 164 * resolver query parameters. The arguments are used to construct a resolver 165 * query object. 166 * 167 * @return \c this if a connection was successfully established, a null 168 * pointer otherwise. 169 */ 170 template <typename T1, ..., typename TN> 171 basic_socket_streambuf<Protocol, StreamSocketService>* connect( 172 T1 t1, ..., TN tn); 173 #elif defined(BOOST_ASIO_HAS_VARIADIC_TEMPLATES) 174 template <typename... T> 175 basic_socket_streambuf<Protocol, StreamSocketService, connect(T...x)176 Time, TimeTraits, TimerService>* connect(T... x) 177 { 178 init_buffers(); 179 this->basic_socket<Protocol, StreamSocketService>::close(ec_); 180 typedef typename Protocol::resolver resolver_type; 181 typedef typename resolver_type::query resolver_query; 182 resolver_query query(x...); 183 resolve_and_connect(query); 184 return !ec_ ? this : 0; 185 } 186 #else 187 BOOST_PP_REPEAT_FROM_TO( BOOST_PP_INC(BOOST_ASIO_SOCKET_STREAMBUF_MAX_ARITY)188 1, BOOST_PP_INC(BOOST_ASIO_SOCKET_STREAMBUF_MAX_ARITY), 189 BOOST_ASIO_PRIVATE_CONNECT_DEF, _ ) 190 #endif 191 192 /// Close the connection. 193 /** 194 * @return \c this if a connection was successfully established, a null 195 * pointer otherwise. 196 */ 197 basic_socket_streambuf<Protocol, StreamSocketService, 198 Time, TimeTraits, TimerService>* close() 199 { 200 sync(); 201 this->basic_socket<Protocol, StreamSocketService>::close(ec_); 202 if (!ec_) 203 init_buffers(); 204 return !ec_ ? this : 0; 205 } 206 207 /// Get the last error associated with the stream buffer. 208 /** 209 * @return An \c error_code corresponding to the last error from the stream 210 * buffer. 211 */ puberror() const212 const boost::system::error_code& puberror() const 213 { 214 return error(); 215 } 216 217 /// Get the stream buffer's expiry time as an absolute time. 218 /** 219 * @return An absolute time value representing the stream buffer's expiry 220 * time. 221 */ expires_at() const222 time_type expires_at() const 223 { 224 return timer_service_ 225 ? timer_service_->expires_at(timer_implementation_) 226 : time_type(); 227 } 228 229 /// Set the stream buffer's expiry time as an absolute time. 230 /** 231 * This function sets the expiry time associated with the stream. Stream 232 * operations performed after this time (where the operations cannot be 233 * completed using the internal buffers) will fail with the error 234 * boost::asio::error::operation_aborted. 235 * 236 * @param expiry_time The expiry time to be used for the stream. 237 */ expires_at(const time_type & expiry_time)238 void expires_at(const time_type& expiry_time) 239 { 240 construct_timer(); 241 242 boost::system::error_code ec; 243 timer_service_->expires_at(timer_implementation_, expiry_time, ec); 244 boost::asio::detail::throw_error(ec, "expires_at"); 245 246 start_timer(); 247 } 248 249 /// Get the stream buffer's expiry time relative to now. 250 /** 251 * @return A relative time value representing the stream buffer's expiry time. 252 */ expires_from_now() const253 duration_type expires_from_now() const 254 { 255 return TimeTraits::subtract(expires_at(), TimeTraits::now()); 256 } 257 258 /// Set the stream buffer's expiry time relative to now. 259 /** 260 * This function sets the expiry time associated with the stream. Stream 261 * operations performed after this time (where the operations cannot be 262 * completed using the internal buffers) will fail with the error 263 * boost::asio::error::operation_aborted. 264 * 265 * @param expiry_time The expiry time to be used for the timer. 266 */ expires_from_now(const duration_type & expiry_time)267 void expires_from_now(const duration_type& expiry_time) 268 { 269 construct_timer(); 270 271 boost::system::error_code ec; 272 timer_service_->expires_from_now(timer_implementation_, expiry_time, ec); 273 boost::asio::detail::throw_error(ec, "expires_from_now"); 274 275 start_timer(); 276 } 277 278 protected: underflow()279 int_type underflow() 280 { 281 if (gptr() == egptr()) 282 { 283 if (timer_state_ == timer_has_expired) 284 { 285 ec_ = boost::asio::error::operation_aborted; 286 return traits_type::eof(); 287 } 288 289 io_handler handler = { this }; 290 this->get_service().async_receive(this->get_implementation(), 291 boost::asio::buffer(boost::asio::buffer(get_buffer_) + putback_max), 292 0, handler); 293 294 ec_ = boost::asio::error::would_block; 295 this->get_service().get_io_service().reset(); 296 do this->get_service().get_io_service().run_one(); 297 while (ec_ == boost::asio::error::would_block); 298 if (ec_) 299 return traits_type::eof(); 300 301 setg(&get_buffer_[0], &get_buffer_[0] + putback_max, 302 &get_buffer_[0] + putback_max + bytes_transferred_); 303 return traits_type::to_int_type(*gptr()); 304 } 305 else 306 { 307 return traits_type::eof(); 308 } 309 } 310 overflow(int_type c)311 int_type overflow(int_type c) 312 { 313 if (unbuffered_) 314 { 315 if (traits_type::eq_int_type(c, traits_type::eof())) 316 { 317 // Nothing to do. 318 return traits_type::not_eof(c); 319 } 320 else 321 { 322 if (timer_state_ == timer_has_expired) 323 { 324 ec_ = boost::asio::error::operation_aborted; 325 return traits_type::eof(); 326 } 327 328 // Send the single character immediately. 329 char_type ch = traits_type::to_char_type(c); 330 io_handler handler = { this }; 331 this->get_service().async_send(this->get_implementation(), 332 boost::asio::buffer(&ch, sizeof(char_type)), 0, handler); 333 334 ec_ = boost::asio::error::would_block; 335 this->get_service().get_io_service().reset(); 336 do this->get_service().get_io_service().run_one(); 337 while (ec_ == boost::asio::error::would_block); 338 if (ec_) 339 return traits_type::eof(); 340 341 return c; 342 } 343 } 344 else 345 { 346 // Send all data in the output buffer. 347 boost::asio::const_buffer buffer = 348 boost::asio::buffer(pbase(), pptr() - pbase()); 349 while (boost::asio::buffer_size(buffer) > 0) 350 { 351 if (timer_state_ == timer_has_expired) 352 { 353 ec_ = boost::asio::error::operation_aborted; 354 return traits_type::eof(); 355 } 356 357 io_handler handler = { this }; 358 this->get_service().async_send(this->get_implementation(), 359 boost::asio::buffer(buffer), 0, handler); 360 361 ec_ = boost::asio::error::would_block; 362 this->get_service().get_io_service().reset(); 363 do this->get_service().get_io_service().run_one(); 364 while (ec_ == boost::asio::error::would_block); 365 if (ec_) 366 return traits_type::eof(); 367 368 buffer = buffer + bytes_transferred_; 369 } 370 setp(&put_buffer_[0], &put_buffer_[0] + put_buffer_.size()); 371 372 // If the new character is eof then our work here is done. 373 if (traits_type::eq_int_type(c, traits_type::eof())) 374 return traits_type::not_eof(c); 375 376 // Add the new character to the output buffer. 377 *pptr() = traits_type::to_char_type(c); 378 pbump(1); 379 return c; 380 } 381 } 382 sync()383 int sync() 384 { 385 return overflow(traits_type::eof()); 386 } 387 setbuf(char_type * s,std::streamsize n)388 std::streambuf* setbuf(char_type* s, std::streamsize n) 389 { 390 if (pptr() == pbase() && s == 0 && n == 0) 391 { 392 unbuffered_ = true; 393 setp(0, 0); 394 return this; 395 } 396 397 return 0; 398 } 399 400 /// Get the last error associated with the stream buffer. 401 /** 402 * @return An \c error_code corresponding to the last error from the stream 403 * buffer. 404 */ error() const405 virtual const boost::system::error_code& error() const 406 { 407 return ec_; 408 } 409 410 private: init_buffers()411 void init_buffers() 412 { 413 setg(&get_buffer_[0], 414 &get_buffer_[0] + putback_max, 415 &get_buffer_[0] + putback_max); 416 if (unbuffered_) 417 setp(0, 0); 418 else 419 setp(&put_buffer_[0], &put_buffer_[0] + put_buffer_.size()); 420 } 421 422 template <typename ResolverQuery> resolve_and_connect(const ResolverQuery & query)423 void resolve_and_connect(const ResolverQuery& query) 424 { 425 typedef typename Protocol::resolver resolver_type; 426 typedef typename resolver_type::iterator iterator_type; 427 resolver_type resolver( 428 boost::base_from_member<boost::asio::io_service>::member); 429 iterator_type i = resolver.resolve(query, ec_); 430 if (!ec_) 431 { 432 iterator_type end; 433 ec_ = boost::asio::error::host_not_found; 434 while (ec_ && i != end) 435 { 436 this->basic_socket<Protocol, StreamSocketService>::close(ec_); 437 438 if (timer_state_ == timer_has_expired) 439 { 440 ec_ = boost::asio::error::operation_aborted; 441 return; 442 } 443 444 io_handler handler = { this }; 445 this->basic_socket<Protocol, StreamSocketService>::async_connect( 446 *i, handler); 447 448 ec_ = boost::asio::error::would_block; 449 this->get_service().get_io_service().reset(); 450 do this->get_service().get_io_service().run_one(); 451 while (ec_ == boost::asio::error::would_block); 452 453 ++i; 454 } 455 } 456 } 457 458 struct io_handler; 459 friend struct io_handler; 460 struct io_handler 461 { 462 basic_socket_streambuf* this_; 463 operator ()boost::asio::basic_socket_streambuf::io_handler464 void operator()(const boost::system::error_code& ec, 465 std::size_t bytes_transferred = 0) 466 { 467 this_->ec_ = ec; 468 this_->bytes_transferred_ = bytes_transferred; 469 } 470 }; 471 472 struct timer_handler; 473 friend struct timer_handler; 474 struct timer_handler 475 { 476 basic_socket_streambuf* this_; 477 operator ()boost::asio::basic_socket_streambuf::timer_handler478 void operator()(const boost::system::error_code&) 479 { 480 time_type now = TimeTraits::now(); 481 482 time_type expiry_time = this_->timer_service_->expires_at( 483 this_->timer_implementation_); 484 485 if (TimeTraits::less_than(now, expiry_time)) 486 { 487 this_->timer_state_ = timer_is_pending; 488 this_->timer_service_->async_wait(this_->timer_implementation_, *this); 489 } 490 else 491 { 492 this_->timer_state_ = timer_has_expired; 493 boost::system::error_code ec; 494 this_->basic_socket<Protocol, StreamSocketService>::close(ec); 495 } 496 } 497 }; 498 construct_timer()499 void construct_timer() 500 { 501 if (timer_service_ == 0) 502 { 503 TimerService& timer_service = use_service<TimerService>( 504 boost::base_from_member<boost::asio::io_service>::member); 505 timer_service.construct(timer_implementation_); 506 timer_service_ = &timer_service; 507 } 508 } 509 destroy_timer()510 void destroy_timer() 511 { 512 if (timer_service_) 513 timer_service_->destroy(timer_implementation_); 514 } 515 start_timer()516 void start_timer() 517 { 518 if (timer_state_ != timer_is_pending) 519 { 520 timer_handler handler = { this }; 521 handler(boost::system::error_code()); 522 } 523 } 524 525 enum { putback_max = 8 }; 526 enum { buffer_size = 512 }; 527 boost::asio::detail::array<char, buffer_size> get_buffer_; 528 boost::asio::detail::array<char, buffer_size> put_buffer_; 529 bool unbuffered_; 530 boost::system::error_code ec_; 531 std::size_t bytes_transferred_; 532 TimerService* timer_service_; 533 typename TimerService::implementation_type timer_implementation_; 534 enum state { no_timer, timer_is_pending, timer_has_expired } timer_state_; 535 }; 536 537 } // namespace asio 538 } // namespace boost 539 540 #include <boost/asio/detail/pop_options.hpp> 541 542 #if !defined(BOOST_ASIO_HAS_VARIADIC_TEMPLATES) 543 # undef BOOST_ASIO_PRIVATE_CONNECT_DEF 544 #endif // !defined(BOOST_ASIO_HAS_VARIADIC_TEMPLATES) 545 546 #endif // !defined(BOOST_NO_IOSTREAM) 547 548 #endif // BOOST_ASIO_BASIC_SOCKET_STREAMBUF_HPP 549