Как я могу заставить привязку Named Pipe автоматически подключаться в WCF

Я пишу службу, которая будет получать вызовы только от локального хоста. Производительность важна, поэтому я решил попробовать NetNamedPipeBinding вместо NetTcpBinding и посмотрим, могу ли я увидеть заметный прирост производительности.

Если клиент, выполнив один или несколько запросов на сервер, простаивает в течение более длительного периода времени, следующий запрос, по-видимому, не будет выполнен из-за некоторого таймаута простоя в привязке. То же самое происходит и при перезапуске службы.

Мне нужны мои клиенты, чтобы иметь возможность открывать соединение до тех пор, пока оно разрешено, чтобы избежать накладных расходов, связанных с настройкой нового подключения. Мне также нужно иметь возможность перезапускать службу время от времени, и клиенты будут автоматически повторять попытку, если они заметят, что соединение было прервано.

Я знаю, что это подтверждается надежностью в NetTcpBinding, но как можно добиться того же уровня надежности повторного подключения в NetNamedPipeBinding? Возможно ли это?

Вопрос несколько академичен, так как не требуется использовать NetNamedPipes, я мог бы так же легко принять его, чтобы использовать привязку tcp, но It itch, и я действительно хотел бы поцарапать его.

Ответ 1

Мой опыт заключается в том, что при использовании NetNamedPipes "ReceiveTimout" для функций привязки, таких как "Неактивный тайм-аут", а не для получения времени. Обратите внимание, что это отличается от того, как работает NetTCPBinding. С TCP это действительно тайм-аут приема, и есть отдельный тайм-аут бездействия, который вы можете настроить с помощью надежного обмена сообщениями. (Это также противоречит документации SDK, но хорошо).

Чтобы исправить это, установите RecieveTimout на что-то большое, когда вы создаете привязку.
Например, если вы создаете свою привязку процедурно...

NetNamedPipeBinding myBinding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
myBinding.ReceiveTimeout = TimeSpan.MaxValue;

Или, если вам интересно объявить привязку декларативно...

<netNamedPipeBinding>
    <binding name="myBinding" receiveTimeout="infinite">
    </binding>
</netNamedPipeBinding>

Ответ 2

Я не использовал NetNamedPipes в WCF, но я потратил больше времени, чем мне было интересно узнать значения таймаута для NetTcp. Я использую следующие конфигурации для своих NetTcpBindings и имел удачу, когда соединение остается активным.

Сервер:

<binding name="MyBindingName" sendTimeout="00:00:30" receiveTimeout="infinite">
    <reliableSession enabled="true" inactivityTimeout="00:05:00" ordered="true" />
    <security mode="None" />
</binding>

Клиент:

<binding name="MyBindingName" closeTimeout="00:00:30" openTimeout="00:00:30" receiveTimeout="infinite" sendTimeout="00:00:30">
    <reliableSession enabled="true" inactivityTimeout="00:01:00" ordered="true" />
    <security mode="None" />
</binding>

Важными параметрами, которые я провел больше всего, являются sendTimeout и receiveTimeout. Если ваш getTimeout совпадает или меньше вашей отправки, канал будет падать после достижения этого таймаута. Если прием больше, а передача превышает пороговое значение, канал активирует keepalive уровня транспорта. Из моих тестов порог sendTimeout составляет 30 секунд. Что-то меньшее, чем это, и keepalives не отправляются.

Кроме того, у меня есть постоянный вызов таймера, который я выполняю каждую минуту, чтобы убедиться, что канал работает и работает хорошо. Вызов - просто логический возвращаемый элемент:

[OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = false)]
bool KeepAlive();

public bool KeepAlive()
{
    return true;
}

Вы также можете захватить события канала (если вы их получите в нужное время) и снова открыть соединение, если произойдет что-то плохое:

