转载

解析大型.NET ERP系统 分布式应用模式设计与实现

C/S架构的应用程序,将一些复杂的计算逻辑由客户端转移到服务器端可以改善性能,同时也为了其它方面的控制。.NET Remoting在局域网内调用的性能相当不错。ERP系统中基于.NET Remoting和WCF构建一个应用程序服务器(Application Server)。

分布式应用设计目标:

1  客户端的连接,服务器要能控制。服务器根据授权许可文件的内容,控制客户端并发数。

2  服务器崩溃,客户端要得到通知,挂起当前数据输入操作,当服务器可用时,客户端可自动重新连接 。

3  支持数据加密,对敏感的数据可用加密的端口和通道传输。

4  支持数据压缩,改善数据传输效率,因为要做一个压缩与解压缩动作,性能有所降低。

5 安全控制,应用程序服务器阻止未授权的或未签名的应用程序的连接。

6 客户端向服务器传送大文件,传送图片需要时性能优化

7 服务器端发现错误时,支持堆栈传回客户端以诊断原因。

8 开发和部署简单方便。

先设计服务器与客户端通信的接口,一个简单销售合同数据表的访问接口代码如下所示。

public interface ISalesContractManager {  SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo);  SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo, IPrefetchPath2 prefetchPath);  SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList);  EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket);  EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket, ISortExpression sortExpression);  EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath);  EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList);  SalesContractEntity SaveSalesContract(Guid sessionId, SalesContractEntity salesContractEntity);  SalesContractEntity SaveSalesContract(Guid sessionId, SalesContractEntity salesContractEntity, EntityCollection entitiesToDelete);  SalesContractEntity SaveSalesContract(Guid sessionId, SalesContractEntity salesContractEntity, EntityCollection entitiesToDelete, string seriesCode);  void DeleteSalesContract(Guid sessionId, SalesContractEntity salesContractEntity);  bool IsSalesContractExist(Guid sessionId, String ContractNo);  bool IsSalesContractExist(Guid sessionId, IRelationPredicateBucket filterBucket);  int GetSalesContractCount(Guid sessionId, IRelationPredicateBucket filterBucket);  SalesContractEntity CloneSalesContract(Guid sessionId, String ContractNo);  void PostSalesContract(Guid sessionId, String ContractNo);  void PostSalesContract(Guid sessionId, SalesContractEntity salesContractEntity); } 

再设计服务实现SalesContractManager,实现上面的接口。

[CommunicationService("SalesContractManager")] public class SalesContractManager : ManagerBase, ISalesContractManager {  public SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo)  {   return GetSalesContract(sessionId, ContractNo, null);  }  public SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo, IPrefetchPath2 prefetchPath)  {   return GetSalesContract(sessionId, ContractNo, prefetchPath, null);  } 

注意到上面给上面的实现类添加了CommunicationService特性,也就是声明实现类是一个服务。

先来回顾一下最简单的.NET Remoting 客户端与服务器端代码设计模式。

服务器端的设计:

int port = Convert.ToInt32(ConfigurationManager.AppSettings["Port"]); BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider(); provider.TypeFilterLevel = TypeFilterLevel.Full; IDictionary props = new Hashtable(); props["port"] = port; TcpChannel channel = new TcpChannel(props, null, provider); ChannelServices.RegisterChannel(channel, false); RemotingConfiguration.RegisterWellKnownServiceType(typeof(ERP.BusinessLogic.SalesContractManager), "RemotingService", WellKnownObjectMode.SingleCall);

这里是用代码写死服务类,可以用配置文件增加服务类,也可用以反射的方法增加服务类。

客户端调用的代码如下:

ISalesContractManager  salesContractManager =(IPdmServer)Activator.GetObject(typeof(ISalesContractManager),string.Format("{0}RemotingService", ApplicationServerUrl)); if (salesContractManager == null)         throw new AppException("Sever configuration error"); salesContractManager.SaveSalesContract(guid, salesContract);

改善服务器端代码,让服务器主动搜索系统中打上CommunicationService特性的服务类。当新增加服务类型时,框架可自动识别并加载服务类型:

Assembly assembly = typeof(Manager).Assembly; Type[] types = assembly.GetTypes(); foreach (Type type in types) {      if (type.Namespace == "ERP.BusinessLogic.Managers")    {      string serviceName = string.Empty;      object[] attributes = type.GetCustomAttributes(typeof(CommunicationService), true);       if (attributes.Length > 0)          serviceName = type.Name;  if (!string.IsNullOrEmpty(serviceName))        {         if (clientActivatedServices.Contains(serviceName))         {          RemotingConfiguration.RegisterActivatedServiceType(type);         }         else if (singletonServices.Contains(serviceName))         {          RemotingConfiguration.RegisterWellKnownServiceType(type, serviceName + ".rem", WellKnownObjectMode.Singleton);         }         else         {            RemotingConfiguration.RegisterWellKnownServiceType(type, serviceName + ".rem", WellKnownObjectMode.SingleCall);         }        } 

这样节省了开发人员的服务发布时间。客户端主要方法如下:

instance = ReflectionHelper.CreateObjectInstance<T>(type);

.NET Remoting可识别当前服务类型是否注册过,如果有则会创建一个远程代理,实现向服务器发送请求。

控制客户端并发数:

.NET Remoting支持单件调用模式,客户端不论调用次数,服务器端都只会是相同的一份对象,这样可实现会话Session控制。ERP系统用户登入时,检查服务器登入会话表(Session,本质上是一个DataTable),判断是否已经登入。同时也可以实现并发用户控制,当登入的用户数超过授权许可规定的用户数,可阻止登入。

服务器崩溃,客户端要得到通知,挂起当前数据输入操作,当服务器可用时,客户端可自动重新连接:

.NET Remoting支持客户端服务器订阅模式,服务器端可向客户端发送消息。当服务器进程崩溃,或是无法连接到数据库等原因发生时,需要及时向订阅过的客户端发送消息通知,客户端界面收到通知后需要立即挂起ERP主界面,不允许任何操作。这样可避免用户辛苦的输入数据后,点击保存却连接不上服务器,只好关闭重新输入。

安全控制,应用程序服务器阻止未授权的或未签名的应用程序的连接:

服务器控制客户端的连接调用,在客户端登入时,需要传入当前客户端程序集的版本,签名标识Token,还有系统参数等,服务器端会将这些参数整合在一起,用MD5计算出一个哈希值。只有客户端传入的参数值经过MD5运算后,与服务器中这些相同的参数值MD5运算之后的值,完全相同。服务器端才允许客户端继续登入。

服务器端发现错误时,支持堆栈传回客户端以诊断原因:

上面创建服务器端的代码中,有以下两句是为了实现服务器端堆栈回传到客户端的,参考下面的代码:

BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider(); provider.TypeFilterLevel = TypeFilterLevel.Full;

支持数据加密和数据压缩:

使用自定义的GTCP信道(Channel)通信,此通道支持加密传输和数据压缩传输。

开发和部署简单方便:

Code Smith 6.5的模板会帮助生成ISalesContractManager和SalesContractManager两个类型的源代码,通过上面的讲解知道,只需要给SalesContractManager加上CommunicationService特性即实现服务类的部署。

客户端向服务器传送大文件性能:

为了改善性能,对于文件传输类服务,单独开放一个端口用于文件传输。

正文到此结束
Loading...