如何在 WCF 中自动重新连接命名管道绑定

Posted

技术标签:

【中文标题】如何在 WCF 中自动重新连接命名管道绑定【英文标题】:How can I make Named Pipe binding reconnect automatically in WCF 【发布时间】:2010-09-25 07:51:05 【问题描述】:

我正在编写一个只接收来自本地主机的调用的服务。性能很重要,所以我想我会尝试使用NetNamedPipeBinding 而不是NetTcpBinding,看看是否能看到任何明显的性能提升。

如果客户端在向服务器执行了一个或多个请求后,长时间处于空闲状态,则下一个请求似乎由于绑定中的一些空闲超时而失败。当服务重新启动时也会发生同样的事情。

我需要我的客户能够在允许的时间内保持连接打开,以避免与设置新连接相关的开销。我还需要能够不时重新启动服务,并让客户端在注意到连接已终止时自动重试。

我知道 NetTcpBinding 中的可靠性内容支持这一点,但是如何在 NetNamedPipeBinding 中获得相同级别的重新连接可靠性?有可能吗?

这个问题有点学术性,因为它不是使用 NetNamedPipes 的要求,我可以很容易地采用它来使用 tcp 绑定,但它很痒,我真的很想从头开始。

【问题讨论】:

IIRC,CreateNamedPipe 的倒数第二个参数(NamedPipeBinding 下的非托管 win32 函数 - msdn.microsoft.com/en-us/library/windows/desktop/…)充当客户端连接超时,相当短。这可能与您在服务器启动时看到的超时有关;也许您可以使用反射器/dotpeek/调试器来查看哪些参数从 WCF 传递到本机函数,以及是否可以使用配置更改这些参数 既然问题是同一个学术问题,一般我会这样处理:看看调用了哪些本机函数,有哪些超时,然后追溯这些函数在托管代码中的调用位置,查看参数的来源。很长,但很有趣,可以帮助你弄清楚它是如何工作的 :) 我用这种方式调试了一个 Sharepoint 问题...... 【参考方案1】:

我的经验是,当使用 NetNamedPipes 时,绑定功能上的“ReceiveTimout”类似于“Inactivity Timeout”而不是接收 timout。请注意,这与 NetTCPBinding 的工作方式不同。使用 TCP,它确实是一个接收超时,并且有一个单独的不活动超时,您可以通过可靠的消息传递进行配置。 (这似乎也与 SDK 文档相反,但是哦,好吧)。

要解决此问题,请在创建绑定时将 RecieveTimout 设置为较大的值。 例如,如果您在程序上创建绑定...

NetNamedPipeBinding myBinding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
myBinding.ReceiveTimeout = TimeSpan.MaxValue;

或者,如果您关心以声明方式创建绑定...

<netNamedPipeBinding>
    <binding name="myBinding" receiveTimeout="infinite">
    </binding>
</netNamedPipeBinding>

【讨论】:

感谢您的提示。我已将我的(接收)超时设置为 20:00(20 分钟),并且我的所有操作都没有花费超过 2 分钟......甚至可能更可能 5 秒,并且事情正在发生。人们还应该使用同样吸引我的 using 语句来查看这个微软的“功能”。 ***.com/questions/573872/…【参考方案2】:

我没有在 WCF 中使用过 NetNamedPipes,但我花在学习 NetTcp 超时值上的时间比我关心的要多。我为我的 NetTcpBindings 使用了以下配置,并祝连接保持活跃。

服务器:

<binding name="MyBindingName" sendTimeout="00:00:30" receiveTimeout="infinite">
    <reliableSession enabled="true" inactivityTimeout="00:05:00" ordered="true" />
    <security mode="None" />
</binding>

客户:

<binding name="MyBindingName" closeTimeout="00:00:30" openTimeout="00:00:30" receiveTimeout="infinite" sendTimeout="00:00:30">
    <reliableSession enabled="true" inactivityTimeout="00:01:00" ordered="true" />
    <security mode="None" />
</binding>

我花费最多时间的重要设置是 sendTimeout 和 receiveTimeout。如果您的 receiveTimeout 与您的 send 相同或小于您的 send,则一旦达到该超时,通道将丢弃。如果接收更高并且发送高于阈值,则通道将触发传输级别保持活动。根据我的测试,sendTimeout 阈值为 30 秒。小于此值的任何内容都不会发送。

此外,我有一个基于计时器的 keepalive 调用,我每分钟执行一次以尝试确保通道正常运行。该调用只是对一个布尔返回成员:

[OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = false)]
bool KeepAlive();

public bool KeepAlive()

    return true;

您还可以获取频道事件(如果您在正确的时间获得它们)并在发生不良情况时重新打开连接:

