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 | |
75 | namespace boost { |
76 | namespace asio { |
77 | namespace 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. |
81 | class socket_streambuf_base |
82 | { |
83 | protected: |
84 | io_service io_service_; |
85 | }; |
86 | |
87 | } // namespace detail |
88 | |
89 | /// Iostream streambuf for a socket. |
90 | template <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 |
102 | class basic_socket_streambuf |
103 | : public std::streambuf, |
104 | private detail::socket_streambuf_base, |
105 | public basic_socket<Protocol, StreamSocketService> |
106 | { |
107 | private: |
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 | |
116 | public: |
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(c: 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(err: ec, location: "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(err: ec, location: "expires_from_now" ); |
296 | |
297 | start_timer(); |
298 | } |
299 | |
300 | protected: |
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(gbeg: &get_buffer_[0], gnext: &get_buffer_[0] + putback_max, |
324 | gend: &get_buffer_[0] + putback_max + bytes_transferred_); |
325 | return traits_type::to_int_type(c: *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(c1: c, c2: traits_type::eof())) |
338 | { |
339 | // Nothing to do. |
340 | return traits_type::not_eof(c: 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: c); |
352 | io_handler handler = { this }; |
353 | this->get_service().async_send(this->get_implementation(), |
354 | boost::asio::buffer(data: &ch, size_in_bytes: 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(b: 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(b: 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(pbeg: &put_buffer_[0], pend: &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(c1: c, c2: traits_type::eof())) |
396 | return traits_type::not_eof(c: c); |
397 | |
398 | // Add the new character to the output buffer. |
399 | *pptr() = traits_type::to_char_type(c: c); |
400 | pbump(n: 1); |
401 | return c; |
402 | } |
403 | } |
404 | |
405 | int sync() |
406 | { |
407 | return overflow(c: 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(pbeg: 0, pend: 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 | |
432 | private: |
433 | void init_buffers() |
434 | { |
435 | setg(gbeg: &get_buffer_[0], |
436 | gnext: &get_buffer_[0] + putback_max, |
437 | gend: &get_buffer_[0] + putback_max); |
438 | if (unbuffered_) |
439 | setp(pbeg: 0, pend: 0); |
440 | else |
441 | setp(pbeg: &put_buffer_[0], pend: &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 | |