wcf使用netTcpBinding實現雙工通訊
原來在學習Silverlight的時候使用過WCF的雙工通訊,不過是使用pollingDuplexHttpBinding來實現雙工,而且服務也是繫結在web程式中。也許是架構沒有設計好,在客戶端多了的時候有些訊息通道就會異常,覺得很不爽,剛好網上看到一個大俠的基於netTcpBinding的聊天室程式碼(參見http://www.cnblogs.com/ShadowLoki/archive/2012/08/30/2663931.html)。於是就想重新使用netTcpBinding優化一下。大俠提供了兩個程式程式碼,分別是WCF程式寄宿在控制檯程式中,下載回來後應該是SimplePush.rar檔案。另外一個程式則是silverlight +wcf,下載回來後的檔名是
好了,按照上面連結介紹的步驟建立service和contract單獨專案,這兩個專案型別為'WCF服務庫'。
一、我們定義一個基礎的WCF服務契約(contract)
IPushService/// <summary> /// 推送服務契約 /// /// Tips: /// 契約提供兩個服務,一個是訂閱,一個是退訂。 /// 服務端會向訂閱的客戶端釋出訊息 /// </summary> [ServiceContract(CallbackContract = typeof(IPushCallback))] publicinterface IPushService { /// <summary> /// 訂閱服務 /// </summary> [OperationContract(IsOneWay = true)] void Regist(); /// <summary> /// 退訂服務 /// </summary> [OperationContract(IsOneWay = true)] void UnRegist(); }
在這裡定義了兩個行為,訂閱和退訂。
同時還有一個回撥契約如下
IPushCallback/// <summary> /// 回撥介面 IOC思想的體現 /// </summary> public interface IPushCallback { [OperationContract(IsOneWay = true)] void NotifyMessage(string message); }
在這裡,我們對每一個行為都標記為OneWay,表示客戶端在呼叫完服務之後不需要等待服務的回覆,同理回撥時服務端只管廣播,同樣不需要理會客戶端
二 、下面就是我們服務的實現了,服務的實現很簡單,僅僅是捕獲到客戶端的回撥通道,對集合進行操作。這裡需要注意的是ServiceBehavior標記的InstanceContextMode屬性的設定。我們需要為每一個單獨的通道建立新的例項,但是在呼叫玩服務後,不對通道立刻進行回收,因此我們需要設定為InstanceContextModel.Single。
PushService/// <summary> /// 服務的實現 /// Tips: /// 實現釋出訂閱,要注意:每個通道在呼叫後不要回收,否則會在回撥時報錯 /// </summary> [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)] public class PushService : IPushService { public void Regist() { IPushCallback callbackChannel = OperationContext.Current.GetCallbackChannel<IPushCallback>(); //新增到管理列表中 ChannelManager.Instance.Add(callbackChannel); } public void UnRegist() { IPushCallback callbackChannel = OperationContext.Current.GetCallbackChannel<IPushCallback>(); //從管理列表中移除 ChannelManager.Instance.Remove(callbackChannel); } }
這樣我們的服務基本就完成了,
三、建立服務宿主程式Hosting,程式需要新增對Chat.Hosting和Chat.Contracts的引用
namespace Chat.Hosting
{
class Program
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(ChatService));
ServiceHost crossDomainHost = CrossDomainPolicy.GetHost();
//註冊事件
ChatChannelManager.Instance.Added += (sender, e) =>
{
Console.WriteLine("新增來自{0}:{1}的客戶端到列表中 {2}", e.Channel.IP, e.Channel.Port, DateTime.Now);
};
ChatChannelManager.Instance.Removed += (sender, e) =>
{
Console.WriteLine("移除來自{0}:{1}的客戶端從列表中 {2}", e.Channel.IP, e.Channel.Port, DateTime.Now);
};
ChatChannelManager.Instance.Faulted += (sender, e) =>
{
Console.WriteLine("Key為{0}的通道已經報廢啦,直接回收了 {1}\n錯誤內容:{2}", e.Name, DateTime.Now, e.ErrorMessage);
ChatChannelManager.Instance.RemoveChannel(e.Name);
};
try
{
host.Open();
crossDomainHost.Open();
Console.WriteLine("服務啟動,按任意鍵退出");
Console.ReadKey();
host.Close();
crossDomainHost.Close();
}
catch
{
Console.WriteLine("服務寄宿失敗");
}
}
}
}
配置檔案App.config如下,需要注意回撥介面是contract="FIIS.Contracts.IChatCallback"而不是contract="FIIS.Services.IChatCallback"。否則會載入服務失敗
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MessageBehavior">
<serviceMetadata httpGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="MessageBehavior" name="Chat.Services.ChatService">
<endpoint address="" binding="netTcpBinding" bindingConfiguration="Binding1" contract="FIIS.Contracts.IChatCallback"/>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:4503/Service"/>
</baseAddresses>
</host>
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="Binding1" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions" hostNameComparisonMode="StrongWildcard" listenBacklog="10"
maxBufferPoolSize="524288" maxBufferSize="65536" maxConnections="10" maxReceivedMessageSize="65536">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false"/>
<security mode="None"></security>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
四、客戶端程式。在他的該示例中使用的是silverlight5,我的是4.0使用不了,不知道能否正常執行。所以就新建一個控制檯客戶端,名為Chat.Client。步驟:
1、新增System.ServiceModel引用
2、新增服務引用,地址為:net.tcp://localhost:4503/Service,服務引用名稱為MessageServiceRef。服務不能選用非同步方式,否則會有些問題。
3、新增Chat.Client.MessageServiceRef引用。
4、建立回撥介面類IChatCallback的實現,這裡需要注意繼承的介面必須是MessageServiceRef下面的回撥類,應該是IChatServiceCallback而不是直接引用Chat.Contracts.IChatCallback。否則就會報如下的錯誤“向
ChannelFactory 提供的 InstanceContext 包含未實現
CallbackContractType”
class MessageCallback :IChatServiceCallback
{
Console.WriteLine(chat.Message);
}
5、客戶端主程式程式碼
static void Main(string[] args)
{
InstanceContext instanceContext = new InstanceContext(new MessageCallback());
MessageServiceRef.ChatServiceClient msgClient = new
MessageServiceRef.ChatServiceClient(instanceContext);
Console.WriteLine("歡迎來到測試聊天室,請輸入使用者名稱:");
string name = Console.ReadLine();
msgClient.LoginChat(name);
string msg =Console.ReadLine();
msgClient.Chat(msg);
Console.ReadLine();
}
---------------------------------
文章寫的匆忙,主要是為了記錄建立這個程式過程中遇到的錯誤。如服務非同步方式、回撥介面類的實現報錯等等