I'm trying to handle network messages in a dynamic, and effective way, though I feel I've overthought it again. As of now, I have a very structured approach, but it feels as if it's a lot of overhead for such a simple process.
The other issue, is that if I start making more types of Login
messages, then I will have many small classes floating around. Is this an appropriate design? Should I allow micro-classes here?
This code stems from the code mentioned over on this other question. Though, I am working on a different portion of it now. That said, most (if not all) of the suggestions from that question have been implemented in some manner.
I have rewritten the entire NetworkServer
as follows:
public class NetworkServer
{
private readonly NetServer _MainNetServer;
private readonly HashSet<NetConnection> _ConnectedClients = new HashSet<NetConnection>();
private readonly ILogger _Logger;
private readonly DataMessageHandler _DataMessageHandler;
public NetworkServer(NetPeerConfiguration serverConfiguration, ILogger logger)
{
_Logger = logger;
_Logger.LogInformation("Initializing server...");
_MainNetServer = new NetServer(serverConfiguration);
_DataMessageHandler = new DataMessageHandler(_Logger);
}
public Task RunServer()
{
return Task.Run(() =>
{
_MainNetServer.Start();
_Logger.LogImportant("Server starting on socket {0}...", _MainNetServer.Socket.LocalEndPoint);
DateTime started = DateTime.UtcNow;
//_Logger.LogImportant(_Logger.FormatMessage("The server was started on {0} at {1}.", started.ToString("dd-MM-yyyy"), started.ToString("HH:mm:ss.fffffff")));
_Logger.LogImportant("The server was started at: {0}", started.ToString("O"));
while (true)
{
NetIncomingMessage msg;
while ((msg = _MainNetServer.ReadMessage()) != null)
{
switch (msg.MessageType)
{
case NetIncomingMessageType.VerboseDebugMessage:
case NetIncomingMessageType.DebugMessage:
case NetIncomingMessageType.WarningMessage:
case NetIncomingMessageType.ErrorMessage:
ProcessErrorMessage(msg);
break;
case NetIncomingMessageType.StatusChanged:
ProcessStatusChangeMessage(msg.SenderConnection);
break;
case NetIncomingMessageType.Data:
ProcessDataMessage(msg);
break;
default:
_Logger.LogWarning("Unhandled type: {0}", msg.MessageType);
break;
}
_MainNetServer.Recycle(msg);
}
System.Threading.Thread.Sleep(1);
}
});
}
private void ProcessDataMessage(NetIncomingMessage message)
{
_Logger.LogInformation("Data recieved from: {0}, payload size: {1} bytes", message.SenderConnection.RemoteEndPoint, message.LengthBytes);
IDataMessage messageResult = _DataMessageHandler.HandleMessage(message);
switch (messageResult.Type)
{
case DataMessageType.Login:
// Do login-ey things
string username = ((LoginMessage)messageResult).Username;
byte[] password = ((LoginMessage)messageResult).Password;
break;
}
}
private void ProcessErrorMessage(NetIncomingMessage message)
{
_Logger.LogError("Network error: ", message.ReadString());
}
private void AddClient(NetConnection client)
{
if (_ConnectedClients.Add(client))
_Logger.LogInformation("New client discovered: {0}", client.RemoteEndPoint);
else
_Logger.LogError("Duplicate client discovered: {0}", client.RemoteEndPoint);
}
private void RemoveClient(NetConnection client)
{
if (_ConnectedClients.Contains(client))
_ConnectedClients.Remove(client);
_Logger.LogInformation("Client disconnected: {0}", client.RemoteEndPoint);
}
private void ProcessStatusChangeMessage(NetConnection connection)
{
switch (connection.Status)
{
case NetConnectionStatus.Disconnected:
RemoveClient(connection);
break;
case NetConnectionStatus.Connected:
AddClient(connection);
break;
default:
_Logger.LogWarning("Unhandled StatusChanged: {0} now {1}", connection.RemoteEndPoint, connection.Status);
break;
}
}
}
Then I added an interface
called IDataMessage
, which represents a message that contains some data:
public interface IDataMessage
{
ILogger Logger { get; }
DataMessageType Type { get; }
void WriteNetworkMessage(NetOutgoingMessage message);
void ReadNetworkMessage(NetIncomingMessage message);
}
I have a LoginMessage
which represents a username/password combination:
public class LoginMessage : IDataMessage
{
private readonly ILogger _Logger;
public ILogger Logger { get { return _Logger; } }
public string Username { get; private set; }
public byte[] Password { get; private set; }
public DataMessageType Type { get { return DataMessageType.Login; } }
public LoginMessage(ILogger logger, string username, byte[] password)
: this(logger)
{
Username = username;
Password = password;
}
public LoginMessage(ILogger logger)
{
_Logger = logger;
}
public void WriteNetworkMessage(NetOutgoingMessage message)
{
message.Write((int)Type);
message.Write(Username);
message.Write((byte)Password.Length);
message.Write(Password);
}
public void ReadNetworkMessage(NetIncomingMessage message)
{
Username = message.ReadString();
byte passLength = message.ReadByte();
Password = message.ReadBytes(passLength);
}
}
The DataMessageType
enum
:
public enum DataMessageType
{
Login,
}
And lastly, the DataMessageHandler
class which builds the appropriate IDataMessage
:
public class DataMessageHandler
{
private readonly ILogger _Logger;
public ILogger Logger { get { return _Logger; } }
public DataMessageHandler(ILogger logger)
{
_Logger = logger;
}
public IDataMessage HandleMessage(NetIncomingMessage message)
{
DataMessageType type = (DataMessageType)message.ReadInt32();
IDataMessage dataMessage = null;
switch (type)
{
case DataMessageType.Login:
dataMessage = new LoginMessage(_Logger, message);
break;
default:
_Logger.LogWarning("Unhandled DataMessageType: {0}", type);
break;
}
return dataMessage;
}
}
Then, on the client, it's written as so:
NetOutgoingMessage sendMsg = client.CreateMessage();
using (SHA256 sha = SHA256.Create()) // SHA256 is by default a 32-byte hash.
{
LoginMessage loginMessage = new LoginMessage(mainLogger, "EBrown", sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes("Password")));
loginMessage.WriteNetworkMessage(sendMsg);
}
client.SendMessage(sendMsg, NetDeliveryMethod.ReliableOrdered);
Now, for every message I add, I have to do several things:
- Add to the
DataMessageType
enumeration. - Build a class that can inherit and implement
IDataMessage
. - Add the appropriate code in
DataMessageHandler
to build and return the correctIDataMessage
. - Update
private void ProcessDataMessage(NetIncomingMessage message)
for the new type.
As implied by the site title, I am looking for any and all suggestions.
DataMessageType
smells funny to me, but I can't see the whole thing on my mobile. \$\endgroup\$