设计帮助一个网络应用程序?
注意
这是一个相当大的问题,所以请原谅我,我提前道歉,如果这还不清楚。为了使这个问题的管理,并最大限度地减少混乱,我省略复制和粘贴类的某些属性。
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.
背景
我正在写一个网络应用程序 - 一个远程桌面之类的应用,使普通技术人员书呆子能帮助他的朋友或邻居计算机的问题。我知道自由和更先进的软件一样的TeamViewer存在,但这个问题不讨论创建这个软件的实用性。
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.
常用术语
在客户端的是技术人员的书呆子,助手 - 中的控制器的。在服务器的是'受害者',一遇险。所述的客户端的是,通常一个启动命令到服务器
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.
信息
本软件是多了实时显示/控制应用。我想额外的的模块的,比如文件浏览器模块的和的聊天模块的(这样他们就可以不需要使用这两种沟通额外的即时通讯软件的)。
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).
本来,我的方法来发送和UDP信息接收消息是手动的,效率低下。 (我用的是Lidgren库UDP网络所以这就是为什么我的包没有表现出低层次的字节像头邮件大小。的)
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)
该方法变得很难总结我的头,一旦我把头100+消息。源变得神秘和修改是一件苦差事。即使通过编码的目标模块和命令作为一个枚举(还是间接的号码),但仍难以开发应用程序。当接收到这些数据包,分析命令的具体参数将成为一个可笑的长开关梯嵌套在一个长的离谱的if-else阶梯内。
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
问题
但这个问题是设计中的一个。我没有问题,序列化和反序列化我的班。问题在于我怎么设计我的命令/包类。我发现自己坚持做this.我的课的设计是如下:
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:
包基类
/// <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中列举的每次的命令的所有的模块,将是一个很长的枚举和pretty的杂乱无章。这就是为什么我有一个的不同的的'ModuleCommand枚举每个模块。
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.
连接包
/// <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(优先级排序,还记得在数据包类有一个'PacketPriority属性)。当余出队这个分组时,它被出列作为一个的包的,而不是一个的 ClientRequestingConnectionPacket 的。在这种情况下,因为在那里孤单只有一个数据包,我明明知道数据包的原始类型的 ClientRequestingConnectionPacket 的,但是当我存储数百个数据包在排队,我没有任何办法才知道,原来特定类型的转换回。
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.
因此,基于该链接的上述其他的StackOverflow问题,我会做这样的错误?如何解决呢?什么是术语,这个叫什么?我应该如何设计我的应用程序?
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?
修订的问题:什么是适当的设计模式,数据包班
您可以使用的GetType
作为FishBasketGordo建议,或者您也可以使用测试类型是
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)
....