/Utilities/Tests/NetworkingTests.cs
C# | 808 lines | 485 code | 99 blank | 224 comment | 23 complexity | 74c1cf987ad63f4957e4c0b3668240cc MD5 | raw file
Possible License(s): Apache-2.0
- using System;
- using System.Diagnostics;
- using System.IO;
- using System.Net;
- using System.Net.Sockets;
- using System.Threading;
- using Delta.Utilities.Cryptography;
- using Delta.Utilities.Helpers;
- using Delta.Utilities.Networking;
- using Delta.Utilities.Xml;
- using NUnit.Framework;
-
- namespace Delta.Utilities.Tests
- {
- /// <summary>
- /// Tests for Delta.Utilities.Networking (mostly BaseClient and BaseServer)
- /// </summary>
- internal class NetworkingTests
- {
- #region Helpers
-
- #region TextClient
- /// <summary>
- /// Helper class for receiving client data and outputting it to the console
- /// </summary>
- public class TextClient : BaseClient
- {
- #region Constants
- public const string TestServer = "localhost";
-
- public const string TestUsername = "TestUser";
- #endregion
-
- #region Constructors
- /// <summary>
- /// Constructor to create TextClient for the server side.
- /// </summary>
- public TextClient(Socket setSocket, BaseServer setLinkToServer)
- : base(setSocket, TextServer.NumOfMessageTypes, setLinkToServer)
- {
- }
-
- /// <summary>
- /// Constructor to create TextClient for the client side.
- /// </summary>
- public TextClient()
- : base(TestServer, TextServer.ServerPort, TextServer.NumOfMessageTypes,
- true, TestUsername)
- {
- }
- #endregion
-
- #region Methods (Private)
-
- #region OnMessageReceived
- /// <summary>
- /// Every time we receive a message, we show it in the console and log
- /// </summary>
- protected override void OnMessageReceived(byte messageType,
- BinaryReader data)
- {
- if (messageType == TextServer.TextMessageType)
- {
- Log.Info("TextMessage received: " +
- data.ReadString());
- }
- else if (messageType == TextServer.EmptyMessageType)
- {
- Log.Info("EmptyMessage received");
- }
- else
- {
- Log.Info("Received unknown message type=" + messageType +
- " from server");
- }
- }
- #endregion
-
- #endregion
- }
- #endregion
-
- #region TestTcpServer
- /// <summary>
- /// Test tcp server
- /// </summary>
- public class TestTcpServer : BaseServer
- {
- #region Constants
- public const int ServerPort = 12345;
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create test tcp server
- /// </summary>
- public TestTcpServer()
- : base(ServerPort, BaseClient.NumberOfBasicMessageTypes)
- {
- }
- #endregion
-
- #region Methods (Private)
-
- #region CreateClient
- /// <summary>
- /// Create TextClient
- /// </summary>
- protected override BaseClient CreateClient(Socket clientSocket)
- {
- return new TextClient(clientSocket, this);
- }
- #endregion
-
- #region OnMessageReceived
- /// <summary>
- /// On message received
- /// </summary>
- protected internal override void OnMessageReceived(BaseClient client,
- byte messageType, BinaryReader data)
- {
- if (messageType == (byte)BasicMessageTypes.TextMessage)
- {
- Console.WriteLine("Received TextMessage on server from client=" +
- client + ": " + data.ReadString());
- }
- else
- {
- Console.WriteLine("Received unknown message type=" + messageType +
- " on server from client=" + client);
- }
- }
- #endregion
-
- #region OnClientLogin
- /// <summary>
- /// This method is called for every client that successfully connects.
- /// </summary>
- protected override bool OnClientLogin(BaseClient client,
- BinaryReader data)
- {
- base.OnClientLogin(client, data);
- Console.WriteLine("TestTcpServer.OnClientLogin " + client);
- return true;
- }
- #endregion
-
- #region OnClientDisconnected
- /// <summary>
- /// This method is called for every client disconnect.
- /// </summary>
- protected override void OnClientDisconnected(BaseClient client)
- {
- Console.WriteLine("TestTcpServer.OnClientDisconnected " + client);
- base.OnClientDisconnected(client);
- }
- #endregion
-
- #endregion
- }
- #endregion
-
- #region TestEncryptedServer
- /// <summary>
- /// Test encrypted server that uses AES cryptography for network
- /// transmission and even protects the AES seed value with RSA encryption.
- /// </summary>
- public class TestEncryptedServer : BaseServer
- {
- #region Constants
- /// <summary>
- /// Server port used for this test server
- /// </summary>
- public const int ServerPort = 12345;
- #endregion
-
- #region aesPrivateKey (Static)
- /// <summary>
- /// AES private key that must be known both to the client and the server.
- /// </summary>
- public static byte[] aesPrivateKey = AES.CreatePrivateKey();
- #endregion
-
- #region secretRsaKey (Static)
- /// <summary>
- /// Note: In order for this to work a Private.RSA.key file must exist,
- /// start the CryptographyTests.TestRsaCrypto unit test to generate.
- /// </summary>
- public static XmlNode secretRsaKey = XmlNode.FromFile("Private.RSA.key");
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create encrypted test tcp server (using both AES and RSA)
- /// </summary>
- public TestEncryptedServer()
- : base(ServerPort, BaseClient.NumberOfBasicMessageTypes,
- aesPrivateKey, secretRsaKey)
- {
- }
- #endregion
-
- #region Methods (Private)
-
- #region CreateClient
- /// <summary>
- /// Create TextClient
- /// </summary>
- protected override BaseClient CreateClient(Socket clientSocket)
- {
- return new TextClient(clientSocket, this);
- }
- #endregion
-
- #region OnMessageReceived
- /// <summary>
- /// On message received, which is sent for every successful message
- /// received other than Connect or Login.
- /// </summary>
- protected internal override void OnMessageReceived(BaseClient client,
- byte messageType, BinaryReader data)
- {
- if (messageType == (byte)BasicMessageTypes.TextMessage)
- {
- string textString = data.ReadString();
- Console.WriteLine("Received encrypted TextMessage on server from " +
- "client=" + client + ": " +
- textString.MaxStringLength(50,
- StringHelper.CutModes.EndWithDots) +
- " (total length=" + textString.Length + ")");
- }
- else
- {
- Console.WriteLine("Received unknown encrypted message type=" +
- messageType + " on server from client=" + client);
- }
- }
- #endregion
-
- #region OnClientLogin
- /// <summary>
- /// This method is called for every client that successfully connected
- /// and is logged in now with some user data.
- /// </summary>
- protected override bool OnClientLogin(BaseClient client,
- BinaryReader data)
- {
- base.OnClientLogin(client, data);
- Console.WriteLine("TestEncryptedServer.OnClientLogin " + client);
- return true;
- }
- #endregion
-
- #region OnClientDisconnected
- /// <summary>
- /// This method is called for every client disconnect.
- /// </summary>
- protected override void OnClientDisconnected(BaseClient client)
- {
- Console.WriteLine("TestEncryptedServer.OnClientDisconnected " + client);
- base.OnClientDisconnected(client);
- }
- #endregion
-
- #endregion
- }
- #endregion
-
- #region TextServer
- public class TextServer : BaseServer
- {
- #region Constants
- /// <summary>
- /// Listen for connections on port 12345
- /// </summary>
- public const int ServerPort = 12345;
-
- // Note: Message Type 0 and 1 are reserved for Connect and Login!
- public const byte TextMessageType = 2;
-
- public const byte EmptyMessageType = 3;
-
- public const byte StressTestMessageType = 4;
-
- public const byte NumOfMessageTypes = 5;
- #endregion
-
- #region StressTestMessagesReceived (Public)
- /// <summary>
- /// Helper for the StressTestMessageType, will count how much data
- /// we have received to make sure the StressTest below works fine.
- /// </summary>
- public int StressTestMessagesReceived;
- #endregion
-
- #region StressTestDataReceived (Public)
- /// <summary>
- /// Helper for the StressTestMessageType, will count how much data
- /// we have received to make sure the StressTest below works fine.
- /// </summary>
- public int StressTestDataReceived;
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create test tcp server
- /// </summary>
- public TextServer()
- : base(ServerPort, NumOfMessageTypes)
- {
- }
- #endregion
-
- #region Methods (Private)
-
- #region CreateClient
- /// <summary>
- /// Create TextClient
- /// </summary>
- protected override BaseClient CreateClient(Socket clientSocket)
- {
- return new TextClient(clientSocket, this);
- }
- #endregion
-
- #region OnMessageReceived
- /// <summary>
- /// On message received method, which is called whenever this server
- /// receives any message from clients (except for Connect and Login
- /// messages, which are already handled by the BaseServer class).
- /// </summary>
- protected internal override void OnMessageReceived(BaseClient client,
- byte messageType, BinaryReader data)
- {
- if (messageType == TextMessageType)
- {
- string textString = data.ReadString();
- if (textString.Length > 200)
- {
- Log.Info("Received really big TextMessage on server from client=" +
- client + ": " + textString.MaxStringLength(30,
- StringHelper.CutModes.EndWithDots) +
- " (total length=" + textString.Length + ")");
- }
- else
- {
- Log.Info("Received TextMessage on server from client=" +
- client + ": " + textString);
- }
- }
- else if (messageType == EmptyMessageType)
- {
- Log.Info("EmptyMessageType received from client=" + client);
- }
- else if (messageType == StressTestMessageType)
- {
- // This lock is important for the StressTest to count up correctly!
- lock (this)
- {
- StressTestMessagesReceived++;
- StressTestDataReceived += (int)data.BaseStream.Length;
- }
- }
- else
- {
- Log.Info("Received unknown message type=" + messageType +
- " on server from client=" + client);
- }
- }
- #endregion
-
- #region OnClientLogin
- /// <summary>
- /// This method is called for every client that successfully connects.
- /// </summary>
- protected override bool OnClientLogin(BaseClient client,
- BinaryReader data)
- {
- base.OnClientLogin(client, data);
- // This lock is important for the StressTest not to mess up the log!
- lock (this)
- {
- Log.Info("TextServer.OnClientLogin " + client);
- }
- return true;
- }
- #endregion
-
- #region OnClientDisconnected
- /// <summary>
- /// This method is called for every client disconnect.
- /// </summary>
- protected override void OnClientDisconnected(BaseClient client)
- {
- Log.Info("TextServer.OnClientDisconnected " + client);
- base.OnClientDisconnected(client);
- }
- #endregion
-
- #endregion
- }
- #endregion
-
- #endregion
-
- #region ConnectTcpSocket (LongRunning)
- [Test, Category("LongRunning")]
- public static void ConnectTcpSocket()
- {
- // First start the server we want to connect
- using (BaseServer testServer = new TestTcpServer())
- {
- Assert.Equal(SocketHelper.LastConnectionError, "");
- Socket socket = SocketHelper.ConnectTcpSocket("localhost",
- TestTcpServer.ServerPort);
- Assert.Equal(socket.RemoteEndPoint.ToString(),
- "127.0.0.1:" + TestTcpServer.ServerPort);
- socket.Dispose();
- }
- }
- #endregion
-
-
- #region StartServerAndConnect (LongRunning)
- /// <summary>
- /// Start server and connect. This will just test the server side, this
- /// test will make no use of the Client class.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void StartServerAndConnect()
- {
- // Start server (listens in an extra thread, see above)
- BaseServer testServer = new TestTcpServer();
-
- // And connect (in this thread)!
- // See Client.Tests for more useful examples.
- Socket serverSocket = SocketHelper.ConnectTcpSocket(
- "127.0.0.1", TestTcpServer.ServerPort);
- Assert.True(serverSocket.Connected);
-
- // We need to send out a Connect message first to the server, else he
- // will disconnect us right away. First byte of the Connect message
- // must be 0 to indicate we use no encryption.
- MemoryStream connectData = new MemoryStream();
- connectData.WriteByte(0);
- SocketHelper.SendMessageBytes(serverSocket,
- (byte)BasicMessageTypes.Connect,
- connectData.ToArray(), false);
- // Next send the Login message with the Username
- SocketHelper.SendMessageBytes(serverSocket,
- (byte)BasicMessageTypes.Login,
- StringHelper.StringToBytes("TestClient"), false);
-
- // Send out a simple text message
- Console.WriteLine("Sending: Hi from StartServerAndConnect");
- SocketHelper.SendMessageBytes(serverSocket,
- (byte)BasicMessageTypes.TextMessage,
- StringHelper.StringToBytes("Hi from StartServerAndConnect"), false);
-
- // Wait a bit for server to receive and display data!
- Thread.Sleep(500);
-
- // Always close the tcp server after starting it!
- testServer.Dispose();
- serverSocket.Close();
- Assert.False(serverSocket.Connected);
- }
- #endregion
-
- #region StartEncryptedServerAndConnect (LongRunning)
- /// <summary>
- /// Start encrypted server and connect
- /// </summary>
- [Test, Category("LongRunning")]
- public static void StartEncryptedServerAndConnect()
- {
- // Start server (listens in an extra thread, see above)
- BaseServer testServer = new TestEncryptedServer();
-
- // And connect (in this thread)!
- // See Client.Tests for more useful examples.
- Socket serverSocket = SocketHelper.ConnectTcpSocket(
- "127.0.0.1", TestTcpServer.ServerPort);
- Assert.True(serverSocket.Connected);
-
- // We need to send out a Connect message first to the server, else he
- // will disconnect us right away. First byte of the Connect message
- // must be 2 to indicate we use AES and RSA encryption.
- MemoryStream connectData = new MemoryStream();
- connectData.WriteByte(2);
- // Next send the RSA encrypted seed value.
- AES clientAes = new AES(TestEncryptedServer.aesPrivateKey);
- byte[] encryptedSeed = testServer.rsa.Encrypt(clientAes.Seed);
- connectData.Write(encryptedSeed, 0, encryptedSeed.Length);
- //no RSA: connectData.Write(clientAes.Seed, 0, clientAes.Seed.Length);
- SocketHelper.SendMessageBytes(serverSocket,
- (byte)BasicMessageTypes.Connect,
- connectData.ToArray(), false);
-
- // And finally send the username with the Login message
- SocketHelper.SendMessageBytes(serverSocket,
- (byte)BasicMessageTypes.Login,
- StringHelper.StringToBytes("TestEncryptedClient"), false, clientAes);
-
- // Send out a simple text message
- Console.WriteLine("Sending: Hi from StartServerAndConnect");
- SocketHelper.SendMessageBytes(serverSocket,
- (byte)BasicMessageTypes.TextMessage,
- StringHelper.StringToBytes("Hi from StartServerAndConnect"), false,
- clientAes);
-
- // Send a big message (to test compression)!
- string bigEmptyString = new string(' ', 10000);
- SocketHelper.SendMessageBytes(serverSocket,
- (byte)BasicMessageTypes.TextMessage,
- StringHelper.StringToBytes("Hi Server " + bigEmptyString), true,
- clientAes);
-
- // Wait a bit for server to receive and display data!
- Thread.Sleep(500);
-
- // Always close the tcp server after starting it!
- testServer.Dispose();
- serverSocket.Close();
- Assert.False(serverSocket.Connected);
- }
- #endregion
-
- #region SendBigCompressableMessages (LongRunning)
- /// <summary>
- /// Helper method to test the compression features of the networking
- /// classes, which are especially useful when sending big data messages.
- /// In this test a very big message (10 KB) that is easily compressible
- /// is send out to the server to see if it all arrives nice and dandy.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void SendBigCompressableMessages()
- {
- // Start server (listens in an extra thread, see TcpServer)
- BaseServer server = new TextServer();
-
- // And connect (in this thread)!
- BaseClient client = new TextClient();
-
- // Send a message from the client to the server!
- string bigEmptyString = new string(' ', 10000);
- client.SendTextMessage("Hi Server " + bigEmptyString);
-
- // Wait a bit until the client is connected!
- Thread.Sleep(100);
-
- // Always close the client and server!
- client.Dispose();
- server.Dispose();
- }
- #endregion
-
- #region JustConnectAndDisconnectSockets (LongRunning)
- /// <summary>
- /// Connect and disconnect via sockets. We cannot really send or receive
- /// any useful message data here because we have to go through the whole
- /// connection and login process and without using the Client and Server
- /// classes this is just too much work. See the other unit tests for that.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void JustConnectAndDisconnectSockets()
- {
- TcpListener listener =
- new TcpListener(IPAddress.Any, TextServer.ServerPort);
-
- // Run the server thread, must be in parallel to the client code below
- ThreadHelper.Start(delegate
- {
- listener.Start();
- // Wait for client to connect (see below), this blocks.
- Socket client = listener.AcceptSocket();
- Log.Info("Client connected: " +
- SocketHelper.WriteClientInfo(client));
- BaseClient clientInfo = new TextClient(client, null);
-
- // Time to receive some data (see below again)
- SocketHelper.SetupReceiveCallback(clientInfo.Socket,
- clientInfo.clientBufferData,
- SocketHelper.OnReceivedDataAsyncCallback, clientInfo);
- Thread.Sleep(300);
-
- if (client.Connected)
- {
- Log.Info("Disconnecting client: " +
- SocketHelper.WriteClientInfo(client));
- }
- client.Close();
- listener.Stop();
- });
-
- // On the client side we just connect
- Socket serverSocket = SocketHelper.ConnectTcpSocket(
- TextClient.TestServer, TextServer.ServerPort);
- /*Note: This will produce a warning because we did not correctly connect,
- * use the other unit tests instead!
- // Send out a simple data package
- Log.Info("Sending EmptyMessage");
- SocketHelper.SendMessageBytes(serverSocket, TextServer.EmptyMessageType,
- null, false);
- // And send out something more complex!
- Log.Info("Sending TextMessage: Hi from " +
- "SendAndReceiveMessageData");
- SocketHelper.SendMessageBytes(serverSocket, TextServer.TextMessageType,
- StringHelper.StringToBytes("Hi from SendAndReceiveMessageData"),
- false);
- */
- // Wait a bit for data to arrive
- Thread.Sleep(300);
-
- // And finally disconnect, the thread above should have displayed
- // everything we sent by now.
- serverSocket.Shutdown(SocketShutdown.Both);
- serverSocket.Disconnect(false);
- }
- #endregion
-
- #region ConnectToServer (LongRunning)
- /// <summary>
- /// Connect to server
- /// </summary>
- [Test, Category("LongRunning")]
- public static void ConnectToServer()
- {
- // Start server (listens in an extra thread, see TcpServer)
- BaseServer server = new TestTcpServer();
-
- // And connect (in this thread)!
- BaseClient client = new TextClient();
-
- // Send a message from the client to the server!
- client.SendTextMessage("Hi Server");
-
- // Wait a bit until the client is connected!
- Thread.Sleep(100);
- // And also send a message from the server to all clients
- server.SendTextToAllClients("Hi Minions");
-
- // Wait a bit for server to receive and display data!
- Thread.Sleep(500);
-
- // Always close the tcp server after starting it!
- client.Dispose();
- server.Dispose();
- }
- #endregion
-
- #region ConnectToServerShortDelay (LongRunning)
- /// <summary>
- /// Connect to server with a short delay. This means we are trying to
- /// connect to a server when it is not there yet. However, after a short
- /// while we will have a server and then the client should automatically
- /// connect (well in time for the timeout to still work) :)
- /// </summary>
- [Test, Category("LongRunning")]
- public static void ConnectToServerShortDelay()
- {
- // Connect to the non-existing server (warning might appear)!
- BaseClient client = new TextClient();
-
- // Even send a message from the client to the not-connected server!
- client.SendTextMessage("Hi Server");
-
- // Wait a short while (well in time for the timeout to still work
- // on the first connection attempt above).
- Thread.Sleep(250);
-
- // Now start the server delayed (listens in an extra thread)
- BaseServer server = new TextServer();
-
- // Wait a bit until the client is connected!
- Thread.Sleep(500);
- // And also send a message from the server to all clients
- server.SendTextToAllClients("Hi Minions");
-
- // Wait a bit for server to receive and display data!
- Thread.Sleep(500);
-
- // Always close the tcp server after starting it!
- client.Dispose();
- server.Dispose();
- }
- #endregion
-
- #region ConnectToServerLongDelay (LongRunning)
- /// <summary>
- /// Connect to server long delay (more than the timeout allows us to
- /// wait). This means we are trying to connect to a server when it is
- /// not there yet. However, after a while we will have a server and then
- /// the client should automatically attempt to re-connect.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void ConnectToServerLongDelay()
- {
- // Connect to the non-existing server (warning might appear)!
- BaseClient client = new TextClient();
-
- // Even send a message from the client to the not-connected server!
- client.SendTextMessage("Hi Server");
-
- Console.WriteLine("Waiting 5 seconds without a real connection!");
-
- // Wait a long while (more than the timeout = 750ms allow us).
- // Note: 10000ms is about 1 loop of extra waiting and then connecting.
- Thread.Sleep(5000);
-
- // Now start the server delayed (listens in an extra thread)
- BaseServer server = new TextServer();
-
- // Note: Increase time to 10s to be save (works fine with 5+6s now).
- Console.WriteLine("Server is now on, waiting for another 5 seconds " +
- "for the client to connect (it will try every 1-3 seconds)!");
-
- // Wait a bit until the client is connected, can take up to 10 seconds
- Thread.Sleep(5000);
- // And also send a message from the server to all clients
- server.SendTextToAllClients("Hi Minions");
-
- // Wait a bit for server to receive and display data!
- Thread.Sleep(500);
-
- // Always close the tcp server after starting it!
- client.Dispose();
- server.Dispose();
- }
- #endregion
-
- #region StressTest (LongRunning)
- /// <summary>
- /// Stress test with many packages. Usually tested with 100-1000
- /// iterations. Enable LOG_STUFF to see more details and what happens.
- /// Note: Works also fine with millions of messages send out (averages
- /// to 128 MB for 1 million messages because of 1+random(255) message
- /// length), takes about 10 seconds. For 1-1000 messages it is always
- /// about 3-6ms (not dependant on the number of messages, so this is
- /// just the setup time). So 1 message needs less than 10ns to be send
- /// out and received on average, usually less!
- /// </summary>
- [Test, Category("LongRunning")]
- public static void StressTest()
- {
- // Use 100 clients, which each send out 100 messages that are 1-252 bytes
- // long, which results in about 1.2 MB network data. What is slow here
- // is the logging, so it is disabled for this stress test message type.
- const int NumberOfClients = 100;
- const int NumberOfMessages = 100;
-
- // Send blocks between 0 and 250 bytes
- byte[] dummyData = new byte[250];
- for (int i = 0; i < dummyData.Length; i++)
- {
- dummyData[i] = (byte)i;
- }
-
- // Create the server (will also count how much data arrives there)
- TextServer server = new TextServer();
-
- // And all our clients
- TextClient[] clients = new TextClient[NumberOfClients];
- int totalMessageDataSent = 0;
- for (int num = 0; num < NumberOfClients; num++)
- {
- clients[num] = new TextClient();
- // For each of them send out all messages right away.
- for (int i = 0; i < NumberOfMessages; i++)
- {
- byte[] thisData = dummyData.GetSubArray(0,
- RandomHelper.RandomInt(dummyData.Length));
- clients[num].Send(TextServer.StressTestMessageType,
- new MemoryStream(thisData));
- totalMessageDataSent += thisData.Length;
- }
- } // for
-
- // Wait a bit for data to arrive
- Console.WriteLine("Waiting for all data to arrive");
-
- int numberMessagesToReceive = NumberOfMessages * NumberOfClients;
- Stopwatch watch = new Stopwatch();
- watch.Start();
- while (server.StressTestMessagesReceived < numberMessagesToReceive &&
- watch.Elapsed.TotalSeconds < 5)
- {
- Log.Test("Received=" + server.StressTestMessagesReceived);
- Thread.Sleep(100);
- }
-
- // And finally disconnect the server from all clients
- server.Dispose();
-
- Console.WriteLine("NumberOfMessages * NumberOfClients=" +
- numberMessagesToReceive +
- ", server.StressTestMessagesReceived=" +
- server.StressTestMessagesReceived);
- Console.WriteLine("totalMessageDataSent=" + totalMessageDataSent +
- ", server.StressTestDataReceived=" +
- server.StressTestDataReceived);
-
- // Send and received byte data length must be equal!
- Assert.Equal(totalMessageDataSent, server.StressTestDataReceived);
- }
- #endregion
- }
- }