Ищу WCF Дуплекс "TwoWay" Подписаться + Пример обратного вызова - PullRequest
18 голосов
/ 13 августа 2010

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

Я добавил альтернативное объяснение проблемы здесь.

Неимоверно трудно получить двусторонний (IsOneWay = false) клиент-сервер WCF для работы в .Net 3 / 3.5.

После того, как клиент успешно зарегистрировался в службе, служба периодически сообщает () обратным вызовам зарегистрированных клиентов. Теперь клиент или сервер зависает до тех пор, пока не истечет значение SendTimeout сервера, настроенное на 2 секунды. Тогда на стороне сервера есть исключение тайм-аута следующим образом. Только после этого клиентский код клиента немедленно ПОЛУЧИТ ВЫЗОВ МЕТОДА и попытается вернуть значение. К тому времени клиентский сокет будет прерван, и работа WCF завершится неудачно.

Мне кажется, что что-то на клиенте висит из локальной очереди WCF от обработки до истечения времени ожидания сокета, но не достаточно рано, чтобы отменить локальный вызов метода. Но если верить приведенному ниже исключению, сервер пытается отправить операцию на http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous (неуместно!) И истекает время ожидания. Может быть, этот URI - это просто «Имя» удаленно подключенного клиента, так как WCF знает, что он ссылается на него в целях сообщения об ошибке, и это просто означает, что он не может загрузить URI. Я не могу сказать, произошел ли сбой сервера первым или сбой клиента первым.

Я пытался добавить трассировку WCF, но больше информации не получаю.

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

TimeoutException 'This request operation sent to http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous did not receive a reply within the configured timeout (00:00:00).  The time allotted to this operation may have been a portion of a longer timeout.  This may be because the service is still processing the operation or because the service was unable to send a reply message.  Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.'

