Otclient  14/8/2020
connection.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2010-2020 OTClient <https://github.com/edubart/otclient>
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20  * THE SOFTWARE.
21  */
22 
23 #include "connection.h"
24 
27 
28 #include <boost/asio.hpp>
29 #include <memory>
30 
31 asio::io_service g_ioService;
32 std::list<std::shared_ptr<asio::streambuf>> Connection::m_outputStreams;
33 
35  m_readTimer(g_ioService),
36  m_writeTimer(g_ioService),
37  m_delayedWriteTimer(g_ioService),
38  m_resolver(g_ioService),
39  m_socket(g_ioService)
40 {
41  m_connected = false;
42  m_connecting = false;
43 }
44 
46 {
47 #ifndef NDEBUG
48  assert(!g_app.isTerminated());
49 #endif
50  close();
51 }
52 
54 {
55  // reset must always be called prior to poll
56  g_ioService.reset();
57  g_ioService.poll();
58 }
59 
61 {
62  g_ioService.stop();
63  m_outputStreams.clear();
64 }
65 
67 {
68  if(!m_connected && !m_connecting)
69  return;
70 
71  // flush send data before disconnecting on clean connections
74 
75  m_connecting = false;
76  m_connected = false;
77  m_connectCallback = nullptr;
78  m_errorCallback = nullptr;
79  m_recvCallback = nullptr;
80 
81  m_resolver.cancel();
82  m_readTimer.cancel();
83  m_writeTimer.cancel();
84  m_delayedWriteTimer.cancel();
85 
86  if(m_socket.is_open()) {
87  boost::system::error_code ec;
88  m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
89  m_socket.close();
90  }
91 }
92 
93 void Connection::connect(const std::string& host, uint16 port, const std::function<void()>& connectCallback)
94 {
95  m_connected = false;
96  m_connecting = true;
97  m_error.clear();
98  m_connectCallback = connectCallback;
99 
100  asio::ip::tcp::resolver::query query(host, stdext::unsafe_cast<std::string>(port));
101  m_resolver.async_resolve(query, std::bind(&Connection::onResolve, asConnection(), std::placeholders::_1, std::placeholders::_2));
102 
103  m_readTimer.cancel();
104  m_readTimer.expires_from_now(boost::posix_time::seconds(static_cast<uint32>(READ_TIMEOUT)));
105  m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
106 }
107 
108 void Connection::internal_connect(asio::ip::basic_resolver<asio::ip::tcp>::iterator endpointIterator)
109 {
110  m_socket.async_connect(*endpointIterator, std::bind(&Connection::onConnect, asConnection(), std::placeholders::_1));
111 
112  m_readTimer.cancel();
113  m_readTimer.expires_from_now(boost::posix_time::seconds(static_cast<uint32>(READ_TIMEOUT)));
114  m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
115 }
116 
117 void Connection::write(uint8* buffer, size_t size)
118 {
119  if(!m_connected)
120  return;
121 
122  // we can't send the data right away, otherwise we could create tcp congestion
123  if(!m_outputStream) {
124  if(!m_outputStreams.empty()) {
126  m_outputStreams.pop_front();
127  } else
128  m_outputStream = std::make_shared<asio::streambuf>();
129 
130  m_delayedWriteTimer.cancel();
131  m_delayedWriteTimer.expires_from_now(boost::posix_time::milliseconds(0));
132  m_delayedWriteTimer.async_wait(std::bind(&Connection::onCanWrite, asConnection(), std::placeholders::_1));
133  }
134 
135  std::ostream os(m_outputStream.get());
136  os.write((const char*)buffer, size);
137  os.flush();
138 }
139 
141 {
142  if(!m_connected)
143  return;
144 
145  std::shared_ptr<asio::streambuf> outputStream = m_outputStream;
146  m_outputStream = nullptr;
147 
148  asio::async_write(m_socket,
149  *outputStream,
150  std::bind(&Connection::onWrite, asConnection(), std::placeholders::_1, std::placeholders::_2, outputStream));
151 
152  m_writeTimer.cancel();
153  m_writeTimer.expires_from_now(boost::posix_time::seconds(static_cast<uint32>(WRITE_TIMEOUT)));
154  m_writeTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
155 }
156 
157 void Connection::read(uint16 bytes, const RecvCallback& callback)
158 {
159  if(!m_connected)
160  return;
161 
162  m_recvCallback = callback;
163 
164  asio::async_read(m_socket,
165  asio::buffer(m_inputStream.prepare(bytes)),
166  std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
167 
168  m_readTimer.cancel();
169  m_readTimer.expires_from_now(boost::posix_time::seconds(static_cast<uint32>(READ_TIMEOUT)));
170  m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
171 }
172 
173 void Connection::read_until(const std::string& what, const RecvCallback& callback)
174 {
175  if(!m_connected)
176  return;
177 
178  m_recvCallback = callback;
179 
180  asio::async_read_until(m_socket,
182  what,
183  std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
184 
185  m_readTimer.cancel();
186  m_readTimer.expires_from_now(boost::posix_time::seconds(static_cast<uint32>(READ_TIMEOUT)));
187  m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
188 }
189 
190 void Connection::read_some(const RecvCallback& callback)
191 {
192  if(!m_connected)
193  return;
194 
195  m_recvCallback = callback;
196 
197  m_socket.async_read_some(asio::buffer(m_inputStream.prepare(RECV_BUFFER_SIZE)),
198  std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
199 
200  m_readTimer.cancel();
201  m_readTimer.expires_from_now(boost::posix_time::seconds(static_cast<uint32>(READ_TIMEOUT)));
202  m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
203 }
204 
205 void Connection::onResolve(const boost::system::error_code& error, asio::ip::basic_resolver<asio::ip::tcp>::iterator endpointIterator)
206 {
207  m_readTimer.cancel();
208 
209  if(error == asio::error::operation_aborted)
210  return;
211 
212  if(!error)
213  internal_connect(endpointIterator);
214  else
215  handleError(error);
216 }
217 
218 void Connection::onConnect(const boost::system::error_code& error)
219 {
220  m_readTimer.cancel();
222 
223  if(error == asio::error::operation_aborted)
224  return;
225 
226  if(!error) {
227  m_connected = true;
228 
229  // disable nagle's algorithm, this make the game play smoother
230  boost::asio::ip::tcp::no_delay option(true);
231  m_socket.set_option(option);
232 
235  } else
236  handleError(error);
237 
238  m_connecting = false;
239 }
240 
241 void Connection::onCanWrite(const boost::system::error_code& error)
242 {
243  m_delayedWriteTimer.cancel();
244 
245  if(error == asio::error::operation_aborted)
246  return;
247 
248  if(m_connected)
249  internal_write();
250 }
251 
252 void Connection::onWrite(const boost::system::error_code& error, size_t writeSize, std::shared_ptr<asio::streambuf> outputStream)
253 {
254  m_writeTimer.cancel();
255 
256  if(error == asio::error::operation_aborted)
257  return;
258 
259  // free output stream and store for using it again later
260  outputStream->consume(outputStream->size());
261  m_outputStreams.push_back(outputStream);
262 
263  if(m_connected && error)
264  handleError(error);
265 }
266 
267 void Connection::onRecv(const boost::system::error_code& error, size_t recvSize)
268 {
269  m_readTimer.cancel();
271 
272  if(error == asio::error::operation_aborted)
273  return;
274 
275  if(m_connected) {
276  if(!error) {
277  if(m_recvCallback) {
278  const char* header = boost::asio::buffer_cast<const char*>(m_inputStream.data());
279  m_recvCallback((uint8*)header, recvSize);
280  }
281  } else
282  handleError(error);
283  }
284 
285  if(!error)
286  m_inputStream.consume(recvSize);
287 }
288 
289 void Connection::onTimeout(const boost::system::error_code& error)
290 {
291  if(error == asio::error::operation_aborted)
292  return;
293 
294  handleError(asio::error::timed_out);
295 }
296 
297 void Connection::handleError(const boost::system::error_code& error)
298 {
299  if(error == asio::error::operation_aborted)
300  return;
301 
302  m_error = error;
303  if(m_errorCallback)
304  m_errorCallback(error);
306  close();
307 }
308 
310 {
311  boost::system::error_code error;
312  const boost::asio::ip::tcp::endpoint ip = m_socket.remote_endpoint(error);
313  if(!error)
314  return boost::asio::detail::socket_ops::host_to_network_long(ip.address().to_v4().to_ulong());
315 
316  g_logger.error("Getting remote ip");
317  return 0;
318 }
Connection::onResolve
void onResolve(const boost::system::error_code &error, asio::ip::tcp::resolver::iterator endpointIterator)
Definition: connection.cpp:205
Connection::m_error
boost::system::error_code m_error
Definition: connection.h:94
stdext::timer::restart
void restart()
Definition: time.h:42
eventdispatcher.h
Connection::m_connectCallback
std::function< void()> m_connectCallback
Definition: connection.h:79
Connection::m_readTimer
asio::deadline_timer m_readTimer
Definition: connection.h:83
Connection::handleError
void handleError(const boost::system::error_code &error)
Definition: connection.cpp:297
Connection::read
void read(uint16 bytes, const RecvCallback &callback)
Definition: connection.cpp:157
g_ioService
asio::io_service g_ioService
Definition: connection.cpp:31
Connection::poll
static void poll()
Definition: connection.cpp:53
uint32
uint32_t uint32
Definition: types.h:35
Connection::m_inputStream
asio::streambuf m_inputStream
Definition: connection.h:91
Connection::getIp
int getIp()
Definition: connection.cpp:309
Logger::error
void error(const std::string &what)
Definition: logger.h:54
Connection::m_outputStreams
static std::list< std::shared_ptr< asio::streambuf > > m_outputStreams
Definition: connection.h:89
Connection::onCanWrite
void onCanWrite(const boost::system::error_code &error)
Definition: connection.cpp:241
uint16
uint16_t uint16
Definition: types.h:36
Connection::m_delayedWriteTimer
asio::deadline_timer m_delayedWriteTimer
Definition: connection.h:85
Connection::Connection
Connection()
Definition: connection.cpp:34
Connection::onConnect
void onConnect(const boost::system::error_code &error)
Definition: connection.cpp:218
Connection::onWrite
void onWrite(const boost::system::error_code &error, size_t writeSize, std::shared_ptr< asio::streambuf > outputStream)
Definition: connection.cpp:252
g_logger
Logger g_logger
Definition: logger.cpp:35
Connection::m_recvCallback
RecvCallback m_recvCallback
Definition: connection.h:81
Connection::close
void close()
Definition: connection.cpp:66
Connection::m_connected
bool m_connected
Definition: connection.h:92
Connection::m_socket
asio::ip::tcp::socket m_socket
Definition: connection.h:87
Connection::internal_connect
void internal_connect(asio::ip::basic_resolver< asio::ip::tcp >::iterator endpointIterator)
Definition: connection.cpp:108
g_app
ConsoleApplication g_app
Definition: consoleapplication.cpp:32
Connection::write
void write(uint8 *buffer, size_t size)
Definition: connection.cpp:117
Connection::connect
void connect(const std::string &host, uint16 port, const std::function< void()> &connectCallback)
Definition: connection.cpp:93
Connection::read_until
void read_until(const std::string &what, const RecvCallback &callback)
Definition: connection.cpp:173
Connection::~Connection
~Connection()
Definition: connection.cpp:45
connection.h
Connection::asConnection
ConnectionPtr asConnection()
Definition: connection.h:66
Connection::m_resolver
asio::ip::tcp::resolver m_resolver
Definition: connection.h:86
Connection::m_errorCallback
ErrorCallback m_errorCallback
Definition: connection.h:80
Application::isTerminated
bool isTerminated()
Definition: application.h:50
Connection::m_writeTimer
asio::deadline_timer m_writeTimer
Definition: connection.h:84
Connection::m_outputStream
std::shared_ptr< asio::streambuf > m_outputStream
Definition: connection.h:90
Connection::m_activityTimer
stdext::timer m_activityTimer
Definition: connection.h:95
Connection::m_connecting
bool m_connecting
Definition: connection.h:93
Connection::onTimeout
void onTimeout(const boost::system::error_code &error)
Definition: connection.cpp:289
Connection::read_some
void read_some(const RecvCallback &callback)
Definition: connection.cpp:190
Connection::terminate
static void terminate()
Definition: connection.cpp:60
uint8
uint8_t uint8
Definition: types.h:37
application.h
Connection::internal_write
void internal_write()
Definition: connection.cpp:140
Connection::onRecv
void onRecv(const boost::system::error_code &error, size_t recvSize)
Definition: connection.cpp:267