[英]Problem with WCF dual binding callback channel: channel is faulty
我遇到了客戶端無法在第二次啟動時注冊回調通道的問題。 我嘗試了各種方法,似乎找到了在關閉服務時刪除 Close() 通道(包裝器中的 ResetProxy() 調用)調用的解決方案。 但事實證明這會導致另一個問題:服務器崩潰並出現錯誤“線程試圖讀取或寫入它沒有適當訪問權限的虛擬地址” 。 此外,還有一些與網絡相關的問題也會導致相同的行為。 解決方案始終是重新啟動服務器,這不是修復回調通道注冊的正確選項。 誰能建議可能是什么問題? 我做了很多測試,這似乎與所有客戶端的突然停止(多個服務的全局停止)和服務數量有關。 結果,即使是新客戶端也無法在服務器重新啟動之前注冊到服務器。 這聽起來像服務器正在阻止關閉的頻道。
合同:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IAgentCallback))]
public interface IAgentManager
{
[OperationContract]
string RegisterAgent(string hostName);
[OperationContract]
bool Ping();
}
客戶端創建回調代碼(截斷):
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);
}
}
客戶端回調代碼(截斷):
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}");
}
}
所有這些都包裝到代理包裝器中,並在服務停止或任何錯誤(包括網絡)時調用 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 }
}
}
}
服務器只維護回調列表。
為客戶端添加的日志記錄在第一次啟動時顯示:
第二次啟動服務失敗:
錯誤:由於 System.TimeoutException 沒有代理:請求通道在 00:00:00 之后嘗試發送超時。 增加傳遞給 Request 調用的超時值或增加 Binding 上的 SendTimeout 值。 分配給此操作的時間可能是較長超時的一部分。 ---> System.TimeoutException:對“ http://xxxxx:9003/AgentManager ”的 HTTP 請求已超過分配的超時時間 00:00:00。 分配給此操作的時間可能是較長超時的一部分。 在 System.ServiceModel.Channels.HttpChannelUtilities.SetRequestTimeout(HttpWebRequest 請求,TimeSpan 超時)在 System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.SendRequest(消息消息,TimeSpan 超時)在 System.ServiceModel.Channels.RequestChannel.Request (消息消息,TimeSpan 超時) --- 內部異常堆棧跟蹤結束 ---
您的客戶端數量可能超過 Maxconcurrentinstances 的值。 Maxconcurrentinstances 是一個正數 integer,它限制了一次可以通過 ServiceHost 執行的 instancecontext 對象的數量。 當槽數低於限制時,創建其他實例的請求將排隊並完成。 默認值為maxconcurrentsessions和maxconcurrentcalls之和。需要在每個客戶端完成后關閉通道。否則客戶端排隊時間長了會出現異常。
try
{
if (client.State != System.ServiceModel.CommunicationState.Faulted)
{
client.Close();
}
}
catch (Exception ex)
{
client.Abort();
}
關閉方法可能會失敗。 如果 close 方法失敗,則需要 abort 方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.