Server stack trace: 
   at System.ServiceModel.Dispatcher.DuplexChannelBinder.SyncDuplexRequest.WaitForReply(TimeSpan timeout)
   at System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(Message message, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Ответы [ 4 ]

27 голосов
/ 19 августа 2010

Сначала получите себе копию Программирование служб WCF , если у вас ее еще нет.

Если клиент WinForm или WPF, вам нужно использовать [CallbackBehavior(UseSynchronizationContext = false)] какв противном случае клиент не будет обрабатывать входящее сообщение, пока поток пользовательского интерфейса не войдет в цикл обработки сообщений.

Во-первых, «дуплексный» канал в WCF не является действительно дуплексным! Сообщение от

  • Клиент-сервер
  • Может заблокировать сообщение, которое сервер ожидает от клиента
  • (или наоборот)

Поскольку сообщения отправляются только по порядку на одном канале WCF.Дуплексный канал WCF НЕ дает вам две очереди входящих сообщений.Результаты, возвращаемые вызовом «TwoWay», точно такие же, как и «вызов» с этим уровнем стека WCF. Как только вы обдумаете это, многие проблемы станут понятнее.

Если клиент WinForm или WPF, вам может потребоваться использовать [CallbackBehavior(UseSynchronizationContext = false)], иначе клиент не будет обрабатывать входящее сообщение, пока поток пользовательского интерфейса не войдет в цикл обработки сообщений.

Некоторые правила, которые я нашел, чтобы помочьизбегать тупиков.(Посмотрите на мои вопросы WCF, чтобы увидеть, какую боль я испытывал!)

Сервер никогда не должен вызывать клиента по тому же соединению, когда выполняется вызов от того же клиента.

И / или

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

В следующий раз, я думаю, я просто буду использовать два контракта (и, следовательно, TCP-соединения), один для обратного вызова, а другой для всех запросов клиент-сервер.Или используйте мою собственную систему опроса, так как это доставило мне столько боли.

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

Лучший веб-сайт, который я знаю для примеров WCF, - это Сеть Ювала Лоуиsite .

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

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

4 голосов
/ 22 августа 2010

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

[CallbackBehavior(UseSynchronizationContext = false)]
internal class ServiceCallback : IServiceCallback
{
    ChangeMainFormLabel(string text)
    {
        frmMain.Instance.BeginInvoke(new Action()(() => frmMain.Instance.lblSomething.Text = text));
    }
}

(Instance - это статическое свойство, которое возвращает единственный экземпляр frmMain, а lblSomething - это некоторая Label, которую сервер хотел бы изменить.) Этот метод немедленно вернется и освободит сервер от ожидания пользовательский интерфейс клиента и пользовательский интерфейс будут обновлены, как только это станет бесплатным. И самое главное, никаких тупиков, потому что никто никого не ждет.

2 голосов
/ 16 сентября 2010
0 голосов
/ 15 сентября 2010

Извините, я полностью забыл пример (: - $).

Вот мой код для сервера:
ISpotifyServer.cs

[ServiceContract(CallbackContract = typeof(ISpotifyCallback))]
public interface ISpotifyService
{
    [OperationContract(IsOneWay = true)]
    void Login(string username, string password);
}

ISpotifyCallback.cs

[ServiceContract]
public interface ISpotifyCallback
{
    [OperationContract(IsOneWay = true)]
    void OnLoginComplete();

    [OperationContract(IsOneWay = true)]
    void OnLoginError();
}

Program.cs

class Program
{
    static void Main(string[] args)
    {

        using (ServiceHost host = new ServiceHost(typeof(SpotifyService)))
        {
            host.Open();

            Console.WriteLine("Service running.");
            Console.WriteLine("Endpoints:");

            foreach (ServiceEndpoint se in host.Description.Endpoints)
                Console.WriteLine(se.Address.ToString());


            Console.ReadLine();

            host.Close();
        }
    }
}

AppData.xml

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MetadataEnabledBehavior">
          <serviceMetadata />
          <serviceDebug includeExceptionDetailInFaults="True"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="MetadataEnabledBehavior" name="SpotiServer.SpotifyService">
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:9821" />
          </baseAddresses>
        </host>
        <clear />
        <endpoint address="spotiserver" binding="netTcpBinding"
            name="TcpEndpoint" contract="SpotiServer.ISpotifyService"
            listenUriMode="Explicit">
          <identity>
            <dns value="localhost"/>
            <certificateReference storeName="My" storeLocation="LocalMachine"
                x509FindType="FindBySubjectDistinguishedName" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

А для клиента:
Program.cs

class Program
{
    static void Main(string[] args)
    {
        InstanceContext context = new InstanceContext(new CallbackHandler());

        String username;
        String password;

        Console.Write("Username: ");
        username = Console.ReadLine();

        Console.WriteLine("Password: ");
        password = ReadPassword();

        SpotiService.SpotifyServiceClient client = new SpotiService.SpotifyServiceClient(context);
        client.Login(username, password);

        Console.ReadLine();
    }

    private static string ReadPassword()
    {
        Stack<string> passbits = new Stack<string>();
        //keep reading
        for (ConsoleKeyInfo cki = Console.ReadKey(true); cki.Key != ConsoleKey.Enter; cki = Console.ReadKey(true))
        {
            if (cki.Key == ConsoleKey.Backspace)
            {
                //rollback the cursor and write a space so it looks backspaced to the user
                Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                Console.Write(" ");
                Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                passbits.Pop();
            }
            else
            {
                Console.Write("*");
                passbits.Push(cki.KeyChar.ToString());
            }
        }
        string[] pass = passbits.ToArray();
        Array.Reverse(pass);
        return string.Join(string.Empty, pass);
    }
}

Я думаю, что это все.У меня также есть реализация интерфейсов, которая (на стороне клиента) выводит результат на консоль, а на стороне сервера запускает «OnLoginComplete», если имя пользователя и пароль верны, в противном случае запускается «OnLoginError».Дайте мне знать, если это не сработает или вам нужна помощь в настройке.

...