InstanceContext site = new InstanceContext(this);
_proxy = new MyServiceChannel(site);
if (_proxy != null) 
{
    if (_proxy.Login()) 
    {
        //Login was successful
        //Add channel event handlers so we can determine if something goes wrong
        foreach (IChannel a in site.OutgoingChannels) 
        {
            a.Opened += Channel_Opened;
            a.Faulted += Channel_Faulted;
            a.Closing += Channel_Closing;
            a.Closed += Channel_Closed;
        }
    }
}

Я надеюсь, что некоторые из них переведут и имеют ценность для вас с помощью NetNamedPipes.

Изменить: дополнительные параметры для захвата перезапуска сервера

Когда сервер перезагружается, он должен привести к закрытию или сбою клиентского канала. Захват этих событий на стороне клиента даст вам возможность использовать таймер повторного подключения до тех пор, пока служба не будет доступна снова.

private void Channel_Faulted(object sender, EventArgs e)
{
    IChannel channel = sender as IChannel;
    if (channel != null) 
    {
        channel.Abort();
        channel.Close();
    }

    //Disable the keep alive timer now that the channel is faulted
    _keepAliveTimer.Stop();

    //The proxy channel should no longer be used
    AbortProxy();

    //Enable the try again timer and attempt to reconnect
    _reconnectTimer.Start();
}

private void _reconnectTimer_Tick(object sender, System.EventArgs e)
{
    if (_proxy == null) 
    {
        InstanceContext site = new InstanceContext(this);
        _proxy = new StateManagerClient(site);
    }
    if (_proxy != null) 
    {
        if (_proxy.Login()) 
        {
            //The connection is back up
            _reconnectTimer.Stop();
            _keepAliveTimer.Start();
        }
        else 
        {
            //The channel has likely faulted and the proxy should be destroyed
            AbortProxy();
        }
    }
}

public void AbortProxy()
{
    if (_proxy != null) 
    {
        _proxy.Abort();
        _proxy.Close();
        _proxy = null;
    }
}

Вы хотите, чтобы попытки входа в таймер повторного подключения выполнялись в фоновом потоке асинхронно, чтобы они не повесили пользовательский интерфейс каждый раз, когда они пытались войти в систему. YMMV

Ответ 3

Я рассмотрел проблему сброшенных TCP-соединений в течение двух дней и пришел к выводу, что многие люди не имеют критической точки при настройке соединений в WCF. То, что все, кажется, делают, это создать канал один раз, а затем попытаться удержать его на всю жизнь приложения, играя всевозможные грязные трюки, чтобы поддерживать сеанс TCP. Это не значит, что это должно было быть.

Вы должны создать канал, выполнить один (или несколько более коротких после первых) вызовов своей службы, затем закрыть и удалить канал. Это даст вам (фактически) операцию без гражданства, и вам не нужно беспокоиться о том, чтобы держать сеансы живыми, чего вам не нужно в первую очередь.

Вы обнаружите, что накладные расходы на создание и закрытие канала (из повторно используемого ChannelFactory) незначительны, и на обычной машине требуется всего несколько десятков наносекунд.

Атрибут receiveTimeout, который все кринируют, определяет время, в течение которого канал может оставаться бездействующим, прежде чем он будет автоматически отброшен, что говорит о том, что каналы не должны оставаться открытыми очень долго (по умолчанию - 1 минута). Если вы установите метод receiveTimeout в TimeSpan.MaxValue, он продолжит продление вашего канала, но для практического сценария это не то, что вам нужно, и то, что вы хотите.

Что, наконец, привело меня на правильный путь, было http://msdn.microsoft.com/en-us/library/ms734681.aspx который показывает ужасно глючный пример, пока не показывает, как следует использовать ChannelFactory. Ответчики указывают на ошибки и фиксируют запись прямо, поэтому все, что вам нужно, можно получить здесь.

И тогда все мои проблемы закончились. Больше нет "Операция была предпринята для чего-то, что не является сокетом" и не более "Существующее соединение было принудительно закрыто удаленным хостом".