一、 WCF是什么?

Windows Communication Foundation(WCF)是由微软发展的一组数据通信的应用程序开发接口,可以翻译为Windows通讯接口。

二、 WCF是用来做什么的?

WCF就是用来通信的。它集合了多种平台,多种协议,多种方式收发客户和服务之间的消息。

例子:我们在windows下玩网络游戏,每一个操作都会通过网络传给服务器,服务器可能是windows也可能是Unix,它接收到这个操作命令,处理后反馈给我们。接收,处理,反馈,这中间的部分就是WCF的用途。

这时候不妨先放下WCF的相关知识概念,用VS创建一个WCF程序来一探究竟。

1> 新建项目,选择WCF服务应用程序

2> 新建完成后删除自动生成的IService1.cs和Service1.svc

3> 添加新建项,选择WCF服务文件,命名为Test.svc,系统会自动生成它的接口ITest

4> 如下所示,修改ITest和Test的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
[ServiceContract]
public interface ITest
{
[OperationContract]
string DoWork(string name);
}
public class Test : ITest
{
public string DoWork(string name)
{
return "hello " + name;
}
}

5> F5运行程序,VS会启动WCF客户端测试工具。双击DoWork方法,填入一个值,点击调用,即可看到结果。

这就是WCF,我们提交一个请求,和服务通讯,服务处理后给我们响应。这个程序和普通的c#程序唯一的区别就是在ITest接口中出现了[ServiceContract]和[OperationContract]。

[ServiceContract]用来说明是一个WCF的接口

[OperationContract]用来说明是一个WCF接口的方法

它们都需要System.ServiceModel这个引用。运行完这个程序,大概能对WCF有个印象了,知道它是一个通信接口,那么它是如何来通讯的?

三、WCF通信过程是怎样的?

通过终结点EndPoint实现。

Endpoint是由地址Address,通讯方式Binding和契约Contract组成。也就是通常说的ABC。其中:
A是WCF服务寄宿的地址,通过这个地址,才能够联系到WCF;
B是通讯方式,它决定了客户端和服务端通讯时候的编码,协议等等;
C是契约,也就是WCF提供的方法,用来处理具体问题。就是上面例子中ITest下所有可能的方法。

WCF把endpoint当做标识分发给客户端和服务端。服务端监听所有客户的endpoint请求,当发现与客户的endpoint相同时,建立起通讯。那么Endpoint在哪?我们已经有了一个WCF程序,当然还需要一个客户端,一个服务端。看下面一个例子:

首先需要写一个服务端,把WCF寄宿在上面。(请用管理员身份打开VS以免运行时权限不够造成报错)

1> 新建项目,创建一个winform程序

2> 添加System.ServiceModel引用,添加上一步WCF程序的dll引用

3> 添加应用程序配置文件App.config并填写如下内容(注意WcfService2这个命名空间填写自己上一步创建的WCF程序的命名空间)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="WcfService2.Test">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8082/Test"/>
</baseAddresses>
</host>
<endpoint address="" binding="wsHttpBinding" contract="WcfService2.ITest"></endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

4> 前台添加两个按钮和一个Label,同时后台代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public partial class Form1 : Form
{
ServiceHost host;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
try
{
host = new ServiceHost(typeof(Test));
//打开宿主
host.Open();
label1.Text = "WCF启动";
}
catch(Exception er)
{
label1.Text = "WCF启动异常!"
}
}
private void button2_Click(object sender, EventArgs e)
{
host.Close();
label1.Text = "WCF关闭";
}
}

运行程序,点击打开WCF服务看看是否成功。至此,我们把WCF寄宿在winform上,并建立了服务端。同时我们也看到刚才填写的App.config中填入了下面的信息:

1
2
3
4
5
6
<host>
<baseAddresses>
<add baseAddress="http://localhost:8082/Test"/>
</baseAddresses>
</host>
<endpoint address="" binding="wsHttpBinding" contract="WcfService2.ITest">

这里就是服务端的endpoint所在,由a,b,c三部分组成。但是address却为空,这是因为我们用了一个HOST,把WCF附在上面,此时的endpoint的a就默认是host的address。

接下来创建一个客户端:

1> 打开刚才的服务端,开启WCF服务,然后新建一个空的ASP.NET应用程序

2> 添加引用服务,地址填写host的地址http://localhost:8082/Test,点击转到,然后确定

