summaryrefslogtreecommitdiffstats
blob: a538ae5588543da427a2d986d9e0f6c14a4163d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//
// socket_select_interrupter.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef BOOST_ASIO_DETAIL_SOCKET_SELECT_INTERRUPTER_HPP
#define BOOST_ASIO_DETAIL_SOCKET_SELECT_INTERRUPTER_HPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include <boost/asio/detail/push_options.hpp>

#include <boost/asio/detail/push_options.hpp>
#include <cstdlib>
#include <boost/throw_exception.hpp>
#include <boost/system/system_error.hpp>
#include <boost/asio/detail/pop_options.hpp>

#include <boost/asio/error.hpp>
#include <boost/asio/detail/socket_holder.hpp>
#include <boost/asio/detail/socket_ops.hpp>
#include <boost/asio/detail/socket_types.hpp>

namespace boost {
namespace asio {
namespace detail {

class socket_select_interrupter
{
public:
  // Constructor.
  socket_select_interrupter()
  {
    boost::system::error_code ec;
    socket_holder acceptor(socket_ops::socket(
          AF_INET, SOCK_STREAM, IPPROTO_TCP, ec));
    if (acceptor.get() == invalid_socket)
    {
      boost::system::system_error e(ec, "socket_select_interrupter");
      boost::throw_exception(e);
    }

    int opt = 1;
    socket_ops::setsockopt(acceptor.get(),
        SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt), ec);

    using namespace std; // For memset.
    sockaddr_in4_type addr;
    std::size_t addr_len = sizeof(addr);
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = 0;
    if (socket_ops::bind(acceptor.get(), (const socket_addr_type*)&addr,
          addr_len, ec) == socket_error_retval)
    {
      boost::system::system_error e(ec, "socket_select_interrupter");
      boost::throw_exception(e);
    }

    if (socket_ops::getsockname(acceptor.get(), (socket_addr_type*)&addr,
          &addr_len, ec) == socket_error_retval)
    {
      boost::system::system_error e(ec, "socket_select_interrupter");
      boost::throw_exception(e);
    }

    // Some broken firewalls on Windows will intermittently cause getsockname to
    // return 0.0.0.0 when the socket is actually bound to 127.0.0.1. We
    // explicitly specify the target address here to work around this problem.
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (socket_ops::listen(acceptor.get(),
          SOMAXCONN, ec) == socket_error_retval)
    {
      boost::system::system_error e(ec, "socket_select_interrupter");
      boost::throw_exception(e);
    }

    socket_holder client(socket_ops::socket(
          AF_INET, SOCK_STREAM, IPPROTO_TCP, ec));
    if (client.get() == invalid_socket)
    {
      boost::system::system_error e(ec, "socket_select_interrupter");
      boost::throw_exception(e);
    }

    if (socket_ops::connect(client.get(), (const socket_addr_type*)&addr,
          addr_len, ec) == socket_error_retval)
    {
      boost::system::system_error e(ec, "socket_select_interrupter");
      boost::throw_exception(e);
    }

    socket_holder server(socket_ops::accept(acceptor.get(), 0, 0, ec));
    if (server.get() == invalid_socket)
    {
      boost::system::system_error e(ec, "socket_select_interrupter");
      boost::throw_exception(e);
    }
    
    ioctl_arg_type non_blocking = 1;
    if (socket_ops::ioctl(client.get(), FIONBIO, &non_blocking, ec))
    {
      boost::system::system_error e(ec, "socket_select_interrupter");
      boost::throw_exception(e);
    }

    opt = 1;
    socket_ops::setsockopt(client.get(),
        IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt), ec);

    non_blocking = 1;
    if (socket_ops::ioctl(server.get(), FIONBIO, &non_blocking, ec))
    {
      boost::system::system_error e(ec, "socket_select_interrupter");
      boost::throw_exception(e);
    }

    opt = 1;
    socket_ops::setsockopt(server.get(),
        IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt), ec);

    read_descriptor_ = server.release();
    write_descriptor_ = client.release();
  }

  // Destructor.
  ~socket_select_interrupter()
  {
    boost::system::error_code ec;
    if (read_descriptor_ != invalid_socket)
      socket_ops::close(read_descriptor_, ec);
    if (write_descriptor_ != invalid_socket)
      socket_ops::close(write_descriptor_, ec);
  }

  // Interrupt the select call.
  void interrupt()
  {
    char byte = 0;
    socket_ops::buf b;
    socket_ops::init_buf(b, &byte, 1);
    boost::system::error_code ec;
    socket_ops::send(write_descriptor_, &b, 1, 0, ec);
  }

  // Reset the select interrupt. Returns true if the call was interrupted.
  bool reset()
  {
    char data[1024];
    socket_ops::buf b;
    socket_ops::init_buf(b, data, sizeof(data));
    boost::system::error_code ec;
    int bytes_read = socket_ops::recv(read_descriptor_, &b, 1, 0, ec);
    bool was_interrupted = (bytes_read > 0);
    while (bytes_read == sizeof(data))
      bytes_read = socket_ops::recv(read_descriptor_, &b, 1, 0, ec);
    return was_interrupted;
  }

  // Get the read descriptor to be passed to select.
  socket_type read_descriptor() const
  {
    return read_descriptor_;
  }

private:
  // The read end of a connection used to interrupt the select call. This file
  // descriptor is passed to select such that when it is time to stop, a single
  // byte will be written on the other end of the connection and this
  // descriptor will become readable.
  socket_type read_descriptor_;

  // The write end of a connection used to interrupt the select call. A single
  // byte may be written to this to wake up the select which is waiting for the
  // other end to become readable.
  socket_type write_descriptor_;
};

} // namespace detail
} // namespace asio
} // namespace boost

#include <boost/asio/detail/pop_options.hpp>

#endif // BOOST_ASIO_DETAIL_SOCKET_SELECT_INTERRUPTER_HPP