How can I make Named Pipe binding reconnect automatically in WCF

淺唱寂寞╮ 提交于 2019-11-28 18:02:31
Matthew Hess

My experience is that, when using NetNamedPipes, the "ReceiveTimout" on the binding functions like an "Inactivity Timeout" rather than a receive timout. Note that this different than how a NetTCPBinding works. With TCP, it really is a receive timeout and there's a separate inactivity timeout you can configure via reliable messaging. (It's also appears to be contrary to the SDK documentation, but oh well).

To fix this, set the RecieveTimout to something big when you create the binding.
For example, if you are creating your binding procedurally...

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

Or, if you care creating your binding declaratively...

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

I haven't used NetNamedPipes in WCF but I spent more time than I cared to learning the timeout values for NetTcp. I use the following configs for my NetTcpBindings and had good luck with the connection staying active.

Server:

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

Client:

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

The important settings that I spent the most time on are the sendTimeout and receiveTimeout. If your receiveTimeout is the same or less than your send, the channel will drop once that timeout is reached. If the receive is higher and the send is above a threshold, the channel will fire a transport level keepalive. From my tests, the sendTimeout threshold is 30 seconds. Anything less than that and the keepalives aren't sent.

Additionally, I have a timer based keepalive call that I execute every minute to try and ensure the channel is up and working well. The call is simply to a boolean return member:

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

public bool KeepAlive()
{
    return true;
}

You can also grab the channel events (if you get them at the right time) and reopen the connection if something bad happens:

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

I hope some of this translates and has value for you with NetNamedPipes.

Edit: More options for capturing the server restarted issue

When the server restarts it should cause the client's channel to either close or fault. Capturing those events on the client side would give you the option of using reconnect timer until the service is available again.

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

You would want to ensure the reconnect timer's login attempts are done on a background thread asynchronously so they don't hang the UI every time they attempt to login. YMMV

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".

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!