3> 新建一个aspx窗体,前台后台代码如下:

1
2
3
4
5
6
<body>
<form id="form1" runat="server">
<asp:TextBox ID="txtName" runat="server"></asp:TextBox><br />
<asp:Button ID="btnSubmit" runat="server" Text="提交" OnClick="btnClick" />
</form>
</body>
1
2
3
4
5
6
protected void btnClick(object sender, EventArgs e)
{
TestClient t = new TestClient();
string result = t.DoWork(this.txtName.Text);
Response.Write(result);
}

4> 运行这个页面,就可以和服务端进行交互了。如果这时候关闭服务端的WCF服务,提交页面就会报错。

那么客户端的endpoint在哪?我们看看这个ASP.NET的web.config文件,发现endpoint已经在这了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_ITest" />
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:8082/Test" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_ITest" contract="ServiceReference1.ITest"
name="WSHttpBinding_ITest">
<identity>
<userPrincipalName value="DAFFODIL-PC\Rang" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>

这是我们在添加引用服务的时候,VS帮我们自动填好了与之对应的endpoint。

四、WCF如何存在?

依附宿主,不能独立存在。

经过上面几个问题和实例,我们先是用了VS自带的WCF客户端测试工具调试,然后又把WCF附加在winform上。所以抛出了这个问题,WCF如何存在?在问题一中已经说到WCF集合了多种平台多种协议,那么它也能有多种存在方式。但不管是哪一种,要想WCF程序发挥作用,它一定要有一个宿主来依附,不能独立存在!在实际应用中,WCF常常部署在IIS上,也能附在windows窗体,windows服务,控制台程序,was等等。

我们可以把第二个问题中创建的WCF程序发布到IIS上。

1> 右键创建好的WCF项目,选择发布

2> 新建一个配置文件

3> 服务器填写localhost,站点名称填写IIS中建立好的站点名,目标url填写后可以让浏览器在发布后自动打开这个目标url。点击发布即可。

4> 用iis启动这个网站,点击Test.svc,出现如下界面说明部署成功

五、WCF通讯方式?

问答,单向,双向

在上面的例子中,我们使用的WCF通讯方式是默认的问答模式,也就是请求与答复模式。它有返回值可以为客户提供消息反馈,但必须要等待事物处理完毕后才能反馈消息,在这段时间中,客户只能等待响应。我们可以在DoWork方法中加一个Sleep,让程序停止5秒,然后运行客户端,就会发现客户端收到反馈是5秒钟以后的事了。

WCF还提供了单向通讯模式,客户端发出请求后不用等待服务端反馈就能继续执行。所以单向通讯模式要求操作不能有返回值,不能有引用参数和输出参数。我们可以把DoWork的返回值改成void并声明它是一个单向通讯

1
2
3
4
5
public interface ITest
{
[OperationContract(IsOneWay=true)]
void DoWork();
}

还是sleep 5秒,在客户端调用一下这个方法,并且在调用方法前后加一个时间输出,可以看到输出的时间是没有超过5秒间隔的。

1
2
3
4
Console.WriteLine(DateTime.Now);
//调用WCF服务方法
client.DoWork();
Console.WriteLine(DateTime.Now);

最后是双向模式,这个模式在上面两种模式的基础上允许服务端可以调用客户端的方法。

修改WCF代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[ServiceContract(CallbackContract = typeof(ICallback))]
public interface ITest
{
[OperationContract]
string DoWork(string name);
}
public interface ICallback
{
[OperationContract(IsOneWay=true)]
void Call(string str);
}
public class Test : ITest
{
public ICallback callback;
public string DoWork(string name)
{
callback = OperationContext.Current.GetCallbackChannel<ICallback>();
callback.Call("服务端调用");
return "hello " + name;
}
}

修改客户端继承回调接口

1
2
3
4
5
6
7
public class CallBack : ITestCallback
{
public void Call(string str)
{
Console.Write(str);
}
}

因为加入了回调,这时候直接实例化TestClient t = new TestClient() 是会报错的,需要加入服务的上下文来使用回调:

1
2
InstanceContext instanceContext = new InstanceContext(new CallBack());
TestClient t = new TestClient(instanceContext);

当客户端请求WCF执行DoWork时,服务端回调客户端的CallBack方法。这就是WCF双向通讯模式。