C/S架構的應用程序,將一些複雜的計算邏輯由客戶端轉移到服務器端能夠改善性能,同時也爲了其它方面的控制。.NET Remoting在局域網內調用的性能至關不錯。ERP系統中基於.NET Remoting和WCF構建一個應用程序服務器(Application Server)。html
分佈式應用設計目標:數據庫
1 客戶端的鏈接,服務器要能控制。服務器根據受權許可文件的內容,控制客戶端併發數。設計模式
2 服務器崩潰,客戶端要獲得通知,掛起當前數據輸入操做,當服務器可用時,客戶端可自動從新鏈接 。安全
3 支持數據加密,對敏感的數據可用加密的端口和通道傳輸。性能優化
4 支持數據壓縮,改善數據傳輸效率,由於要作一個壓縮與解壓縮動做,性能有所下降。服務器
5 安全控制,應用程序服務器阻止未受權的或未簽名的應用程序的鏈接。session
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特性即實現服務類的部署。
客戶端向服務器傳送大文件性能:
爲了改善性能,對於文件傳輸類服務,單獨開放一個端口用於文件傳輸。