Выбор правильных свойств соединения для долго работающих приложений WCF

Я пишу клиент-серверное приложение на С# с использованием WCF. Все мои тесты прошли нормально, но как только я развернул сервис, я заметил случайные проблемы в общении с сервером. Я включил отладку и увидел на сервере такие сообщения:

The communication object, System.ServiceModel.Channels.ServerReliableDuplexSessionChannel, cannot be used for communication because it has been Aborted.

Схема такая:

  • клиент отправляет запрос
  • сервис обрабатывает запрос
  • служба отправляет что-то обратно
  • Граница активности на уровне Стоп - вроде все нормально
  • Добавьте inactivityTimeout надежного сеанса к дате и времени последнего контакта, и у вас будет метка времени исключения, созданного службой.

Приложение выглядит следующим образом: экземпляр службы предоставляет методы API для взаимодействия с базой данных и имеет тип netTcpBinding. Несколько клиентов (около 40) подключены и случайным образом вызывают методы из сервиса. Клиенты могут оставаться открытыми в течение нескольких дней, даже ничего не отправляя и не получая.

Вот соответствующие биты:

Сервис:

    [ServiceContract(CallbackContract = typeof(ISVCCallback), SessionMode = SessionMode.Required)]
    [ExceptionMarshallingBehavior]
...

и

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext=true)]
    public class SVCService : ISVC
...

Конфигурация службы:

    <behaviors>
      <serviceBehaviors>
        <behavior name="behaviorConfig">
          <serviceMetadata httpGetEnabled="false" httpGetUrl="" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceThrottling maxConcurrentCalls="50" maxConcurrentSessions="1000"
            maxConcurrentInstances="50" />
        </behavior>
      </serviceBehaviors>
    </behaviors>

    <bindings>
      <netTcpBinding>
        <binding name="tcpBinding" closeTimeout="00:01:00" openTimeout="00:10:00"
          receiveTimeout="23:59:59" sendTimeout="00:01:30" transferMode="Buffered"
          listenBacklog="1000" maxBufferPoolSize="671088640" maxBufferSize="671088640"
          maxConnections="1000" maxReceivedMessageSize="671088640"     portSharingEnabled="true">
          <readerQuotas maxStringContentLength="671088640" maxArrayLength="671088640"
            maxBytesPerRead="671088640" />
          <reliableSession inactivityTimeout="23:59:59" enabled="true" />
          <security mode="None">
          </security>
        </binding>
      </netTcpBinding>
    </bindings>

Конфигурация клиента:

        <bindings>
            <netTcpBinding>
                <binding name="NetTcpBinding_ISVC" closeTimeout="00:01:00" openTimeout="00:10:00"
                    receiveTimeout="23:59:59" sendTimeout="00:01:30" transactionFlow="false"
                    transferMode="Buffered" transactionProtocol="OleTransactions"
                    hostNameComparisonMode="StrongWildcard" listenBacklog="1000"
                    maxBufferPoolSize="671088640" maxBufferSize="671088640" maxConnections="1000"
                    maxReceivedMessageSize="671088640">
                    <readerQuotas maxStringContentLength="671088640" maxArrayLength="671088640"
                        maxBytesPerRead="671088640" />
                    <reliableSession ordered="true" inactivityTimeout="23:59:59"
                        enabled="true" />
                    <security mode="None">
                        <message clientCredentialType="Windows" />
                    </security>
                </binding>
            </netTcpBinding>
        </bindings>

Что-то здесь не так? Какова наилучшая конфигурация для таких приложений?

Обновлять:

Я столкнулся с одной вещью:

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

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

Теперь вопрос, как избежать этих таймаутов.

  • Должен ли я использовать многопоточность в службе? Я думаю, что потоки будут уничтожены, как только вызов службы завершится, я здесь?
  • Я мог бы реализовать статическую функцию очереди, которая выполняет все уведомления обратного вызова (это то, что предложил Marc_S)
  • Есть ли способ надежно обнаружить обрыв соединения внутри сервера?

