十五天精通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文件
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好。
三: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上新建一个消息队列,如下:
然后我们再改动以下配置文件
纵观上面的三种binding,配置起来何其简单,底层的各种通讯协议貌似对我来说都是透明的,其实呢???wcf在底层做了何其多的事情,而我却没有挖掘。。。
这对码农里说也是一种悲哀啊。。。出了问题就只能祷告上天。。。下一篇我会开始深入剖析。
十五天精通WCF——第二天 告别烦恼的config配置
经常搞wcf的基友们肯定会知道,当你的应用程序有很多的“服务引用”的时候,是不是有一种疯狂的感觉。。。从一个环境迁移到另外一个环境,你需要改变的
endpoint会超级tmd的多,简直就是搞死了人。。。好了,这篇我们来看看如何最小化配置。
一:精简service的config配置
就像上一篇的代码一样,我的service端的config配置如下:
1 23 4 285 126 117 108 9 13 2714 2615 1916 1817 20 21 2522 2423
通过上面的代码,你应该知道在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里面去不就完事了,对不对。
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 23 4 195 96 87 10 1813 1714 1615
同服务器端一样,如果我用code做掉,是不是非常的爽呢???那可不可以做掉呢? 我们还得看一下proxy的源码,首先你会看到其实所谓的proxy只是一个继承
自clientbase的一个类,如下图。
上面的两幅图,你会发现,最后的proxy类是通过ChannelFactory
那何不我在代码里面就用ChannelFactory
1 static void Main(string[] args) 2 { 3 ChannelFactoryfactory = new ChannelFactory (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 ChannelFactoryfactory = new ChannelFactory (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中的
的
好了,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 IChannelFactoryfactory = bingding.BuildChannelFactory (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("ip", string.Empty); 8 9 var currentTime = header.GetHeader ("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 IChannelFactoryfactory = bingding.BuildChannelFactory (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 ChannelFactoryfactory = new ChannelFactory (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 (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, ListmarketingIdList) 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, ListmarketingIdList) 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 ChannelFactoryfactory = new ChannelFactory (); 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 ChannelFactoryfactory = new ChannelFactory (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 ChannelFactoryfactory = new ChannelFactory (new BasicHttpBinding(), new EndpointAddress("http://localhost:1920/HomeServie"));
很简单,我们简要的看一下代码,看里面是否会有“异常”抛出即可。。。。
可以看到,在new的过程中可能,或许会有异常的产生,所以最好把try catch改成下面这样。。。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 ChannelFactoryfactory = null; 6 try 7 { 8 factory = new ChannelFactory (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 ///11 /// 出站的操作 12 /// 13 /// 14 /// 15 /// 16 /// 17 public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState) 18 { 19 20 } 21 22 ///23 /// 入站的参数 24 /// 25 /// 26 /// 27 ///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 SDKs\Windows下面,你会找到很多的版本,如下图:
对吧,你已经看到了很多的版本,当然啦,我肯定要找最新的啦,一禁脔,我进去了v8.0A,如下图:
C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 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监听到,下面我们
看看怎么玩。
从上面的配置中可以看到,你有没有发现我在配置system.diagnostics的时候和wcf一点关系都没有,我并没有在system.ServiceModel
下对diagnostics有一丁点的配置,对吧,这说明什么,说明“踪迹跟踪”功能和wcf一点关系都没有,但却可以完整的记录wcf的踪迹信息,然
后我稍微解释下listeners节点,在这里我配置了一个XmlWriterTraceListener的监听器,然后把输出文件的路径配置在initializeData属性下,
其实都是diagnostics本身的知识范畴,和wcf一点关系都没有,好了,下面我开启下程序,看看到底都追踪到什么?
有没有看到,当我的服务启动之后,追踪信息就全部来了。。。但是接下来有一个问题来了,这个很杂乱的xml该怎么看才能最舒舒服服的
呢???不用着急啦,wcf同样给我们提供了一个叫做SvcTraceView的工具,专门就是用来查找这个“踪迹信息”的,工具的路径在:
C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools
下面的事情就是打开它,附加一下1.txt文件就好了,如下图:
从左边的“活动图”中大概可以看到HomeService这个服务启动到运行经历了一些什么样的悲惨故事。。。有兴趣的话,大家可以自己动
手试试啦。
2. 监控input和ouput的message
如果要监控message的话,我们需要再定义一个TraceSource 和 TraceListener即可,不过这次监听的是System.ServiceModel.
MessageLogging跟踪源,然后在System.ServiceModel下面配置一下message的参数,如下:
1 23 4 5 19 206 11 16 1718 21 22 53 5423 25 2624 27 34 3528 3329 3230 31 36 51 5237 5039 4340 4241 44 45 4946 4847
这次我准备来跑一下客户端,调用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 23 4 5 19 206 11 16 1718 21 22 58 5923 25 2624 27 39 4028 3329 3230 31 34 3835 3736 41 56 5742 5544 4845 4746 49 50 5451 5352
其实呢?也就是代码中的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中模拟测试如下。
好了,大概就说这么多了,如果说你不嫌麻烦,你可以用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了。
有很多资料在配置这个坑的时候,也会使用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就好了。
然后我们再把程序跑起来看一看。。。
现在你可以发现并发早已突破800了,不过你要记住,如果并发数太多,容易造成系统资源耗尽,导致崩溃,这时候负载均衡就来
了,对吧,wcf需要修改的配置还有很多,正因为wcf框架庞大,很多默认配置不符合生产需求,所以大家在工作中需要注意,这个系列
就到此打住了,希望对你有帮助。