学习笔记---Web服务、Remoting、WCF (下) --- Remoting and WCF
接上:
2. .NET Remoting
.NET Remoting的思想: 通过通道(协议+端口)来获得远程服务器类库中对象的引用. 在学习Socket的时候, 我们知道微软将IP地址和端口号封装在了终结点EndPoint中, 类似的Remoting中的通道就是把通信协议和端口号封装在一起的产物. .Net支持两种访问远程服务器对象的模式: 服务器起激活模式(well-know)和客户端激活模式(client-activated).
两种激活模式的通信方式可以见下图:
注意, Remoting的服务器激活模式中, 客户端获得的是服务器类库中某个类的对象的引用, 而不是具体的那个对象. 如图所示, .NET的服务器激活模式, 有两种工作方式: Singleton和SingleCall, Singleton将在整个服务器运行期间只创建一个对象为所有客户的所有请求服务, 因此该对象将非常繁忙, 效率不会很高; 而SingleCall将会为所有客户的所有请求都创建一个对象, 此时理论上速度会快, 但实际上当访问人数较多或频繁访问较多时, 服务器内存开销很大, 导致服务器运行速度缓慢. 我们在客户端要使用服务器上的类库, 首先要添加类库, 但是既然在项目中可以直接拿到服务器类库的程序集, 为什么还要去访问服务器呢? 所以, 服务器激活模式通常配合接口使用, 我们可以制作客户端和服务器共用的接口, 服务器传仍然传递类, 但客户端用接口去接收, 如此便可以避免添加服务器程序集的问题.
.NET的客户端激活模式中, 改进了上述的缺点: 在客户端模式中, 服务器将针对每个客户创建一个对象, 这样避免了服务器激活模式中的两个极端. 同时在客户端也加入了伪类库, 伪类库并不需要具体的服务器端的实现代码, 只要在伪类库中返回最简单的初始化结果就行. 因此在开发过程中, 只要把伪类库中的所有的方法返回初始值即可, 这种情况就类似于接口但不是接口, 所有的方法名称, 参数列表都和服务器真类库相同, 只是方法体直接返回默认值而已. 可以从建模的角度来将伪类库理解为接口.
a.服务器激活模式:
服务器端:
1.) 共享代码的类: 新建类库项目, 类库中提供了接口和要共享方法的类, 因为涉及到远程调用(封送), 所以共享代码的类必须继承自MarshalByRefObject类
2.)注册通道: 新建服务端, 服务端添加程序集System..Runtime.Remoting , 之后服务器端只要通过System..Runtime.Remoting.Channels.ChannelService类注册通道即可, 注意注册方法的参数是个接口(多肽), 而System.Runtime.Remoting.Channels.Tcp.TcpServerChannel实现了该接口
3.) 声明远程数据类型: 通过System.Runtime.Remoting.RemotingConfiguration声明远程类型的数据类型
服务器和客户端共用的接口代码, // RemotingServerActivedMode\ICompute\ICompute\Class1.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ICompute
{
public interface ICompute
{
double Compute(string expression);
}
}
服务器上的类库代码, // RemotingServerActivedMode\RemotingSrv\RemotingSvcLib\SrbLib.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RemotingSvcLib
{
public class SrvLib : MarshalByRefObject, ICompute.ICompute
{
//每当创建一个对象时, 便会输出一句话
public SrvLib()
{
Console.WriteLine("服务器类库: 创建了一个新对象");
}
public double Compute(string expression)
{
//处理最开始的-或者+号
if (expression.StartsWith("+") || expression.StartsWith("-"))
{
expression = "0" + expression;
}
//1. 将+、-、*、/分离成出来
expression = expression.Replace("+", ",+,");
expression = expression.Replace("-", ",-,");
expression = expression.Replace("*", ",*,");
expression = expression.Replace("/", ",/,");
string[] infos = expression.Split(',');
Stack<string> stack = new Stack<string>(); //使用list模拟堆栈
//2. 利用堆栈计算乘除的结果
double prenum;
double nextnum;
for (int i = 0; i < infos.Length; i++)
{
if (infos[i] == "*" || infos[i] == "/")
{
prenum = Convert.ToDouble(stack.Pop());
nextnum = Convert.ToDouble(infos[i + 1]);
if (infos[i] == "*")
{
stack.Push((prenum * nextnum).ToString());
}
else
{
stack.Push((prenum / nextnum).ToString());
}
i++; //别忘了i要++
}
else
{
stack.Push(infos[i]);
}
}
//3. 利用堆栈计算加减的结果
//infos = stack.ToArray(); //将stack转存到数组中, 这里转存的结果是逆序的
infos = new string[stack.Count];
for (int i = infos.Length - 1; i >= 0; i--) //将stack正序转存到数组中
{
infos[i] = stack.Pop();
}
prenum = 0; //重置prenum和nextnum
nextnum = 0;
for (int i = 0; i < infos.Length; i++)
{
if (infos[i] == "+" || infos[i] == "-")
{
prenum = Convert.ToDouble(stack.Pop());
nextnum = Convert.ToDouble(infos[i + 1]);
if (infos[i] == "+")
{
stack.Push((prenum + nextnum).ToString());
}
else
{
stack.Push((prenum - nextnum).ToString());
}
i++; //别忘了i++
}
else
{
stack.Push(infos[i]);
}
}
return Convert.ToDouble(stack.Pop());
}
}
}
服务器上的服务端代码,// RemotingServerActivedMode\RemotingSrv\RemotingSrv\Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RemotingSrv
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("服务器: 这里是服务器, 我已启动.");
#region 普通方式, 客户端会直接拿到类库, 因此就不再需要通过通道获取了
/*
//1.注册通道:
//System.Runtime.Remoting.Channels.Tcp.TcpServerChannel实现了System.Runtime.Remoting.Channels.IChannel(多肽)
System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(new System.Runtime.Remoting.Channels.Tcp.TcpServerChannel(5267),false);
//2.声明远程类型, 服务器上可以有多个类库, "Compute"是供客户端识别用的
System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingSvcLib.SrvLib),"Compute",System.Runtime.Remoting.WellKnownObjectMode.SingleCall);
//System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownServiceType(typeof(SrvLib), System.Runtime.Remoting.WellKnownObjectMode.Singleton);
*/
#endregion
#region 通过接口实现, 可以避免上面的问题. 客户端拿到的只是接口
//1.注册通道:
//System.Runtime.Remoting.Channels.Tcp.TcpServerChannel实现了System.Runtime.Remoting.Channels.IChannel(多肽)
System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(new System.Runtime.Remoting.Channels.Tcp.TcpServerChannel(5267), false);
//2.声明远程类型, 服务器上可以有多个类库, "Compute"是供客户端识别用的. 因为接口不能继承MarshalByRefObject, 这里只能传递类
System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingSvcLib.SrvLib), "Compute", System.Runtime.Remoting.WellKnownObjectMode.SingleCall);
//System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownServiceType(typeof(SrvLib), System.Runtime.Remoting.WellKnownObjectMode.Singleton);
#endregion
Console.ReadKey();
}
}
}
客户端:
1.) 注册通道: 添加System.Runtime.Remoting程序集, 使用ChannelService注册客户端通道TcpClientChannel
2.) 通过Activator.GetObject()方法获得远程服务器上对象的引用. 注意: 采用的协议和端口要匹配, 并且uri的标识必须与服务器上的相同. 之后就可以使用对象上的方法了.
客户端的代码, // RemotingServerActivedMode\RemotingClient\RemotingClient\Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace RemotingClient
{
public partial class mainForm : Form
{
//RemotingSvcLib.SrvLib sl; //这里, 在客户端去掉了服务器端的程序集RemotingSvcLib.dll
ICompute.ICompute ic;
public mainForm()
{
InitializeComponent();
Initdata();
}
private void Initdata()
{
//1. 注册通道
System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(new System.Runtime.Remoting.Channels.Tcp.TcpClientChannel(), false);
//2. 通过Activator远程获得服务器上对象的引用, 后便的服务器
//object obj = Activator.GetObject(typeof(RemotingSvcLib.SrvLib), "tcp://192.168.0.67:5267/Compute");
object obj = Activator.GetObject(typeof(ICompute.ICompute), "tcp://192.168.0.67:5267/Compute"); //客户端使用接口来接收
//sl = obj as RemotingSvcLib.SrvLib;
ic = obj as ICompute.ICompute;
}
public delegate double DelCompute(string expression);
private void btn_cal_Click(object sender, EventArgs e)
{
#region 同步调用
//this.txt_cal.Text += " = " + sl.Compute(this.txt_cal.Text.Trim()).ToString();
#endregion
#region 异步调用, 系统没有提供Async方法和事件处理器. 因此, 使用委托+回调函数
//DelCompute dc = new DelCompute(sl.Compute); //注意, 这里绑定的是要传递或执行的方法, 即从类库获得的对象的Compute方法
DelCompute dc = new DelCompute(ic.Compute);
dc.BeginInvoke(this.txt_cal.Text.Trim(), new AsyncCallback(DealResult), dc);
#endregion
}
private void DealResult(IAsyncResult ia)
{
DelCompute dc = ia.AsyncState as DelCompute;
double d = dc.EndInvoke(ia);
this.txt_cal.Text += " = " + d.ToString();
}
}
}
b. 客户端激活模式:
服务器端: 服务器端的代码和服务器激活模式中的服务器代码非常相似, 只是在声明类型时声明为Activated数据类型即可.
服务器上的类库代码, // RemotingClientActivedMode\RemotingSrv\RemotingSvcLib\SrvLib.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RemotingSvcLib
{
public class SrvLib : MarshalByRefObject, ICompute.ICompute
{
//每当创建一个对象时, 便会输出一句话
public SrvLib()
{
Console.WriteLine("服务器类库: 创建了一个新对象");
}
#region ICompute Members
public double Compute(string expression)
{
//处理最开始的-或者+号
if (expression.StartsWith("+") || expression.StartsWith("-"))
{
expression = "0" + expression;
}
//1. 将+、-、*、/分离成出来
expression = expression.Replace("+", ",+,");
expression = expression.Replace("-", ",-,");
expression = expression.Replace("*", ",*,");
expression = expression.Replace("/", ",/,");
string[] infos = expression.Split(',');
Stack<string> stack = new Stack<string>(); //使用list模拟堆栈
//2. 利用堆栈计算乘除的结果
double prenum;
double nextnum;
for (int i = 0; i < infos.Length; i++)
{
if (infos[i] == "*" || infos[i] == "/")
{
prenum = Convert.ToDouble(stack.Pop());
nextnum = Convert.ToDouble(infos[i + 1]);
if (infos[i] == "*")
{
stack.Push((prenum * nextnum).ToString());
}
else
{
stack.Push((prenum / nextnum).ToString());
}
i++; //别忘了i要++
}
else
{
stack.Push(infos[i]);
}
}
//3. 利用堆栈计算加减的结果
//infos = stack.ToArray(); //将stack转存到数组中, 这里转存的结果是逆序的
infos = new string[stack.Count];
for (int i = infos.Length - 1; i >= 0; i--) //将stack正序转存到数组中
{
infos[i] = stack.Pop();
}
prenum = 0; //重置prenum和nextnum
nextnum = 0;
for (int i = 0; i < infos.Length; i++)
{
if (infos[i] == "+" || infos[i] == "-")
{
prenum = Convert.ToDouble(stack.Pop());
nextnum = Convert.ToDouble(infos[i + 1]);
if (infos[i] == "+")
{
stack.Push((prenum + nextnum).ToString());
}
else
{
stack.Push((prenum - nextnum).ToString());
}
i++; //别忘了i++
}
else
{
stack.Push(infos[i]);
}
}
return Convert.ToDouble(stack.Pop());
}
#endregion
}
}
服务器端代码, // RemotingClientActivedMode\RemotingSrv\RemotingSrv\Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RemotingSvcLib;
namespace RemotingSrv
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("服务器: 这里是服务器, 我已启动.");
//1.注册通道:
//System.Runtime.Remoting.Channels.Tcp.TcpServerChannel实现了System.Runtime.Remoting.Channels.IChannel(多肽)
System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(new System.Runtime.Remoting.Channels.Tcp.TcpChannel(5267),false);
//2.声明远程类型, 服务器上可以有多个类库, "Compute"是供客户端识别用的. 由于接口不能继承MarshalByRefObject, 注册类型不能为ICompute接口
System.Runtime.Remoting.RemotingConfiguration.RegisterActivatedServiceType(typeof(RemotingSvcLib.SrvLib));
Console.ReadKey();
}
}
}
客户端:
1.) 在客户端创建类库(伪类库), 只进行简单的实现(从建模的角度讲, 可以认为是一种接口. 但其实际上不是接口, 是一种欺骗客户端的手法, 成为替代类).
2.) 如同服务器激活模式中的客户端: 注册通道、 声明远程类型(Activated)类型, 之后便可以通过new创建对象并调用该对象的方法了.
特别注意:
客户端激活模式的接口和实现分离的情况, 目前百度能找到的是两种解决方法: 一种是替代类; 一种是工厂+Singleton, 这里为简单使用替代类. 使用替代类时, 当创建RemotingSvcLib.SrvLib对象时, 实际上会到服务器上找, 采取的是一种欺骗手段.
关于这种欺骗手段有2个注意事项: (好多人报错: 类型没有注册到激活、 Type … is not registered for Activation. 就是这个原因)
1.客户端的假类库要和服务器的真类库一样(除了方法的实现), 除了类名、命名空间外, 还要特别注意程序集名称也要一样, 可以在类库项目的属性中, 修改程序集名称.
2.另外, 为了使生成的程序集不包含项目文件的名称, 特别选择Release方式来生成. 最后就是类库项目的程序集文件不要添加错了.
//客户端上的类库代码, //RemotingClientActivedMode\RemotingClient\RemotingCltLib\SrvLib.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RemotingSvcLib //使用替代类欺骗客户端, 需要客户端的命名空间和服务器一样
//namespace RemotingClient
{
public class SrvLib : MarshalByRefObject , ICompute.ICompute
{
#region ICompute Members
public double Compute(string expression)
{
return 0;
}
#endregion
}
}
//客户端上的代码, //RemotingClientActivedMode\RemotingClient\RemotingClient\Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace RemotingClient
{
public partial class mainForm : Form
{
RemotingSvcLib.SrvLib ic;
public mainForm()
{
InitializeComponent();
this.btn_con.Enabled = true;
this.btn_con_srv.Enabled = false;
this.btn_cal.Enabled = true;
}
public delegate double DelCompute(string expression);
private void btn_cal_Click(object sender, EventArgs e)
{
this.btn_con.Enabled = false;
this.btn_con_srv.Enabled = false;
this.btn_cal.Enabled = true;
AsyncCompute();
}
private void AsyncCompute()
{
ic = new RemotingSvcLib.SrvLib();
#region 同步调用
//this.txt_cal.Text += " = " + ic.Compute(this.txt_cal.Text.Trim()).ToString();
#endregion
#region 异步调用, 系统没有提供Async方法和事件处理器. 因此, 使用委托+回调函数
DelCompute dc = new DelCompute(ic.Compute); //注意, 这里绑定的是要传递或执行的方法, 即从类库获得的对象的Compute方法
dc.BeginInvoke(this.txt_cal.Text.Trim(), new AsyncCallback(DealResult), dc);
//为了保证子线程能够正常结束, 这里Sleep10秒
System.Threading.Thread.Sleep(10000);
#endregion
}
private void DealResult(IAsyncResult ia)
{
DelCompute dc = ia.AsyncState as DelCompute;
double d = dc.EndInvoke(ia);
this.txt_cal.Text += " = " + d.ToString();
}
private void btn_con_Click(object sender, EventArgs e)
{
this.btn_con.Enabled = false;
this.btn_con_srv.Enabled = true;
this.btn_cal.Enabled = false;
#region 使用替代类来实现
//1. 注册通道, 使用TcpChannel
System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(new System.Runtime.Remoting.Channels.Tcp.TcpChannel(), false);
//2. 声明客户Activated数据类型
System.Runtime.Remoting.RemotingConfiguration.RegisterActivatedClientType(typeof(RemotingSvcLib.SrvLib), "tcp://192.168.0.67:5267");
//这种接口和实现分离的情况, 在百度找到两种解决方案: 一种是替代类; 一种是工厂+Singleton, 这里为简单使用替代类. 使用替代类时, 当创建RemotingSvcLib.SrvLib对象时, 实际上会到服务器上找, 采取的是一种欺骗手段.
//关于这种欺骗手段有2个注意事项:
//1.客户端的假类库要和服务器一样(除了方法的实现), 除了类名、命名空间外, 还要特别注意程序集名称也要一样, 可以在类库项目的属性中, 修改程序集名称.
//2.另外, 为了使生成的程序集不包含项目文件的名称, 特别选择Release方式来生成. 最后就是类库项目的程序集文件不要添加错了
#endregion
}
private void btn_con_srv_Click(object sender, EventArgs e)
{
AsyncCompute();
}
}
}
3. WCF: Windows Communication Foundation, Windows的通信基础.
WCF是融合了Web服务、Remoting、WSE、MSMQ等技术的集大成者, 并且为我们提供了统一的开发标准. WCF的基本思想是: Web服务+Remoting, 通过本地代理类来访问远程服务器上的类库, 单纯对我们开发人员来说, 使用WCF进行开发将会更加方便和容易. 值得留意的是, WCF的大量配置工作都放在了配置文件中.
WCF项目通常分为三部分: 服务器、客户端、包含功能模块的类库.
a. 创建WCF服务类库项目, 该项目默认包含一个接口、一个实现类和一个配置文件. 接口文件在WCF中称作契约或者合约(Contract)用来声明允许用户使用的方法. 实现类就是真正为用户提供服务或者说用来共享给用户的代码. 最后的配置文件, 并不是类库项目用的, 而是给服务器应用程序(服务端)使用的.
但我们想把某个方法放到WCF服务库中去时, 首先要在WCF服务库项目的IService接口中, 添加方法的规定(也就是先声明方法). 在这个接口中, [ServiceContract]标签用于放在提供方法约定的接口上, [OperationContract]用于放在要共享的方法约定上, 也就是说我们自己添加了方法约定后, 要在前面加上[OperationContract]标签属性. 之后, 我们可以再Service.cs文件中, 也就是该接口的实现类中, 添加自己的是实现代码.
接口中定义的方法, 若涉及到复杂类型. 那么复杂类型也需要在接口文件中声明, 并且在类型前加[DataContract]标签, 类的属性成员前也要加[DataMember]标签, 这样便可以在通信过程中传递复杂类型.
WCF服务类库契约代码, //Wcf\WcfSvcLib\WcfSvcLib\IService.sc
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WcfSvcLib
{
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
[ServiceContract]
public interface IService //接口中定义的方法, 就是将来允许客户使用的方法
{
[OperationContract]
string GetData(int value);
[OperationContract] //接口中定义的方法, 有复杂类型. 你那么复杂类型也需要在接口文件中声明, 并且在类型前加[DataContract]标签, 类的属性成员前也要加[DataMember]标签, 这样便可以在通信过程中传递复杂类型.
CompositeType GetDataUsingDataContract(CompositeType composite);
// TODO: Add your service operations here
[OperationContract]
double Compute(string expression);
}
// Use a data contract as illustrated in the sample below to add composite types to service operations
[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; }
}
}
}
WCF服务类库实现类代码. // Wcf\WcfSvcLib\WcfSvcLib\Service.sc
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WcfSvcLib
{
// NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in App.config.
public class Service : IService
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
if (composite.BoolValue)
{
composite.StringValue += "Suffix";
}
return composite;
}
#region IService Members
public double Compute(string expression)
{
//处理最开始的-或者+号
if (expression.StartsWith("+") || expression.StartsWith("-"))
{
expression = "0" + expression;
}
//1. 将+、-、*、/分离成出来
expression = expression.Replace("+", ",+,");
expression = expression.Replace("-", ",-,");
expression = expression.Replace("*", ",*,");
expression = expression.Replace("/", ",/,");
string[] infos = expression.Split(',');
Stack<string> stack = new Stack<string>(); //使用list模拟堆栈
//2. 利用堆栈计算乘除的结果
double prenum;
double nextnum;
for (int i = 0; i < infos.Length; i++)
{
if (infos[i] == "*" || infos[i] == "/")
{
prenum = Convert.ToDouble(stack.Pop());
nextnum = Convert.ToDouble(infos[i + 1]);
if (infos[i] == "*")
{
stack.Push((prenum * nextnum).ToString());
}
else
{
stack.Push((prenum / nextnum).ToString());
}
i++; //别忘了i要++
}
else
{
stack.Push(infos[i]);
}
}
//3. 利用堆栈计算加减的结果
//infos = stack.ToArray(); //将stack转存到数组中, 这里转存的结果是逆序的
infos = new string[stack.Count];
for (int i = infos.Length - 1; i >= 0; i--) //将stack正序转存到数组中
{
infos[i] = stack.Pop();
}
prenum = 0; //重置prenum和nextnum
nextnum = 0;
for (int i = 0; i < infos.Length; i++)
{
if (infos[i] == "+" || infos[i] == "-")
{
prenum = Convert.ToDouble(stack.Pop());
nextnum = Convert.ToDouble(infos[i + 1]);
if (infos[i] == "+")
{
stack.Push((prenum + nextnum).ToString());
}
else
{
stack.Push((prenum - nextnum).ToString());
}
i++; //别忘了i++
}
else
{
stack.Push(infos[i]);
}
}
return Convert.ToDouble(stack.Pop());
}
#endregion
}
}
WCF服务类库配置文件, //Wcf\WcfSvcLib\WcfSvcLib\App.config //是给服务器端用的
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<!--包含了提供的所有服务, name属性其实就是WCf服务库的实现类的名字(带命名空间), 而behaviorConfiguration是用来调试的, 部署时需要删掉-->
<service name="WcfSvcLib.Service" behaviorConfiguration="WcfSvcLib.ServiceBehavior">
<host>
<baseAddresses>
<!--baseAddresses指明了服务器的通信地址, 端口后面的内容仅是设计时的地址, 发布后需要改成服务器的路径-->
<add baseAddress = "http://192.168.0.67:8731/WcfSvcLib/Service/" />
</baseAddresses>
</host>
<!-- Service Endpoints -->
<!-- Unless fully qualified, address is relative to base address supplied above -->
<!--终结点的配置: address表示相对地址, 而上面的baseAddress就是基地址, 最终地址是"基地址+/相对地址"; binding是绑定的用于通信的协议, 而Contract就是契约的名字-->
<endpoint address ="" binding="wsHttpBinding" contract="WcfSvcLib.IService">
<!--
Upon deployment, the following identity element should be removed or replaced to reflect the
identity under which the deployed service runs. If removed, WCF will infer an appropriate identity
automatically.
-->
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<!-- Metadata Endpoints -->
<!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
<!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<!--配置实施行为, 与服务节点中behaviorConfiguration相关, 在工作时整个behavior和上面的Service中的behaviorConfiguration都要删掉-->
<serviceBehaviors>
<behavior name="WcfSvcLib.ServiceBehavior">
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
b.创建WCF服务端项目: 微软产品的一贯特色就是”越复杂的东西, 留给我们程序员的接口就越简单”, 服务器端代码一共4行, 很简单. 关于WCF通信的协议、端口等等信息都放在了配置文件中, 因此WCF中配置文件很重要.
要做的第一件事, 添加System.Runtime.Serialization和System.ServiceModel程序集, 之后讲类库中的App.config也添加到服务端. 当然, WCF服务库也要添加到WCF服务端中, 不然拿啥去服务?
服务端代码, //WcfServer\Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace WcfServer
{
class Program
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(WcfSvcLib.Service)); //WcfSvcLib.Service类是WCF服务类库中的实现类
host.Open();
Console.WriteLine("服务端已启动");
Console.ReadLine();
host.Close();
}
}
}
服务端配置文件, //WcfServer\App.config --- 就是从WCF服务类库项目中拿过来的
c.创建客户端项目:
首先从C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin目录中, 拷贝SvcUtil.exe到c:\temp临时文件夹下, 在cmd窗口中输入: svcutil http://192.168.0.67:8731/wcfsvclib/service/ /out:c:\temp\proxy.cs /config:app 后生成客户端用的代理类及配置文件.
然后, 创建客户端项目, 添加System.Runtime.Serialization、System.ServiceModel程序集. 之后在项目中添加生成的代理类proxy.cs及配置文件app.config.
最后, 在客户端通过ServiceClient类型的对象调用方法. 可以看出WCF中, 服务器端用SerivceHost类行的对象处理, 而客户端用SeriviceClient类的对象进行处理.
客户端代码, //WcfClient\WcfClient\Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace WcfClient
{
public partial class mainform : Form
{
public mainform()
{
InitializeComponent();
}
public delegate double DelCom(string expression);
private void btn_cal_Click(object sender, EventArgs e)
{
ServiceClient sc = new ServiceClient();
DelCom dc = new DelCom(sc.Compute);
dc.BeginInvoke(this.txt_showrst.Text.Trim(), new AsyncCallback(DealCallback), dc);
}
private void DealCallback(IAsyncResult ia)
{
DelCom dc = ia.AsyncState as DelCom;
double d = dc.EndInvoke(ia);
this.txt_showrst.Text += " = " + d.ToString();
}
private void btn_showclass_Click(object sender, EventArgs e)
{
ServiceClient sc = new ServiceClient();
WcfSvcLib.CompositeType ct = new WcfSvcLib.CompositeType();
ct.BoolValue = true;
ct.StringValue = this.txt_showrst.Text.Trim();
WcfSvcLib.CompositeType ct1 = sc.GetDataUsingDataContract(ct);
this.txt_showrst.Text = ct1.StringValue;
}
}
}
客服端用工具生成的代理类, //WcfClient\WcfClient\proxy.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.3615
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace WcfSvcLib
{
using System.Runtime.Serialization;
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="CompositeType", Namespace="http://schemas.datacontract.org/2004/07/WcfSvcLib")]
public partial class CompositeType : object, System.Runtime.Serialization.IExtensibleDataObject
{
private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
private bool BoolValueField;
private string StringValueField;
public System.Runtime.Serialization.ExtensionDataObject ExtensionData
{
get
{
return this.extensionDataField;
}
set
{
this.extensionDataField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public bool BoolValue
{
get
{
return this.BoolValueField;
}
set
{
this.BoolValueField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string StringValue
{
get
{
return this.StringValueField;
}
set
{
this.StringValueField = value;
}
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="IService")]
public interface IService
{
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IService/GetData", ReplyAction="http://tempuri.org/IService/GetDataResponse")]
string GetData(int value);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IService/GetDataUsingDataContract", ReplyAction="http://tempuri.org/IService/GetDataUsingDataContractResponse")]
WcfSvcLib.CompositeType GetDataUsingDataContract(WcfSvcLib.CompositeType composite);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IService/Compute", ReplyAction="http://tempuri.org/IService/ComputeResponse")]
double Compute(string expression);
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public interface IServiceChannel : IService, System.ServiceModel.IClientChannel
{
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class ServiceClient : System.ServiceModel.ClientBase<IService>, IService
{
public ServiceClient()
{
}
public ServiceClient(string endpointConfigurationName) :
base(endpointConfigurationName)
{
}
public ServiceClient(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public ServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public ServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public string GetData(int value)
{
return base.Channel.GetData(value);
}
public WcfSvcLib.CompositeType GetDataUsingDataContract(WcfSvcLib.CompositeType composite)
{
return base.Channel.GetDataUsingDataContract(composite);
}
public double Compute(string expression)
{
return base.Channel.Compute(expression);
}
}
客服端用工具生成配置文件, //WcfClient\WcfClient\App.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true"
algorithmSuite="Default" establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://192.168.0.67:8731/WcfSvcLib/Service/"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService"
contract="IService" name="WSHttpBinding_IService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>