远程端点请求的确认地址与应用程序消息的地址不同

The remote endpoint requested an address for acknowledgements that is not the same as the address for application messages

我正在为带有 WPF 客户端的聊天应用程序编写 WCF Duplex 服务。服务代码如下

IChatCallback

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
}

IChatService

[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
}

IChatService

[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
}

IChatServiceChannel

[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);
        }
    }

...
}

我得到的错误是:

The remote endpoint requested an address for acknowledgements that is not the same as the address for application messages. The channel could not be opened because this is not supported. Ensure the endpoint address used to create the channel is identical to the one the remote endpoint was set up with.

我的问题(很抱歉 post 很长,但我完全坚持这个问题)只是是否有其他人遇到过这个问题并可以指出我的答案吗?

编辑: 我已更新以包括来自服务器和客户端的完整 serviceModel 部分,还更新了 ProxySingleton 以表明它确实实现了回调接口

这是您 ChatService 的全功能设置:

主持人:

class ProgramHost
{
    static void Main(string[] args)
    {
        try
        {
            ServiceHost host = new ServiceHost(typeof(ChatLib.ChatService));

            host.Open();

            Console.WriteLine(string.Format("WCF {0} host is running...", host.Description.ServiceType));

            Console.WriteLine("Endpoints:");
            foreach (ServiceEndpoint se in host.Description.Endpoints)
            {
                Console.WriteLine("***********************************************");
                Console.WriteLine(string.Format("Address = {0}", se.Address));
                Console.WriteLine(string.Format("Binding = {0}", se.Binding));
                Console.WriteLine(string.Format("Contract = {0}", se.Contract.Name));
            }

            Console.WriteLine(string.Empty);
            Console.WriteLine("Press <ENTER> to terminate.");

            Console.ReadLine();

            host.Close();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }
}

客户:

class ProgramClient
{
    static void Main(string[] args)
    {
        try
        {
            if (args.Length != 1)
                Console.WriteLine("usage: clientconsole username");
            else
            {
                Person user = new Person(args[0]);

                IChatServiceCallback callback = new SimpleChatCallback();

                InstanceContext instanceContext = new InstanceContext(callback);
                ChatServiceClient serviceProxy = new ChatServiceClient(instanceContext);

                Console.WriteLine("Endpoint:");
                Console.WriteLine("***********************************************");
                Console.WriteLine(string.Format("Address = {0}", serviceProxy.Endpoint.Address));
                Console.WriteLine(string.Format("Binding = {0}", serviceProxy.Endpoint.Binding));
                Console.WriteLine(string.Format("Contract = {0}", serviceProxy.Endpoint.Contract.Name));

                Person[] people = serviceProxy.Join(user);

                Console.WriteLine("***********************************************");
                Console.WriteLine("Connected !");
                Console.WriteLine("Online users:");

                foreach (Person p in people)
                    Console.WriteLine(p.Name);

                string msg;
                while ((msg = Console.ReadLine()) != "exit")
                    serviceProxy.Say(msg);

                serviceProxy.Leave();

                if (serviceProxy.State != CommunicationState.Faulted)
                    serviceProxy.Close();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

客户端回调:

public class SimpleChatCallback : IChatServiceCallback
{
    public void Receive(Person sender, string message)
    {
        Console.WriteLine("{0}: {1}", sender.Name, message);
    }

    public void ReceiveWhisper(Person sender, string message)
    {
        Console.WriteLine("{0}: {1}", sender.Name, message);
    }

    public void UserEnter(Person person)
    {
        Console.WriteLine("{0} has entered", person.Name);
    }

    public void UserLeave(Person person)
    {
        Console.WriteLine("{0} has left", person.Name);
    }
}

主机配置:

  <system.serviceModel>
    <services>
      <service behaviorConfiguration="mexBehavior" name="ChatLib.ChatService">
        <clear />
        <endpoint address="ChatService.svc" binding="netTcpBinding" bindingConfiguration=""
          name="netTcpEndpoint" bindingName="NonSecureTcpBinding" contract="Common.IChatService" />
        <endpoint binding="mexHttpBinding" bindingConfiguration="mexHttpBinding"
          name="mexHttpEndpoint" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:33334/chatservice" />
            <add baseAddress="net.tcp://localhost:33333/chatservice" />
          </baseAddresses>
          <timeouts openTimeout="00:10:00" />
        </host>
      </service>
    </services>
    <bindings>
      <netTcpBinding>
        <binding name="NonSecureTcpBinding">
          <security mode="None">
            <transport clientCredentialType="None" protectionLevel="None" />
            <message clientCredentialType="None" />
          </security>
        </binding>
      </netTcpBinding>
      <mexHttpBinding>
        <binding name="mexHttpBinding" />
      </mexHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="mexBehavior">
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
      <remove scheme="net.tcp" />
      <add scheme="net.tcp" binding="netTcpBinding" bindingConfiguration="NonSecureTcpBinding" />
      <add scheme="https" binding="basicHttpsBinding" />
    </protocolMapping>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>

客户端配置:

<system.serviceModel>
    <bindings>
        <netTcpBinding>
            <binding name="netTcpEndpoint" />
        </netTcpBinding>
    </bindings>
    <client>
        <endpoint address="net.tcp://localhost:33333/chatservice/ChatService.svc"
            binding="netTcpBinding" bindingConfiguration="netTcpEndpoint"
            contract="ServiceReference1.IChatService" name="netTcpEndpoint">
            <identity>
                <userPrincipalName value="ComputerName\UserName" />
            </identity>
        </endpoint>
    </client>
</system.serviceModel>

主机输出:

客户端 1 输出:

客户端 2 输出:

备注:

ServiceReference1是Visual Studio分配给生成的代理客户端ChatServiceClient.

的默认命名空间

ChatLib 是本地分配给您的 ChatService 实现的命名空间。