PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/Utilities/Networking/BaseServer.cs

#
C# | 580 lines | 256 code | 46 blank | 278 comment | 25 complexity | 26bfe0dd2c85b1157810c86fd68aefff MD5 | raw file
Possible License(s): Apache-2.0
  1. // Helper to figure out networking issues, will write into console only!
  2. //#define LOG_STUFF
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Net;
  7. using System.Net.Sockets;
  8. using System.Threading;
  9. using Delta.Utilities.Cryptography;
  10. using Delta.Utilities.Helpers;
  11. using Delta.Utilities.Xml;
  12. namespace Delta.Utilities.Networking
  13. {
  14. /// <summary>
  15. /// Tcp Server base class. Provides useful functionality for server programs
  16. /// like the Log server or the Content and Build Server. Basically listens
  17. /// for clients with a TcpListener and manages a list of Sockets for
  18. /// connected clients. Build on top of the SocketHelper and StreamHelper
  19. /// functionality of the Delta.Utilities.Helpers assembly. Also provides
  20. /// compression functionality automatically for all messages with a payload
  21. /// of over 1024 bytes (like files). Additionally all network traffic can
  22. /// be encrypted with AES cryptography and the initial seed value can even
  23. /// be decrypted with a secret RSA key using a public RSA key on the clients.
  24. /// <para />
  25. /// Please keep in mind that a server usually has many clients that can
  26. /// connect and send data. All client methods like OnClientLogin and
  27. /// OnMessageReceived can be called from different data receiving threads
  28. /// and should be made thread safe (put a lock around critical code blocks).
  29. /// </summary>
  30. public abstract class BaseServer : IDisposable
  31. {
  32. #region Internal
  33. #region privateKey (Internal)
  34. /// <summary>
  35. /// Private key for encryption, see Cryptography class for details, by
  36. /// default this is null and unused. Please note that for encryption the
  37. /// first message send from the client to the server must be the Connect
  38. /// message (message type 0) and it contains the random seed (optionally
  39. /// even encrypted with a public RSA key from the server). See the
  40. /// <see cref="HandleConnectDecryption"/> method for details.
  41. /// <para />
  42. /// Note: The AES decryption instance can be found in each client.
  43. /// Most importantly the Client must also have the same privateKey, which
  44. /// should be kept secret (but even if it is known by an attacker, if you
  45. /// encrypted the random seed with RSA, the transmission is still secure).
  46. /// </summary>
  47. internal byte[] privateKey;
  48. #endregion
  49. #region rsa (Internal)
  50. /// <summary>
  51. /// You can even use an secret RSA key to decrypt incoming client requests
  52. /// (that must have been signed with the matching public RSA key). If this
  53. /// variable is null, no RSA cryptography is used.
  54. /// </summary>
  55. internal RSA rsa;
  56. #endregion
  57. #endregion
  58. #region Protected
  59. #region numOfMessageTypes (Protected)
  60. /// <summary>
  61. /// Number of message types this server supports. Derived classes set this
  62. /// in the constructor and handle each message type in OnMessageReceived.
  63. /// </summary>
  64. protected readonly byte numOfMessageTypes;
  65. #endregion
  66. #region clients (Protected)
  67. /// <summary>
  68. /// List of client sockets current connected, will be enumerated in the
  69. /// HandleClients method, which handles all the connection, disconnection
  70. /// and receiving data logic.
  71. /// </summary>
  72. protected List<BaseClient> clients = new List<BaseClient>();
  73. #endregion
  74. #region shutdownServer (Protected)
  75. /// <summary>
  76. /// Set this variable to true to shutdown all threads and pending
  77. /// operations on this server. Done in the Close method.
  78. /// </summary>
  79. protected bool shutdownServer;
  80. #endregion
  81. #endregion
  82. #region Private
  83. #region listenerThreads (Private)
  84. /// <summary>
  85. /// Helper threads for running the TcpServer in a different thread (because
  86. /// it is blocking in ListenForClients). This starts the data receiving
  87. /// as well (which works completely asynchrony so we don't need an extra
  88. /// thread for that). Multiple threads are used for each listener port.
  89. /// </summary>
  90. private readonly Dictionary<TcpListener, Thread> listenerThreads =
  91. new Dictionary<TcpListener, Thread>();
  92. #endregion
  93. #region clientDisconnectThread (Private)
  94. /// <summary>
  95. /// Because tcpServerThread just listens for clients and adds new ones
  96. /// into the clients list, we need this extra thread to remove disconnected
  97. /// clients again when the underlying socket is not longer connected.
  98. /// </summary>
  99. private Thread clientDisconnectThread;
  100. #endregion
  101. #endregion
  102. #region Constructors
  103. /// <summary>
  104. /// Create TCP server, which will immediately start to listen on the
  105. /// specified TCP port. Listening actually happens in a separate thread
  106. /// and handling all the clients and receiving their messages happens in
  107. /// another thread (because the listening thread blocks). This constructor
  108. /// will return right away and you can continue with your code. Use the
  109. /// delegates to be notified when clients connect or disconnect or new
  110. /// data arrives. Note: All messages are automatically compressed if they
  111. /// have above 1024 bytes of payload (see Delta.Utilities.Compression.Zip).
  112. /// <para />
  113. /// Encryption is also optionally possible and can be enabled by using a
  114. /// private key (null by default, which means unused) and you can even
  115. /// use an secret RSA key to decrypt incoming client requests (that must
  116. /// have been signed with the matching public RSA key).
  117. /// </summary>
  118. /// <param name="setIPAddressesToListenOn">Optional list of IP addresses
  119. /// to listen on (one for each port). Can be null for IPAddress.Any</param>
  120. /// <param name="setListenerPorts">Lists of ports to listen on for
  121. /// incoming clients.</param>
  122. /// <param name="setNumberOfMessageTypes">
  123. /// Set number of message types to be used in all network communications
  124. /// with clients (any incoming data with a message type value above this
  125. /// is not allowed and will be rejected).
  126. /// </param>
  127. /// <param name="setPrivateKey">
  128. /// Private key for encryption, see Cryptography class for details, by
  129. /// default this is null and unused. Please note that for encryption the
  130. /// first message send from the client to the server must be the Connect
  131. /// message (message type 0) and it contains the random seed (optionally
  132. /// even encrypted with a public RSA key from the server).
  133. /// Most importantly the ClientData must also have the same privateKey,
  134. /// which should be kept secret (but even if it is known by an attacked
  135. /// if you encrypted the random seed with RSA, the transmission is still
  136. /// secure).
  137. /// </param>
  138. /// <param name="secretRsaKey">Secret RSA key on the server (2048 bits)
  139. /// that is used to decrypt incoming Connect messages from clients, which
  140. /// encrypt their seed value in the Connect message with the matching
  141. /// public RSA key that only the server can decrypt. Note: To generate
  142. /// a secret and public RSA key pair use the CryptographyTests.
  143. /// </param>
  144. /// <exception cref="ArgumentNullException">
  145. /// Thrown if no ports are specified. The BaseServer needs at least one
  146. /// port to listen to!
  147. /// </exception>
  148. /// <exception cref="Exception">
  149. /// Thrown if we failed to initialize the TcpListener on given ports.
  150. /// </exception>
  151. public BaseServer(string[] setIPAddressesToListenOn,
  152. int[] setListenerPorts, byte setNumberOfMessageTypes,
  153. byte[] setPrivateKey = null, XmlNode secretRsaKey = null)
  154. {
  155. if (setListenerPorts == null ||
  156. setListenerPorts.Length == 0)
  157. {
  158. throw new ArgumentNullException(
  159. "BaseServer needs at least one port to listen to!");
  160. }
  161. numOfMessageTypes = setNumberOfMessageTypes;
  162. privateKey = setPrivateKey;
  163. if (privateKey != null &&
  164. secretRsaKey != null)
  165. {
  166. rsa = new RSA(secretRsaKey);
  167. }
  168. try
  169. {
  170. int portNum = 0;
  171. foreach (int port in setListenerPorts)
  172. {
  173. // Listener for clients to connect to. Each new client is added to the
  174. // clients list. We can have as many listeners as ports we listen to.
  175. IPAddress address = IPAddress.Any;
  176. if (setIPAddressesToListenOn != null &&
  177. portNum < setIPAddressesToListenOn.Length)
  178. {
  179. IPAddress.TryParse(setIPAddressesToListenOn[portNum], out address);
  180. if (address == null)
  181. {
  182. address = IPAddress.Any;
  183. }
  184. }
  185. TcpListener listener = new TcpListener(address, port);
  186. // Create server thread and name it the same way as the used class
  187. listenerThreads.Add(listener,
  188. ThreadHelper.Start(ListenForClients, listener));
  189. portNum++;
  190. }
  191. // And also handle all client disconnects in an extra thread.
  192. clientDisconnectThread = ThreadHelper.Start(HandleClientDisconnects);
  193. }
  194. catch (Exception ex)
  195. {
  196. throw new Exception(
  197. "Failed to initialize the TcpListener on port '" +
  198. setListenerPorts.Write() + "', server was not started.",
  199. ex);
  200. }
  201. }
  202. /// <summary>
  203. /// Create TCP server, which will immediately start to listen on the
  204. /// specified TCP port. Listening actually happens in a separate thread
  205. /// and handling all the clients and receiving their messages happens in
  206. /// another thread (because the listening thread blocks). This constructor
  207. /// will return right away and you can continue with your code. Use the
  208. /// delegates to be notified when clients connect or disconnect or new
  209. /// data arrives. Note: All messages are automatically compressed if they
  210. /// have above 1024 bytes of payload (see Delta.Utilities.Compression.Zip).
  211. /// <para />
  212. /// Encryption is also optionally possible and can be enabled by using a
  213. /// private key (null by default, which means unused) and you can even
  214. /// use an secret RSA key to decrypt incoming client requests (that must
  215. /// have been signed with the matching public RSA key).
  216. /// </summary>
  217. /// <param name="setListenerPort">
  218. /// Port to listen on for incoming clients.
  219. /// </param>
  220. /// <param name="setNumberOfMessageTypes">
  221. /// Set number of message types to be used in all network communications
  222. /// with clients (any incoming data with a message type value above this
  223. /// is not allowed and will be rejected).
  224. /// </param>
  225. /// <param name="setPrivateKey">
  226. /// Private key for encryption, see Cryptography class for details, by
  227. /// default this is null and unused. Please note that for encryption the
  228. /// first message send from the client to the server must be the Connect
  229. /// message (message type 0) and it contains the random seed (optionally
  230. /// even encrypted with a public RSA key from the server).
  231. /// Most importantly the ClientData must also have the same privateKey,
  232. /// which should be kept secret (but even if it is known by an attacked
  233. /// if you encrypted the random seed with RSA, the transmission is still
  234. /// secure).
  235. /// </param>
  236. /// <param name="secretRsaKey">Secret RSA key on the server (2048 bits)
  237. /// that is used to decrypt incoming Connect messages from clients, which
  238. /// encrypt their seed value in the Connect message with the matching
  239. /// public RSA key that only the server can decrypt. Note: To generate
  240. /// a secret and public RSA key pair use the CryptographyTests.
  241. /// </param>
  242. public BaseServer(int setListenerPort, byte setNumberOfMessageTypes,
  243. byte[] setPrivateKey = null, XmlNode secretRsaKey = null)
  244. : this(null, new[]
  245. {
  246. setListenerPort
  247. }, setNumberOfMessageTypes,
  248. setPrivateKey, secretRsaKey)
  249. {
  250. }
  251. #endregion
  252. #region Destructor
  253. /// <summary>
  254. /// ~Base server
  255. /// </summary>
  256. ~BaseServer()
  257. {
  258. Dispose();
  259. }
  260. #endregion
  261. #region IDisposable Members
  262. /// <summary>
  263. /// Stop tcp server, should be called at the end of the application.
  264. /// </summary>
  265. public virtual void Dispose()
  266. {
  267. // Abort any loops that wait for clients to connect, see ListenForClients
  268. shutdownServer = true;
  269. foreach (TcpListener listener in listenerThreads.Keys)
  270. {
  271. listener.Stop();
  272. }
  273. // Give it some time to disconnect and stop all listeners
  274. Thread.Sleep(10);
  275. // Disconnect all clients
  276. for (int i = 0; i < clients.Count; i++)
  277. {
  278. if (clients[i] != null)
  279. {
  280. clients[i].Dispose();
  281. clients[i] = null;
  282. }
  283. }
  284. // Clear the clients list
  285. clients.Clear();
  286. // And finally kill all listener threads we created that might still be
  287. // open (this might throw ThreadAbortExceptions, but usually those
  288. // threads have ended already and we should be fine).
  289. foreach (KeyValuePair<TcpListener, Thread> listener in listenerThreads)
  290. {
  291. listener.Value.Abort();
  292. }
  293. listenerThreads.Clear();
  294. shutdownServer = true;
  295. }
  296. #endregion
  297. #region SendTextToAllClients (Public)
  298. /// <summary>
  299. /// Send text message with the predefined TextMessage type.
  300. /// </summary>
  301. /// <param name="textMessage">Text Message</param>
  302. public void SendTextToAllClients(string textMessage)
  303. {
  304. foreach (BaseClient client in clients)
  305. {
  306. client.SendTextMessage(textMessage);
  307. }
  308. }
  309. #endregion
  310. #region Methods (Private)
  311. #region CreateClient
  312. /// <summary>
  313. /// Create client data helper method to allow creating derived client data
  314. /// classes in derived BaseServer classes.
  315. /// </summary>
  316. /// <param name="clientSocket">Client Socket</param>
  317. /// <returns>
  318. /// Client instance for keeping the socket and all client data around as
  319. /// long as the server needs this.
  320. /// </returns>
  321. protected abstract BaseClient CreateClient(Socket clientSocket);
  322. #endregion
  323. #region ListenForClients
  324. /// <summary>
  325. /// Listen for clients. Note: This method will block in an extra thread
  326. /// while waiting for client connections and run forever in an endless
  327. /// listening mode. To abort kill the whole thread, which will be catched
  328. /// here and the listener will be killed in the finally block.
  329. /// </summary>
  330. private void ListenForClients(object param)
  331. {
  332. TcpListener listener = param as TcpListener;
  333. try
  334. {
  335. listener.Start();
  336. }
  337. catch (Exception ex)
  338. {
  339. Log.Warning(
  340. "Failed to listen on '" + listener.LocalEndpoint + "': " +
  341. ex.Message);
  342. // End this thread
  343. return;
  344. }
  345. try
  346. {
  347. // Enter the listening loop
  348. do
  349. {
  350. try
  351. {
  352. // Block until we have a client connecting to us.
  353. Socket client = listener.AcceptSocket();
  354. // Create a new ClientInfo object and add it to our list.
  355. BaseClient clientData = CreateClient(client);
  356. // Only continue if client is wanted
  357. if (clientData == null)
  358. {
  359. // Abort
  360. continue;
  361. }
  362. clients.Add(clientData);
  363. // And start listening for incoming data! We have to wait for the
  364. // Connect message to process this client any further. Right now
  365. // all we have is the socket connection, this is not a real user yet.
  366. clientData.StartReceivingMessages();
  367. }
  368. catch (SocketException)
  369. {
  370. // Ignore SocketException, which only reports WSACancelBlockingCall
  371. }
  372. catch
  373. {
  374. }
  375. } while (shutdownServer == false);
  376. }
  377. finally
  378. {
  379. // Kill listener to stop accepting new clients
  380. listener.Stop();
  381. /*wtf, this should not be required!
  382. foreach (BaseClient client in clients)
  383. {
  384. client.Dispose();
  385. }
  386. */
  387. }
  388. }
  389. #endregion
  390. #region HandleClientDisconnects
  391. /// <summary>
  392. /// Handle client disconnects, loops through all clients and checks if
  393. /// they are still connected. Currently just handles disconnects, but
  394. /// reconnects could be made possible and Peer-To-Peer logic would be
  395. /// more complex, but for now this does not much.
  396. /// </summary>
  397. private void HandleClientDisconnects()
  398. {
  399. do
  400. {
  401. // Cache current number of clients because it might change in the
  402. // ListenForClients method. The following check does only handle
  403. // disconnects, so the list will change again.
  404. int numberOfClients = clients.Count;
  405. for (int clientNum = 0; clientNum < numberOfClients; clientNum++)
  406. {
  407. BaseClient client = clients[clientNum];
  408. try
  409. {
  410. // Are we not longer connected? Then remove from the list!
  411. if (client.Socket == null ||
  412. client.Socket.Connected == false)
  413. {
  414. // Notify event handler
  415. OnClientDisconnected(client);
  416. // Shutdown and end connection
  417. client.Dispose();
  418. // And remove from the list
  419. clients.Remove(client);
  420. numberOfClients--;
  421. clientNum--;
  422. continue;
  423. }
  424. }
  425. catch
  426. {
  427. }
  428. }
  429. // Wait a bit on the server (we usually have not much to do)
  430. if (shutdownServer == false)
  431. {
  432. Thread.Sleep(40);
  433. }
  434. } while (shutdownServer == false);
  435. }
  436. #endregion
  437. #region HandleClientLogin
  438. /// <summary>
  439. /// Handle client login
  440. /// </summary>
  441. /// <param name="client">Client</param>
  442. /// <param name="data">Data</param>
  443. internal bool HandleClientLogin(BaseClient client, BinaryReader data)
  444. {
  445. return OnClientLogin(client, data);
  446. }
  447. #endregion
  448. #region OnClientLogin
  449. /// <summary>
  450. /// Event that is fired every time a new client connects and has send the
  451. /// Login message (after the Connect message), see
  452. /// <see cref="Client.OnRawMessageReceived"/> for details.
  453. /// <para />
  454. /// This gives us a chance to handle the Login request here and reject
  455. /// clients if anything is wrong (wrong password, ip is banned, etc.).
  456. /// Return false if you want to reject the client, you can also send out
  457. /// a Login message to the client first with the reason for rejecting him.
  458. /// If this method returns true you also must send out a Login message to
  459. /// notify the client that he is now connected and logged in.
  460. /// </summary>
  461. /// <param name="client">Client that send the Login message</param>
  462. /// <param name="data">Data the client send for this Login message
  463. /// (already decrypted and uncompressed). This is by default just
  464. /// the username, but you can send whatever login information you need.
  465. /// </param>
  466. /// <returns>True if the client is successfully logged in or false if we
  467. /// had to reject this client (see Login message for reject reasons).
  468. /// </returns>
  469. protected virtual bool OnClientLogin(BaseClient client,
  470. BinaryReader data)
  471. {
  472. // Set the username from the Connect message!
  473. client.Username = data.ReadString();
  474. // Note: In derived classes you can also send more login data like
  475. // the login status, extra user information, etc. Here just an empty
  476. // message is send to notify the client that he is now logged in.
  477. client.Send((byte)BasicMessageTypes.Login);
  478. return true;
  479. }
  480. #endregion
  481. #region OnClientDisconnected
  482. /// <summary>
  483. /// Event that is fired every time a client disconnects to inform the
  484. /// server about the not longer connected client. This can happen on
  485. /// purpose by either side or because the connection was disconnected.
  486. /// <para />
  487. /// Note: Does just call client.Dispose, if you need more server side logic
  488. /// implement it in derived classes.
  489. /// </summary>
  490. /// <param name="client">Client that has been connected. Please note that
  491. /// this could even happen before OnClientConnected is even called (but
  492. /// this is very unlikely, then the Connect message must be messed up).
  493. /// </param>
  494. protected virtual void OnClientDisconnected(BaseClient client)
  495. {
  496. client.Dispose();
  497. }
  498. #endregion
  499. #region HandleMessageReceived
  500. /// <summary>
  501. /// Handle message received
  502. /// </summary>
  503. /// <param name="client">Client</param>
  504. /// <param name="messageType">Message type</param>
  505. /// <param name="data">Data</param>
  506. internal void HandleMessageReceived(BaseClient client,
  507. byte messageType, BinaryReader data)
  508. {
  509. OnMessageReceived(client, messageType, data);
  510. }
  511. #endregion
  512. #region OnMessageReceived
  513. /// <summary>
  514. /// Event that is fired every time a full message is received, called by
  515. /// <see cref="BaseClient.OnRawMessageReceived"/>. Note: This is not the
  516. /// same as receiving raw network data as a new data package might not
  517. /// contain a full message yet, or even contain multiple message for that
  518. /// matter. Additionally decompression and decryption is already handled.
  519. /// <para />
  520. /// Each message consists of a message type (byte) and the data (bytes).
  521. /// Note: OnPreCheckMessage is not supported by default in
  522. /// BaseServer, but you can easily add it if needed (for checking big
  523. /// messages) or just use the Client.OnPreCheckMessage method.
  524. /// </summary>
  525. /// <param name="client">Client we are receiving data for</param>
  526. /// <param name="messageType">Message type we received</param>
  527. /// <param name="data">Data we have received for this message type
  528. /// (already uncompressed and decrypted)</param>
  529. protected internal abstract void OnMessageReceived(BaseClient client,
  530. byte messageType, BinaryReader data);
  531. #endregion
  532. #endregion
  533. }
  534. }