Otclient 1.0  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  }
128  else
129  m_outputStream = std::make_shared<asio::streambuf>();
130 
131  m_delayedWriteTimer.cancel();
132  m_delayedWriteTimer.expires_from_now(boost::posix_time::milliseconds(0));
133  m_delayedWriteTimer.async_wait(std::bind(&Connection::onCanWrite, asConnection(), std::placeholders::_1));
134  }
135 
136  std::ostream os(m_outputStream.get());
137  os.write((const char*)buffer, size);
138  os.flush();
139 }
140 
142 {
143  if (!m_connected)
144  return;
145 
146  std::shared_ptr<asio::streambuf> outputStream = m_outputStream;
147  m_outputStream = nullptr;
148 
149  asio::async_write(m_socket,
150  *outputStream,
151  std::bind(&Connection::onWrite, asConnection(), std::placeholders::_1, std::placeholders::_2, outputStream));
152 
153  m_writeTimer.cancel();
154  m_writeTimer.expires_from_now(boost::posix_time::seconds(static_cast<uint32>(WRITE_TIMEOUT)));
155  m_writeTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
156 }
157 
158 void Connection::read(uint16 bytes, const RecvCallback& callback)
159 {
160  if (!m_connected)
161  return;
162 
163  m_recvCallback = callback;
164 
165  asio::async_read(m_socket,
166  asio::buffer(m_inputStream.prepare(bytes)),
167  std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
168 
169  m_readTimer.cancel();
170  m_readTimer.expires_from_now(boost::posix_time::seconds(static_cast<uint32>(READ_TIMEOUT)));
171  m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
172 }
173 
174 void Connection::read_until(const std::string& what, const RecvCallback& callback)
175 {
176  if (!m_connected)
177  return;
178 
179  m_recvCallback = callback;
180 
181  asio::async_read_until(m_socket,
183  what,
184  std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
185 
186  m_readTimer.cancel();
187  m_readTimer.expires_from_now(boost::posix_time::seconds(static_cast<uint32>(READ_TIMEOUT)));
188  m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
189 }
190 
191 void Connection::read_some(const RecvCallback& callback)
192 {
193  if (!m_connected)
194  return;
195 
196  m_recvCallback = callback;
197 
198  m_socket.async_read_some(asio::buffer(m_inputStream.prepare(RECV_BUFFER_SIZE)),
199  std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
200 
201  m_readTimer.cancel();
202  m_readTimer.expires_from_now(boost::posix_time::seconds(static_cast<uint32>(READ_TIMEOUT)));
203  m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
204 }
205 
206 void Connection::onResolve(const boost::system::error_code& error, asio::ip::basic_resolver<asio::ip::tcp>::iterator endpointIterator)
207 {
208  m_readTimer.cancel();
209 
210  if (error == asio::error::operation_aborted)
211  return;
212 
213  if (!error)
214  internal_connect(endpointIterator);
215  else
216  handleError(error);
217 }
218 
219 void Connection::onConnect(const boost::system::error_code& error)
220 {
221  m_readTimer.cancel();
223 
224  if (error == asio::error::operation_aborted)
225  return;
226 
227  if (!error) {
228  m_connected = true;
229 
230  // disable nagle's algorithm, this make the game play smoother
231  boost::asio::ip::tcp::no_delay option(true);
232  m_socket.set_option(option);
233 
234  if (m_connectCallback)
236  }
237  else
238  handleError(error);
239 
240  m_connecting = false;
241 }
242 
243 void Connection::onCanWrite(const boost::system::error_code& error)
244 {
245  m_delayedWriteTimer.cancel();
246 
247  if (error == asio::error::operation_aborted)
248  return;
249 
250  if (m_connected)
251  internal_write();
252 }
253 
254 void Connection::onWrite(const boost::system::error_code& error, size_t, std::shared_ptr<asio::streambuf> outputStream)
255 {
256  m_writeTimer.cancel();
257 
258  if (error == asio::error::operation_aborted)
259  return;
260 
261  // free output stream and store for using it again later
262  outputStream->consume(outputStream->size());
263  m_outputStreams.push_back(outputStream);
264 
265  if (m_connected && error)
266  handleError(error);
267 }
268 
269 void Connection::onRecv(const boost::system::error_code& error, size_t recvSize)
270 {
271  m_readTimer.cancel();
273 
274  if (error == asio::error::operation_aborted)
275  return;
276 
277  if (m_connected) {
278  if (!error) {
279  if (m_recvCallback) {
280  const char* header = boost::asio::buffer_cast<const char*>(m_inputStream.data());
281  m_recvCallback((uint8*)header, recvSize);
282  }
283  }
284  else
285  handleError(error);
286  }
287 
288  if (!error)
289  m_inputStream.consume(recvSize);
290 }
291 
292 void Connection::onTimeout(const boost::system::error_code& error)
293 {
294  if (error == asio::error::operation_aborted)
295  return;
296 
297  handleError(asio::error::timed_out);
298 }
299 
300 void Connection::handleError(const boost::system::error_code& error)
301 {
302  if (error == asio::error::operation_aborted)
303  return;
304 
305  m_error = error;
306  if (m_errorCallback)
307  m_errorCallback(error);
308  if (m_connected || m_connecting)
309  close();
310 }
311 
313 {
314  boost::system::error_code error;
315  const boost::asio::ip::tcp::endpoint ip = m_socket.remote_endpoint(error);
316  if (!error)
317  return boost::asio::detail::socket_ops::host_to_network_long(ip.address().to_v4().to_ulong());
318 
319  g_logger.error("Getting remote ip");
320  return 0;
321 }
Connection::onResolve
void onResolve(const boost::system::error_code &error, asio::ip::tcp::resolver::iterator endpointIterator)
Definition: connection.cpp:206
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:300
Connection::read
void read(uint16 bytes, const RecvCallback &callback)
Definition: connection.cpp:158
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:312
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:243
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:219
Connection::onWrite
void onWrite(const boost::system::error_code &error, size_t writeSize, std::shared_ptr< asio::streambuf > outputStream)
Definition: connection.cpp:254
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:174
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:292
Connection::read_some
void read_some(const RecvCallback &callback)
Definition: connection.cpp:191
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:141
Connection::onRecv
void onRecv(const boost::system::error_code &error, size_t recvSize)
Definition: connection.cpp:269