Как правильно закрыть клиентский прокси (существующее соединение было принудительно закрыто удаленным хостом)?
Пожалуйста, не закрывайте как дубликат, пока не прочитаете вопрос до конца; Я уже часами гуглил безуспешно.
РЕДАКТИРОВАТЬ: Теперь я убежден, что это связано с тем, как кэши WCF открывали соединения TCP (пул соединений). Пожалуйста, взгляните на правку № 5 в конце вопроса.
У меня в основном есть служба WCF, которая использует netTcpBinding
конфигурации. Даже если я изящно закрываю клиентский прокси (см. Код ниже), сервер всегда регистрирует "System.Net.Sockets.SocketException (0x80004005): An existing connection was forcibly closed by the remote host
".
Я сузил проблему до самого простого примера WCF, который я мог написать. Я получаю исключение в журналах, создаваемых трассировкой WCF при каждом закрытии клиентского приложения. Я не получаю никаких исключений в своем собственном коде, но это означает, что он работает как положено, и я не могу ничего отладить, чтобы увидеть, что происходит, что WCF добавляет ошибку в мои журналы.
Сервисный интерфейс / реализация:
[ServiceContract]
public interface IService1
{
[OperationContract]
string DoWork();
}
...
public class Service1 : IService1
{
public string DoWork()
{
return "12";
}
}
Конфигурация на стороне сервера:
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
<service name="WebApplication1.Service1">
<endpoint address="" binding="netTcpBinding" bindingConfiguration="netTcpEndpointBinding" contract="WebApplication1.IService1" />
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="netTcpEndpointBinding">
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
Конфигурация на стороне клиента:
<configuration>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IService1">
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://localhost/WebApplication1/Service1.svc"
binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IService1"
contract="ServiceReference1.IService1" name="NetTcpBinding_IService1" />
</client>
</system.serviceModel>
</configuration>
Код на стороне клиента, который использует службу (VS2012 сгенерировал для меня прокси-клиент с помощью "Добавить ссылку на службу"):
private async Task<string> TestTask()
{
Service1Client proxy = null;
try
{
Console.WriteLine("Calling service");
proxy = new Service1Client();
return await proxy.DoWorkAsync();
}
finally
{
if (proxy.State != System.ServiceModel.CommunicationState.Faulted)
{
Console.WriteLine("Closing client");
proxy.Close();
}
else
{
Console.WriteLine("Aborting client");
proxy.Abort();
}
}
}
Все отлично работает
Служба вызова
Закрытие клиента
12
Но как только приложение завершает работу, сервер регистрирует исключение. Я понимаю, что не следует беспокоиться об этом исключении, потому что оно работает как положено (исключение появляется только в журналах) и может произойти в любом случае в случае внезапного завершения работы клиента перед вызовом .Close()
/.Abort()
,
Но все же, это нормальное поведение? Я имею в виду, что если я правильно закрываю свой клиентский прокси, я ожидаю, что сервер не будет регистрировать исключение (то есть загрязняет мои журналы). Я также предполагаю, что после закрытия клиентского прокси-сервера между клиентом и сервером все еще устанавливается TCP-соединение (неизвестное состояние), поскольку сервер регистрирует исключение только после завершения всего клиентского приложения. Если такое соединение все еще открыто, не может ли это привести к неожиданному поведению (например, максимальное количество подключенных клиентов)? Это действительно ожидается?
Я нашел разные темы о проблеме:
wcf "Существующее соединение было принудительно закрыто удаленным хостом" после закрытия клиента
Вывод будет "не волнует об этом".
Может ли кто-нибудь подтвердить это некоторыми ссылками и объяснить, почему это исключение все равно выбрасывается?
РЕДАКТИРОВАТЬ:
Журнал трассировки исключения:
<Exception>
<ExceptionType>System.Net.Sockets.SocketException, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>
<Message>An existing connection was forcibly closed by the remote host</Message>
<StackTrace>
à System.ServiceModel.Channels.SocketConnection.HandleReceiveAsyncCompleted()
à System.ServiceModel.Channels.SocketConnection.OnReceiveAsync(Object sender, SocketAsyncEventArgs eventArgs)
à System.Net.Sockets.SocketAsyncEventArgs.FinishOperationAsyncFailure(SocketError socketError, Int32 bytesTransferred, SocketFlags flags)
à System.Net.Sockets.SocketAsyncEventArgs.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
à System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
</StackTrace>
<ExceptionString>System.Net.Sockets.SocketException (0x80004005): An existing connection was forcibly closed by the remote host</ExceptionString>
<NativeErrorCode>2746</NativeErrorCode>
</Exception>
большое спасибо
РЕДАКТИРОВАТЬ 2: у меня та же проблема при размещении службы в IIS или когда она размещена в службе Windows
РЕДАКТИРОВАТЬ 3: вот полный пример, чтобы воспроизвести проблему: http://speedy.sh/ENB59/wcf-test.zip
РЕДАКТИРОВАТЬ 4:
Я пытался следить за тем, что на самом деле происходит под капотом с помощью TCP-соединения, которое устанавливает для меня WCF.
После закрытия клиентского прокси я все еще вижу открытое TCP-соединение с моим сервером:
Я предполагаю, что это связано с тем, что TCP-соединение будет кэшироваться для последующего повторного использования (т. Е. Пула соединений), поскольку открытие нового соединения с сервером (после закрытия первого клиентского прокси-сервера) не создает нового TCP-соединения., Если я позвоню Console.WriteLine(new Test().TestTask().Result);
дважды в моем приложении я все еще вижу только одно открытое TCP-соединение.
Я также отметил, что это соединение прерывается из-за тайм-аута, если я слишком долго жду после закрытия клиентского канала.
РЕДАКТИРОВАТЬ 5: ОК, я нашел документацию на MSDN об этом пуле соединений:
NetTcpBinding использует пул TCP-соединений на основе DNS-имени хоста службы и номера порта, который прослушивает служба. Это хорошо работает, когда клиент выполняет вызовы к различным службам на разных портах или службы размещаются в одном процессе и совместно используют порт. Если один клиент вызывает несколько служб, совместно использующих порт, который размещен в разных процессах, или размещен в WAS/IIS, объединение в пул на стороне клиента может привести к проблемам, когда соединение со службой A повторно используется для службы B, что приводит к возникновению исключения, соединение прервано, и создан новый канал. Чтобы избежать этой проблемы, используйте CustomBinding и укажите разные ConnectionPoolSettings.GroupName для каждой службы, с которой клиент связывается.
Итак, теперь мой вопрос будет таким: если это нормальное поведение, что я могу сделать, чтобы предотвратить загрязнение моего журнала всеми этими исключениями?
3 ответа
Чтобы устранить эту ошибку, просто закройте Channel Factory.
private async Task<string> TestTask()
{
Service1Client proxy = null;
try
{
Console.WriteLine("Calling service");
proxy = new Service1Client();
return await proxy.DoWorkAsync();
}
finally
{
if (proxy.State != System.ServiceModel.CommunicationState.Faulted)
{
Console.WriteLine("Closing client");
proxy.ChannelFactory.Close();
proxy.Close();
}
else
{
Console.WriteLine("Aborting client");
proxy.Abort();
}
}
}
Редактировать ОК. Я смог воспроизвести проблему, используя ваш код. Хорошей новостью является то, что я также наткнулся на способ не воспроизводить его. Я не думаю, что у вас есть какие-либо проблемы в вашем коде. Я думаю, что ошибка регистрируется, потому что вы запускаете ее в отладчике в VS; и когда отладчик завершает работу службы, ошибка регистрируется.
Выполните следующие действия и посмотрите, не появляется ли больше ошибка (у меня это работает идеально каждый раз):
- Щелкните правой кнопкой мыши проект службы и выберите "Отладка"> "Начать новый экземпляр".
- Щелкните правой кнопкой мыши консольное приложение и выберите "Отладка"> "Начать новый экземпляр".
- Запустите консоль до завершения.
- Проверьте свой файл журнала.
- Остановите службу с помощью окна тестового клиента WCF, а не кнопки остановки отладчика.
- Проверьте свой файл журнала.
Вот ссылка на видео о том, как я делаю вышеуказанные шаги: swf-файл
Оригинал Код, который вы разместили, прекрасно работает в моей системе. Я не получаю ошибок ни в одном журнале. Кроме того, я бы полностью избавился от блока catch, так как он ничего не делает, только перебрасывает исключение. Затем я напишу блок finally, как показано ниже. Я думаю, что это делает код чище и передает читателю идею, что вы ничего не делаете, если выдается исключение.
Service1Client proxy = null;
try
{
Console.WriteLine("Calling service");
proxy = new Service1Client();
return await proxy.DoWorkAsync();
}
finally
{
if (proxy != null)
{
if (proxy.State == CommunicationState.Faulted)
{
Console.WriteLine("Aborting client");
proxy.Abort();
}
else
{
Console.WriteLine("Closing client");
proxy.Close();
}
}
}
Я подозреваю, что ваша команда return в блоке try вызывает выполнение, чтобы пропустить блок finally, в результате чего ваше соединение остается открытым до тех пор, пока завершение работы клиента не вызовет исключение. Это возможно? Вы убедились, что ваш блок finally выполняется?