简体   繁体   English

WCF双绑定回调通道问题:通道故障

[英]Problem with WCF dual binding callback channel: channel is faulty

I am facing problem with client which cannot register callback channel on second start.我遇到了客户端无法在第二次启动时注册回调通道的问题。 I tried various things it seems I found a solution to remove Close() channel (ResetProxy() call in wrapper) call on shutdown of service.我尝试了各种方法,似乎找到了在关闭服务时删除 Close() 通道(包装器中的 ResetProxy() 调用)调用的解决方案。 But this it turns out causing another problem: the server is crashing with error "The thread tried to read from or write to a virtual address for which it does not have the appropriate access" .但事实证明这会导致另一个问题:服务器崩溃并出现错误“线程试图读取或写入它没有适当访问权限的虚拟地址” Additionally, there are network related issues which also result in same behavior.此外,还有一些与网络相关的问题也会导致相同的行为。 The solution is always to restart the server which is not really right option to fix callback channel registration.解决方案始终是重新启动服务器,这不是修复回调通道注册的正确选项。 Could anyone suggest what could be the problem?谁能建议可能是什么问题? I've done a lot of testing, and it seems this somehow relates to sudden stop of all clients (global stop of multiple services) and the number of services matters too.我做了很多测试,这似乎与所有客户端的突然停止(多个服务的全局停止)和服务数量有关。 As result even new client cannot register with server until server is rebooted.结果,即使是新客户端也无法在服务器重新启动之前注册到服务器。 This sounds like server is holding up closed channels.这听起来像服务器正在阻止关闭的频道。

Contract:合同:

[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IAgentCallback))]
public interface IAgentManager
{
    [OperationContract]
    string RegisterAgent(string hostName);

    [OperationContract]
    bool Ping();
}

Client create callback code (truncated):客户端创建回调代码(截断):

public class AgentCallbackDaemon
{
    private void CreateManagerProxy()
    {
        Reset();
        var isUnbound = true;
        while (isUnbound)
        {
            try
            {
                ManagerProxy = new ProxyWrapper<AgentManagerProxy, IAgentManager>(CreateProxy);
                ManagerProxy.ChannelStateChanged += HandleProxyConnectionStateChanged;
                isUnbound = false;
            }
            catch (AddressAlreadyInUseException)
            {
                sLog.ErrorFormat("Port is already reserved, binding failed.");
            }
            catch (Exception error)
            {
                sLog.ErrorFormat($"No proxy due to {error}");
                throw;
            }
        }
    }

    private AgentManagerProxy CreateProxy()
        => mCallbackChannelPortRange.IsCallbackPortRageSet() ? GetProxyWithPortRange() : GetProxyDefault();

    private AgentManagerProxy GetProxyDefault()
        => new AgentManagerProxy(mAgentCallback, mManagerUri, GetServiceName());

    private AgentManagerProxy GetProxyWithPortRange()
    {
        var minPort = mCallbackChannelPortRange.MinimumCallbackPortNumber;
        var maxPort = mCallbackChannelPortRange.MaximumCallbackPortNumber;
        return new AgentManagerProxy(mAgentCallback, mManagerUri, GetServiceName(), minPort, maxPort);
    }
}

Client callback code (truncated):客户端回调代码(截断):

public class AgentManagerProxy : DuplexClientBase<IAgentManager>, IAgentManager
{
    public const string SERVICE_NAME = "AgentManager";

    public AgentManagerProxy(IAgentCallback callback, string serviceAddress, string connectionId,
        ushort minPort, ushort maxPort)
        : base(callback, BindingAbstractFactory.DuplexServiceClientBindingFactory(connectionId,
        minPort, maxPort), BindingUtility.CreateEndpoint(serviceAddress, SERVICE_NAME))
    {
    }

    public string RegisterAgent(string hostName)
    => Channel.RegisterAgent(hostName);


    public bool Ping() => return Channel.Ping();
}


public static class BindingAbstractFactory
{
    public static Binding DuplexServiceClientBindingFactory(string connectionId, ushort minPort, ushort maxPort)
    => CreateDuplexServiceClientBinding(connectionId, minPort, maxPort);


    private static Binding CreateDuplexServiceClientBinding(string connectionId, ushort minPort, ushort maxPort)
    {
        var binding = CreateDuplexServiceHostBinding();
        if (binding is WSDualHttpBinding)
        {
            lock (sLock)
            {
                try
                {
                    ((WSDualHttpBinding)binding).ClientBaseAddress =
                        CreateClientBaseAddress(connectionId, minPort, maxPort);
                }
                catch (Exception error)
                {
                    sLog.ErrorFormat("Unexpected exception: {0}", error);
                    throw error;
                }
                finally
                {
                    Monitor.PulseAll(sLock);
                }
            }
        }

        return binding;
    }

