12

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

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

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

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

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

4

3 回答 3

23

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

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

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

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

<netNamedPipeBinding>
    <binding name="myBinding" receiveTimeout="infinite">
    </binding>
</netNamedPipeBinding>
于 2011-01-05T21:55:52.917 回答
18

我没有在 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 秒。任何小于此的内容都不会发送keepalives。

此外,我有一个基于计时器的 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

于 2008-12-04T15:01:37.453 回答
14

I have been looking into the problem of dropped TCP connections for two days now and came to the conclusion that a lot of people are missing a crutial point when setting up connections in WCF. What everybody seems to be doing is create a channel once and then trying to hold on to it for the lifetime of the application, playing all sorts of dirty tricks to keep the TCP session alive. This is not how it was meant to be.

You should create a channel, perform one (or some more shortly after the first) calls on your service, then close and dispose the channel. This will give you (virtually) stateless operation and you don't have to be bothered with keeping sessions alive, which you should not want in the first place.

You will find that the overhead of creating and closing a channel (from a reused ChannelFactory) is negligible, taking only some tens of nanoseconds on a typical machine.

The receiveTimeout attribute that everyone is cranking up defines the time a channel can remain idle before it is automatically dropped, which tells you channels are not meant to be kept open for very long (the default is 1 minute). If you set receiveTimeout to TimeSpan.MaxValue it will keep your channel open longer but this is not what it is for nor what you want in a practical scenario.

What finally got me on the right track was http://msdn.microsoft.com/en-us/library/ms734681.aspx which provides a horribly buggy example yet does show how one should go about using ChannelFactory. Responders point out the bugs and set the record straight so all in all you can get everything you need here.

And then, all my problems were over. No more "An operation was attempted on something that is not a socket" and no more "An existing connection was forcibly closed by the remote host".

于 2010-01-14T16:30:38.680 回答