为长时间运行的WCF应用程序选择正确的连接属性

时间:2010-03-06 12:40:44

标签: wcf wcf-binding wcf-client

我正在使用WCF在C#中编写客户端/服务器应用程序。我的所有测试都没问题,但是一旦我部署了服务,我发现与服务器通信时出现了随机问题。 我启用了调试并在服务器中看到了这样的消息:

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

模式是这样的:

  • 客户端正在发送查询
  • 服务正在处理查询
  • 服务正在发回一些东西
  • 活动边界是“停止”级别 - 一切似乎都很好
  • 将可靠会话的inactivetivityTimeout添加到上次联系的日期时间,并且您具有服务抛出的异常的时间戳

应用程序如下所示:服务实例提供了与数据库交互的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建议的)
  • 有没有办法可靠地检测服务器内的连接丢失?

2 个答案:

答案 0 :(得分:6)

只是一个疯狂的想法:既然你的客户似乎在很长一段时间内发送消息(甚至几天),但似乎不经常发送它们 - 你是否可以重新设计你的应用程序以使用类似消息的内容队列而不是回调合同?

队列是解耦两个系统并减少超时等潜力的好方法。

在基于队列的方案中,您的客户端会将邮件放入队列(例如,每个Windows Server副本附带的MSMQ),您的服务将在该传入队列中侦听邮件。该服务将获取消息,处理它们,并且通常将某种响应消息放回第二个队列(客户端然后监听)。

这里的主要好处是:

  • 您不需要相当脆弱和复杂的回拨合同设置
  • 您的系统已解耦,即使它们之间的连接断开几秒钟,几分钟,几小时也会继续工作
  • 您的服务可以响应收到的消息并发回更多“目标”响应,例如某些客户端可以监听“正常”响应队列,其他客户端可以监听“优先级”响应队列等等 - 系统只是为您提供更大的灵活性IMO

查看更多资源:

并查看

等内容

和其他系统(MassTransit等人)支持排队通信,以便在系统之间提供可靠的可扩展,可靠的消息传递。

答案 1 :(得分:0)

好的,问题解决了。

每当一个客户端意外死亡而没有注销时,就会出现问题。 因此,每当客户端从服务中调用方法时,该方法本身希望向所有连接的客户端广播消息,它就会遇到超时,从而阻止客户端。 因此,将所有回调包装到委托函数中有助于解决问题。

我是这样做的:

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
        }
    }

谢谢!