注意
这是一个相当大的问题,请耐心阅读,如果不清楚请提前谅解。为了使这个问题更易管理和减少混淆,我省略了一些复制粘贴的类中的属性。
背景
我正在编写一个网络应用程序 - 一种“远程桌面”应用程序,以便普通技术人员可以帮助他的朋友或邻居解决计算机问题。我知道存在免费且更先进的软件,如TeamViewer,但本问题并不讨论创建此软件的实用性。
常见术语
客户端是技术人员,帮助者 - 控制器。服务器是“受害者”,处于困境中的人。通常,客户端是向服务器发出命令的人。
信息
该软件不仅是一个实时查看/控制应用程序。我想要额外的模块,例如文件浏览器模块和聊天模块(因此他们可以在不需要使用额外即时通讯软件的情况下进行通信)。
最初,我使用手动和低效的方法发送和接收UDP消息。(我使用Lidgren库进行UDP网络传输,因此我的数据包不显示像头部消息大小这样的低级字节。)
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)
当我收到100多个消息时,这种方法变得难以理解。源代码变得晦涩难懂,修改也变得很繁琐。即使将“目标模块”和“命令”编码为枚举(仍然是间接的数字),开发应用程序仍然很困难。当接收这些数据包时,解析特定于命令的参数会成为一条非常长的switch嵌套在一条非常长的if-else中。
后来,我决定为我的数据包结构使用序列化。现在,我可以将每个命令设计为一个类,然后将其序列化为紧凑的字节数组(或字符串),然后将其反序列化回其原始类并读取类属性作为参数。这似乎更容易,而我愿意为更大的序列化数据结构支付带宽成本。
Revised Packet Structure:
Byte Index Type Purpose/Description
------------------------------------------------------
0-X String Serialized 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; }
}
欢迎模块数据包基础(有关此内容的详细信息如下)
/// <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,
}
'WelcomeModulePacket'类的解释:
由于我有超过两个模块,所以一个枚举类型“CommandModule”枚举所有模块的每个命令,将是一个非常冗长和杂乱无章的做法。这就是为什么我为每个模块都有一个不同的“ModuleCommand”枚举类型的原因。
连接数据包
/// <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; }
}
因此,您可以看到我的类不仅具有两层继承,而是三层。
ClientRequestingConnectionPacket : WelcomeModulePacket : Packet
这种设计的问题显而易见,当我将特定包如“ClientRequestingConnectionPacket”存储到较不具体的数据结构中时,就会出现问题。例如,将“ClientRequestingConnectionPacket”加入通用PriorityQueue(用于优先级排序,记得“Packet”类有一个“PacketPriority”属性)。当我将该包出队时,它将被出队为Packet,而不是ClientRequestingConnectionPacket。在这种情况下,由于只有一个数据包,我知道其原始类型为ClientRequestingConnectionPacket,但当我在队列中存储数百个数据包时,我没有任何方法知道要将其转换回原始特定类型。
因此,我添加了一些奇怪的内容,例如添加以下属性:
Type UltimateType { get; set; }
我觉得将“Packet”基类作为模板似乎有些勉强和原始,我不认为这是一个真正的解决方案。
那么,基于上面链接到的另一个StackOverflow问题,我是否犯了这个错误?我该如何解决它?这种情况应该被称为什么?我该如何设计我的应用程序?
修订后的问题:Packet类的适当设计模式是什么?