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_server.h 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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_dequeue_ts.h"
  47. #include "net_common.h"
  48. #include "net_message.h"
  49. #include "net_connection.h"
  50. #include "easylogging++.h"
  51. namespace net
  52. {
  53. template<typename T>
  54. class ServerInterface
  55. {
  56. public: // Create a server, ready to listen on specified port
  57. ServerInterface(uint16_t port)
  58. : m_asioAcceptor(m_asioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) { }
  59. virtual ~ServerInterface() { Stop(); }
  60. // Starts the server
  61. virtual bool Start()
  62. {
  63. try
  64. {
  65. // Issue a task to the asio context - This is important
  66. // as it will prime the context with "work", and stop it
  67. // from exiting immediately. Since this is a server, we
  68. // want it primed ready to handle clients trying to
  69. // connect.
  70. WaitForClientConnection();
  71. // Launch the asio context in its own thread
  72. m_threadContext = std::thread([this]() { m_asioContext.run(); });
  73. }
  74. catch (std::exception& e)
  75. {
  76. // Something prohibited the server from listening
  77. LOG(ERROR) << "[SERVER] Exception: " << e.what();
  78. return false;
  79. }
  80. return true;
  81. }
  82. bool isStopped()const {
  83. return m_asioContext.stopped();
  84. }
  85. // Stops the server!
  86. virtual void Stop()
  87. {
  88. // Request the context to close
  89. m_asioContext.stop();
  90. // Tidy up the context thread
  91. if (m_threadContext.joinable()) m_threadContext.join();
  92. // Inform someone, anybody, if they care...
  93. LOG(INFO) << "[SERVER] Stopped!\n";
  94. }
  95. // ASYNC - Instruct asio to wait for connection
  96. void WaitForClientConnection()
  97. {
  98. // Prime context with an instruction to wait until a socket connects. This
  99. // is the purpose of an "acceptor" object. It will provide a unique socket
  100. // for each incoming connection attempt
  101. m_asioAcceptor.async_accept(
  102. [this](std::error_code ec, asio::ip::tcp::socket socket)
  103. {
  104. // Triggered by incoming connection request
  105. if (!ec)
  106. {
  107. LOG(INFO) << "[SERVER] New Connection: " << socket.remote_endpoint();
  108. // Create a new connection to handle this client
  109. std::shared_ptr<Connection<T>> newconn =
  110. std::make_shared<Connection<T>>(Connection<T>::Owner::SERVER,
  111. m_asioContext, std::move(socket), m_qMessagesIn);
  112. // Give the user server a chance to deny connection
  113. if (OnClientConnect(newconn))
  114. {
  115. // Connection allowed, so add to container of new connections
  116. m_deqConnections.push_back(std::move(newconn));
  117. // Issue a task to the connection's
  118. // asio context to sit and wait for bytes to arrive!
  119. m_deqConnections.back()->ConnectToClient(nIDCounter++);
  120. LOG(INFO) << "[" << m_deqConnections.back()->GetID() << "] Connection Approved";
  121. }
  122. else
  123. {
  124. LOG(INFO) << "[SERVER] Connection Denied";
  125. // Connection will go out of scope with no pending tasks, so will
  126. // get destroyed automatically (smart pointer)
  127. }
  128. }
  129. else
  130. {
  131. // Error has occurred during acceptance
  132. LOG(INFO) << "[SERVER] New Connection Error: " << ec.message();
  133. }
  134. // Prime the asio context with more work - again simply wait for
  135. // another connection...
  136. WaitForClientConnection();
  137. });
  138. }
  139. // Send a message to a specific client
  140. void MessageClient(std::shared_ptr<Connection<T>> client, const Message<T>& msg)
  141. {
  142. // Check client is legitimate...
  143. if (client && client->IsConnected())
  144. {
  145. // ...and post the message via the connection
  146. client->Send(msg);
  147. }
  148. else
  149. {
  150. // If we cant communicate with client then we may as
  151. // well remove the client - let the server know, it may
  152. // be tracking it somehow
  153. OnClientDisconnect(client);
  154. // Off you go now, bye bye!
  155. client.reset();
  156. // Then physically remove it from the container
  157. m_deqConnections.erase(
  158. std::remove(m_deqConnections.begin(), m_deqConnections.end(), client), m_deqConnections.end());
  159. }
  160. }
  161. // Send message to all clients
  162. void MessageAllClients(const Message<T>& msg, std::shared_ptr<Connection<T>> pIgnoreClient = nullptr)
  163. {
  164. bool bInvalidClientExists = false;
  165. // Iterate through all clients in container
  166. for (auto& client : m_deqConnections)
  167. {
  168. // Check client is connected...
  169. if (client && client->IsConnected())
  170. {
  171. // ..it is!
  172. if(client != pIgnoreClient)
  173. client->Send(msg);
  174. }
  175. else
  176. {
  177. // The client couldnt be contacted, so assume it has
  178. // disconnected.
  179. OnClientDisconnect(client);
  180. client.reset();
  181. // Set this flag to then remove dead clients from container
  182. bInvalidClientExists = true;
  183. }
  184. }
  185. // Remove dead clients, all in one go - this way, we dont invalidate the
  186. // container as we iterated through it.
  187. if (bInvalidClientExists)
  188. m_deqConnections.erase(
  189. std::remove(m_deqConnections.begin(), m_deqConnections.end(), nullptr), m_deqConnections.end());
  190. }
  191. // Force server to respond to incoming messages
  192. // size_t nmaxMessages: Assign -1 to unsigned to unspecify max message count
  193. // bool bWait: if queue is empty, wait synchronously until message arrives
  194. void Update(size_t nMaxMessages = -1, bool bWait = false)
  195. {
  196. if (bWait) m_qMessagesIn.wait();
  197. // Process as many messages as you can up to the value
  198. // specified
  199. size_t nMessageCount = 0;
  200. while (nMessageCount < nMaxMessages && !m_qMessagesIn.empty())
  201. {
  202. // Grab the front message
  203. auto msg = m_qMessagesIn.pop_front();
  204. // Pass to message handler
  205. OnMessage(msg.remote, msg.msg);
  206. nMessageCount++;
  207. }
  208. }
  209. protected:
  210. //Overwritable functions to customize server behaviour
  211. // Called when a client connects, you can veto the connection by returning false
  212. virtual bool OnClientConnect(std::shared_ptr<Connection<T>> client) = 0;
  213. // Called when a client appears to have disconnected
  214. virtual void OnClientDisconnect(std::shared_ptr<Connection<T>> client) = 0;
  215. // Called when a message arrives
  216. virtual void OnMessage(std::shared_ptr<Connection<T>> client, Message<T>& msg) = 0;
  217. // Thread Safe Queue for incoming message packets
  218. ts_dequeue<OwnedMessage<T>> m_qMessagesIn;
  219. // Container of active validated connections
  220. std::deque<std::shared_ptr<Connection<T>>> m_deqConnections;
  221. // Order of declaration is important - it is also the order of initialisation
  222. asio::io_context m_asioContext;
  223. std::thread m_threadContext;
  224. // These things need an asio context
  225. asio::ip::tcp::acceptor m_asioAcceptor; // Handles new incoming connection attempts...
  226. // Clients will be identified in the "wider system" via an ID
  227. uint32_t nIDCounter = 10000;
  228. };
  229. }