person klausliebknecht    schedule 06.03.2010    source источник


Ответы (2)


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

Очереди — отличный способ разделить две системы и уменьшить вероятность тайм-аутов и т. д.

В сценарии на основе очереди ваши клиенты будут помещать сообщение в очередь (например, MSMQ, который поставляется с каждой копией Windows Server), и ваша служба будет прослушивать эту входящую очередь для сообщений. Служба захватывает сообщения, обрабатывает их и, как правило, помещает какое-то ответное сообщение обратно во вторую очередь (которую затем слушают клиенты).

Основными преимуществами здесь являются:

  • вам не нужна довольно хрупкая и сложная настройка контракта обратного звонка
  • ваши системы развязаны и будут продолжать работать, даже если связь между ними прервется на несколько секунд, минут, часов
  • ваша служба может отвечать на входящие сообщения и отправлять более «целевые» ответы, например. некоторые клиенты могут прослушивать «обычную» очередь ответов, другие — «приоритетную» очередь ответов и т. д. — система просто дает вам больше гибкости IMO

Смотрите больше ресурсов:

и проверьте такие вещи, как

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

person marc_s    schedule 06.03.2010
comment
Привет Марк, большое спасибо за ваш ответ. К сожалению, проект готов на 98%, и у меня осталось всего 2 недели, чтобы реализовать недостающие вещи. Отказ от текущей архитектуры и переход к другому подходу в данный момент неуместны. К сожалению, сейчас мне нужно решить эту проблему с помощью WCF, а затем, возможно, перепроектировать архитектуру в версии 2.0. Я надеюсь, что вы, ребята, можете дать мне ответ на мою проблему. Первоначально я думал, что WCF был разработан именно для таких вещей.... - person klausliebknecht; 06.03.2010
comment
Да, конечно — WCF великолепен и очень гибок. Он также поддерживает обмен сообщениями MSMQ! Материал контракта обратного вызова иногда немного ненадежен, не обязательно по вине WCF, это просто сложно сделать. - person marc_s; 06.03.2010

Хорошо, проблема решена.

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

Вот как я это сделал:

public enum CallbackType
{
    callbackfunc1,
    callbackfunc2,
    callbackfunc3
};
public class CallbackEventArgs : EventArgs
{
    public ISVCCallback callback;
    public CallbackType type;
    public string s1;
    public string s2;
    public string s3;
    public List<string> ls1;
}

// Declare delegate
public delegate void SVCEventHandler(object sender, CallbackEventArgs e);

... теперь в определении сервиса

public string Login(...)
{
    Client cl = new Client(MyCallbackHandler);
    ....

    CallbackEventArgs ce = new CallbackEventArgs();
    ce.callback = cl.CallbackChannel;
    ce.type = CallbackType.callbackfunc1;
    ce.s1 = "Parameter A";
    ce.s2 = "Parameter B";
    cl.MyCallBackHandler.BeginInvoke(this, ce, new AsyncCallback(EndAsync), null);
}

private void MyCallbackHandler(object sender,  CallbackEventArgs e)
    {
        try
        {
            switch (e.type)
            {
                 case CallbackType.callbackfunc1:
                       e.callback.callbackfunc1(e.s1, e.s2);
                       break;
                 default:
                       throw new MissingFieldException(e.type);
            }
    }
    catch(TimeoutException tex){
        // Remove current client, channel timed out
    }
    catch(Exception ex){
        // Do something
    }
}

private void EndAsync(IAsyncResult iar)
    {
        SVCEventHandler d = null;
        try
        {
            System.Runtime.Remoting.Messaging.AsyncResult asres = (System.Runtime.Remoting.Messaging.AsyncResult)iar;
            d = ((SVCEventHandler)asres.AsyncDelegate);
            d.EndInvoke(ar);
        }
        catch(Exception ex)
        {
            // Do something
        }
    }

Спасибо!!

person klausliebknecht    schedule 06.03.2010