在SilverLight中通过标准的BasicHttpBinding来调用WCF服务是非常容易的, 只要通过VS的添加服务引用功能添加一下就直接能用了, 但是通过net.tcp绑定来调用则相当麻烦.
一. 创建解决方案
首先在VS中创建一个新的SilverLight项目, 将项目命名为SilverLightTcpBindingSample, 在随后弹出的对话框中选择创建一个新的WebApplication作为SilverLight的宿主网站, 该网站的名字被自动命名为SilverLightTcpBindingSample.Web.
解决方案的结构为:
SilverLightTcpBindingSample(Solution)
--SilverLightTcpBindingSample(SilverLight Project)
--SilverLightTcpBindingSample.Web
二. 服务端的配置和部署
在SilverLightTcpBindingSample.Web项目中右击->Add->New Item->WCF Service, 保持默认的名字Service1, 项目中就会添加IService.cs和Service1.svc 两个文件.
在IService.cs中会自动生成一个名为IService1的接口 和一个默认的DoWork函数, 将它的定义稍做修改:
[ServiceContract]
public interface IService1
{
[OperationContract]
string DoWork(string text);
}
然后在Service1.svc中为这个函数添加点内容:
public class Service1 : IService1
{
public string DoWork(string text)
{
return string.Format("{0}, {1}", DateTime.Now.ToString("HH:mm:ss") , text.Length);
}
}
功能上就算完工了, 然后打开web.config, <system.serviceModel>节应该已经存在了, 如果不存在, 手工添加之.
按下述代码定义<system.serviceModel>节:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="tcpBindingConfig1">
<security mode="None"></security>
</binding>
</netTcpBinding>
</bindings>
<services>
<service name="SilverLightTCPBindingSample.Web.Service1">
<endpoint address=""
binding="netTcpBinding"
bindingConfiguration="tcpBindingConfig1"
contract="SilverLightTCPBindingSample.Web.IService1">
</endpoint>
<endpoint address="mex"
binding="mexTcpBinding"
contract="IMetadataExchange">
</endpoint>
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:4502/SilverLightTCPBindingSample.Web/Service1.svc"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
</system.serviceModel>
这里为不熟悉配置文件的同学稍微解释两句, 熟悉的请直接跳过这几段:
<bindings>节实际上是起configuration的作用, 它与任何的服务都不相关, 它定义具体某种binding的细节配置, 例如这里将tcpBinding的securityMode配置为None.
*需要注意的是: SilverLight不支持WCF的Security模型, 所以如果想在SL中调用此服务, 就必须把Security设置为None. 缺省模式下, SecurityMode是Transport, 所以这一节不可省略, 必须显式配置.
关于服务的信息都配置在Services节中. 这里有两个endpoint, 一个是供客户端调用的, 另一个是公布元数据的, 用于服务信息的生成. 在host节中添加的baseAddress需要注意, 4502这个端口号不是我瞎编的, 而是SilverLight只能使用4502至4534之间的端口( 我怎么知道的? 在SilverLight的异常中就有非常完整的提示, 这里节录一句: You may need to contact the owner of the service to expose a sockets cross-domain policy over HTTP and host the service in the allowed sockets port range 4502-4534) , 所以这里就使用了4502端口.
下面的Behaviors节和serviceHostingEnvironment 节是自动生成的, 不用管它.
好了!
现在直接就把SilverLightTcpBindingSample.Web项目发布到某个文件夹下(这里发布到D:\Websites\SilverLightTcpBindingSample.Web). 然后开始IIS的配置. (因为只有IIS才支持net.tcp绑定, 所以必须发布到iis才能进行后续的测试)
2.1 检查WCF Activation
WCF Activation是Windows的一个可选组件, 默认情况下并没有安装, 只有先装上它, IIS才能支持非HTTP管道的WCF调用.
在控制面板->程序->打开或关闭Windows功能中, 找到.Net framework 3.5 ( 或4.5, 如下图)
这里因为我的操作系统是win8, 所以同时存在.net 3.5和.net 4.5两项, 如果是win7及以下的操作系统, 应该只有.net framework 3.5, 将.net 3.5项展开, 勾选下面的WCF Http Activation和WCF Non-HTTP Activation.
*需要注意的是: 当确认并安装完成上述两项以后, IIS会变异常, 运行任意一个IIS下的项目都会报如下错误:
未能从程序集"System.ServiceModel,Version=3.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089"中加载类型"System.ServiceModel.Activation.HttpModule"
这时需要打开cmd, 定位到C:\Windows\Microsoft.NET\Framework\v4.0.30319目录下, 执行命令: aspnet_regiis –i, 重新安装iis才能修复.
因此如果需要在正式的web服务器上安装WCF Activation, 需要注意这个问题.
2.2 编辑默认网站的net.tcp绑定端口
本来这是一件很简单的事情, 在IIS中找到默认网站节点, 右击->Edit Bindings:
在弹出的窗口中找到net.tcp, 点击edit(默认是808, 这里我已经改成了4502) :
然后在弹出的编辑框中把808:* 改成4502:* 即可:
但是, 我的win8系统无论是点Add, 还是编辑, 一点保存一定报错, 错误信息是: Object reference not set to an instance of an object, 对象空引用…
难以相信微软老大会犯这么低级的错误, 但是他们就是这么干了, 咱能怎么着? 我在网上查了一下, 也有其他人说遇到了跟我同样的问题, 但是微软并没有正面回复. 我不知道win7下有没有问题, 但是windows server 2008下确定是没有问题.
总之, 如果你跟我一样遇到了这个令人蛋疼的问题, 就得换一种方式来修改端口号:
先从上面的编辑窗口中把net.tcp删了(删除是不会报错的..) ,
然后打开cmd, 定位到C:\Windows\System32\inetsrv 目录下, 执行下述命令:
appcmd set site /site.name:"Default Web Site" /+bindings.[protocol='net.tcp',bindingInformation='4502:*']
执行完了以后回到刚才的编辑binding窗口, 会发现net.tcp已经绑定到4502端口了.
然后在默认网站中添加Application, 把刚才发布的项目添加进IIS.
在项目上右击->Manage Applicatoin->Advanced Settings:
在弹出的窗口中最下面一行, Enabled Protocols栏, 原来只有一个http, 在后面加一个逗号, 写上net.tcp.
2.3 允许跨域访问
SilverLight有一个很令人厌恶的设定就是跨域访问限制, 如果要跨域访问, 必须在服务网站上放一个policy文件… 不说废话了, 直接上代码:
<?xml version="1.0" encoding ="utf-8"?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from>
<domain uri="*"/>
</allow-from>
<grant-to>
<socket-resource port="4502-4506" protocol="tcp" />
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
在IIS中定位到默认网站节点, 右击, 选explore, 即可打开网站所在的目录:
如果你没有修改过它, 这个路径应该是 C:\inetpub\wwwroot, 在这个文件下新一个文本文件, 将其命名为clientaccesspolicy.xml, 注意! 这个文件名不能弄错, 必须叫这个名字, 要连同原文本文件的扩展名一起改掉( 什么? 你的电脑上没有显示扩展名? …你真的是一个程序员吗? ) , 然后随便用什么文本编辑器打开这个文件, 把上面那一段贴进去, 保存.
2.4 测试服务
服务端到这里就差不多算是大功告成了, 从IIS中浏览一下Service1.svc吧, 你应该会看到如下的界面:
当然, 其实这个界面还是有问题的(问题无处不在啊…) , 由于我们在配置服务的时候使用的是tcp元数据endpoint, 所以实际上很多人说在上面这个界面中, 自动生成的提示应该类似于这样:
svcutil.exe net.tcp://localhost:4502/……
而不是像我的电脑这样, 还是http打头的地址.
事实上, 同样的步骤, 我在windows server 2008上确实看到IIS自动生成的界面上写着net.tcp://打头的地址, 可惜, 那是我很久以后才看到的, 一开始我在我自己电脑上测试的时候, 为此困惑了很久, 不明白为什么它要生成http://打头的地址, 可能又是万恶的win8吧…(我到底为什么要装win8?)
any way, 如果你跟我一样看到的是http://打头的地址, 也没关系, 实际上还是可以通过tcp地址生成服务引用的.
打开developer command promp for vs2012, (在开始菜单->vs工具正面), 先输入d:, 回车, 切换到d盘根目录, 然后输入下述命令:
svcutil net.tcp://localhost:4502/SilverLightTCPBindingSample.Web/Service1.svc/mex
它应该会在d盘根目录下生成service1.cs和output.config两个文件:
当然, 实际上这一步骤并不是必需, 这仍然是为了做测试, 以确保服务确实被正确配置了.
如果到这一步都没有出现问题, 那就说明服务器端的配置和部署, 是真的接近于完成了… ( 如果仅在localhost上测试, 就已经完成了, 如果这是正式的web server, 很可能还需要在防火墙中开放4502端口, 所以说是接近于完成)
三. SilverLight端的配置
3.1 引用服务
这里是个有点歧义的地方, 我见有人说这里可以直接引用net.tcp://打头的地址, 但是我测试是不可以的, 用的是浏览器中的http地址: http://localhost/SilverLightTCPBindingSample.Web/Service1.svc.
引用完成之后, 会自动生成一个ServiceReference.ClientConfig文件, 该文件的内容如下:
<configuration>
<system.serviceModel>
<bindings>
<customBinding>
<binding name="NetTcpBinding_IService1">
<binaryMessageEncoding />
<tcpTransport maxReceivedMessageSize="2147483647" maxBufferSize="2147483647" />
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="net.tcp://localhost:4502/SilverLightTCPBindingSample.Web/Service1.svc"
binding="customBinding" bindingConfiguration="NetTcpBinding_IService1"
contract="ServiceReference1.IService1" name="NetTcpBinding_IService1" />
</client>
</system.serviceModel>
</configuration>
这里需要注意的是: 如果严格按照前面我所说的顺序操作, 这里会自动生成这个配置文件, 但是我一开始做的时候, 服务端的元数据endpoint用的不是tcp而是namedPipes, 生成的配置文件是空的, 我困惑了很久无解, 后来无奈在这里手动敲入的配置. 最后才发现原来是元数据endpoint的问题.
3.2 编写测试页面
非常简单地, 在MainWindow.xaml中放两个控件:
<Grid>
<StackPanel>
<TextBox x:Name="txt1"></TextBox>
<Button x:Name="btn1" Content="Click me" Click="btn1_Click"></Button>
</StackPanel>
</Grid>
在cs文件中:
private void btn1_Click(object sender, RoutedEventArgs e)
{
var proxy = new ServiceReference1.Service1Client();
proxy.DoWorkCompleted += proxy_DoWorkCompleted;
proxy.DoWorkAsync(txt1.Text);
}
void proxy_DoWorkCompleted(object sender, ServiceReference1.DoWorkCompletedEventArgs e)
{
txt1.Text = e.Result;
}
编译整个项目, 运行SilverLightTcpBindingSample.Web项目中的SilverLightTCPBindingSampleTestPage.aspx, 会看到一个文本框和一个按钮 , 在文本框中输入一个a, 点击按钮, 应该会看到类似下面的界面:
前面是一个时间, 后面是字符串长度.
结语:
Tcp相对于basic http来说的性能优势显而易见, 所以为此麻烦一些是完全值得的.