网络应用程序的设计帮助

本文关键字:帮助 应用程序 网络 | 更新日期: 2023-09-27 18:00:39

注意

这是一个相当大的问题,所以请耐心等待,如果不清楚,我提前道歉。为了使这个问题易于管理,并尽量减少混淆,我从复制粘贴类中省略了一些属性。

上下文

我正在写一个网络应用程序——一种"远程桌面"应用程序,这样普通的技术书呆子就可以帮助他的朋友或邻居解决电脑问题。我意识到像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多条信息,这种方法就很难让我清醒过来。来源变得晦涩难懂,修改是件烦人的事。即使将"目标模块"answers"命令"编码为枚举(仍然是间接数字),开发应用程序仍然很困难。当接收到这些数据包时,解析特定于命令的参数将变成一个长得离谱的开关梯形图,嵌套在一个长到离谱的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"属性)。当我将此数据包出列时,它将作为数据包而非ClientRequestingConnectionPacket出列。在这种情况下,由于其中只有一个数据包,我显然知道数据包的原始类型是ClientRequestingConnectionPacket,但当我在队列中存储数百个数据包时,我无法知道原始的特定类型来将其抛出。

所以我做了一些奇怪的添加,比如添加这个属性:

Type UltimateType { get; set; }

到"Packet"基类。但我的意思是,这个"解决方案"似乎真的是被迫的和原始的,我不相信这是一个真正的解决方案。

那么,根据上面另一个StackOverflow问题的链接,我犯了那个错误吗?我该如何修复它?这个词叫什么?我应该如何设计我的应用程序?

修订问题:包类的合适设计模式是什么

网络应用程序的设计帮助

您可以按照FishBasketGordo的建议使用GetType,也可以使用is 测试类型

if (packet is ClientRequestingConnectionPacket)
{ /* do something */ }
else if (packet is SomeOtherPacket)
....

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