]>
Commit | Line | Data |
---|---|---|
1 | // | |
2 | // basic_socket_streambuf.hpp | |
3 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
4 | // | |
5 | // Copyright (c) 2003-2016 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(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 | ||
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(&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 | ||
432 | private: | |
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 |