Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design help for a networked application?

NOTICE

This is a rather large question, so please bear with me and I apologize in advance if this is unclear. For the sake of making this question manageable, and to minimize confusion, I omit some properties from copy-and-pasted classes.

CONTEXT

I'm writing a networked application - a 'remote desktop' sort of application so that the average techie nerd can help his friends or neighbors with computer issues. I realize free and much more advanced software like TeamViewer exists, but this question isn't discussing the practicality of creating this software.

COMMON TERMS

The client is the techie nerd, the helper - the controller. The server is the 'victim', the one in distress. The client is usually the one to initiate commands to the server.

INFORMATION

This software is more than a live view/control application. I wanted extra modules, such as a File Explorer Module, and a Chat Module (so they could both communicate without needing to use extra instant messaging software).

Originally, my approach to sending and receiving messages across UDP was manual and inefficient. (I use the Lidgren library for UDP networking so that's why my packets don't show low-level bytes like a header message size.)

Original Packet Structure:

Byte Index    Type      Purpose/Description
------------------------------------------------------
0             byte      The intended destination module (e.g. 1 -> Chat module)
1             byte      The command for this module to perform (e.g. 0 -> NewChatMessage)
2             byte      Encryption/compression flag
3             byte      Packet priority flag (e.g. Cancel packets should be processed first)
4-X            ?        Command-specific arguments of variable type (e.g. For this example, the argument would be the actual chat message text)

This approach became difficult to wrap my head once I head 100+ messages. The source became cryptic and modifications were a chore. Even by coding the 'destination module' and 'command' as an enum (still indirectly numbers), it was still difficult to develop the application. When receiving these packets, parsing the command-specific arguments would become a ridiculously long switch ladder nested inside a ridiculously long if-else ladder.

I later decided to employ serialization for my packet structure. I could now design each command as a class, and then serialize it into a compact byte array (or string), and then de-serialize it back into its original class and read the arguments as class properties. It seemed much easier, and I was willing to pay the cost of bandwidth for the larger serialized data structure.

Revised Packet Structure:

Byte Index    Type      Purpose/Description
------------------------------------------------------
0-X           String    Serialized class

THE PROBLEM

But this question is one of design. I have no problem serializing and de-serializing my classes. The problem lies in how I design my command/packet classes. I find myself stuck doing this. The designs of my classes are below:

Packet Base Class

/// <summary>
/// The fundamental unit of network communication which can be transmitted and received from client to server. Contains the destination module and module-specific command.
/// </summary>
public class Packet
{
    /// <summary>
    /// Specifies the module this packet is forwarded to.
    /// </summary>
    public Module DestinationModule { get; set; }

    /// <summary>
    /// Specifies whether this packet is encrypted or compressed.
    /// </summary>
    public EncryptionCompressionFlag EncryptedOrCompressed { get; set; }

    /// <summary>
    /// Specifies the packet's priority.
    /// </summary>
    public PacketPriority Priority { get; set; }
}

Welcome Module Packet Base (more about this below)

/// <summary>
/// Base packet structure for packets specific to this module. Adds a ModuleCommand property from the base Packet class.
/// </summary>
public class WelcomeModulePacket : Packet
{
    /// <summary>
    /// Specifies the command number this particular packet is instructing the module to execute.
    /// </summary>
    public ModuleCommand Command { get; set; }
}

/// <summary>
/// Specifies the commands for this module.
/// </summary>
public enum ModuleCommand : byte
{
    /// <summary>
    /// The user has just clicked the Connect button (on form's GUI) and is sending this connect packet.
    /// </summary>
    ClientRequestingConnectionPacket = 0,

}

Explanation for a 'WelcomeModulePacket' class:

So since I have more than two modules, having a single enumeration 'CommandModule' enumerating every command for all modules, would be a really long enumeration and pretty disorganized. That's why I have a different 'ModuleCommand' enumeration for each module.

Connection Packet

/// <summary>
/// The user has just clicked "Connect" on the Welcome form of the client. The client is now requesting a connection to the server.
/// </summary>
public class ClientRequestingConnectionPacket : WelcomeModulePacket
{
    public string Username { get; set; }
    public string Password { get; set; }
}

So, you can see how my classes don't only have two layers of inheritance, but three.

ClientRequestingConnectionPacket : WelcomeModulePacket : Packet

The issue with this design is apparent when I store specific packets like 'ClientRequestingConnectionPacket' into a less specific data structure. For example, enqueuing 'ClientRequestingConnectionPacket' into a generic PriorityQueue (for priority sorting, remember how the 'Packet' class has a 'PacketPriority' property). When I dequeue this packet, it is dequeued as a Packet, not a ClientRequestingConnectionPacket. In this case, since theres only one packet in there, I obviously know that the packet's original type is ClientRequestingConnectionPacket, but when I'm storing hundreds of packets in the queue, I don't have any way to know the original specific type to cast it back.

So I made weird additions like adding this property:

Type UltimateType { get; set; }

to the 'Packet' base class. But I mean, this 'solution' seems really forced and primitive, and I don't believe its a real solution.

So, based on that link to the other StackOverflow question above, am I making that mistake? How do I fix it? What's the term for this called? How should I design my application?

REVISED QUESTION: What's the appropriate design pattern for the Packet classes?

like image 363
Jason Avatar asked Nov 05 '22 19:11

Jason


1 Answers

You can use GetType as FishBasketGordo suggests, or you can test the type using is

if (packet is ClientRequestingConnectionPacket)
{ /* do something */ }
else if (packet is SomeOtherPacket)
....
like image 154
Kirk Broadhurst Avatar answered Nov 09 '22 05:11

Kirk Broadhurst