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