Как сделать так, чтобы привязка именованных каналов переподключалась автоматически в WCF? - PullRequest
10 голосов
/ 04 декабря 2008

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

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

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

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

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

Ответы [ 3 ]

22 голосов
/ 06 января 2011

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

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

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

Или, если вы хотите создать декларативное связывание ...

<netNamedPipeBinding>
    <binding name="myBinding" receiveTimeout="infinite">
    </binding>
</netNamedPipeBinding>
17 голосов
/ 04 декабря 2008

Я не использовал 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. Если ваш receiveTimeout равен или меньше, чем ваш send, канал будет сброшен, как только истечет время ожидания. Если уровень приема выше, а уровень отправки превышает пороговое значение, канал сработает на уровне активности транспортного уровня. Из моих тестов порог sendTimeout составляет 30 секунд. Все, что меньше этого, и сообщения активности не отправляются.

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

[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

14 голосов
/ 14 января 2010

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

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

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

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

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

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

...