• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // third_party_lib.cpp
3 // ~~~~~~~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2003-2021 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 #include <boost/asio.hpp>
12 #include <boost/array.hpp>
13 #include <boost/bind/bind.hpp>
14 #include <boost/shared_ptr.hpp>
15 #include <boost/enable_shared_from_this.hpp>
16 #include <iostream>
17 
18 using boost::asio::ip::tcp;
19 
20 namespace third_party_lib {
21 
22 // Simulation of a third party library that wants to perform read and write
23 // operations directly on a socket. It needs to be polled to determine whether
24 // it requires a read or write operation, and notified when the socket is ready
25 // for reading or writing.
26 class session
27 {
28 public:
session(tcp::socket & socket)29   session(tcp::socket& socket)
30     : socket_(socket),
31       state_(reading)
32   {
33   }
34 
35   // Returns true if the third party library wants to be notified when the
36   // socket is ready for reading.
want_read() const37   bool want_read() const
38   {
39     return state_ == reading;
40   }
41 
42   // Notify that third party library that it should perform its read operation.
do_read(boost::system::error_code & ec)43   void do_read(boost::system::error_code& ec)
44   {
45     if (std::size_t len = socket_.read_some(boost::asio::buffer(data_), ec))
46     {
47       write_buffer_ = boost::asio::buffer(data_, len);
48       state_ = writing;
49     }
50   }
51 
52   // Returns true if the third party library wants to be notified when the
53   // socket is ready for writing.
want_write() const54   bool want_write() const
55   {
56     return state_ == writing;
57   }
58 
59   // Notify that third party library that it should perform its write operation.
do_write(boost::system::error_code & ec)60   void do_write(boost::system::error_code& ec)
61   {
62     if (std::size_t len = socket_.write_some(
63           boost::asio::buffer(write_buffer_), ec))
64     {
65       write_buffer_ = write_buffer_ + len;
66       state_ = boost::asio::buffer_size(write_buffer_) > 0 ? writing : reading;
67     }
68   }
69 
70 private:
71   tcp::socket& socket_;
72   enum { reading, writing } state_;
73   boost::array<char, 128> data_;
74   boost::asio::const_buffer write_buffer_;
75 };
76 
77 } // namespace third_party_lib
78 
79 // The glue between asio's sockets and the third party library.
80 class connection
81   : public boost::enable_shared_from_this<connection>
82 {
83 public:
84   typedef boost::shared_ptr<connection> pointer;
85 
create(const boost::asio::any_io_executor & ex)86   static pointer create(const boost::asio::any_io_executor& ex)
87   {
88     return pointer(new connection(ex));
89   }
90 
socket()91   tcp::socket& socket()
92   {
93     return socket_;
94   }
95 
start()96   void start()
97   {
98     // Put the socket into non-blocking mode.
99     socket_.non_blocking(true);
100 
101     start_operations();
102   }
103 
104 private:
connection(const boost::asio::any_io_executor & ex)105   connection(const boost::asio::any_io_executor& ex)
106     : socket_(ex),
107       session_impl_(socket_),
108       read_in_progress_(false),
109       write_in_progress_(false)
110   {
111   }
112 
start_operations()113   void start_operations()
114   {
115     // Start a read operation if the third party library wants one.
116     if (session_impl_.want_read() && !read_in_progress_)
117     {
118       read_in_progress_ = true;
119       socket_.async_wait(tcp::socket::wait_read,
120           boost::bind(&connection::handle_read,
121             shared_from_this(),
122             boost::asio::placeholders::error));
123     }
124 
125     // Start a write operation if the third party library wants one.
126     if (session_impl_.want_write() && !write_in_progress_)
127     {
128       write_in_progress_ = true;
129       socket_.async_wait(tcp::socket::wait_write,
130           boost::bind(&connection::handle_write,
131             shared_from_this(),
132             boost::asio::placeholders::error));
133     }
134   }
135 
handle_read(boost::system::error_code ec)136   void handle_read(boost::system::error_code ec)
137   {
138     read_in_progress_ = false;
139 
140     // Notify third party library that it can perform a read.
141     if (!ec)
142       session_impl_.do_read(ec);
143 
144     // The third party library successfully performed a read on the socket.
145     // Start new read or write operations based on what it now wants.
146     if (!ec || ec == boost::asio::error::would_block)
147       start_operations();
148 
149     // Otherwise, an error occurred. Closing the socket cancels any outstanding
150     // asynchronous read or write operations. The connection object will be
151     // destroyed automatically once those outstanding operations complete.
152     else
153       socket_.close();
154   }
155 
handle_write(boost::system::error_code ec)156   void handle_write(boost::system::error_code ec)
157   {
158     write_in_progress_ = false;
159 
160     // Notify third party library that it can perform a write.
161     if (!ec)
162       session_impl_.do_write(ec);
163 
164     // The third party library successfully performed a write on the socket.
165     // Start new read or write operations based on what it now wants.
166     if (!ec || ec == boost::asio::error::would_block)
167       start_operations();
168 
169     // Otherwise, an error occurred. Closing the socket cancels any outstanding
170     // asynchronous read or write operations. The connection object will be
171     // destroyed automatically once those outstanding operations complete.
172     else
173       socket_.close();
174   }
175 
176 private:
177   tcp::socket socket_;
178   third_party_lib::session session_impl_;
179   bool read_in_progress_;
180   bool write_in_progress_;
181 };
182 
183 class server
184 {
185 public:
server(boost::asio::io_context & io_context,unsigned short port)186   server(boost::asio::io_context& io_context, unsigned short port)
187     : acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
188   {
189     start_accept();
190   }
191 
192 private:
start_accept()193   void start_accept()
194   {
195     connection::pointer new_connection =
196       connection::create(acceptor_.get_executor());
197 
198     acceptor_.async_accept(new_connection->socket(),
199         boost::bind(&server::handle_accept, this, new_connection,
200           boost::asio::placeholders::error));
201   }
202 
handle_accept(connection::pointer new_connection,const boost::system::error_code & error)203   void handle_accept(connection::pointer new_connection,
204       const boost::system::error_code& error)
205   {
206     if (!error)
207     {
208       new_connection->start();
209     }
210 
211     start_accept();
212   }
213 
214   tcp::acceptor acceptor_;
215 };
216 
main(int argc,char * argv[])217 int main(int argc, char* argv[])
218 {
219   try
220   {
221     if (argc != 2)
222     {
223       std::cerr << "Usage: third_party_lib <port>\n";
224       return 1;
225     }
226 
227     boost::asio::io_context io_context;
228 
229     using namespace std; // For atoi.
230     server s(io_context, atoi(argv[1]));
231 
232     io_context.run();
233   }
234   catch (std::exception& e)
235   {
236     std::cerr << "Exception: " << e.what() << "\n";
237   }
238 
239   return 0;
240 }
241