2

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

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 建议的)
  • 有没有办法可靠地检测服务器内部的连接断开?
4

2 回答 2

6

只是一个疯狂的想法:因为您的客户似乎在很长一段时间内(甚至几天)发送消息,但似乎并不经常发送它们 - 您是否可以重新构建您的应用程序以使用消息队列之类的东西而不是回调合约?

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

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

这里的主要好处是:

  • 你不需要相当脆弱和复杂的回调合约设置
  • 您的系统已解耦,即使它们之间的连接中断了几秒钟、几分钟、几小时,它们仍将继续工作
  • 您的服务可以响应传入消息并发送回更多“有针对性”的响应,例如某些客户端可以在“正常”响应队列上侦听,其他客户端可以在“优先级”响应队列上侦听等 - 系统只会为您提供更大的灵活性 IMO

查看更多资源:

并检查诸如

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

于 2010-03-06T14:38:58.270 回答
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
        }
    }

谢谢!!

于 2010-03-06T23:46:10.430 回答