    private static Binding CreateDuplexServiceHostBinding()
    {
        var binding = new WSDualHttpBinding
        {
            ReceiveTimeout = TimeSpan.MaxValue,
            MaxReceivedMessageSize = int.MaxValue,
            ReaderQuotas =
            {
                // Set the maximum sizes to allow big message sizes.
                MaxArrayLength = int.MaxValue,
                MaxBytesPerRead = int.MaxValue,
                MaxDepth = int.MaxValue,
                MaxNameTableCharCount = int.MaxValue,
                MaxStringContentLength = int.MaxValue
            },
            Security =
            {
                // Disable security on control services.
                // TODO: Once security concept has been clarified, update this to reflect security rules.
                Mode = WSDualHttpSecurityMode.None,
                Message = { ClientCredentialType = MessageCredentialType.None }
                //// binding.Security.Mode = SecurityMode.None;
                //// binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
            }
        };

        return binding;
    }

    private static Uri CreateClientBaseAddress(string connectionId, ushort minPort, ushort maxPort)
    {
        var fullDnsName = Dns.GetHostEntry(Dns.GetHostName()).HostName;
        int portNumber;
        if (maxPort == ushort.MaxValue)
            portNumber = LocalPorts.GetRandomAvailablePort(connectionId, minPort, maxPort);
        else
            portNumber = LocalPorts.GetNextAvailablePort(connectionId, minPort, maxPort);
        return new Uri($@"http://{fullDnsName}:{portNumber}");
    }
}

All this wrapped into proxy wrapper with Reset function which is called on service stop or any error (including network):所有这些都包装到代理包装器中,并在服务停止或任何错误(包括网络)时调用 Reset function:

public class ProxyWrapper<TPROXY, TSERVICE> where TPROXY : ClientBase<TSERVICE> where TSERVICE : class
{
    public delegate TPROXY CreateProxy();
    private readonly object mProxyLock = new object();</summary>
    private readonly CreateProxy mCreateProxy;

    private TPROXY mProxy;

    public ProxyWrapper(CreateProxy createProxyCallback)
    {
        mLog.Info.Write($"Creating Proxy for '{typeof(TPROXY).FullName}'");
        mCreateProxy = createProxyCallback;
        BuildProxy();
    }

    public event EventHandler<CommunicationState> ChannelStateChanged;

    public TPROXY Proxy
    {
        get
        {
            lock (mProxyLock)
            {
                if (mProxy == null)
                    BuildProxy();

                return mProxy;
            }
        }
    }

    public void ResetProxy()
    {
        mLog.Info.Write("Call ResetProxy()");
        lock (mProxyLock)
        {
            try
            {
                if (mProxy == null)
                    return;

                UnSubscribeFromChannelEvents();
                CloseProxy();
            }
            catch (Exception ex)
            {
                // Catch all exceptions, and ignore them.
            }
            finally
            {
                mProxy = null;
            }
        }
    }

    private void RaiseChannelStateChanged(object sender, EventArgs e)
        => ChannelStateChanged?.Invoke(this, mProxy.InnerChannel.State);

    private void RaiseChannelEnteredFaultyState()
        => ChannelStateChanged?.Invoke(this, CommunicationState.Faulted);

    private void BuildProxy()
    {
        lock (mProxyLock)
        {
            if (mProxy != null)
                ResetProxy();

            mProxy = mCreateProxy();
            SubscribeToChannelEvents();
        }
    }

    private void SubscribeToChannelEvents()
    {
        if (mProxy?.InnerChannel == null)
            return;

        mProxy.InnerChannel.Faulted += OnInnerChannelFaulted;
        mProxy.InnerChannel.Closed += RaiseChannelStateChanged;
        mProxy.InnerChannel.Closing += RaiseChannelStateChanged;
        mProxy.InnerChannel.Opened += RaiseChannelStateChanged;
        mProxy.InnerChannel.Opening += RaiseChannelStateChanged;
    }

    private void UnSubscribeFromChannelEvents()
    {
        if (mProxy?.InnerChannel == null)
            return;

        mProxy.InnerChannel.Faulted -= OnInnerChannelFaulted;
        mProxy.InnerChannel.Closed -= RaiseChannelStateChanged;
        mProxy.InnerChannel.Closing -= RaiseChannelStateChanged;
        mProxy.InnerChannel.Opened -= RaiseChannelStateChanged;
        mProxy.InnerChannel.Opening -= RaiseChannelStateChanged;
    }

