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