.net remoting和wcf自托管——一个bug引发的警示
一、解決問題,需要深入,并從細節入手,多從代碼找原因,不能認為代碼是死的,不會出錯:
之前代碼都運行良好,突然某一天,在我電腦上出問題了。出了問題,那就應該找出原因。其實這個問題,本身并不難,好歹給你報出了個錯:
獲取Word遠程代理服務失敗:無法加載類型“clr:NoteFirst.KMS.Clients.RomoteInterface.IOfficeService, NoteFirst.KMS.Clients.RomoteInterface”。, Server stack trace: 在 System.Runtime.Remoting.Messaging.MethodCall.ResolveMethod(Boolean bThrowIfNotResolved)在 System.Runtime.Remoting.Messaging.MethodCall.HeaderHandler(Header[] h)在 System.Runtime.Serialization.Formatters.Soap.ObjectReader.ParseObject(ParseRecord pr)在 System.Runtime.Serialization.Formatters.Soap.SoapHandler.StartChildren()在 System.Runtime.Serialization.Formatters.Soap.SoapParser.ParseXml()在 System.Runtime.Serialization.Formatters.Soap.SoapParser.Run()在 System.Runtime.Serialization.Formatters.Soap.ObjectReader.Deserialize(HeaderHandler handler, ISerParser serParser)在 System.Runtime.Serialization.Formatters.Soap.SoapFormatter.Deserialize(Stream serializationStream, HeaderHandler handler)在 System.Runtime.Remoting.Channels.CoreChannel.DeserializeSoapRequestMessage(Stream inputStream, Header[] h, Boolean bStrictBinding, TypeFilterLevel securityLevel)在 System.Runtime.Remoting.Channels.SoapServerFormatterSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg,ITransportHeaders& responseHeaders, Stream& responseStream)
net remoting在調用定義的接口時報錯,無法加載類型,這錯誤是個什么樣的錯誤,怎么就不能加載了,之前都好好的。為了解決這個問題,我花了一天多的時間。從系統運行環境,到office重新安裝,折騰了個遍,就差裝系統了。都說出了問題,從內部找原因,可是同事機器上的代碼運行良好,我們的代碼絕對一致。于是,我把目光就聚焦到外部環境上了。不過話說回來,外部環境也是有點問題的,比如安裝了多個版本的office。在安裝和卸載的頻繁操作之下,很難知道注冊表會不會出問題。
到了第二天,我就去改改代碼,試著用另外一種方法解決問題。結果改著改著,就發現了代碼原來是有bug的。前輩的代碼,看似高深,調用了c++的很多方法。
TcpChannel tcpChannel = new TcpChannel(9998); ChannelServices.RegisterChannel(tcpChannel, false); RemotingConfiguration.RegisterWellKnownServiceType(typeof(OfficeServiceImplement), CHANNEL_NAME, WellKnownObjectMode.SingleCall);EventLog.WriteEntry("NoteFirst", "注冊tcp remote服務成功");之前remoting采用的是http通道,我給改成tcp通道,結果問題就解決了。我就想,僅僅是通道不同,就會解決問題嗎,所以想著http通道肯定是可以的。
channel = new HttpServerChannel(CHANNEL_NAME, GetEnablePort(), Provider);RemotingConfiguration.RegisterWellKnownServiceType(typeof(OfficeServiceImplement), OBJECT_URI, WellKnownObjectMode.Singleton);看下GetEnablePort的定義:
private static int GetEnablePort(){Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);int result = 4211;while (true){try{socket.Bind(new IPEndPoint(IPAddress.Any, result));socket.Listen(100);socket.Close();ShareDataRW.OfficeAddinServicesPort = result;break;}catch{++result;}}return result;}動態獲取了端口,并有賦值操作:ShareDataRW.OfficeAddinServicesPort = result;
service = Activator.GetObject(typeof(IOfficeService), string.Format(OfficeService.ServiceUrl, ShareDataRW.OfficeAddinServicesPort)) as IOfficeService;這個是客戶端調用remoting的代碼,看看?ShareDataRW.OfficeAddinServicesPort 端口是怎么獲取的:
public static int OfficeAddinServicesPort{get{return ReadShareDataStruct().OfficeAddinServicesPort;}set{ShareData sd = ReadShareDataStruct();sd.OfficeAddinServicesPort = value;WriteReadShareDataStruct(sd);}}這里又引入了幾個方法:
//將數據從非托管內存塊封送到新分配的指定類型的托管對象private static ShareData ReadShareDataStruct(){return (ShareData)Marshal.PtrToStructure(ShareDataMemoryPoint, ShareDataType);}
//將數據從托管對象封送到非托管內存塊中private static void WriteReadShareDataStruct(ShareData data){Marshal.StructureToPtr(data, ShareDataMemoryPoint, false);}
ShareData是個結構體:
[StructLayout(LayoutKind.Sequential)]private struct ShareData{public int ClientServicesPort;public int OfficeAddinServicesPort;public int WpsAddinServicesPort;public int MainWindowsHandle;} Type ShareDataType = typeof(ShareData); ShareDataMemoryPoint因為牽扯到c++里面的東西,不過從字面上看,共享內存地址,我猜的。看了這么多代碼,我們大致理解,它是通過共享內存實現的端口存放,那為什么服務器端存進去的端口和客戶端取出來的端口就不一樣呢?這是我的疑惑點。為什么之前的代碼就沒有發生過這樣的事情,請不要老提過去好不好,代碼是動態運行的,內存當中的活動也是動態的。有一種可能性,就是發布服務的端口在代碼執行到那句的時候已經定好了,并把它寫到內存中了。等客戶端再去拿的時候,在這之前值被動了手腳。至于誰修改了它,什么時候修改的,這將是一個秘密,等待探尋。
二、WCF實現:
在這漫長的解決問題當中,我無意間看到微軟的建議:把.net remoting遷移到wcf中。微軟給出了具體的遷移步驟,特別詳細,于是我就改寫了代碼,用wcf去實現:
定義協議 [ServiceContract]public interface IOfficeService{[OperationContract]void InsertTo(Bibliography[] bibliographies);[OperationContract]IntPtr GetActiveDocumentWindowHandle();[OperationContract]void Insert(string stream);/// <summary>/// 獲取文檔的初始化時間/// </summary>/// <returns></returns> [OperationContract]DateTime GetDateTimeOfActivedDocument();}
注意:方法不能同名
怎么實現并不重要,想怎么實現就怎么實現,我只管定義接口,這是發布服務,自托管服務:
NetTcpBinding binding = new NetTcpBinding();Uri baseAddress = new Uri("net.tcp://localhost:8099/wcfserver");ServiceHost serviceHost = new ServiceHost(typeof(OfficeServiceImplement), baseAddress);serviceHost.AddServiceEndpoint(typeof(IOfficeService), binding, baseAddress);serviceHost.Open();EventLog.WriteEntry("NoteFirst", string.Format("The WCF server is ready at {0}", baseAddress));再來看看客戶端的調用:
NetTcpBinding binding = new NetTcpBinding();String url = "net.tcp://localhost:8099/wcfserver";EndpointAddress address = new EndpointAddress(url);ChannelFactory<IOfficeService> channelFactory = new ChannelFactory<IOfficeService>(binding, address);service = channelFactory.CreateChannel();拿到service,即遠程對象的代理,我們就可以調用接口中的方法了。
注意:實際代碼中,需要考慮通道的釋放等問題。
?
轉載于:https://www.cnblogs.com/wangqiang3311/p/9373483.html
總結
以上是生活随笔為你收集整理的.net remoting和wcf自托管——一个bug引发的警示的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python3 - Dockerfile
- 下一篇: Pointnet网络结构与代码解读