一个网络应用程序的设计帮助?

5

注意

这是一个相当大的问题,请耐心阅读,如果不清楚请提前谅解。为了使这个问题更易管理和减少混淆,我省略了一些复制粘贴的类中的属性。

背景

我正在编写一个网络应用程序 - 一种“远程桌面”应用程序,以便普通技术人员可以帮助他的朋友或邻居解决计算机问题。我知道存在免费且更先进的软件,如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类的适当设计模式是什么?


这听起来像是工厂模式。工厂模式可以让你在运行时指定对象的类型。 - Robert Harvey
也许考虑使用组合(并公开暴露所组成的类型)而不是继承。 - user166390
请考虑编辑您的问题。它过于冗长,大部分信息都不相关。标签“网络编程”似乎与您的问题完全无关。 - Kirk Broadhurst
为什么不使用protobuf或类似的序列化方式?或者使用现有的RPC实现? - jgauffin
因为序列化不是真正的问题。使用YAXSerializer可以完成工作。我认为我已经简要地探索了protobuf。它看起来很有前途,但是这是不必要的。 - Jason
2个回答

1
当你反序列化你的对象时,当你调用GetType时返回什么?如果我没有犯错,它将是实际的、特定的类型,而不是一般的基础类型。这样你就需要添加一个额外的属性来知道实际的类型。

1
你可以像FishBasketGordo建议的那样使用`GetType`,或者你可以使用`is`测试类型。
if (packet is ClientRequestingConnectionPacket)
{ /* do something */ }
else if (packet is SomeOtherPacket)
....

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接