zoukankan      html  css  js  c++  java
  • .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,即远程对象的代理,我们就可以调用接口中的方法了。

    注意:实际代码中,需要考虑通道的释放等问题。

  • 相关阅读:
    1063. Set Similarity
    A1047. Student List for Course
    A1039. Course List for Student
    最大公约数、素数、分数运算、超长整数计算总结
    A1024. Palindromic Number
    A1023. Have Fun with Numbers
    A1059. Prime Factors
    A1096. Consecutive Factors
    A1078. Hashing
    A1015. Reversible Primes
  • 原文地址:https://www.cnblogs.com/wangqiang3311/p/9373483.html
Copyright © 2011-2022 走看看