在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來說的性能優勢顯而易見, 所以為此麻煩一些是完全值得的.