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

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

  • 相关阅读:
    今天18:40分左右一部价值500多块捷安特自行车被盗!
    利用ASP.net上传文件
    _desktop.ini
    Visual Studio .NET 设置移植工具
    审计厅的项目终于可以告一段落了
    Word2CHM Assistant(Word2CHM助手)V2.1.0 破解版
    最近比较烦!
    delphi 中 Format 用法总汇
    谈谈公司管理及需求方面的问题
    [待续]SQLSERVER无法访问远程服务器问题
  • 原文地址:https://www.cnblogs.com/wangqiang3311/p/9373483.html
Copyright © 2020-2023  润新知