zoukankan      html  css  js  c++  java
  • wcf精通1-15

    十五天精通WCF——第一天 三种Binding让你KO80%的业务

     

      

      转眼wcf技术已经出现很多年了,也在.net界混的风生水起,同时.net也是一个高度封装的框架,作为在wcf食物链最顶端的我们所能做的任务已经简单的不能再简单了,

    再简单的话马路上的大妈也能写wcf了,好了,wcf最基本的概念我们放在后面慢慢分析,下面我们来看看神奇的3个binding如何KO我们实际场景中的80%的业务场景。

    一:basicHttpBinding

      作为入门第一篇,也就不深入谈谈basic中的信道栈中那些啥东西了,你只需要知道有ABC三个要素,注意不是姨妈巾哦,如果需要详细了解,可以观赏我以前的系列。在

    这里我就不多说了,太简单的东西没意思,先看个例子简单感受了,你只需知道的是basic走的是http协议就好了,传输消息为soap。

    1. 契约

    复制代码
     1 using System.Runtime.Serialization;
     2 using System.ServiceModel;
     3 
     4 namespace MyService
     5 {
     6     [ServiceContract]
     7     public interface IHomeService
     8     {
     9         [OperationContract]
    10         int GetLength(string name);
    11     }
    12 }
    复制代码

    2. 实现类

    复制代码
     1 using System;
     2 using System.Messaging;
     3 using System.Threading;
     4 
     5 namespace MyService
     6 {
     7     public class HomeService : IHomeService
     8     {
     9         public int GetLength(string name)
    10         {
    11             return name.Length;
    12         }
    13     }
    14 }
    复制代码

    3. 服务启动

    复制代码
     1 using System;
     2 using System.ServiceModel;
     3 
     4 namespace MyService
     5 {
     6     class Program
     7     {
     8         static void Main(string[] args)
     9         {
    10             using (ServiceHost host = new ServiceHost(typeof(HomeService)))
    11             {
    12                 try
    13                 {
    14                     host.Open();
    15 
    16                     Console.WriteLine("服务开启!");
    17 
    18                     Console.Read();
    19                 }
    20                 catch (Exception e)
    21                 {
    22                     Console.WriteLine(e.Message);
    23                 }
    24             }
    25         }
    26     }
    27 }
    复制代码

    4. 配置config文件

    复制代码
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.serviceModel>
        <bindings>
          <netTcpBinding>
            <binding name="IHomeServiceBinding" />
          </netTcpBinding>
        </bindings>
    
        <behaviors>
          <serviceBehaviors>
            <behavior name="">
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="true" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
    
        <services>
          <service name="MyService.HomeService">
            <endpoint address="http://127.0.0.1:1920/HomeService" binding="basicHttpBinding" contract="MyService.IHomeService">
              <identity>
                <dns value="localhost" />
              </identity>
            </endpoint>
    
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
            <host>
              <baseAddresses>
                <add baseAddress="http://127.0.0.1:1920"/>
              </baseAddresses>
            </host>
          </service>
        </services>
    
      </system.serviceModel>
    </configuration>
    复制代码

    5. 然后通过 servicehost 启动服务端

    复制代码
    using System;
    using System.ServiceModel;
    
    namespace MyService
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (ServiceHost host = new ServiceHost(typeof(HomeService)))
                {
                    try
                    {
                        host.Open();
    
                        Console.WriteLine("服务开启!");
    
                        Console.Read();
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.Message);
                    }
                }
            }
        }
    }
    复制代码

    好了,到现在为止,服务端全部开启完毕,接下来我们通过“添加服务引用”,来添加对客户端的引用

    复制代码
     1 using System;
     2 
     3 namespace ConsoleApplication1
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9             HomeServiceReference.HomeServiceClient client = new HomeServiceReference.HomeServiceClient();
    10 
    11             var s = client.GetLength("12345");
    12 
    13             Console.WriteLine("长度为:{0}", s);
    14 
    15             Console.Read();
    16         }
    17     }
    18 }
    复制代码

    麻蛋,就这么简单,是的,就这样简单的五步,基于http的通信就这样被不小心的完成了,真不好意思。

    二:netTcpBinding

      有了basic的代码,现在我们要改成tcp通信,这会通信走的是字节流,很简单,改一下服务端的config文件就好了,大家也知道这种性能要比basic好。

    复制代码
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.serviceModel>
        <behaviors>
          <serviceBehaviors>
            <behavior name="mxbehavior">
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="true" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
    
        <services>
          <service name="MyService.HomeService" behaviorConfiguration="mxbehavior">
            <endpoint address="net.tcp://localhost:19200/HomeService" binding="netTcpBinding" contract="MyService.IHomeService">
              <identity>
                <dns value="localhost" />
              </identity>
            </endpoint>
    
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost:1920/HomeService"/>
              </baseAddresses>
            </host>
          </service>
        </services>
    
      </system.serviceModel>
    </configuration>
    复制代码

    三:netMsmqBinding

      msmq这个玩意,我想大家都清楚,一个物理上的文件,好处呢,你也明白,就是client和service的所有通信都要经过它的手,这样任何一方出了问题,只要

    它在就没问题了。同样我们把tcp改成msmq也是非常简单的,不过要注意,msmqbinding中是不可以让契约方法有返回值的。所以我们加上isoneway就好了。

    复制代码
    using System.Runtime.Serialization;
    using System.ServiceModel;
    
    namespace MyService
    {
        [ServiceContract]
        public interface IHomeService
        {
            [OperationContract(IsOneWay = true)]
            void GetLength(string name);
        }
    }
    复制代码

    然后我在mmc上新建一个消息队列,如下:

    然后我们再改动以下配置文件

    复制代码
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.serviceModel>
        <behaviors>
          <serviceBehaviors>
            <behavior name="mxbehavior">
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="true" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <bindings>
          <netMsmqBinding>
            <binding name="msmqbinding">
              <security mode="None"/>
            </binding>
          </netMsmqBinding>
        </bindings>
        <services>
          <service name="MyService.HomeService" behaviorConfiguration="mxbehavior">
            <endpoint address="net.msmq://localhost/private/homequeue" binding="netMsmqBinding"
                      contract="MyService.IHomeService" bindingConfiguration="msmqbinding">
              <identity>
                <dns value="localhost" />
              </identity>
            </endpoint>
    
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost:19200/HomeService"/>
              </baseAddresses>
            </host>
          </service>
        </services>
    
      </system.serviceModel>
    </configuration>
    复制代码

    纵观上面的三种binding,配置起来何其简单,底层的各种通讯协议貌似对我来说都是透明的,其实呢???wcf在底层做了何其多的事情,而我却没有挖掘。。。

    这对码农里说也是一种悲哀啊。。。出了问题就只能祷告上天。。。下一篇我会开始深入剖析。

    十五天精通WCF——第二天 告别烦恼的config配置

     

      经常搞wcf的基友们肯定会知道,当你的应用程序有很多的“服务引用”的时候,是不是有一种疯狂的感觉。。。从一个环境迁移到另外一个环境,你需要改变的

    endpoint会超级tmd的多,简直就是搞死了人。。。好了,这篇我们来看看如何最小化配置。

    一:精简service的config配置

      就像上一篇的代码一样,我的service端的config配置如下:

    复制代码
     1 <?xml version="1.0" encoding="utf-8" ?>
     2 <configuration>
     3 <system.servicemodel>
     4 <behaviors>
     5 <servicebehaviors>
     6 <behavior name="mxbehavior">
     7 <servicemetadata httpgetenabled="true" />
     8 <servicedebug includeexceptiondetailinfaults="true" />
     9 </behavior>
    10 </servicebehaviors>
    11 </behaviors>
    12 <services>
    13 <service name="myservice.homeservice" behaviorconfiguration="mxbehavior">
    14 <endpoint address="net.tcp://localhost:1920/homeservice" binding="nettcpbinding" contract="myservice.ihomeservice">
    15 <identity>
    16 <dns value="localhost" />
    17 </identity>
    18 </endpoint>
    19 <endpoint address="mex" binding="mexhttpbinding" contract="imetadataexchange" />
    20 <host>
    21 <baseaddresses>
    22 <add baseaddress="http://localhost:19200/homeservice"/>
    23 </baseaddresses>
    24 </host>
    25 </service>
    26 </services>
    27 </system.servicemodel>
    28 </configuration>
    复制代码

      通过上面的代码,你应该知道在system.servicemodel下的所有节点都是wcf专属的节点,所有的节点数据都会被开启servicehost这个监听器时捕获到,下面我可以

    通过servicehost这个监听器的源码下面找找相关的读取config节点的代码。

     

    通过上面的截图,你是不是有一种感觉,就是service的底层也是通过代码动态的读取config下面的节点来获取数据,那就意味着我可以直接将代码写入到code中,

    对吧,这样我就可以把我认为该配置的东西配置起来,不该配置的东西全部放到代码里面去,这样我的灵活性是不是非常的强大。。。。爽吧,说干就干。。。

    复制代码
     1     class Program1
     2     {
     3         static void Main(string[] args)
     4         {
     5             ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://localhost:19200/HomeService"));
     6 
     7             host.AddServiceEndpoint(typeof(IHomeService), new NetTcpBinding(), "net.tcp://localhost:1920/HomeService");
     8 
     9             //公布元数据
    10             host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
    11             host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
    12 
    13             host.Open();
    14 
    15             Console.WriteLine("服务已经开启。。。");
    16 
    17             Console.Read();
    18         }
    19     }
    复制代码

    有人就要说了,地址的话肯定不能是写死的,必须变活,简单啊,我就仅仅把ip地址配置到config里面去不就完事了,对不对。

    <configuration>
      <appSettings>
        <add key ="baseurl" value="http://localhost:19200/HomeService"/>
        <add key ="endpoindurl" value="net.tcp://localhost:1920/HomeService"/>
      </appSettings>
    复制代码
     1   class Program1
     2     {
     3         static void Main(string[] args)
     4         {
     5             ServiceHost host = new ServiceHost(typeof(HomeService), new Uri(ConfigurationManager.AppSettings["baseurl"]));
     6 
     7             host.AddServiceEndpoint(typeof(IHomeService), new NetTcpBinding(), ConfigurationManager.AppSettings["endpoindurl"]);
     8 
     9             //公布元数据
    10             host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
    11             host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
    12 
    13             host.Open();
    14 
    15             Console.WriteLine("服务已经开启。。。");
    16 
    17             Console.Read();
    18         }
    19     }
    复制代码

    现在看的话,是不是清楚多了,如果你觉得我的代码比较累赘,你可以封装成一个方法,然后就可以动态的配置nettcp,basic,ws*等等对吧。。。好了,说完服

    务端,接下来我们看看client端如何避免。

    二:精简client的config配置


      就像上一节那样,如果我用“服务引用”的话,vs会偷偷的用svcutil.exe来给我们生成一个proxy类和一个config文件,proxy类也就是你看到的xxxclient。。。

    可恶的是config里面会给我生成一些乱七八糟的东西,如下图:

    复制代码
     1 <?xml version="1.0" encoding="utf-8" ?>
     2 <configuration>
     3 <system.serviceModel>
     4 <bindings>
     5 <netTcpBinding>
     6 <binding name="NetTcpBinding_IHomeService" />
     7 </netTcpBinding>
     8 </bindings>
     9 <client>
    10 <endpoint address="net.tcp://localhost:1920/HomeService" binding="netTcpBinding"
    11 bindingConfiguration="NetTcpBinding_IHomeService" contract="HomeServiceReference.IHomeService"
    12 name="NetTcpBinding_IHomeService">
    13 <identity>
    14 <dns value="localhost" />
    15 </identity>
    16 </endpoint>
    17 </client>
    18 </system.serviceModel>
    19 </configuration>
    复制代码

    同服务器端一样,如果我用code做掉,是不是非常的爽呢???那可不可以做掉呢? 我们还得看一下proxy的源码,首先你会看到其实所谓的proxy只是一个继承

    自clientbase的一个类,如下图。

     

    上面的两幅图,你会发现,最后的proxy类是通过ChannelFactory<TChannel>类来完成助攻的,那话说回来了,既然底层用了ChannelFactory<TChannel>,

    那何不我在代码里面就用ChannelFactory<TChannel>不是更好吗???这样config也省了,对吧,说干就干啦。。。

    复制代码
    1     static void Main(string[] args)
    2         {
    3             ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new NetTcpBinding(), "net.tcp://localhost:1920/homeservice");
    4 
    5             var channel = factory.CreateChannel();
    6 
    7             var result = channel.GetLength("12345");
    8         }
    复制代码

    好了,代码就这么简单,现在是不是感觉自己萌萌大啦~~~

    十五天精通WCF——第三天 client如何知道server提供的功能清单

     

        通常我们去大保健的时候,都会找姑娘问一下这里能提供什么服务,什么价格,这时候可能姑娘会跟你口述一些服务或者提供一份服务清单,这样的话大

    家就可以做到童嫂无欺,这样一份活生生的例子,在wcf中同样是一个道理,只有client了解service能提供哪些功能,client才可以根据server提供的功能进行

    消费,那问题来了,service怎么把功能提供给client进行选择呢???这个就是我这一篇要聊的wsdl(web service description language)。。。

    一:wsdl

       现在你已经知道了,wsdl就是server提供给client的清单,那下面问题就来了。server是如何提供的呢???你要是比较仔细的话,可能会知道我在上一

    篇提到的一个endpoint,如下截图。

    在上面这幅图中,你可以看到,Homeservice提供了两个端点,一个是“服务端点“,一个是“元数据端点”。并且你也看到了,元数据的端点地址是

    http://192.168.16.16:19200/mex,当client通过svcutil访问这个地址的时候,就拿到了server能提供的功能清单,然后client就可以根据这些功能生成一

    个代理文件,然后的然后,就是你懂得,各种啪啪啪,XXXClient。

    二:眼见为实

    1.见证wsdl

     要想看见wsdl,你只需要通过http://localhost:19200打开服务地址、如下图:

    然后点击:http://localhost:19200/?singleWsdl

    现在你看到的就是server功能清单,太tmd的重量级了,已经完完全全果体在世人前了,下一小节我们再详细的分析下。

    2. 见证client端的XXXclient

      刚才我也说了,当你用vs做“服务引用”的时候,svcutil会根据http://localhost:19200/mex的地址来查看wsdl,然后生成代理,下面我们具体来看一下。

    点击确定之后,我们就可以看到在 Service References 文件夹下面生成了一个Reference.cs 文件。

    然后我们打开Reference.cs,就可以看到一个继承于ClientBase的HomeServiceClient。

    三:详细分析wsdl文件

      学wcf,你一定要像svcutil一样能够看得懂wsdl。

    1. 首先看下server提供了一个Update操作,参数是一个id,一个Student这个自定义的复杂类型,同时返回也是Student这个

        复杂类型。

    复制代码
    1 namespace MyService
    2 {
    3     [ServiceContract]
    4     public interface IHomeService
    5     {
    6         [OperationContract]
    7         Student Update(int id, Student stu);
    8     }
    9 }
    复制代码

     2. wsdl这个xml文件,刚才你也看到了,下面我们一个个节点看看

      <1> portType 和 operation节点

      当你看到下面的截图后,我想你也能猜的出来,portType就是契约(IHomeService),operation就是契约方法(Update),不过有点意思的是,在operation

    下面你看到了一个input,一个output,这个就是所谓的 ”输入消息“,”输出消息”,那是什么意思呢??? 也就是说client到server的消息叫做“输入消息”,server到

    client端叫做“输出消息”,到这里你应该似乎明白了,我C#中的Update方法是有入参和出参的,然而这映射到wsdl中就是两条消息,input和output,这个也就是经典

    的“请求-响应“模式。

    好了,继续往下看,在wsdl:input和wsdl:output中分别有一个Action属性,这个非常有意思,wcf的底层就是通过这个地址来找到对应的方法,比如我们看到的代理

    类中的Update方法上面就有这么一段。

     <2> message 和 types节点

      继续往下看的话,你会发现input和output中还有一个message属性,对应的为IHomeService_Update_InputMessage和IHomeService_Update_OutputMessage,

    这个正好是message节点的引用,如下图:

    从这个图中,你可以看到input和output下面都有一个wsdl:part节点,这个就是表明input和output中需要携带的参数,比如element="tns:Update",就引用了

    element中Name=Update的节点,如下图:

    好了,最后我再截一张图,可以看到,传输协议为soap,服务地址等等。。。然后就没什么好说的了。

    十五天精通WCF——第四天 你一定要明白的通信单元Message

     

      转眼你已经学了三天的wcf了,是不是很好奇wcf在传输层上面到底传递的是个什么鸟毛东西呢???应该有人知道是soap,那soap这叼毛长得是什么

    样呢?这一篇我们来揭开答案。。。

    一:soap到底长成什么样子

      为了能看清soap长的啥样,我可以用强大的Fiddler来监视一下,突然好激动啊!!!

    1.Server

    复制代码
     1         static void Main(string[] args)
     2         {
     3             ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://192.168.1.105:19200"));
     4 
     5             host.AddServiceEndpoint(typeof(IHomeService), new BasicHttpBinding(), "HomeServie");
     6 
     7             //公布元数据
     8             host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
     9 
    10             host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
    11 
    12             host.Open();
    13 
    14             Console.WriteLine("服务已经开启。。。");
    15 
    16             Console.Read();
    17         }
    复制代码

    2.Client

    1             ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(), new EndpointAddress("http://192.168.1.105:19200/HomeServie"));
    2 
    3             var client = factory.CreateChannel();
    4 
    5             client.Update("王八蛋");

    现在我想你大概看清楚了这玩意是个么样子,一个建立在xml上面的一种消息格式,根元素是envelope,我知道这叼毛翻译过来就是“信封”,所以就有了”封头“

    和”封体”,就是s:Header 和 s:Body,从这个soap中你可以看到它忽略了header,然后我们继续往下看,还记得Update的意思吗???如果你读懂了上一篇,

    你应该知道这是一个Action,也就是所谓的input消息。与之对应的就是UpdateResponse这个output消息,对吧,还记得xmlns="http://tempuri.org/">吗?

    它就是IHomeService的默认命名空间,对吧。。。

    下一个我们关注的是Update这个Action中的<str>这个,你也看得到,这个就是上图中Update方法中的str参数,最后我们来看一下UpdateResponse中

    的<UpdateResult xmlns:a="http://schemas.datacontract.org/2004/07/MyService,不知道你是否还记得它就是WSDL中关于Student的XSD结

    构,看下图:

    好了,wcf中的soap结构我们也大概了解了一下,不知道有没有引发你对soap更深入的思考呢???

    二:对soap的更深入思考

      通过fiddler观察,你应该也明白了,不管是客户端还是服务端,wcf的高层封装都是仅仅拿出了Envelope中的body节点,而其他节点对我们来说好像并

    没有什么卵用,比如我说的Header节点,这么说来,Header是不是有点浪费呢???那下面有一个问题来了,wcf在底层用什么来构造消息的呢???下面

    我们大概找下client端的源码。。。

    通过上面的图,你现在应该也知道了在.net中其实tmd的就是message构造的,所以我想告诉你的是:既然wcf在底层也是用message来构造的,何不我自己

    就来构造message消息呢???岂不美哉???这样我就可以随意操作message,对吧。。。不然wcf这个高层封装的叼毛,对我来说就是一种束缚。。。因

    为我已经知道了service公布的wsdl,所以我可以轻松构造message。。。

    三:用message来调用Server端

      废话不多说,构造message你一定要知道下图中的三点:(input这个Action,契约方式 和 服务地址)。

    好了,下面我先来构造数据契约,指定服务契约的命名空间 和 Action在Soap中的名称

    1     [DataContract(Namespace = "http://tempuri.org/", Name = "Update")]
    2     class Test
    3     {
    4         [DataMember]
    5         public string str { get; set; }
    6     }

    然后,我把这个数据契约塞到envelope中的body中,如下:

    复制代码
     1             BasicHttpBinding bingding = new BasicHttpBinding();
     2 
     3             BindingParameterCollection param = new BindingParameterCollection();
     4 
     5             var u = new Test() { str = "王八蛋" };
     6 
     7             Message request = Message.CreateMessage(MessageVersion.Soap11, "http://tempuri.org/IHomeService/Update", u);
     8 
     9             IChannelFactory<IRequestChannel> factory = bingding.BuildChannelFactory<IRequestChannel>(param);
    10 
    11             factory.Open();
    12 
    13             IRequestChannel channel = factory.CreateChannel(new EndpointAddress("http://192.168.1.105:19200/HomeServie"));
    14 
    15             channel.Open();
    16 
    17             var result = channel.Request(request);
    18 
    19             channel.Close();
    20 
    21             factory.Close();
    复制代码

    接下来,我们跑起来看一下,效果咋样。。。

    看没看到,这个就是我手工构造的Message,是不是太帅了。。。哈哈,太帅的应该在后面,刚才也说了,既然大家玩的都是Message,而你这个几把wcf却仅仅把

    我的message.body拿出来了,那干脆我直接在契约方法中加message岂不是更好么???自由操作Message还有个什么好处呢??当然啦,我可以在Message的

    Header中加一些参数token,client的ip地址,client的身份,client的时间等等这些统计信息,对吧。。。这样才是最帅的,好了,说干就干,我们修改下server端的

    契约方法,只用来接受Message。

    server端:

    复制代码
     1     public class HomeService : IHomeService
     2     {
     3         public Message Update(Message message)
     4         {
     5             var header = message.Headers;
     6 
     7             var ip = header.GetHeader<string>("ip", string.Empty);
     8 
     9             var currentTime = header.GetHeader<string>("currenttime", string.Empty);
    10 
    11             //这个就是牛逼的 统计信息。。。
    12             Console.WriteLine("客户端的IP=" + ip + " 当前时间=" + currentTime);
    13 
    14             return Message.CreateMessage(message.Version, message.Headers.Action + "Response", "等我吃完肯德基,再打死你这个傻逼!!!");
    15         }
    16     }
    复制代码

    client端:

    复制代码
     1 namespace ConsoleApplication1
     2 {
     3     class Program
     4     {
     5         static void Main(string[] args)
     6         {
     7             BasicHttpBinding bingding = new BasicHttpBinding();
     8 
     9             BindingParameterCollection param = new BindingParameterCollection();
    10 
    11             var u = new Test() { str = "王八蛋" };
    12 
    13             Message request = Message.CreateMessage(MessageVersion.Soap11, "http://tempuri.org/IHomeService/Update", u);
    14 
    15             //在header中追加ip信息
    16             request.Headers.Add(MessageHeader.CreateHeader("ip", string.Empty, Dns.GetHostByName(Dns.GetHostName()).AddressList[0].ToString()));
    17             request.Headers.Add(MessageHeader.CreateHeader("currenttime", string.Empty, DateTime.Now));
    18 
    19             IChannelFactory<IRequestChannel> factory = bingding.BuildChannelFactory<IRequestChannel>(param);
    20 
    21             factory.Open();
    22 
    23             IRequestChannel channel = factory.CreateChannel(new EndpointAddress("http://192.168.1.105:19200/HomeServie"));
    24 
    25             channel.Open();
    26 
    27             var result = channel.Request(request);
    28 
    29             channel.Close();
    30 
    31             factory.Close();
    32         }
    33     }
    34 
    35     [DataContract(Namespace = "http://tempuri.org/", Name = "Update")]
    36     class Test
    37     {
    38         [DataMember]
    39         public string str { get; set; }
    40     }
    41 }
    复制代码

    然后我们用Fiddler监视一下结果:

    现在一切都如我所愿,好了,我想你也大概明白了这个神奇的message,也不要忘了它就是wcf的基本通信单元,我要去吃肯德基了。。。。。。

    十五天精通WCF——第五天 你需要了解的三个小技巧

     

     一: 服务是端点的集合

      当你在开发wcf的时候,你或许已经注意到了一个service可以公布多个endpoint,确实是这样,在wcf中有一句很经典的话,叫做“服务是端点的集合",就

    比如说一个普普通通的服务,它就公布了一个服务端点,一个元数据端点,对吧。。。

    仔细一想,这个问题就好玩了,既然一个service可以公布多个endpoint,而且我还知道wcf中有很多的binding,这些binding对应着很多的传输方式,那是不是

    说我一个service可以用多种协议方法对外公布,比如说同时以nettcp,basic,msmqbinding,udp等方式公布,对吧,那这样的话是不是超级好玩,如果对方

    是非.net程序,那就可以调用我的basic,如果对方是.net程序,那是不是可以调用我的nettcp,对不对。。。当然啦,wcf无所不能,这是一个史上无比强大的牛

    逼框架,牛逼的要死,已经逼得程序员只需随便改几个配置就能达到完全不一样的效果。。。下面我同时用nettcp和basic的方式来同时公布服务,好了,现在我

    们就来见证奇迹吧。。。

    Service:

    复制代码
     1 using System;
     2 using System.Runtime.Serialization;
     3 using System.ServiceModel;
     4 using System.ServiceModel.Channels;
     5 using System.Threading;
     6 
     7 namespace MyService
     8 {
     9     public class HomeService : IHomeService
    10     {
    11         public Student Update(Student message)
    12         {
    13             return new Student() { Name = "一线码农" };
    14         }
    15     }
    16 
    17     [DataContract]
    18     public class Student
    19     {
    20         [DataMember]
    21         public string Name { get; set; }
    22 
    23         [DataMember]
    24         public int Age { get; set; }
    25     }
    26 }
    复制代码

    Host :

    复制代码
     1     class Program1
     2     {
     3         static void Main(string[] args)
     4         {
     5             ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://192.168.1.105:1920"));
     6 
     7             host.AddServiceEndpoint(typeof(IHomeService), new BasicHttpBinding(), "HomeServie");
     8 
     9             host.AddServiceEndpoint(typeof(IHomeService), new NetTcpBinding(), "net.tcp://192.168.1.105:1921/HomeServieTcp");
    10 
    11             host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
    12 
    13             host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
    14 
    15             host.Open();
    16 
    17             Console.Read();
    18         }
    19     }
    复制代码

    Client端:

    复制代码
     1     class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             //basic 方式
     6             ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(),
     7                                                                                     new EndpointAddress("http://192.168.1.105:1920/HomeServie"));
     8 
     9             var client = factory.CreateChannel();
    10 
    11             var result = client.Update(new Student() { });
    12 
    13 
    14             //nettcp方式
    15             factory = new ChannelFactory<IHomeService>(new NetTcpBinding(),
    16                                                                        new EndpointAddress("net.tcp://192.168.1.105:1921/HomeServieTcp"));
    17 
    18             client = factory.CreateChannel();
    19 
    20             result = client.Update(new Student() { });
    21         }
    22     }
    复制代码

    通过上面的代码,是不是已经发现,我在client端,既可以用basic的方式调用,又可以用nettcp的方式调用,这个技巧是不是感觉wcf无比强大呢???

    二:Host寄宿多个Service

      我们知道wcf的寄宿方式有很多种,有iis,有windowservice,还有简单方便的console方式,而默认情况下,我们最通常的方法都是一个service,一个寄宿,

    而其实呢??? 其实一个寄宿host可以承载多个service,看起来是不是很好玩,如果说你有10个servcie,现在你只需要用一个console host就能寄宿起来,废

    话不多说,我演示一下给你看就好了。

    Service:

    复制代码
     1     namespace MyService
     2     {
     3         [ServiceContract]
     4         public interface IHomeService
     5         {
     6             [OperationContract]
     7             Student Update(Student message);
     8         }
     9 
    10         [ServiceContract]
    11         public interface IFlyService
    12         {
    13             [OperationContract]
    14             Student Fly(Student stu);
    15         }
    16     }
    复制代码

    Host:

    复制代码
     1     class Program1
     2     {
     3         static void Main(string[] args)
     4         {
     5             //第一个: 这是Home服务
     6             ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://192.168.1.105:1920"));
     7             host.AddServiceEndpoint(typeof(IHomeService), new BasicHttpBinding(), "HomeServie");
     8             host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
     9             host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
    10             host.Open();
    11 
    12             Console.WriteLine("Home服务开启。。。。");
    13 
    14             //第一个: 这是Fly服务
    15             var host2 = new ServiceHost(typeof(FlyService), new Uri("http://192.168.1.105:1930"));
    16             host2.AddServiceEndpoint(typeof(IFlyService), new BasicHttpBinding(), "FlyServie");
    17             host2.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
    18             host2.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
    19             host2.Open();
    20 
    21             Console.WriteLine("Fly服务开启。。。。");
    22 
    23             Console.Read();
    24         }
    25     }
    复制代码

    有没有看到,现在两个服务都开启了,这种方式看起来是不是很爽呀,否则的话,你需要开启两个Host,这样的话,我的手续就精简了。。。对吧。。

    三: Tcp中的端口共享

       这玩意听起来大家都懂,端口共享嘛,不就是两个程序共享一个端口,对吧,在通常情况下,我们肯定会认为这无法做到,其实呢?在Wcf中我们还是可以玩

    的,也就是一个PortSharingEnabled的事!!!如果说端口可以共享的话,那我们的service是不是就可以少开辟几个端口呢?同样这也方便我们进行service的管

    理,下面我给大家继续演示一下。。。很好玩的,么么哒

    可以看到,我的两个host都是用1920的端口,并且现在我真的开启起来啦。。。。好了,三种技巧都说到了,我想你在现实的wcf开发中,或多或少的都能接

    触的到,希望对你有用~~~~

    十五天精通WCF——第六天 你必须要了解的3种通信模式

     

      

         wcf已经说到第六天了,居然还没有说到这玩意有几种通信模式,惭愧惭愧,不过很简单啦,单向,请求-响应,双工模式,其中的第二种“请求-响应“

    模式,这个大家不用动脑子都清楚,这一篇我大概来分析下。

    一:“请求-响应“模式

        如果你看了我上一篇的博文,你应该非常清楚这种类似“本地调用”的方式,wcf同样也分为“同步”和“异步”两种,不过不管是异步还是同步,最终都逃

    不过是“请求-响应”这个事实,对吧。

    1: 同步方式

      这种方式我想没什么好说的,前面几篇我已经说的非常清楚了,具体使用方法可以参考我的前面几篇文章。。。谢啦~~~~

    2: 异步方式

      通常我们都有这样的一个思维,遇到耗时的东西第一反应就想到了多线程,毕竟多线程也是一种负载均衡,在wcf这种”请求-响应“模式,同样也支持异

    步,很神奇吧,而且神奇到可以在“服务引用“界面上做到一键生成,什么???你不信!!!!不信你看。。。

    然后我非常好奇的看下XXXClient给我们生成的是个什么代码。。。

    通过client端的proxy代码,你可以清楚的看到,这鸡巴WCF真的不容易,给我们生成了两种“异步模式”,第一种是最古老的beginXXX,endXXX模式,

    还有一种是被Jeffrey Richter 严重鄙视的“事件异步模式”。。。没什么好说的,截图一下给大家看看。

    二:“单向“模式

      很多时候,我们或许都有这样的需求,比如说订单提交成功的时候,我需要给客户发送邮件,但是你想想,我发送邮件这个任务只是我订单流程的

    一个“额外任务“,也就是说,它的失败不应该会阻止我的订单流程,并且它的逻辑时间不应该会阻碍我的下单总时间,对吧。。。这样的话,我的订单时

    间才会最小化,为了达到不影响下单总时间的效果,我的想法就是,client端直接把消息丢给信道就好了,然后不管server端有没有真的接收到,处理的

    慢不慢,过的好不好,等等,非常开心的是,这些对wcf来说真的是小菜一碟,只需要一个轻轻松松的”IsOneWay=true“属性就可以了。。。牛逼的要

    死。。。还有就是因为是单向的,所以契约方法就没有存在返回值的必要了,我说的对吧。。。嘿嘿~~~

    1     [ServiceContract]
    2     public interface IHomeService
    3     {
    4         [OperationContract(IsOneWay = true)]
    5         void Update(Student message);
    6     }
    复制代码
     1 namespace MyService
     2 {
     3     public class HomeService : IHomeService
     4     {
     5         public void Update(Student message)
     6         {
     7             Console.WriteLine(message.Name);
     8         }
     9     }
    10 
    11     [DataContract]
    12     public class Student
    13     {
    14         [DataMember]
    15         public string Name { get; set; }
    16 
    17         [DataMember]
    18         public int Age { get; set; }
    19     }
    20 }
    复制代码

    为了验证是否真的是单向通讯,我可以用二种方法验证下。

    1. wsdl中是否有output这个message

      通过下面的图,我想你看的很清楚了,你再也没有找到我们熟悉的“output”这个message,这就说明貌似真的是单向的了,因为wsdl就是web服务的清单。

      

    2. 使用fillder监视一下请求消息

    复制代码
    1     class Program
    2     {
    3         static void Main(string[] args)
    4         {
    5             HomeServiceClient client = new HomeServiceClient();
    6 
    7             client.Update(new Student() { Name = "hxc" });
    8         }
    9     }
    复制代码

      

    正如我在图中说的那样,非常奇怪,我的IsOneWay模式,竟然在http模式下行不通,但是你要记住,http模式天生就是“请求-响应”模式,它完全做不了

    单向模式,说明白一点就是:“wcf发现你的bingding不支持单向“的时候,它并不会报错,还是用自己天生的”请求-相应“模式来模拟”单向通信“,这就是你

    看到的非常奇怪的Http 202这个http状态码,很多人包括我,都不知道http202 是几个意思,没关系,我们百科一下就好了。。。下面框框的里面的字,

    已经说的非常清楚了,感谢感谢。。。

    三:“双向“ 模式

      这个通讯其实没什么好讲的,也只有tcp模式才会天生支持,而http模式天生就不支持,就像上面一样,如果非要用http来支持“双向通讯“,那又是在

    坑"wcf"他爹,这样就会逼着他爹在底层再建立一个“请求-响应“模式来支持所谓的”双向通讯“,而且”双向通讯“这个玩意还不如用两个单向的”请求-响应”模

    式或者两个“单向模式”来支持,而且两个”请求-响应“模式比”双向通讯“有更大的灵活性,反正我是对它不感冒,了解一下即可,如果大家比较感兴趣,可以

    在wcf官网上看一下:https://msdn.microsoft.com/zh-cn/library/ms735119.aspx。

     

      好了,就说到这里,洗洗睡了,晚安~~~~

    十五天精通WCF——第七天 Close和Abort到底该怎么用才对得起观众

     

    一:文起缘由

              写这一篇的目的源自于最近看同事在写wcf的时候,用特别感觉繁琐而且云里雾里的嵌套try catch来防止client抛出异常,特别感觉奇怪,就比如下面的代码。

    复制代码
     1         public void StartNormalMarketing(int shopId, List<int> marketingIdList)
     2         {
     3 
     4             using (SendEventMarketingService.DistributeServiceClient client = new SendEventMarketingService.DistributeServiceClient())
     5             {
     6                 try
     7                 {
     8 
     9                     client.StartByMarketingIDList(shopId, marketingIdList, SendEventMarketingService.MarketingType.NormalMarketing);
    10 
    11                 }
    12                 catch (Exception ex)
    13                 {
    14                     LogHelper.WriteLog("常规营销活动开启服务", ex);
    15                 }
    16                 finally
    17                 {
    18                     try
    19                     {
    20                         client.Close();
    21                     }
    22                     catch (Exception)
    23                     {
    24                         client.Abort();
    25                     }
    26                 }
    27             }
    28         }
    复制代码

    看完上面的代码,不知道你是否有什么感想?而且我还问了同事,为什么try catch要写成这样,同事说是根据什么书上来的什么最佳实践,这话一说,我也不敢轻易

    怀疑了,只能翻翻源代码看看这话是否有道理,首先我来说说对这段代码的第一感觉。。。

    1. 代码特别繁琐

      我们写代码,特别不喜欢繁琐,上面的代码就是一例,你try catch就try catch,还在finally中嵌套一个try catch,真的有点感觉像吃了两只癞蛤蟆一样。。。

    2. 混淆close和abort的用法  

      这种代码给人的感觉就是为什么不精简一下呢???比如下面这样,起码还可以少写一对try catch,对吧。

    复制代码
     1         public void StartNormalMarketing(int shopId, List<int> marketingIdList)
     2         {
     3 
     4             using (SendEventMarketingService.DistributeServiceClient client = new SendEventMarketingService.DistributeServiceClient())
     5             {
     6                 try
     7                 {
     8 
     9                     client.StartByMarketingIDList(shopId, marketingIdList, SendEventMarketingService.MarketingType.NormalMarketing);
    10 
    11                     client.Close();
    12                 }
    13                 catch (Exception ex)
    14                 {
    15                     LogHelper.WriteLog("常规营销活动开启服务", ex);
    16 
    17                     client.Abort();
    18                 }
    19             }
    20         }
    复制代码

    而且乍一看这段代码和文中开头那一段代码貌似实现一样,但是某些人的“最佳实践”却不是这样,所以确实会导致我这样的后来人犯迷糊,对吧。。。反正我就是头晕,

    简直就是弄糊涂到什么时候该用close,什么时候该用abort。。。

          

    二:探索原理

      为了弄明白到底可不可以用一个try catch来替代之,下面我们一起研究一下。

    1.  从代码注释角度甄别

        从类库的注释中,可以比较有意思的看出,abort方法仅仅比close多一个“立即”,再无其他,有意思,不过这对我来说并没有什么卵用,因为这个注释太

    笼统了,为了让自己更加彻底的明白,只能来翻看下close和abort的源代码。

    2.  从源码角度甄别

      为了方便让ILSpy调试Client代码,现在我决定用ChannelFactory来代替,如下图:

    复制代码
     1 namespace ConsoleApplication1
     2 {
     3     class Program
     4     {
     5         static void Main(string[] args)
     6         {
     7             ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>();
     8 
     9             try
    10             {
    11                 var channel = factory.CreateChannel();
    12 
    13                 factory.Close();
    14             }
    15             catch (Exception ex)
    16             {
    17                 factory.Abort();
    18             }
    19         }
    20     }
    21 }
    复制代码

    为了让大家更好的理解,我把close方法的源码提供如下:

    复制代码
     1 // System.ServiceModel.Channels.CommunicationObject
     2 [__DynamicallyInvokable]
     3 public void Close(TimeSpan timeout)
     4 {
     5     if (timeout < TimeSpan.Zero)
     6     {
     7         throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("timeout", SR.GetString("SFxTimeoutOutOfRange0")));
     8     }
     9     using ((DiagnosticUtility.ShouldUseActivity && this.TraceOpenAndClose) ? this.CreateCloseActivity() : null)
    10     {
    11         CommunicationState communicationState;
    12         lock (this.ThisLock)
    13         {
    14             communicationState = this.state;
    15             if (communicationState != CommunicationState.Closed)
    16             {
    17                 this.state = CommunicationState.Closing;
    18             }
    19             this.closeCalled = true;
    20         }
    21         switch (communicationState)
    22         {
    23         case CommunicationState.Created:
    24         case CommunicationState.Opening:
    25         case CommunicationState.Faulted:
    26             this.Abort();
    27             if (communicationState == CommunicationState.Faulted)
    28             {
    29                 throw TraceUtility.ThrowHelperError(this.CreateFaultedException(), Guid.Empty, this);
    30             }
    31             goto IL_174;
    32         case CommunicationState.Opened:
    33         {
    34             bool flag2 = true;
    35             try
    36             {
    37                 TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
    38                 this.OnClosing();
    39                 if (!this.onClosingCalled)
    40                 {
    41                     throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosing"), Guid.Empty, this);
    42                 }
    43                 this.OnClose(timeoutHelper.RemainingTime());
    44                 this.OnClosed();
    45                 if (!this.onClosedCalled)
    46                 {
    47                     throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosed"), Guid.Empty, this);
    48                 }
    49                 flag2 = false;
    50                 goto IL_174;
    51             }
    52             finally
    53             {
    54                 if (flag2)
    55                 {
    56                     if (DiagnosticUtility.ShouldTraceWarning)
    57                     {
    58                         TraceUtility.TraceEvent(TraceEventType.Warning, 524292, SR.GetString("TraceCodeCommunicationObjectCloseFailed", new object[]
    59                         {
    60                             this.GetCommunicationObjectType().ToString()
    61                         }), this);
    62                     }
    63                     this.Abort();
    64                 }
    65             }
    66             break;
    67         }
    68         case CommunicationState.Closing:
    69         case CommunicationState.Closed:
    70             goto IL_174;
    71         }
    72         throw Fx.AssertAndThrow("CommunicationObject.BeginClose: Unknown CommunicationState");
    73         IL_174:;
    74     }
    75 }
    复制代码

    然后我提供一下Abort代码:

    复制代码
     1 // System.ServiceModel.Channels.CommunicationObject
     2 [__DynamicallyInvokable]
     3 public void Abort()
     4 {
     5     lock (this.ThisLock)
     6     {
     7         if (this.aborted || this.state == CommunicationState.Closed)
     8         {
     9             return;
    10         }
    11         this.aborted = true;
    12         this.state = CommunicationState.Closing;
    13     }
    14     if (DiagnosticUtility.ShouldTraceInformation)
    15     {
    16         TraceUtility.TraceEvent(TraceEventType.Information, 524290, SR.GetString("TraceCodeCommunicationObjectAborted", new object[]
    17         {
    18             TraceUtility.CreateSourceString(this)
    19         }), this);
    20     }
    21     bool flag2 = true;
    22     try
    23     {
    24         this.OnClosing();
    25         if (!this.onClosingCalled)
    26         {
    27             throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosing"), Guid.Empty, this);
    28         }
    29         this.OnAbort();
    30         this.OnClosed();
    31         if (!this.onClosedCalled)
    32         {
    33             throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosed"), Guid.Empty, this);
    34         }
    35         flag2 = false;
    36     }
    37     finally
    38     {
    39         if (flag2 && DiagnosticUtility.ShouldTraceWarning)
    40         {
    41             TraceUtility.TraceEvent(TraceEventType.Warning, 524291, SR.GetString("TraceCodeCommunicationObjectAbortFailed", new object[]
    42             {
    43                 this.GetCommunicationObjectType().ToString()
    44             }), this);
    45         }
    46     }
    47 }
    复制代码

    仔细观察完这两个方法,你会发现什么呢???至少我可以提出下面四个问题:

    1:Abort是Close的子集吗?

       是的,因为如果你看懂了Close,你会发现Close只针对Faulted 和Opened做了判断,而其中在Faulted的枚举下会调用原生的Abort方法。。。如下图

    2:我能监视Client的各种状态吗?比如Created,Opening,Fault,Closed等等。。。

       当然可以了,wcf的信道老祖宗就是ICommunicationObject,而它就有5种监听事件,这些就可以随时监听,懂伐???

    复制代码
     1         static void Main(string[] args)
     2         {
     3             ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:1920/HomeServie"));
     4 
     5             try
     6             {
     7                 factory.Opened += (o, e) =>
     8                 {
     9                     Console.WriteLine("Opened");
    10                 };
    11 
    12                 factory.Closing += (o, e) =>
    13                 {
    14                     Console.WriteLine("Closing");
    15                 };
    16 
    17                 factory.Closed += (o, e) =>
    18                 {
    19                     Console.WriteLine("Closed");
    20                 };
    21 
    22                 var channel = factory.CreateChannel();
    23 
    24                 var result = channel.Update(new Student() { });
    25 
    26                 factory.Close();
    27             }
    28             catch (Exception ex)
    29             {
    30                 factory.Abort();
    31             }
    32         }
    复制代码

    3:Abort会抛出异常吗?

      

    从这个截图中可以看到非常有意思的一段,那就是居然abort活生生的把异常给吞了。。。骨头都不给吐出来。。。真tmd的神奇到家了,想想也有道理,因为只有

    这样,我们上层的代码在catch中才不会二次抛出“未处理异常”了,对吧,再转念看一下Close方法。

    从上面图中可以看到,Close在遇到Faulted之后调用Abort方法,如果说Abort方法调用失败,Close方法会再次判断状态,如果还是Faulted的话,就会向上抛出

    异常。。。这就是为什么Abort不会抛异常,Close会的原因,所以Close千万不要放在Catch块中。

    4. Abort代码大概都干了些什么

      这个问题问的好,要能完美解决的话,我们看下代码,如下图,从图中可以看到,Abort的大目的就是用来关闭信道,具体会经过closeing,abort和closed这

    三个方法,同时,这三个事件也会被老祖宗ICommunicationObject监听的到。

     

    好了,最后我们关注的一个问题在于下面这条语句是否应该放在Try块中???

    1  ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:1920/HomeServie"));

    很简单,我们简要的看一下代码,看里面是否会有“异常”抛出即可。。。。

    可以看到,在new的过程中可能,或许会有异常的产生,所以最好把try catch改成下面这样。。。

    复制代码
     1     class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             ChannelFactory<IHomeService> factory = null;
     6             try
     7             {
     8                 factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:1920/HomeServie"));
     9 
    10                 var channel = factory.CreateChannel();
    11 
    12                 var result = channel.Update(new Student() { });
    13 
    14                 factory.Close();
    15 
    16                 throw new Exception();
    17             }
    18             catch (Exception ex)
    19             {
    20                 if (factory != null)
    21                     factory.Abort();
    22             }
    23         }
    24     }
    复制代码

    好了,综合我上面所说的一切,我个人觉得最好的方式应该是上面这样,夜深了,睡觉了,晚安。

    十五天精通WCF——第八天 对“绑定”的最后一点理解

     

      

      转眼已经中断10几天没有写博客了,也不是工作太忙,正好碰到了端午节,然后最近看天津台的爱情保卫战入迷了。。。太好看了,一直都是回味无穷。。。而且

    涂磊老师话说的真是tmd的经典,然后就这样耽搁了,好了,话不多说,这篇我们看看binding中最后一点需要知道的东西。

    一:信道栈

      我在之前的文章中多次提到信道栈,不知道大家对它的概念是否有了解,其实想想也还是蛮简单的,既然是栈,那么这个栈肯定就不止一个元素了,对吧,第二个

    的话,既然是栈,那么肯定就遵循FILO的原则,可能你会说,这个还是蛮抽象的,能给个具体的例子么???恭喜你,wcf中还真有一个方法CreateBindingElements,

    下面我们具体看看。。。

    1.  简单看看各种binding的栈中都有些什么

      

    看到上面的监控窗口,是不是有点意思,在BasicHttpBinding的信道栈中有两个元素,分别是HttpTransportBindingElement和TextMessageEncodingBindingEl

    ement,通过名字也能很容易的判断出来,一个是“http传输协议”,一个是“文本消息编码协议”,然后再看看复杂一点的WSHttpBinding,你会发现,他不光有Basic

    的所有东西,还包括SymmetricSecurityBindingElement(安全协议) 和 TransactionFlowBindingElement(事务流),现在你心中是不是有底了,起码我知道各

    种Binding里面都有些啥,为了更好的理解,我来画一张简图。

    上面这个图,大概也就表达了我的意思,当我们Client在走WSHttpBinding这个协议的时候,Client端的InputMessage会先走 TransactionFlow,SymmetricSec

    urity,TextMessageEncoding,最后走HttpTransport,然后Service端就按照客户端进行“反向处理”,通过一阵禁脔之后,我们就拿到了安全的OutputMessage。

    二:BindingElement的跨绑定性

      你要是很仔细的话,你肯定会发现,其实Binding就是一个预先默认配置好的信道栈,对不对,你也看到了,每一种Binding都有属于自己的BindingElements,

    恰恰这些Elements是可以跨Binding的,也就是说我可以自由组合Elements,这样是不是可以给我们这些寒酸的码农最大的灵活性,对吧,举个简单的例子,

    BasicHttpBinding有两个绑定元素,其中对soap消息进行的是TextMessageEncoding编码对吧,而netTcpBinding对soap进行的BinaryMessageEncoding,

    然后你也应该知道了,我想做一个自定义的Binding,其中消息编码是BinaryMessage,传输协议是HttpTransport,那怎么做呢????

    Host文件:

    复制代码
     1     class Program1
     2     {
     3         static void Main(string[] args)
     4         {
     5             ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://192.168.1.105:1920"));
     6 
     7             var customBinding = new CustomBinding();
     8 
     9             customBinding.Elements.Add(new BinaryMessageEncodingBindingElement());
    10             customBinding.Elements.Add(new HttpTransportBindingElement());
    11 
    12             host.AddServiceEndpoint(typeof(IHomeService), customBinding, "HomeServie");
    13 
    14             host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
    15 
    16             host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
    17 
    18             host.Open();
    19 
    20             Console.WriteLine("服务已经开启!!!");
    21 
    22             Console.Read();
    23         }
    24     }
    复制代码

    Client调用:

    复制代码
     1     static void Main(string[] args)
     2         {
     3             ServiceReference1.HomeServiceClient client = new ServiceReference1.HomeServiceClient();
     4 
     5             var result = client.Update("你好");
     6 
     7             Console.WriteLine("server value:" + result);
     8 
     9             Console.Read();
    10         }
    复制代码

    最后我们用Fiddler监视一下,最后我们看看,都是些乱码。

    这篇就说到这里了,希望对你有帮助,下一篇我们看看WCF中的Behavior,很好玩的哦~~~

    十五天精通WCF——第九天 高级玩法之自定义Behavior

     

      终于我又看完了二期爱情保卫战,太酸爽了,推荐链接:http://www.iqiyi.com/a_19rrgublqh.html?vfm=2008_aldbd,不多说,谁看谁入迷,下面言归正传,

    看看这个很有意思的Behavior。

    一: Behavior这个泼妇的厉害

        在前面的文章中,我也清楚的说明了整个wcf通信流,而Behavior这个泼妇可以在wcf通信流中的任何地方插上一脚,蛮狠无比,利用的好,让你上天堂,利用的不

    好,让你下地狱。。。下面让你看看behavior到底有哪些可以注入的点???先画个简图:

    上面的图,大概就是wcf的通信简图,所有蓝色字体都是Behavior注入的点,其中Client和Service端都可以注入,如果按照功能分的话,又可以分为“操作级别”和

    ”端点级别“,下面我来简要的分解下。

    二:端点级别Behavior

      从图中你也可以看到,消息检查器是放在Channel这个级别的,也就是说它可以监视Client和Server的入站请求,也就是说所有的请求都需要通过它转发,如果

    这样的话,那我是不是可以在这个注入点上自由的修改,变更,拦截入站和出站请求,而且利用这个特性我还可以做很多的事情,比如日志记录,记录统计等等,下

    面我们来看看这个怎么使用??? 只需要extends IEndpointBehavior  和 IDispatchMessageInspector,然后加入EndpointBehaviors即可。。。

     1. IDispatchMessageInspector

    复制代码
     1     public class MyDispatchMessageInspector : IDispatchMessageInspector
     2     {
     3         public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
     4         {
     5             Console.WriteLine(request.ToString());
     6             return request;
     7         }
     8 
     9         public void BeforeSendReply(ref Message reply, object correlationState)
    10         {
    11             Console.WriteLine(reply.ToString());
    12         }
    13     }
    复制代码

    2. IEndpointBehavior

    复制代码
     1     public class MyEndpointBehavior : IEndpointBehavior
     2     {
     3         public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
     4         {
     5         }
     6 
     7         public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
     8         {
     9         }
    10 
    11         public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
    12         {
    13             endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyDispatchMessageInspector());
    14         }
    15 
    16         public void Validate(ServiceEndpoint endpoint)
    17         {
    18         }
    19     }
    复制代码

    3. 将MyEndpointBehavior加入到Host中

    复制代码
     1   static void Main(string[] args)
     2         {
     3             ServiceHost host = new ServiceHost(typeof(HomeService), new Uri("http://127.0.0.1:1920"));
     4 
     5             host.AddServiceEndpoint(typeof(IHomeService), new BasicHttpBinding(), "HomeServie");
     6 
     7             host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
     8 
     9             host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
    10 
    11             host.Description.Endpoints[0].EndpointBehaviors.Add(new MyEndpointBehavior());
    12 
    13             host.Open();
    14 
    15             Console.WriteLine("服务已经开启!!!");
    16 
    17             Console.Read();
    18         }
    复制代码

    4. 最后我们看一下服务方法

    复制代码
    1    public class HomeService : IHomeService
    2     {
    3         public string Update(string message)
    4         {
    5             Console.WriteLine("我在Action方法:" + message);
    6 
    7             return "my reply!!!";
    8         }
    9     }
    复制代码

    下面看看效果。。。在效果图中,你应该看到了。在我的Action中的方法前后各有一段“入站消息”和“出站消息”,是不是很爽???

    三:操作级别Behavior

      从文章开头的简图中,你应该看到了,Operation级别的Behavior比较多,有“操作启动器(IOperationInvoker)","参数检查(IParameterInspector)“,

    “消息格式化器(IDispatchMessageFormatter)”等等。。。 为什么说等等这个词,很简单啊,,,其实还有很多系统内置的,既然是Operation,那就必

    然是针对方法的,还记得OperationContract是怎么套在方法上的吗??? 是特性,对吧,,,同样的道理,OperationBehavior也是一样,那怎么用呢??

    同样也是很简单的,继承几个接口即可。。。

     <1> IParameterInspector 的玩法

       其实没什么好说的,既然是属于Operation下面的Behavior,那都是通过特性注入的,而这个IParameterInspector,可以做到类似Mvc的Model验证,下面

    我做个简单的Action参数长度验证(长度不超过8个字符)。

    1. IParameterInspector

    复制代码
     1     public class MyIParameterInspector : IParameterInspector
     2     {
     3         public int MaxLength { get; set; }
     4 
     5         public MyIParameterInspector(int MaxLength)
     6         {
     7             this.MaxLength = MaxLength;
     8         }
     9 
    10         /// <summary>
    11         /// 出站的操作
    12         /// </summary>
    13         /// <param name="operationName"></param>
    14         /// <param name="outputs"></param>
    15         /// <param name="returnValue"></param>
    16         /// <param name="correlationState"></param>
    17         public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
    18         {
    19 
    20         }
    21 
    22         /// <summary>
    23         /// 入站的参数
    24         /// </summary>
    25         /// <param name="operationName"></param>
    26         /// <param name="inputs"></param>
    27         /// <returns></returns>
    28         public object BeforeCall(string operationName, object[] inputs)
    29         {
    30             foreach (var item in inputs)
    31             {
    32                 if (Convert.ToString(item).Length > MaxLength)
    33                 {
    34                     throw new Exception("码单,长度不能超过 " + MaxLength + " 个长度");
    35                 }
    36             }
    37 
    38             return null;
    39         }
    40     }
    复制代码

    2. IOperationBehavior

    复制代码
     1 public class MyOperationBehavior : Attribute, IOperationBehavior
     2     {
     3         public int MaxLength { get; set; }
     4 
     5         public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
     6         {
     7 
     8         }
     9 
    10         public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
    11         {
    12 
    13         }
    14 
    15         public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    16         {
    17             dispatchOperation.ParameterInspectors.Add(new MyIParameterInspector(MaxLength));
    18         }
    19 
    20         public void Validate(OperationDescription operationDescription)
    21         {
    22 
    23         }
    24     }
    复制代码

    3. 在Action在加上MyOperationBehavior 这个 Attribute

    复制代码
     1     public class HomeService : IHomeService
     2     {
     3         [MyOperationBehavior(MaxLength = 5)]
     4         public string Update(string message)
     5         {
     6             Console.WriteLine("我在Action方法:" + message);
     7 
     8             return "my reply!!!";
     9         }
    10     }
    复制代码

    4. 然后我在客户端故意输入大于5的字符,看看效果怎么样???

    复制代码
     1    public class Program1
     2     {
     3         static void Main(string[] args)
     4         {
     5             HomeServiceClient client = new HomeServiceClient();
     6 
     7             client.Update("我故意输入了很多的字符,哈哈。。。。。");
     8 
     9             Console.Read();
    10         }
    11     }
    复制代码

    5. 最后看看效果图,可以看到,最终的入站消息会抛出一个异常。。。

     

    <2> MessageFormatter,IOperationInvoker 的玩法

       剩下的这两个玩法都差不多,你只需要extends一下,然后加入到OperationBehavior即可,有了上面的思想,我想下面这些使用起来都不是问题吧。。。

    十五天精通WCF——第十天 学会用SvcConfigEditor来简化配置

     

      

           我们在玩wcf项目的时候,都是自己手工编写system.serviceModel下面的配置,虽然在webconfig中做wcf的服务配置的时候,vs提供大多

    数的代码提示,但对于不太熟悉服务配置的小鸟们来说,有些困难,而且一些服务配置也容易遗漏,大多情况下,我们都是copy一份服务配置,然

    后在服务配置上面修修改改,对吧。。。其实呢,.net给我们提供了一个强大的scvconfigeditor这个工具化的软件来帮助我们生成wcf的配置,是

    不是很神奇???

    一:工具在何处

      当然在无比牛逼的Microsoft SDK下面啦,在C:Program Files (x86)Microsoft SDKsWindows下面,你会找到很多的版本,如下图:

    对吧,你已经看到了很多的版本,当然啦,我肯定要找最新的啦,一禁脔,我进去了v8.0A,如下图:

    C:Program Files (x86)Microsoft SDKsWindowsv8.0AinNETFX 4.0 Tools

    你应该也看到了,各种牛逼的工具,很眼馋吧,不过这一篇我们还是看重SvcConfigEditor。

    二: 如何使用SvcConfigEditor

    1.   双击打开,选择“文件” => “新建配置”。

    2.  然后我们选择 “新建服务” => “填写服务名”

    3.  然后我们给service定义一个host, 点击 "主机" => "新建“ => "填写基址"。

    4.  到这一步,你是不是特别想看一看生成的config配置是咋样的???好啊,满足你的虚荣心,我们只需要点

         击"保存“,选择一个路径即可。。。

      

    5.  好了,你的虚荣心得到满足了,下面我们来定义endpoint了,其实也是非常非常简单的, 点击”终结点"

        => "新建服务终结点",然后我们就象征性的填写一些Address,Contract,Binding即可,如下图:

    6. 上面我们就已经定义了一个basichttpbinding了,下一步的话,我们还记得要公布一个mexhttpbinding,

        这样我的svcutil才能服务引用,对吧,所以方法也是很简单,继续“新建终结点”,如下图:

      

    7. 最后我还记得mex需要有一个behavior,让http的get可以访问,有了这个神器,同样简单,我们可以

        点击“高级” => "服务行为" => "新建"。

    8. 最后我们保存来看一下生成的appconfig是啥样的???

    则么样???我不需要写一个字的config配置就完成了基本的服务配置,如果你还想玩高级的,可以自己试着琢磨琢磨SvcConfigEditor。

    好了,差不多可以睡了,下一篇我们来研究研究 SvcConfigEditor中的诊断工具,很好玩的啦~~~~~

    十五天精通WCF——第十一天 如何对wcf进行全程监控

     

      说点题外话,我们在玩asp.net的时候,都知道有一个叼毛玩意叫做“生命周期”,我们可以用httpmodule在先于页面的page_load中

    做一些拦截,这样做的好处有很多,比如记录日志,参数过滤,全局登录验证等等。。。在wcf里面的话也是有类似的功能,第一种就是在

    endpoint中加上runtime的behavior,这样的话就可以先于“服务方法”做拦截,第二种方法呢,也就是我们这一篇所说的全程监控,俗称

    ”诊断功能”。

    一:诊断

      我也说了,“诊断”这是wcf的一个专业术语,意思也就是监控wcf的所有动向,如果往下说的话,可以分为监控 wcf的message 和 wcf

    本身的服务状态信息和端对端的流转消息。

    1. 端对端的流转消息

      在玩wcf之前,不知道有多少人熟悉Diagnostics,对的,它就是.net自带的日志类,当然在这个年代,记录日志的组件有很多,比如

    log4net,Nlog等等。。。不过话说回来,Diagnostics这个叼毛用起来还比较另类,它由“跟踪源” 和 “监听器”组成。分别就是TraceSource

    来指定跟踪源,用TraceListener来指定跟踪源的监听器,所以理所当然,TraceSource的所有踪迹都会被TraceListener监听到,下面我们

    看看怎么玩。

    复制代码
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
    
      <system.diagnostics>
        <sources>
          <source name="System.ServiceModel" switchValue="ActivityTracing">
            <listeners>
              <add name="mylisteners" type="System.Diagnostics.XmlWriterTraceListener" initializeData="E:1.txt" />
            </listeners>
          </source>
        </sources>
        <trace autoflush="true"/>
      </system.diagnostics>
    
      <system.serviceModel>
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="false" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
    
        <services>
          <service name="MyService.HomeService">
            <endpoint address="HomeService" binding="wsHttpBinding"
              contract="MyService.IHomeService">
              <identity>
                <dns value="localhost" />
              </identity>
            </endpoint>
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
            <host>
              <baseAddresses>
                <add baseAddress="http://192.168.1.107:1920" />
              </baseAddresses>
            </host>
          </service>
        </services>
    
      </system.serviceModel>
    
    </configuration>
    复制代码

     从上面的配置中可以看到,你有没有发现我在配置system.diagnostics的时候和wcf一点关系都没有,我并没有在system.ServiceModel

    下对diagnostics有一丁点的配置,对吧,这说明什么,说明“踪迹跟踪”功能和wcf一点关系都没有,但却可以完整的记录wcf的踪迹信息,然

    后我稍微解释下listeners节点,在这里我配置了一个XmlWriterTraceListener的监听器,然后把输出文件的路径配置在initializeData属性下,

    其实都是diagnostics本身的知识范畴,和wcf一点关系都没有,好了,下面我开启下程序,看看到底都追踪到什么?

    有没有看到,当我的服务启动之后,追踪信息就全部来了。。。但是接下来有一个问题来了,这个很杂乱的xml该怎么看才能最舒舒服服的

    呢???不用着急啦,wcf同样给我们提供了一个叫做SvcTraceView的工具,专门就是用来查找这个“踪迹信息”的,工具的路径在:

    C:Program Files (x86)Microsoft SDKsWindowsv8.0AinNETFX 4.0 Tools

    下面的事情就是打开它,附加一下1.txt文件就好了,如下图:

    从左边的“活动图”中大概可以看到HomeService这个服务启动到运行经历了一些什么样的悲惨故事。。。有兴趣的话,大家可以自己动

    手试试啦。

    2. 监控input和ouput的message

      如果要监控message的话,我们需要再定义一个TraceSource 和 TraceListener即可,不过这次监听的是System.ServiceModel.

    MessageLogging跟踪源,然后在System.ServiceModel下面配置一下message的参数,如下:

    复制代码
     1 <?xml version="1.0" encoding="utf-8"?>
     2 <configuration>
     3 
     4   <system.diagnostics>
     5     <sources>
     6       <source name="System.ServiceModel" switchValue="ActivityTracing">
     7         <listeners>
     8           <add name="mylisteners" type="System.Diagnostics.XmlWriterTraceListener" initializeData="E:1.txt" />
     9         </listeners>
    10       </source>
    11       <source name="System.ServiceModel.MessageLogging" switchValue="ActivityTracing">
    12         <listeners>
    13           <add name="messagelogging" type="System.Diagnostics.XmlWriterTraceListener" initializeData="E:2.txt"/>
    14         </listeners>
    15       </source>
    16     </sources>
    17     <trace autoflush="true"/>
    18   </system.diagnostics>
    19 
    20   <system.serviceModel>
    21 
    22     <diagnostics>
    23       <messageLogging logEntireMessage="true" logMalformedMessages="true"  logMessagesAtTransportLevel="true" />
    24     </diagnostics>
    25 
    26     <behaviors>
    27       <serviceBehaviors>
    28         <behavior>
    29           <serviceMetadata httpGetEnabled="true" />
    30           <serviceDebug includeExceptionDetailInFaults="false" />
    31         </behavior>
    32       </serviceBehaviors>
    33     </behaviors>
    34 
    35     <services>
    36       <service name="MyService.HomeService">
    37         <endpoint address="HomeService" binding="basicHttpBinding"
    38           contract="MyService.IHomeService">
    39           <identity>
    40             <dns value="localhost" />
    41           </identity>
    42         </endpoint>
    43         <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
    44         <host>
    45           <baseAddresses>
    46             <add baseAddress="http://192.168.1.107:1920" />
    47           </baseAddresses>
    48         </host>
    49       </service>
    50     </services>
    51 
    52   </system.serviceModel>
    53 
    54 </configuration>
    复制代码

    这次我准备来跑一下客户端,调用Server端的Update方法,看看能抓到啥样的Messsage。

    现在我迫不及待的想用SvcTraceView打开下2.txt,看看都拿到了什么追踪信息。。。

    好了,这篇我也只是引路式的介绍下SvcTraceView,具体更深入的玩法,大家可以琢磨琢磨,对了,如果大家想对Source和Listener的

    一些参数需要进一步了解,可以参考下SvcConfigEditor,比如下面这样,一目了然,你懂的。。。

    十五天精通WCF——第十二天 说说wcf中的那几种序列化

     

      

      我们都知道wcf是由信道栈组成的,在我们传输的参数走到传输信道层之前,先需要经过序列化的过程,也就是将参数序列化为message,这篇

    我们就来说说这里的序列化,蛮有意思的,可能初学者也明白,在wcf中默认的序列化是DataContractSerializer,确实是这样,不过wcf在信道中

    其实不仅仅支持DataContractSerializer,它还支持其他类型的序列化,比如XmlSerializer,NetDataContractSerializer以及DataContractJson

    Serializer,下面我们一起来见证下。

    1. XmlSerializer

       要了解XmlSerializer,我们先来简单看看NetDataContractSerializer,在前面的文章中,我也说过DataContract就是将我们的model序列化为

    XSD,第二点就是使用DataContract的原则就是你必须在Model上加DataContract,而且在你要序列化的字段上加DataMember。这样才能够正确的序列

    化,为了演示,我们先看看默认的序列化Model会变成啥样?

    复制代码
     1     [DataContract]
     2     public class Student
     3     {
     4         [DataMember]
     5         public int ID { get; set; }
     6 
     7         [DataMember]
     8         public string Name { get; set; }
     9 
    10         [DataMember]
    11         public string SNS { get; set; }
    12     }
    复制代码

    但是在有些情况下,你可能并不适合用DataContract,比如Model是第三方提供的,那么这个时候你的Model可能就不会有DataContract标记,那这样的

    话wcf就无法进行序列化,那我如果非要保证wcf能正常跑起来的话,还有其他好的办法吗???当然了,肯定有办法,这就好比谈恋爱一样,总不能

    在一棵树上吊死吧,没人谁离不开谁,也不会谁离开了谁会死,天涯何处无芳草,男儿何患无妻,对吧。Wcf中也一样,既然DataContract用不了,自

    然会有替代它的人,那这个人就是XmlSerializer,使用起来也很简单,就是在契约方法上面加上XmlSerializerFormat即可,然后我们把Model的

    DataContract全部去掉。

    是不是很简单,下面我们就要验证一下,看看这个Format是否进入到了这个Operation的Behavior中,

    从上面的图中,你也看到了, XmlSerializerFormat 已经被注入到Behavior中,并且是由类XmlSerializerOperationBehavior代为处理。

    接下来,我们用fiddler监视一下,看看Message中的Body是否真的按照XmlSerializer 序列化了。

    有没有看到,这次Message的Body已经和文章开头处的Message不一样了。

    2. NetDataContract

           这个玩意也没什么好说的,光从表面上看,它和DataContract唯一不同的地方就是多了一个Net,所以你大概也能猜到,这个功能大概和DataCont

    ract一样,只不过比DataContract多了一个程序集保存,那这句话是什么意思呢???就是NetDataContract会把程序集的命名空间和类名都保存到XSD中,

    在反序列化的过程中必须要用同样的程序集才能解开,其实不管我们是做SOA或者面向对象编程都讲究接口编程,而NetDataContract给你的印象就是面

    向对象编程,当然这也有好处,比如说如果把程序集带进去就好像秘钥一样,必须有它才能解开,对吧,所以导致wcf项目组并不对NetDataContract感冒

    ,所以在实际应用上也不建议使用。

    3. DataContractJsonSerializer

       看到上面这个带有Json的字样,我想大家都知道这玩意是干什么的???没错,他就是将我们的Model序列化成Json,这在wcf的rest编码使用的很广,

    如果大家有兴趣的话,我在下一篇会详细描述,这里我们先简单看一看。

    好了,这一篇就说这些了,洗洗睡了。。。

    十五天精通WCF——第十三天 用WCF来玩Rest

     

      

            在我们玩wcf的时候,都会潜意识的觉得wcf就是通过soap协议交换消息的,并且可以在basic,tcp,msmq等等绑定中任意切换,

    牛逼的一塌糊涂,但是呢,如果说哪一天wcf不再使用soap协议,而是采用json格式的字符串,是不是有一点颠覆你对wcf的认识的???

    从传统意义上说,wcf是非常重量级的,很明白的一个例子就是太多太多的配置,尤其是Behavior的配置,而且behavior对wcf来说又是重

    中之重,它对wcf的扩展和性能又是最重要的,可恨的是wcf在binding,behavior,contract之中的配置又是非常非常的保守,可以说用

    wcf来玩分布式,这些默认配置是完全做不到的,就比如说basicbinding的基类HttpBindingBase。

    抱怨的话我也不说了,可能微软也觉得这个问题是个不小的问题,然后就有了轻量级的 asp.net web api,你可以看到它和wcf比起来精

    简多了,也许让我们这些码农更加的专注于业务吧,既然wcf带了这玩意,我也得必须约谈一下。

    一:UriTemplate

      要说rest,还得先说UriTemplate,因为wcf用UriTemplate来做rest中的uri模板匹配,然后用WebInvoke这个OperationBehavior

    插入到wcf的心脏中,说的玄乎一点,这个就有点像mvc中的路由匹配机制,下面我举个例子:

    1. 用UriTemplate来告知可以监视的完整Url

      从下面的图中,可以看到三个元素:服务地址,模板,入参(这里面的”1“),这三个元素组合在一起,就构成了完整的remote url,

    然后这个完整的url就是我模板(/User/{id})监视的对象。

    2. 通过UriTemplate来解析url中的参数。

      既然可以构建url,那当然可以解析url啦,对吧,下面这张图可以很清晰的告知你,当外来的url=http://127.0.1:1920/HomeService

    /User/1过来的时候应该被哪个uriTemplate所接收。

    正是因为UriTemplate具有这样的url构建和解析能力,所以wcf就把UriTemplate作为WebInvoke和WebGet这两个属性的参数来动态

    解析外来的url,然后根据这个url分配到具体的服务方法上,下面我们具体看一看。

    二:WebGet,WebInvoke的使用

      刚才也说了,WebGet和WebInvoke正是用了UriTemplate,才具有了路由转向的功能,还有就是默认返回的是xml,这里就用json

    值作为服务返回的格式

    复制代码
     1     [ServiceContract]
     2     public interface IHomeService
     3     {
     4         [OperationContract]
     5         [WebGet(UriTemplate = "Get/{id}", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
     6         Student Get(string id);
     7 
     8         [OperationContract]
     9         [WebInvoke(Method = "POST", UriTemplate = "Add", RequestFormat = WebMessageFormat.Json,
    10                    ResponseFormat = WebMessageFormat.Json)]
    11         string Add(Student stu);
    12     }
    复制代码

    对了,Rest推荐使用Http协议中的Get,Post,Delete,Put来作为CURD的状态机制,然后就是你如果看懂了UriTemplate,那你现在应

    该知道这个Template在监视什么类型的url。做完了上面的coding,下面我们需要在webconfig中通过behavior来指定启动“web编程模型”,

    就比如下面这样。

    复制代码
     1 <?xml version="1.0" encoding="utf-8"?>
     2 <configuration>
     3 
     4   <system.diagnostics>
     5     <sources>
     6       <source name="System.ServiceModel" switchValue="ActivityTracing">
     7         <listeners>
     8           <add name="mylisteners" type="System.Diagnostics.XmlWriterTraceListener" initializeData="E:1.txt" />
     9         </listeners>
    10       </source>
    11       <source name="System.ServiceModel.MessageLogging" switchValue="ActivityTracing">
    12         <listeners>
    13           <add name="messagelogging" type="System.Diagnostics.XmlWriterTraceListener" initializeData="E:2.txt"/>
    14         </listeners>
    15       </source>
    16     </sources>
    17     <trace autoflush="true"/>
    18   </system.diagnostics>
    19 
    20   <system.serviceModel>
    21 
    22     <diagnostics>
    23       <messageLogging logEntireMessage="true" logMalformedMessages="true"  logMessagesAtTransportLevel="true" />
    24     </diagnostics>
    25 
    26     <behaviors>
    27       <serviceBehaviors>
    28         <behavior>
    29           <serviceMetadata httpGetEnabled="true" />
    30           <serviceDebug includeExceptionDetailInFaults="true" />
    31         </behavior>
    32       </serviceBehaviors>
    33       <endpointBehaviors>
    34         <behavior name="webbehavior">
    35           <webHttp />
    36         </behavior>
    37       </endpointBehaviors>
    38     </behaviors>
    39 
    40     <services>
    41       <service name="MyService.HomeService">
    42         <endpoint address="HomeService" binding="webHttpBinding" behaviorConfiguration="webbehavior"
    43           contract="MyService.IHomeService">
    44           <identity>
    45             <dns value="localhost" />
    46           </identity>
    47         </endpoint>
    48         <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
    49         <host>
    50           <baseAddresses>
    51             <add baseAddress="http://127.0.0.1:1920" />
    52           </baseAddresses>
    53         </host>
    54       </service>
    55     </services>
    56 
    57   </system.serviceModel>
    58 
    59 </configuration>
    复制代码

    其实呢?也就是代码中的WebHttpBehavior类

    好了,我现在服务地址也出来了:http://127.0.0.1:1920 ,然后服务方法的template也指定了。只要http.sys监控到了template

    匹配的url,服务方法就会被执行,比如我现在在浏览器里面输入:http://127.0.0.1:1920/HomeService/Get/1  来测试下Get操作。

    可以看到,get方法成功了,也正确的匹配了我的服务方法Get。

    复制代码
     1     public class HomeService : IHomeService
     2     {
     3         public Student Get(string id)
     4         {
     5             return new Student() { ID = Convert.ToInt32(id), Name = "hxc", SNS = "001" };
     6         }
     7 
     8         public string Add(Student stu)
     9         {
    10             return "hello";
    11         }
    12     }
    复制代码

    然后我们看看Add方法,我在HttpWebRequest中模拟测试如下。

     View Code

    好了,大概就说这么多了,如果说你不嫌麻烦,你可以用WCF Rest,还有就是不要忘了很多的默认配置,如果你觉得太繁琐,

    可以用用asp.net web api。

    十五天精通WCF——第十四天 一起聊聊FaultException

     

      

       我们在玩web编程的时候,可能你会不经意的见到一些http500的错误,我想你应该不会陌生的,原因你应该也知道,服务器异常嘛,

    这时候clr会把这个未处理的异常抛给iis并且包装成http500的错误返回到客户端,就比如下面这样。

    从这张图中,我故意输入了xss字符,然后的然后,web程序自爆异常,其实我想表达的意思就是,虽然说web程序抛异常了,但不代表iis就

    挂了,所以iis还是需要给客户端做出反馈,这就有了http header,和body信息,同样的道理,wcf的服务器异常机制也是这样。。。service

    抛出了异常,不代表console就挂了,console要做的事情就是把这个异常包装起来丢给调用方,而wcf是怎么包装的呢???就是用了这篇所

    说的FaultException。。。

    一:FaultException

    1. faultexception是干什么的?

      刚才我也说了,这个异常就是wcf来包装远程错误的,具体的类含义就是表示“SOAP错误“,如果你够细心的话,你还会发现到它有个属性

    叫Serializable,有了它,这个叼毛就可以序列化到Soap消息中,对伐???

    2. 如果挖出faultexception?

      挖出这个exception的方法有很多,比如我来造一个“除以0”的异常,如下所示:

    Service:

    复制代码
     1     public class HomeService : IHomeService
     2     {
     3         public Student Get(string id)
     4         {
     5             //这里必然会抛出异常。。。
     6             var result = Convert.ToInt32(id) / Convert.ToInt32("0");
     7 
     8             return new Student() { ID = Convert.ToInt32(id), Name = "hxc", SNS = "001" };
     9         }
    10     }
    复制代码

    Client:

    复制代码
     1     public class Program1
     2     {
     3         static void Main(string[] args)
     4         {
     5             using (HomeServiceClient client = new HomeServiceClient())
     6             {
     7                 try
     8                 {
     9                     var result = client.Get("1");
    10                 }
    11                 catch (Exception ex)
    12                 {
    13 
    14                 }
    15             }
    16         }
    17     }
    复制代码

    看到了没有,虽然wcf的service已经抛出异常了,但是还是被clr用Faultexception包装起来了,正如你看到了s:Fault节点,仔细往下看的话,

    你还会看到faultcode,faultstring,detail等等属性节点,那下面有个问题就来了,我们平时在Client端都习惯这么写。

    复制代码
     1             using (HomeServiceClient client = new HomeServiceClient())
     2             {
     3                 try
     4                 {
     5                     var result = client.Get("1");
     6                 }
     7                 catch (Exception ex)
     8                 {
     9                     client.Abort();
    10                 }
    11             }
    复制代码

    但是这么写有个什么问题呢???就是不管客户端抛出什么异常,我们都习惯用基类异常Exception捕获,但是wcf有一点非常恶心的就是,

    它的异常信息非常的少,第一眼根本看不出个一二三,这是因为所有的异常你都用顶级的exception捕获,自然你能知道的信息就非常少,

    这也很正常,如果你想要更详细的信息,你是不是应该在Client端写上更具体的异常捕获类呢???就比如你现在已经知道的FaultException

    是因为服务器的错误都是由它处理的。

    如果现在你按照上图中所coding的那样,你是不是对异常信息可以了解的更深,起码你知道这个异常的抛出,绝逼是因为通道是正常的,只是

    servcie抛出异常了而已。。。那你可能要问了,我这话的言外之意就是还有其他异常类也会捕获wcf抛出的异常,对的,比如说你的信道出现

    故障,这时候会抛出一个“通信异常(CommunicationException)”。

    三:如何挖出“通信异常”

        挖出这个异常,也是很简单的,现在我们需要使用”会话级别“的binding,比如说nettcpbinding,wshttpbinding,这里的话,我选择

    后者,因为是这样的,第一次服务器抛异常以后,客户端和服务器端通信信道就会关闭,如果你在客户端不重新new一个client,那么这时候你

    第二次再使用client的话,这个时候就会产生“信道故障“,抛出CommunicationException,而当你看到CommunicationException的时候,

    你可以非常有自信的说,老子的wcf根本就没有连接到service,而是在client端就被杀死了。。。下面我演示一下。

    四:自定义FaultException

      现在你应该知道了,只要是Servcie的Exception都会抛出 FaultException,对吧,而且你用Fiddler观察的话,也看的出其中的faultcode

    和faultstring貌似都不是很详细,那我就有一个想法了,既然wcf会自己给我包装个FaultException,那何不我自己就在发生异常的时候自己包

    装一个自定义的FaultException,然后我可以包装一些我自己想要告诉客户端的信息,这样的话是不是灵活性非常的大呢???想法很不错,wcf

    也是恩准这么做的,下面我把service的get方法更改如下,在FaultException中自定义Reason,Code,Action等等自定义信息。

    复制代码
     1  public class HomeService : IHomeService
     2     {
     3         public Student Get(string id)
     4         {
     5             try
     6             {
     7                 //这里必然会抛出异常。。。
     8                 var result = Convert.ToInt32(id) / Convert.ToInt32("0");
     9 
    10                 return new Student() { ID = Convert.ToInt32(id), Name = "hxc", SNS = "001" };
    11             }
    12             catch (Exception ex)
    13             {
    14                 var reason = new FaultReason("你这个战斗力只有五的渣渣。。。 这么简单的错误都出来了,搞个鸡巴毛");
    15 
    16                 var code = new FaultCode("500");
    17 
    18                 var faultException = new FaultException(reason, code, "是Get这个王八蛋");
    19 
    20                 throw faultException;
    21             }
    22         }
    23     }
    复制代码

    好了,大概就说这么多了,我的目的也很简单,在写wcf的client的时候,尽量做到异常越具体越好,这样方便我们尽可能快的排查问题,因为

    wcf的异常信息真的太tmd坑爹了!!!减轻痛苦,从小做起~~~

    十五天精通WCF——终结篇 那些你需要注意的坑

     

        

              终于一路走来,到了本系列的最后一篇了,这一篇也没什么好说的,整体知识框架已经在前面的系列文章中讲完了,wcf的配置众多,如果

    不加一些指定配置,你可能会遇到一些灾难性的后果,快来一睹为快吧。

    一: 第一个大坑 【数据传输量】

       我们使用wcf的目的,就是用来进行分布式的数据交互,既然是交互,就一定要进行数据交换,可能一些新人并没有注意到wcf在数据传输量上

    面做了一个大小限制,比如我现在要传输一个2m的txt给service,会出现什么情况???

    复制代码
     1        static void Main(string[] args)
     2         {
     3             try
     4             {
     5                 var txt = File.ReadAllText("E:\1.txt");
     6 
     7                 HomeServiceClient client = new HomeServiceClient();
     8 
     9                 client.Get(txt);
    10 
    11                 int i = 10;
    12 
    13             }
    14             catch (Exception ex)
    15             {
    16 
    17                 throw;
    18             }
    19         }
    复制代码

    可是的可是,我们在玩aspnet的时候,再大的传输量都见过,但为什么这玩意就抛异常了呢???下面一个问题就来了,这个传输默认值到底

    是多少??? 接下来我们就用ILSpy翻翻看。

    可以看到,这个叼毛玩意居然只有 64k。。。没错,你看到的就是64k,也就说明你的传输量不能大于64k,否则请求就会在client端拒绝,

    知道了原因,我们现在就可以这么修改config了。

        <bindings>
          <netTcpBinding>
            <binding name="MySessionBinding" maxReceivedMessageSize="2147483647"/>
          </netTcpBinding>
        </bindings>

    有很多资料在配置这个坑的时候,也会使用MaxBufferSize 和 MaxBufferPoolSize,就是用来增加缓冲区和缓冲池的大小。

    一: 第二个大坑 【并发量太低】

      说起这个大坑,还得先从一段代码说起,下面是一段对服务进行2w次并发调用,然后我们看看效果。

    复制代码
        public class Program1
        {
            static void Main(string[] args)
            {
                try
                {
                    for (int i = 0; i < 200000; i++)
                    {
                        try
                        {
                            Task.Factory.StartNew((obj) =>
                            {
                                try
                                {
                                    HomeServiceClient client = new HomeServiceClient();
    
                                    Console.WriteLine("第 {0} 个请求开始。。。", obj);
    
                                    client.Get("12312");
    
                                    Console.WriteLine("第 {0} 个请求结束。。。", obj);
                                }
                                catch (Exception ex)
                                {
                                    Console.WriteLine(ex.Message);
                                }
                            }, i);
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }
    
                    Console.Read();
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
        }
    复制代码

        从上面你可以看到,当并发数达到800左右的时候,servcie端就开始拒绝client端过来的请求了,并且之后的1min的时间里,client端

    开始出现超时异常,这肯定不是我想看到的, 那有人就要说了,我的并发达到800多很正常啊,如果提高这个并发呢???其实在wcf里面

    有一个叫做ServiceThrottlingElement绑定元素,它就是用来控制服务端的并发数。

    这三个属性的大概意思,我想大家都看的明白,不过有点奇怪的是,这三个属性的默认值 和 ILSpy中看到的不一样。。。

    也懒的研究源码了,不管怎么样,反正这三个属性值都是int类型的,所以我将他们设置为int.maxValue就好了。

    复制代码
    <system.serviceModel>
        <behaviors >
          <serviceBehaviors >
            <behavior name="nettcpBehavior">
              <serviceMetadata httpGetEnabled="false" />
              <!--是否在错误中包含有关异常的详细信息-->
              <serviceDebug includeExceptionDetailInFaults="True" />
              <serviceThrottling maxConcurrentCalls="2147483647" maxConcurrentInstances="2147483647" maxConcurrentSessions="2147483647" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
    
        <bindings>
          <netTcpBinding>
            <binding name="MySessionBinding" />
          </netTcpBinding>
        </bindings>
    
        <services>
          <service behaviorConfiguration="nettcpBehavior" name="MyService.HomeService">
            <endpoint address="net.tcp://127.0.0.1:19200/HomeService" binding="netTcpBinding"
              bindingConfiguration="MySessionBinding" contract="MyService.IHomeService" />
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
            <host>
              <baseAddresses>
                <add baseAddress="http://127.0.0.1:1920" />
              </baseAddresses>
            </host>
          </service>
        </services>
    
      </system.serviceModel>
    复制代码

    然后我们再把程序跑起来看一看。。。

          现在你可以发现并发早已突破800了,不过你要记住,如果并发数太多,容易造成系统资源耗尽,导致崩溃,这时候负载均衡就来

    了,对吧,wcf需要修改的配置还有很多,正因为wcf框架庞大,很多默认配置不符合生产需求,所以大家在工作中需要注意,这个系列

    就到此打住了,希望对你有帮助。

  • 相关阅读:
    AIX系统/var/adm/wtmp大文件处理
    script & scriptreplay
    Ubuntu/Debianpxe/isopreseed
    Ubuntu12.04安装gimp-2.8
    Ubuntu 3D特效一览
    Unix history图览
    Undelete Files on Linux Systems
    开源界有趣的循环缩写和LOGO
    Ubuntu上的dock
    linux下歌曲、视频、文件等乱码
  • 原文地址:https://www.cnblogs.com/kingCpp/p/4705931.html
Copyright © 2011-2022 走看看