Digitalisierte Elektroverteilung zur permanenten Verbraucherüberwachung
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

net_connection.h 10.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. MMO Client/Server Framework using ASIO
  3. "Happy Birthday Mrs Javidx9!" - javidx9
  4. Videos:
  5. Part #1: https://youtu.be/2hNdkYInj4g
  6. Part #2: https://youtu.be/UbjxGvrDrbw
  7. License (OLC-3)
  8. ~~~~~~~~~~~~~~~
  9. Copyright 2018 - 2020 OneLoneCoder.com
  10. Redistribution and use in source and binary forms, with or without
  11. modification, are permitted provided that the following conditions
  12. are met:
  13. 1. Redistributions or derivations of source code must retain the above
  14. copyright notice, this list of conditions and the following disclaimer.
  15. 2. Redistributions or derivative works in binary form must reproduce
  16. the above copyright notice. This list of conditions and the following
  17. disclaimer must be reproduced in the documentation and/or other
  18. materials provided with the distribution.
  19. 3. Neither the name of the copyright holder nor the names of its
  20. contributors may be used to endorse or promote products derived
  21. from this software without specific prior written permission.
  22. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  23. "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  24. LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  25. A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  26. HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  27. SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  28. LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29. DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  30. THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  31. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  32. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33. Links
  34. ~~~~~
  35. YouTube: https://www.youtube.com/javidx9
  36. Discord: https://discord.gg/WhwHUMV
  37. Twitter: https://www.twitter.com/javidx9
  38. Twitch: https://www.twitch.tv/javidx9
  39. GitHub: https://www.github.com/onelonecoder
  40. Homepage: https://www.onelonecoder.com
  41. Author
  42. ~~~~~~
  43. David Barr, aka javidx9, ©OneLoneCoder 2019, 2020
  44. */
  45. #pragma once
  46. #include "net_common.h"
  47. #include "net_dequeue_ts.h"
  48. #include "net_message.h"
  49. namespace net
  50. {
  51. template<typename T>
  52. class Connection : public std::enable_shared_from_this<Connection<T>>
  53. {
  54. public:
  55. // A connection is "owned" by either a server or a client, and its
  56. // behaviour is slightly different bewteen the two.
  57. enum class Owner
  58. {
  59. SERVER,
  60. CLIENT
  61. };
  62. public:
  63. // Constructor: Specify Owner, connect to context, transfer the socket
  64. // Provide reference to incoming message queue
  65. Connection(Owner parent, asio::io_context& asioContext, asio::ip::tcp::socket socket, ts_dequeue<OwnedMessage<T>>& qIn)
  66. : m_socket(std::move(socket)), m_asioContext(asioContext), m_qMessagesIn(qIn)
  67. {
  68. m_nOwnerType = parent;
  69. }
  70. virtual ~Connection()
  71. {}
  72. // This ID is used system wide - its how clients will understand other clients
  73. // exist across the whole system.
  74. uint32_t GetID() const
  75. {
  76. return id;
  77. }
  78. public:
  79. void ConnectToClient(uint32_t uid = 0)
  80. {
  81. if (m_nOwnerType == Owner::SERVER)
  82. {
  83. if (m_socket.is_open())
  84. {
  85. id = uid;
  86. ReadHeader();
  87. }
  88. }
  89. }
  90. void ConnectToServer(const asio::ip::tcp::resolver::results_type& endpoints)
  91. {
  92. // Only clients can connect to servers
  93. if (m_nOwnerType == Owner::CLIENT)
  94. {
  95. // Request asio attempts to connect to an endpoint
  96. asio::async_connect(m_socket, endpoints,
  97. [this](std::error_code ec, asio::ip::tcp::endpoint endpoint)
  98. {
  99. if (!ec)
  100. {
  101. ReadHeader();
  102. }
  103. });
  104. }
  105. }
  106. void Disconnect()
  107. {
  108. if (IsConnected())
  109. asio::post(m_asioContext, [this]() { m_socket.close(); });
  110. }
  111. bool IsConnected() const
  112. {
  113. return m_socket.is_open();
  114. }
  115. // Prime the connection to wait for incoming messages
  116. void StartListening()
  117. {
  118. }
  119. public:
  120. // ASYNC - Send a message, connections are one-to-one so no need to specifiy
  121. // the target, for a client, the target is the server and vice versa
  122. void Send(const Message<T>& msg)
  123. {
  124. asio::post(m_asioContext,
  125. [this, msg]()
  126. {
  127. // If the queue has a message in it, then we must
  128. // assume that it is in the process of asynchronously being written.
  129. // Either way add the message to the queue to be output. If no messages
  130. // were available to be written, then start the process of writing the
  131. // message at the front of the queue.
  132. bool bWritingMessage = !m_qMessagesOut.empty();
  133. m_qMessagesOut.push_back(msg);
  134. if (!bWritingMessage)
  135. {
  136. WriteHeader();
  137. }
  138. });
  139. }
  140. private:
  141. // ASYNC - Prime context to write a message header
  142. void WriteHeader()
  143. {
  144. // If this function is called, we know the outgoing message queue must have
  145. // at least one message to send. So allocate a transmission buffer to hold
  146. // the message, and issue the work - asio, send these bytes
  147. asio::async_write(m_socket, asio::buffer(&m_qMessagesOut.front().header, sizeof(MessageHeader<T>)),
  148. [this](std::error_code ec, std::size_t length)
  149. {
  150. // asio has now sent the bytes - if there was a problem
  151. // an error would be available...
  152. if (!ec)
  153. {
  154. // ... no error, so check if the message header just sent also
  155. // has a message body...
  156. if (m_qMessagesOut.front().body.size() > 0)
  157. {
  158. // ...it does, so issue the task to write the body bytes
  159. WriteBody();
  160. }
  161. else
  162. {
  163. // ...it didnt, so we are done with this message. Remove it from
  164. // the outgoing message queue
  165. m_qMessagesOut.pop_front();
  166. // If the queue is not empty, there are more messages to send, so
  167. // make this happen by issuing the task to send the next header.
  168. if (!m_qMessagesOut.empty())
  169. {
  170. WriteHeader();
  171. }
  172. }
  173. }
  174. else
  175. {
  176. // ...asio failed to write the message, we could analyse why but
  177. // for now simply assume the connection has died by closing the
  178. // socket. When a future attempt to write to this client fails due
  179. // to the closed socket, it will be tidied up.
  180. std::cout << "[" << id << "] Write Header Fail.\n";
  181. m_socket.close();
  182. }
  183. });
  184. }
  185. // ASYNC - Prime context to write a message body
  186. void WriteBody()
  187. {
  188. // If this function is called, a header has just been sent, and that header
  189. // indicated a body existed for this message. Fill a transmission buffer
  190. // with the body data, and send it!
  191. asio::async_write(m_socket, asio::buffer(m_qMessagesOut.front().body.data(), m_qMessagesOut.front().body.size()),
  192. [this](std::error_code ec, std::size_t length)
  193. {
  194. if (!ec)
  195. {
  196. // Sending was successful, so we are done with the message
  197. // and remove it from the queue
  198. m_qMessagesOut.pop_front();
  199. // If the queue still has messages in it, then issue the task to
  200. // send the next messages' header.
  201. if (!m_qMessagesOut.empty())
  202. {
  203. WriteHeader();
  204. }
  205. }
  206. else
  207. {
  208. // Sending failed, see WriteHeader() equivalent for description :P
  209. std::cout << "[" << id << "] Write Body Fail.\n";
  210. m_socket.close();
  211. }
  212. });
  213. }
  214. // ASYNC - Prime context ready to read a message header
  215. void ReadHeader()
  216. {
  217. // asio to waits until it receives
  218. // enough bytes to form a header of a message. We know the headers are a fixed
  219. // size, so allocate a transmission buffer large enough to store it.
  220. //Call this function to set up an asynchronous listener for a certain Connection
  221. asio::async_read(m_socket, asio::buffer(&m_msgTemporaryIn.header, sizeof(MessageHeader<T>)),
  222. [this](std::error_code ec, std::size_t length)
  223. {
  224. if (!ec)
  225. {
  226. //Full header readed
  227. //Check for message body
  228. if (m_msgTemporaryIn.header.size > 0)
  229. {
  230. //Allocate storage
  231. m_msgTemporaryIn.body.resize(m_msgTemporaryIn.header.size);
  232. //Read if available
  233. ReadBody();
  234. }
  235. else
  236. {
  237. //Bodyless message, add to queue
  238. AddToIncomingMessageQueue();
  239. }
  240. }
  241. else
  242. {
  243. //Failure, probably a disconnection
  244. std::cout << "[" << id << "] Error: " << ec.message() << std::endl;
  245. m_socket.close();
  246. }
  247. });
  248. }
  249. // ASYNC - Prime context to read a message body
  250. void ReadBody()
  251. {
  252. //Called after header has been read successfully
  253. //Read the body in the pre allocated storage
  254. asio::async_read(m_socket, asio::buffer(m_msgTemporaryIn.body.data(), m_msgTemporaryIn.body.size()),
  255. [this](std::error_code ec, std::size_t length)
  256. {
  257. if (!ec)
  258. {
  259. // Complete message, add to queue
  260. AddToIncomingMessageQueue();
  261. }
  262. else
  263. {
  264. //Failure, probably a disconnection
  265. std::cout << "[" << id << "] Error reading body: " << ec.message() << std::endl;
  266. m_socket.close();
  267. }
  268. });
  269. }
  270. // Add a complete message to the incoming queue, with or without body
  271. void AddToIncomingMessageQueue()
  272. {
  273. //Put it in the queue, put a owner to the object to let the server know who send the message (which connection)
  274. if(m_nOwnerType == Owner::SERVER)
  275. m_qMessagesIn.push_back({ this->shared_from_this(), m_msgTemporaryIn });
  276. else
  277. m_qMessagesIn.push_back({ nullptr, m_msgTemporaryIn });
  278. //Done queueing the message, now initialize a new async read to wait for next message
  279. ReadHeader();
  280. }
  281. protected:
  282. // unique socket to a remote
  283. asio::ip::tcp::socket m_socket;
  284. // This context is shared with the whole asio instance
  285. asio::io_context& m_asioContext;
  286. // This queue holds all messages to be sent to the remote side
  287. // of this connection
  288. ts_dequeue<Message<T>> m_qMessagesOut;
  289. // This references the incoming queue of the parent object
  290. ts_dequeue<OwnedMessage<T>>& m_qMessagesIn;
  291. // Incoming messages are constructed asynchronously, so we will
  292. // store the part assembled message here, until it is ready
  293. Message<T> m_msgTemporaryIn;
  294. // The "owner" decides how some of the connection behaves
  295. Owner m_nOwnerType = Owner::SERVER;
  296. uint32_t id = 0;
  297. };
  298. }