之前在公司用的服务端是wcf写的,但是没有深入研究,最近找工作,面试的时候好多人看到这个总提问,这里做个复习
就用微软官方上的例子,搭一个简单的wcf服务,分6步
1 定义服务协定也就是契约,其实就是定义一个服务接口,这玩意后边是公开客户端用的,然后也告诉后边承载程序应该如何加载服务
主要涉及两个特性:一个是ServiceContract(接口的特性,定义这个是服务契约,里边又一些设置参数可以设置一下),OperationContract设置接口的方法的,如果不设置,方法就不会呗公开
这里是直接新建的wcf的服务程序,vs自动给生成了一个接口,就直接在这个里边添加了几个计算的接口函数了
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; namespace GettingStartedLib { // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IService1”。 //协定 [ServiceContract(//CallbackContract =typeof(ICallBack),//双工时的返回协定 ConfigurationName = "Calculator",//配置文件重的服务名 Name = "ICalculator",//webservice描述文件重的portType名 Namespace ="http://SampleWcfTest",//webservice描述文件重的portType命名空间 ProtectionLevel =System.Net.Security.ProtectionLevel.EncryptAndSign,//保护等级 SessionMode =SessionMode.Allowed)]//设置会话的支持模式 public interface ICalculator { [OperationContract] string GetData(int value); [OperationContract] CompositeType GetDataUsingDataContract(CompositeType composite); //定义方法的操作,带了该特性才会被公布 [OperationContract] double Add(double n1, double n2); [OperationContract] double Subtract(double n1, double n2); [OperationContract] double Multiply(double n1, double n2); [OperationContract] double Divide(double n1, double n2); // TODO: 在此添加您的服务操作 } public interface ICallBack { [OperationContract(IsOneWay = true)] void Reply(string responseToGreeting); } // 使用下面示例中说明的数据约定将复合类型添加到服务操作。 [DataContract] public class CompositeType { bool boolValue = true; string stringValue = "Hello "; [DataMember] public bool BoolValue { get { return boolValue; } set { boolValue = value; } } [DataMember] public string StringValue { get { return stringValue; } set { stringValue = value; } } } }
2 实现上边的接口,vs自动生成了svc文件和对应的svc.cs文件,直接双击就是对应的实现类了,吧接口实现就好了,这个svc文件右键用文本编辑器打开可以进行编辑,如果后边用IIS加载的时候需要修改里边的东西
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; namespace GettingStartedLib { // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码、svc 和配置文件中的类名“Service1”。 // 注意: 为了启动 WCF 测试客户端以测试此服务,请在解决方案资源管理器中选择 Service1.svc 或 Service1.svc.cs,然后开始调试。 public class Calculator : ICalculator { public string GetData(int value) { return string.Format("You entered: {0}", value); } public CompositeType GetDataUsingDataContract(CompositeType composite) { if (composite == null) { throw new ArgumentNullException("composite"); } if (composite.BoolValue) { composite.StringValue += "Suffix"; } return composite; } public double Add(double n1, double n2) { double result = n1 + n2; Console.WriteLine("Received Add({0},{1})", n1, n2); // Code added to write output to the console window. Console.WriteLine("Return: {0}", result); return result; } public double Subtract(double n1, double n2) { double result = n1 - n2; Console.WriteLine("Received Subtract({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } public double Multiply(double n1, double n2) { double result = n1 * n2; Console.WriteLine("Received Multiply({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } public double Divide(double n1, double n2) { double result = n1 / n2; Console.WriteLine("Received Divide({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } } }
3 承载服务,承载服务有几种方式,同时又设计到协议的绑定问题,
绑定分类:BasicHttpBinding 最基础的http绑定,
NetTcpbingding TCP的
NetNamePipeBinding IPC,也就是进程间通信,没用过。。
WSHttpBinding 这个是特殊的http/Https协议 加了其他东西的
NetMsmqBindiing,这个是消息队列,我没用过,这玩意好像需要额外装微软的消息队列才可以用
这里我基本就用wshttpbinding来实现,
承载方式:1)用控制台程序,直接用servicehost来承载服务,自己构造终结点:
//服务的地址 Uri baseAddress = new Uri("http://localhost:8000/GettingStarted/"); //承载服务的宿主 ServiceHost selfHost = new ServiceHost(typeof(Calculator), baseAddress); try { selfHost.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "Calculator"); ServiceMetadataBehavior smb = new ServiceMetadataBehavior(); smb.HttpGetEnabled = true; selfHost.Description.Behaviors.Add(smb); selfHost.Open(); Console.WriteLine("The service is ready."); Console.WriteLine("input<exit> to terminate service."); Console.WriteLine(); while ("exit" == Console.ReadLine()) { selfHost.Close(); } } catch (CommunicationException ex) { Console.WriteLine(ex.Message); selfHost.Abort(); Console.ReadLine(); }
2) 用IIS承载,这里又两种方式,通过internet的方式 这个可以直接在vs中调试的,选中svc文件点调试会弹出一个wcf的客户端测试程序,可以直接进行测试,但是这个客户端不能测试异步,只能测同步
这中方式主要是需要配置一个web.config文件,部署的话,直接在IIS添加网站服务程序,把生成的DLL和web.config放在一个目录下应该跟这个调试是一样的(这种方式IIS5 6 只支持HTTP)
还可以通过WAS(进程激活服务,支持的协议比上边多,这个就没有深入研究了···)
<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /> </appSettings> <system.web> <compilation debug="true" targetFramework="4.5.2" /> <httpRuntime targetFramework="4.5.2"/> </system.web> <system.serviceModel> <services> <service name="GettingStartedLib.Calculator" behaviorConfiguration="MyServiceTypeBehaviors" > <host> <baseAddresses> <add baseAddress="http://localhost:8001/"/> </baseAddresses> </host> <endpoint address="CalculatorService" binding="wsHttpBinding" contract="Calculator"> </endpoint> </service> </services> <behaviors> <serviceBehaviors> <behavior name="MyServiceTypeBehaviors" > <!-- 将下列元素添加到服务行为配置中。 --> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> <system.webServer> <!--<modules runAllManagedModulesForAllRequests="true"/>--> <!-- 若要在调试过程中浏览 Web 应用程序根目录,请将下面的值设置为 True。 在部署之前将该值设置为 False 可避免泄露 Web 应用程序文件夹信息。 --> <directoryBrowse enabled="true"/> </system.webServer> </configuration>
3)通过windows服务程序承载,这个内部起始也是servicehost来实现,只不过是挂在windows服务程序上,需要自己实现一个windows服务,也就是继承servicebase类,重写onstart,在onstart中用servicehost加载服务,在stop中停止服务,同时需要配置文件就是程序的配置文件app.config记得生成到目录下,配置终结点。服务类生成的程序需要安装,还需要继承install实现一个安装类,把生成的服务程序安装到windows系统服务去,然后就可以在服务中启动服务,
这个安装是在管理员权限下进入对应的.netframework目录下运行installutil path(程序目录) 这里用的是4.5 ,就是v4.0。。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using GettingStartedLib; using System.ServiceModel.Description; using System.ServiceModel; using System.ServiceProcess; using System.Configuration.Install; using System.ComponentModel; namespace WcfConsole { public class serviceCalss : ServiceBase { public ServiceHost serviceHost = null; public serviceCalss() { ServiceName = "WCFWindowsServiceSample"; } protected override void OnStart(string[] args) { if (serviceHost != null) { serviceHost.Close(); } serviceHost = new ServiceHost(typeof(Calculator)); serviceHost.Open(); } protected override void OnStop() { if (serviceHost != null) { serviceHost.Close(); serviceHost = null; } } } class Program { static void Main(string[] args) { ServiceBase.Run(new serviceCalss()); //服务的地址 Uri baseAddress = new Uri("http://localhost:8000/GettingStarted/"); //承载服务的宿主 ServiceHost selfHost = new ServiceHost(typeof(Calculator), baseAddress); //try //{ // selfHost.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "Calculator"); // ServiceMetadataBehavior smb = new ServiceMetadataBehavior(); // smb.HttpGetEnabled = true; // selfHost.Description.Behaviors.Add(smb); // selfHost.Open(); // Console.WriteLine("The service is ready."); // Console.WriteLine("input<exit> to terminate service."); // Console.WriteLine(); // while ("exit" == Console.ReadLine()) // { // selfHost.Close(); // } //} //catch (CommunicationException ex) //{ // Console.WriteLine(ex.Message); // selfHost.Abort(); // Console.ReadLine(); //} } } [RunInstaller(true)] public class ProjectInstaller : Installer { private ServiceProcessInstaller process; private ServiceInstaller service; public ProjectInstaller() { process = new ServiceProcessInstaller(); process.Account = ServiceAccount.LocalSystem; service = new ServiceInstaller(); service.ServiceName = "WCFWindowsServiceSample"; Installers.Add(process); Installers.Add(service); } } }
对应的配置文件
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <system.serviceModel> <services> <!-- This section is optional with the new configuration model introduced in .NET Framework 4. --> <service name="GettingStartedLib.Calculator" behaviorConfiguration="CalculatorServiceBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:8000/"/> </baseAddresses> </host> <endpoint address="" binding="wsHttpBinding" contract="Calculator" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="CalculatorServiceBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="False"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
4 服务端承载好了,就可以用客户端调用服务了,又两种方式
客户端直接引用已经运行的服务,vs会自动生成一个对应的client类,里边又设置好的服务路径和接口之类的,也可以自己指定路径,只用他的函数,记得close
// CalculatorClient client = new CalculatorClient(); var client = myChannelFactory.CreateChannel(); double value1 = 100.00D; double value2 = 15.99D; double result = client.Add(value1, value2); Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
也可以用ChannelFactory<TChannel>类来构造一个client,这种模式是引用服务的契约接口,
var myBinding = new WSHttpBinding(); EndpointAddress myEndpoint = new EndpointAddress("http://localhost:8000/GettingStarted/"); //EndpointAddress myEndpoint = new EndpointAddress("http://localhost:8000/"); ChannelFactory<ICalculator> myChannelFactory = new ChannelFactory<ICalculator>(myBinding, myEndpoint);
这里边都可以用配置文件来设置终结点,也就是绑定类型和地址,配置方式与服务端类似
5,6就是配置和调用,这里就不说了
这里只是简单的wcf实例,
实际还有内容没有涉及:
1 不同的绑定类型,怎么选择,网上有个图是判断什么情况选择对应的绑定类型,其实就是选择协议
2 加密,因为之间的程序用的是basichttpbingding就是就是基本的http绑定,客户端没用引用服务端,服务端以web服务的形式公开服务,客户端直接以http请求的形式来与服务端通信,这样客户端cs和bs可以公用一个服务端,但是没有加密,传输的内容是完全公开的··讲道理web服务这玩意本来就是发过去给人看的·公开应该也没啥好像····,面试的时候问到这个问题直接懵逼了···这里暂时没写 ··这玩意好像挺麻烦的···
3 这里在4.5的环境下编写的,服务公开以后会自动生成对应的异步执行方法,与之前的方式不同,之前是老式的beginxxx的异步方式,返回一个Iasyncresult的结果,这里是4.5的async/await的形式来执行异步操作
4 wcf服务是可以兼容asp.net ,也可以返回json格式的对象··这个可以通过设置方法的操作特性可以设置
5 wcf服务支持双工,这个就很流弊了,不过讲道理tcp这种本身就支持双工,没具体研究
6 在双工模式下支持回调,这个意思好像是客户端请求完成再出发一个回调服务么··· 没研究···
。。。。。。