    private void OnInnerChannelFaulted(object sender, EventArgs e)
    {
        ResetProxy();
        RaiseChannelEnteredFaultyState();
    }

    private void CloseProxy()
    {
        try
        {
            mProxy.Close();
        }
        catch (Exception ex)
        {
            try
            {
                mProxy.Abort();
            }
            catch (Exception abortException) {  // ignored  }
        }

        try
        {
            mProxy.ChannelFactory.Close();
        }
        catch (Exception ex)
        {
            try
            {
                mProxy.ChannelFactory.Abort();
            }
            catch (Exception abortException) {  // ignored  }
        }
    }
}

The server only maintains the callback list.服务器只维护回调列表。

  • Create Proxy for 'AgentManagerProxy'为“AgentManagerProxy”创建代理
  • Call BuildProxy()调用 BuildProxy()
  • mProxy.Open() [State=Created] mProxy.Open() [状态=已创建]
  • call SubscribeToChannelEvents()调用 SubscribeToChannelEvents()
  • ............... …………………………………………………………………………
  • Service stopping...服务停止...
  • call ResetProxy()调用 ResetProxy()
  • Call UnSubscribeFromChannelEvents()调用 UnSubscribeFromChannelEvents()
  • call CloseProxy(): mProxy.Close()调用 CloseProxy(): mProxy.Close()
  • call CloseProxy(): mProxy.ChannelFactory.Close()调用 CloseProxy(): mProxy.ChannelFactory.Close()

Added logging for client shows this on first time start:为客户端添加的日志记录在第一次启动时显示:

On second start service fails:第二次启动服务失败:

  • Create Proxy for 'AgentManagerProxy'为“AgentManagerProxy”创建代理
  • Call BuildProxy()调用 BuildProxy()
  • mProxy.Open() [State=Created] mProxy.Open() [状态=已创建]

Error: No proxy due to System.TimeoutException: The request channel timed out attempting to send after 00:00:00.错误:由于 System.TimeoutException 没有代理:请求通道在 00:00:00 之后尝试发送超时。 Increase the timeout value passed to the call to Request or increase the SendTimeout value on the Binding.增加传递给 Request 调用的超时值或增加 Binding 上的 SendTimeout 值。 The time allotted to this operation may have been a portion of a longer timeout.分配给此操作的时间可能是较长超时的一部分。 ---> System.TimeoutException: The HTTP request to ' http://xxxxx:9003/AgentManager ' has exceeded the allotted timeout of 00:00:00. ---> System.TimeoutException:对“ http://xxxxx:9003/AgentManager ”的 HTTP 请求已超过分配的超时时间 00:00:00。 The time allotted to this operation may have been a portion of a longer timeout.分配给此操作的时间可能是较长超时的一部分。 at System.ServiceModel.Channels.HttpChannelUtilities.SetRequestTimeout(HttpWebRequest request, TimeSpan timeout) at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.SendRequest(Message message, TimeSpan timeout) at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout) --- End of inner exception stack trace ---在 System.ServiceModel.Channels.HttpChannelUtilities.SetRequestTimeout(HttpWebRequest 请求,TimeSpan 超时)在 System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.SendRequest(消息消息,TimeSpan 超时)在 System.ServiceModel.Channels.RequestChannel.Request (消息消息,TimeSpan 超时) --- 内部异常堆栈跟踪结束 ---

The number of your clients may exceed the value of Maxconcurrentinstances.您的客户端数量可能超过 Maxconcurrentinstances 的值。 Maxconcurrentinstances is a positive integer that limits the number of instancecontext objects that can be executed through ServiceHost at a time. Maxconcurrentinstances 是一个正数 integer,它限制了一次可以通过 ServiceHost 执行的 instancecontext 对象的数量。 When the number of slots is below the limit, requests to create other instances are queued and completed.当槽数低于限制时,创建其他实例的请求将排队并完成。 The default value is the sum of maxconcurrentsessions and maxconcurrentcalls.You need to close the channel after each client-side completes.Otherwise, there will be an exception after the client has been queuing for a long time.默认值为maxconcurrentsessions和maxconcurrentcalls之和。需要在每个客户端完成后关闭通道。否则客户端排队时间长了会出现异常。

try
{
   if (client.State != System.ServiceModel.CommunicationState.Faulted)
   {
      client.Close();
   }
}
catch (Exception ex)
{
   client.Abort();
}

The close method may fail.关闭方法可能会失败。 If the close method fails, the abort method is required.如果 close 方法失败,则需要 abort 方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM