18

再次更新赏金,因为我真的需要知道如何让它发挥作用,或者明确回答为什么它不会。

我在这里添加了对该问题的另一种解释。

让双向 (IsOneWay = false) WCF 客户端-服务器在 .Net 3/3.5 中工作真是费尽心思。

客户端成功注册服务后,服务的周期性 Announcement() 回调注册的客户端。现在是客户端或服务器挂起,直到服务器的 SendTimeout(调整为 2 秒)过去。那么服务器端出现超时异常如下。只有这样,客户端用户代码才会立即接收方法调用并尝试返回一个值。到那时,客户端的套接字被中止并且 WCF 的东西失败了。

在我看来,客户端上的某些东西正在将其本地 WCF 队列从处理中挂起,直到套接字超时,但还不足以取消本地方法调用。但是,如果可以相信以下异常,则服务器正在尝试将操作发送到http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous(不合适!)并且正在超时。也许该 URI 只是远程连接客户端的“名称”,因为 WCF 知道出于错误消息的目的引用它,它似乎只是意味着它无法加载 URI。我不知道是服务器先失败还是客户端先失败。

我已经尝试添加 WCF 跟踪,但我没有获得更多信息。

类似的示例代码是here,但它一定是太多无法消化。我已经尝试过该代码的变体。

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

4 回答 4

27

如果您还没有,请首先为自己获取一份Programming WCF Services的副本。

如果客户端是 WinForm 或 WPF,则需要使用[CallbackBehavior(UseSynchronizationContext = false)],否则客户端不会处理传入的消息,直到 UI 线程进入消息循环。

首先,WCF 中的“双工”通道并不是真正的双工! 来自的消息

  • 客户端到服务器
  • 可以阻止服务器正在等待来自客户端的消息
  • (或反过来)

因为消息仅在单个 WCF 通道上按顺序分派。双工 WCF 通道不会为您提供两个传入消息队列。从“TwoWay”调用返回的结果与 WCF 堆栈的这一级别的“调用”相同。 一旦你明白了这一点,很多问题就会变得更容易理解。

如果客户端是 WinForm 或 WPF,你可能需要使用[CallbackBehavior(UseSynchronizationContext = false)],否则客户端不会处理传入的消息,直到 UI 线程进入消息循环。

我发现了一些有助于避免死锁的规则。(看看我的 WCF 问题,看看我的痛苦!)

当来自同一客户端的呼叫正在进行时,服务器绝不能在同一连接上呼叫客户端。

和/或

在处理回调时,客户端绝不能在与“回调”相同的连接上回调服务器。

下次我想我将只使用两个合同(以及因此的 TCP 连接)一个用于回调,另一个用于所有客户端->服务器请求。或者使用我自己的投票系统,因为这让我非常痛苦。

对不起,我今天没有时间写一个例子。无论如何,大多数示例都适用于示例试图做的事情,但在现实生活中由于某种原因与您的应用程序有关。

我所知道的关于 WCF 示例的最佳网站是Juval Lowy 的网站

您可能还会发现我在 Stack Overflow 上提出的有关 WCF 的问题很有用,因为我遇到了与您相同的问题。

花一两天时间阅读 Stack Overflow 上的所有 WCF 问题和答案,可以很好地了解要避免的问题。

于 2010-08-19T09:21:11.527 回答
4

假设客户端是一个 WinForms 应用程序,您应该使用 Ian 的建议加上委托在 UI 线程上完成的工作(如果需要),使回调的处理独立于应用程序的其余部分。例如,如果服务器想要通知客户端某事,例如更改标签的文本,您可以执行以下操作:

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

Instance是一个静态属性,它返回单个实例frmMain并且是服务器想要更改lblSomething的一些实例。)此方法将立即返回并使服务器从等待客户端的 UI 中解放出来,并且 UI 将在它空闲时立即更新Label这样做。最重要的是,没有死锁,因为没有人在等待任何人。

于 2010-08-22T09:34:02.027 回答
2

http://www.codeproject.com/KB/WCF/WCF_Duplex_UI_Threads.aspx

于 2010-09-16T14:57:38.573 回答
0

对不起,我完全忘记了这个例子(:-$)。

这是我的服务器代码:
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”。让我知道它是否不起作用,或者您是否需要帮助来设置它。

于 2010-09-14T23:41:46.017 回答