InstanceContext site = new InstanceContext(this);
_proxy = new MyServiceChannel(site);
if (_proxy != null) 

    if (_proxy.Login()) 
    
        //Login was successful
        //Add channel event handlers so we can determine if something goes wrong
        foreach (IChannel a in site.OutgoingChannels) 
        
            a.Opened += Channel_Opened;
            a.Faulted += Channel_Faulted;
            a.Closing += Channel_Closing;
            a.Closed += Channel_Closed;
        
    

我希望其中的一些内容可以通过 NetNamedPipes 转化并为您带来价值。

编辑:用于捕获服务器重新启动问题的更多选项

当服务器重新启动时,它应该会导致客户端的通道关闭或出现故障。在客户端捕获这些事件将使您可以选择使用重新连接计时器,直到服务再次可用。

private void Channel_Faulted(object sender, EventArgs e)

    IChannel channel = sender as IChannel;
    if (channel != null) 
    
        channel.Abort();
        channel.Close();
    

    //Disable the keep alive timer now that the channel is faulted
    _keepAliveTimer.Stop();

    //The proxy channel should no longer be used
    AbortProxy();

    //Enable the try again timer and attempt to reconnect
    _reconnectTimer.Start();


private void _reconnectTimer_Tick(object sender, System.EventArgs e)

    if (_proxy == null) 
    
        InstanceContext site = new InstanceContext(this);
        _proxy = new StateManagerClient(site);
    
    if (_proxy != null) 
    
        if (_proxy.Login()) 
        
            //The connection is back up
            _reconnectTimer.Stop();
            _keepAliveTimer.Start();
        
        else 
        
            //The channel has likely faulted and the proxy should be destroyed
            AbortProxy();
        
    


public void AbortProxy()

    if (_proxy != null) 
    
        _proxy.Abort();
        _proxy.Close();
        _proxy = null;
    

您希望确保重新连接计时器的登录尝试在后台线程上异步完成,这样它们就不会在每次尝试登录时都挂起 UI。 YMMV

【讨论】:

感谢 Chris,但不幸的是,修改超时和添加 keepalive 方法只能解决我的两个问题中的第一个。重新启动服务后,我仍然会遇到同样的问题。 如何以编程方式设置绑定/reliableSession?【参考方案3】:

我这两天一直在研究 TCP 连接断开的问题,得出的结论是很多人在 WCF 中设置连接时遗漏了一个关键点。每个人似乎都在做的是创建一个通道,然后尝试在应用程序的整个生命周期中保持它,玩各种肮脏的技巧来保持 TCP 会话的活动。这不是它的本意。

您应该创建一个通道,对您的服务执行一次(或在第一次之后不久)调用,然后关闭并处置该通道。这将为您提供(实际上)无状态操作,并且您不必为保持会话活动而烦恼,这是您一开始就不应该想要的。

您会发现创建和关闭通道(来自重用的 ChannelFactory)的开销可以忽略不计,在典型机器上只需几十纳秒。

每个人都在启动的 receiveTimeout 属性定义了通道在自动删除之前可以保持空闲的时间,这告诉您通道不应该保持打开很长时间(默认值为 1 分钟)。如果您将 receiveTimeout 设置为 TimeSpan.MaxValue,它将使您的频道保持更长的时间,但这不是它的用途,也不是您在实际场景中想要的。

最终让我走上正轨的是 http://msdn.microsoft.com/en-us/library/ms734681.aspx 它提供了一个可怕的错误示例,但确实显示了应该如何使用 ChannelFactory。响应者指出错误并直接记录,因此总而言之,您可以在这里获得所需的一切。

然后,我所有的问题都解决了。不再有“尝试对不是套接字的东西进行操作”,也不再有“现有连接被远程主机强制关闭”。

【讨论】:

social.msdn.microsoft.com/forums/en-US/wcf/thread/… - Dave Cliffe (MSFT) 建议这是一项相当昂贵的操作,如果可能的话应该重用频道 @Martin:“创建和关闭频道的开销……可以忽略不计”。这不取决于安全协商之类的东西吗?我想与 AD 服务器之类的东西重新协商凭据绝非微不足道。 @romkyns:这里有一篇很好的 MSDN 博客,介绍了 .NET 3.5 对 ClientBase 的性能改进:blogs.msdn.com/wenlong/archive/2007/10/27/… “双工”通信怎么样?服务器到客户端的事件等? 是的,我什至想知道双工通道的情况。如果客户端在订阅后关闭频道,服务器如何将数据发送回订阅的客户端?我正在尝试使用客户端订阅服务器事件的那个频道。

以上是关于如何在 WCF 中自动重新连接命名管道绑定的主要内容,如果未能解决你的问题,请参考以下文章

WCF 命名管道流

使用命名管道 WCF 服务时通信对象出错

WCF 服务命名管道故障

带有命名管道的 WCF:如何允许并行调用?

POS Ready 2009 上的 WCF 命名管道

如何使用命名管道从 C++ 调用 WCF 方法?