我正在为带有 WPF 客户端的聊天应用程序编写 WCF Duplex 服务。服务代码如下
IChat回调
public interface IChatCallback
{
#region Public Methods and Operators
[OperationContract(IsOneWay = true)]
void Receive(Person sender, string message);
[OperationContract(IsOneWay = true)]
void ReceiveWhisper(Person sender, string message);
[OperationContract(IsOneWay = true)]
void UserEnter(Person person);
[OperationContract(IsOneWay = true)]
void UserLeave(Person person);
#endregion
}
聊天服务
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))]
public interface IChatService
{
#region Public Methods and Operators
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void Say(string message);
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void Whisper(string to, string message);
[OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]
Person[] Join(Person person);
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)]
void Leave();
#endregion
}
聊天服务
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class ChatService : IChatService
{
#region Static Fields
private static object _syncObj = new object();
private static Dictionary<Person, ChatEventHandler> _chatters = new Dictionary<Person, ChatEventHandler>();
#endregion
#region Fields
private IChatCallback _callback = null;
private ChatEventHandler _myEventHandler;
private Person _person;
#endregion
#region Delegates
public delegate void ChatEventHandler(object sender, ChatEventArgs e);
#endregion
#region Public Events
public static event ChatEventHandler ChatEvent;
#endregion
#region Public Methods and Operators
public void Say(string message)
{
ChatEventArgs e = new ChatEventArgs(MessageType.Receive, this._person, message);
this.BroadcastMessage(e);
}
public void Whisper(string to, string message)
{
ChatEventArgs e = new ChatEventArgs(MessageType.ReceiveWhisper, this._person, message);
try
{
ChatEventHandler chatterTo;
lock (_syncObj)
{
chatterTo = this.GetPersonHandler(to);
if (chatterTo == null)
{
throw new KeyNotFoundException(
string.Format(
CultureInfo.InvariantCulture,
"The person with name [{0}] could not be found",
to));
}
}
chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);
}
catch (KeyNotFoundException)
{
}
}
public Person[] Join(Person person)
{
bool userAdded = false;
this._myEventHandler = new ChatEventHandler(this.MyEventHandler);
lock (_syncObj)
{
if (!this.CheckIfPersonExists(person.Name) && person != null)
{
this._person = person;
_chatters.Add(person, this.MyEventHandler);
userAdded = true;
}
}
if (userAdded)
{
this._callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();
ChatEventArgs e = new ChatEventArgs(MessageType.UserEnter, this._person);
this.BroadcastMessage(e);
ChatEvent += this._myEventHandler;
Person[] list = new Person[_chatters.Count];
lock (_syncObj)
{
_chatters.Keys.CopyTo(list, 0);
}
return list;
}
else
{
return null;
}
}
public void Leave()
{
if (this._person == null)
{
return;
}
ChatEventHandler chatterToRemove = this.GetPersonHandler(this._person.Name);
lock (_syncObj)
{
_chatters.Remove(this._person);
}
ChatEvent -= chatterToRemove;
ChatEventArgs e = new ChatEventArgs(MessageType.UserLeave, this._person);
this.BroadcastMessage(e);
}
#endregion
private void MyEventHandler(object sender, ChatEventArgs e)
{
try
{
switch (e.MessageType)
{
case MessageType.Receive:
this._callback.Receive(e.Person, e.Message);
break;
case MessageType.ReceiveWhisper:
this._callback.ReceiveWhisper(e.Person, e.Message);
break;
case MessageType.UserEnter:
this._callback.UserEnter(e.Person);
break;
case MessageType.UserLeave:
this._callback.UserLeave(e.Person);
break;
}
}
catch
{
this.Leave();
}
}
private void BroadcastMessage(ChatEventArgs e)
{
ChatEventHandler temp = ChatEvent;
if (temp != null)
{
foreach (ChatEventHandler handler in temp.GetInvocationList())
{
handler.BeginInvoke(this, e, new AsyncCallback(this.EndAsync), null);
}
}
}
private bool CheckIfPersonExists(string name)
{
foreach (Person p in _chatters.Keys)
{
if (p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
private void EndAsync(IAsyncResult ar)
{
ChatEventHandler d = null;
try
{
AsyncResult asres = (AsyncResult)ar;
d = (ChatEventHandler)asres.AsyncDelegate;
d.EndInvoke(ar);
}
catch
{
ChatEvent -= d;
}
}
private ChatEventHandler GetPersonHandler(string name)
{
foreach (Person p in _chatters.Keys)
{
if (p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
{
ChatEventHandler chatTo = null;
_chatters.TryGetValue(p, out chatTo);
return chatTo;
}
}
return null;
}
}
这托管在一个终端为 net.tcp://localhost:33333/chatservice 的控制台应用程序中,使用带有以下绑定配置的 netTcpBinding
<system.serviceModel>
<services>
<service name="Cleo.Services.Chat.ChatService" behaviorConfiguration="CleoChatBehavior">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:33333/chatservice"/>
</baseAddresses>
</host>
<endpoint address="" binding="netTcpBinding" bindingConfiguration="DuplexBinding" contract="Cleo.Services.Chat.IChatService"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="CleoChatBehavior">
<serviceThrottling maxConcurrentSessions="10000"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="DuplexBinding" maxBufferSize="67108864" maxReceivedMessageSize="67108864" maxBufferPoolSize="67108864" transferMode="Buffered" closeTimeout="00:00:10" openTimeout="00:00:10" receiveTimeout="00:20:00" sendTimeout="00:01:00" maxConnections="100">
<reliableSession enabled="true" inactivityTimeout="00:20:00" />
<security mode="None" />
<readerQuotas maxArrayLength="67108864" maxBytesPerRead="67108864" maxStringContentLength="67108864" />
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
在我的 WPF 客户端中,我使用 svcutil 实现了服务代理,如下所示:
IChatServiceCallback
[GeneratedCode("System.ServiceModel", "4.0.0.0")]
public interface IChatServiceCallback
{
#region Public Methods and Operators
[OperationContract(IsOneWay = true, Action = "http://tempuri.org/IChatService/Receive")]
void Receive(Person sender, string message);
[OperationContract(IsOneWay = true, AsyncPattern = true, Action = "http://tempuri.org/IChatService/Receive")]
IAsyncResult BeginReceive(Person sender, string message, AsyncCallback callback, object asyncState);
void EndReceive(IAsyncResult result);
[OperationContract(IsOneWay = true, Action = "http://tempuri.org/IChatService/ReceiveWhisper")]
void ReceiveWhisper(Person sender, string message);
[OperationContract(IsOneWay = true, AsyncPattern = true,
Action = "http://tempuri.org/IChatService/ReceiveWhisper")]
IAsyncResult BeginReceiveWhisper(Person sender, string message, AsyncCallback callback, object asyncState);
void EndReceiveWhisper(IAsyncResult result);
[OperationContract(IsOneWay = true, Action = "http://tempuri.org/IChatService/UserEnter")]
void UserEnter(Person person);
[OperationContract(IsOneWay = true, AsyncPattern = true, Action = "http://tempuri.org/IChatService/UserEnter")]
IAsyncResult BeginUserEnter(Person person, AsyncCallback callback, object asyncState);
void EndUserEnter(IAsyncResult result);
[OperationContract(IsOneWay = true, Action = "http://tempuri.org/IChatService/UserLeave")]
void UserLeave(Person person);
[OperationContract(IsOneWay = true, AsyncPattern = true, Action = "http://tempuri.org/IChatService/UserLeave")]
IAsyncResult BeginUserLeave(Person person, AsyncCallback callback, object asyncState);
void EndUserLeave(IAsyncResult result);
#endregion
}
聊天服务
[GeneratedCode("System.ServiceModel", "4.0.0.0")]
[ServiceContract(ConfigurationName = "IChatService", CallbackContract = typeof(IChatServiceCallback),
SessionMode = SessionMode.Required)]
public interface IChatService
{
#region Public Methods and Operators
[OperationContract(IsOneWay = true, IsInitiating = false, Action = "http://tempuri.org/IChatService/Say")]
void Say(string message);
[OperationContract(IsOneWay = true, IsInitiating = false, AsyncPattern = true,
Action = "http://tempuri.org/IChatService/Say")]
IAsyncResult BeginSay(string message, AsyncCallback callback, object asyncState);
void EndSay(IAsyncResult result);
[OperationContract(IsOneWay = true, IsInitiating = false, Action = "http://tempuri.org/IChatService/Whisper")]
void Whisper(string to, string message);
[OperationContract(IsOneWay = true, IsInitiating = false, AsyncPattern = true,
Action = "http://tempuri.org/IChatService/Whisper")]
IAsyncResult BeginWhisper(string to, string message, AsyncCallback callback, object asyncState);
void EndWhisper(IAsyncResult result);
[OperationContract(Action = "http://tempuri.org/IChatService/Join",
ReplyAction = "http://tempuri.org/IChatService/JoinResponse")]
Person[] Join(Person person);
[OperationContract(AsyncPattern = true, Action = "http://tempuri.org/IChatService/Join",
ReplyAction = "http://tempuri.org/IChatService/JoinResponse")]
IAsyncResult BeginJoin(Person person, AsyncCallback callback, object asyncState);
Person[] EndJoin(IAsyncResult result);
[OperationContract(IsOneWay = true, IsTerminating = true, IsInitiating = false,
Action = "http://tempuri.org/IChatService/Leave")]
void Leave();
[OperationContract(IsOneWay = true, IsTerminating = true, IsInitiating = false, AsyncPattern = true,
Action = "http://tempuri.org/IChatService/Leave")]
IAsyncResult BeginLeave(AsyncCallback callback, object asyncState);
void EndLeave(IAsyncResult result);
#endregion
}
IChatService频道
[GeneratedCode("System.ServiceModel", "4.0.0.0")]
public interface IChatServiceChannel : IChatService, IClientChannel
{
}
和聊天代理
[DebuggerStepThrough]
[GeneratedCode("System.ServiceModel", "4.0.0.0")]
public class ChatProxy : DuplexClientBase<IChatService>, IChatService
{
#region Constructors and Destructors
public ChatProxy(InstanceContext callbackInstance)
: base(callbackInstance)
{
}
public ChatProxy(InstanceContext callbackInstance, string endpointConfigurationName)
: base(callbackInstance, endpointConfigurationName)
{
}
public ChatProxy(InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress)
: base(callbackInstance, endpointConfigurationName, remoteAddress)
{
}
public ChatProxy(
InstanceContext callbackInstance,
string endpointConfigurationName,
EndpointAddress remoteAddress)
: base(callbackInstance, endpointConfigurationName, remoteAddress)
{
}
public ChatProxy(InstanceContext callbackInstance, Binding binding, EndpointAddress remoteAddress)
: base(callbackInstance, binding, remoteAddress)
{
}
#endregion
#region Public Methods and Operators
public void Say(string message)
{
this.Channel.Say(message);
}
public IAsyncResult BeginSay(string message, AsyncCallback callback, object asyncState)
{
return this.Channel.BeginSay(message, callback, asyncState);
}
public void EndSay(IAsyncResult result)
{
this.Channel.EndSay(result);
}
public void Whisper(string to, string message)
{
this.Channel.Whisper(to, message);
}
public IAsyncResult BeginWhisper(string to, string message, AsyncCallback callback, object asyncState)
{
return this.Channel.BeginWhisper(to, message, callback, asyncState);
}
public void EndWhisper(IAsyncResult result)
{
this.Channel.EndWhisper(result);
}
public Person[] Join(Person person)
{
return this.Channel.Join(person);
}
public IAsyncResult BeginJoin(Person person, AsyncCallback callback, object asyncState)
{
return this.Channel.BeginJoin(person, callback, asyncState);
}
public Person[] EndJoin(IAsyncResult result)
{
return this.Channel.EndJoin(result);
}
public void Leave()
{
this.Channel.Leave();
}
public IAsyncResult BeginLeave(AsyncCallback callback, object asyncState)
{
return this.Channel.BeginLeave(callback, asyncState);
}
public void EndLeave(IAsyncResult result)
{
this.Channel.EndLeave(result);
}
#endregion
}
在主应用程序中使用客户端配置:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="CleoDefaultBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="33554432" maxReceivedMessageSize="4194304" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="4194304" maxArrayLength="32768" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
<netTcpBinding>
<binding name="DuplexBinding" sendTimeout="00:00:30">
<reliableSession enabled="true"/>
<security mode="None"/>
</binding>
</netTcpBinding>
</bindings>
<client>
<!-- Cleo Chat Client -->
<endpoint name="CleoChatWcfServiceClient" address="net.tcp://localhost:33333/chatservice" binding="netTcpBinding" bindingConfiguration="DuplexBinding" contract="IChatService"/>
<endpoint address="net.tcp://localhost:51638/services/chat/wcf" binding="netTcpBinding" bindingConfiguration="DuplexBinding" contract="CleoChatClient.ICleoChatWcfService" name="chatWcfService" />
</client>
</system.serviceModel>
好的,除了出于某种原因我在运行以下代码以连接到服务时遇到错误之外,一切都很好,代码是:
public class ProxySingleton : IChatServiceCallback
{
...
public void Connect(Person p)
{
var site = new InstanceContext(this);
this._proxy = new ChatProxy(site);
var iar = this._proxy.BeginJoin(p, this.OnEndJoin, null);
}
private void OnEndJoin(IAsyncResult ar)
{
try
{
var list = this._proxy.EndJoin(ar); --> Errors here!!
this.HandleEndJoin(list);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
...
}
我得到的错误是:
远程端点请求的确认地址与应用程序消息的地址不同。无法打开通道,因为不支持此操作。确保用于创建通道的端点地址与设置远程端点的地址相同。
我的问题(对于很长的帖子感到抱歉,但我完全坚持这一点)只是是否有其他人遇到过这个问题并且可以指出我的答案吗?
编辑:我已更新以包含来自服务器和客户端的完整 serviceModel 部分,还更新了 ProxySingleton 以显示它确实实现了回调接口