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 11KB

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