views:

3636

answers:

3

I'm writing a service that will only get calls from the local host. Performance is important so I thought I'd try the NetNamedPipeBinding instead of NetTcpBinding and see If I could see any noticeable performance gains.

If a client, after having performed one or more requests to the server, is idle for a longer period of time the next request will fail seemingly due to some idle timeout in the binding. The same thing also happens when the service gets restarted.

I need my clients to be able to keep a connection open for as long as it's allowed in order to avoid the overhead associated with setting up a new connection. I also need to be able to restart the service from time to time and have the clients to automatically retry if they notice that the connection has been terminated.

I know that this is suppported by the reliability stuff in NetTcpBinding but how would one go about getting the same level of re-connect reliability in the NetNamedPipeBinding? Is it even possible?

The question is somewhat academic as it isn't a requirement to use NetNamedPipes, I could just as easily adopt it to use the tcp-binding but It's an itch and I'd really like to scratch it.

+4  A: 

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

Chris Porter
Thanks Chris but modifying timeouts and adding a keepalive method would unfortunately only solve the first of my two problems. I'd still have the same problem when the service gets restarted.
Markus Olsson
A: 

While the previous answer is also excellent (thanks), you can get reliable messaging on named pipes by creating a custombinding and creating your channels with that.

Could you please expand your answer a bit? How exactly would I go about doing that?
Markus Olsson
Very incomplete answer.
Timwi
+2  A: 

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

Martin Maat
http://social.msdn.microsoft.com/forums/en-US/wcf/thread/d5ab7e93-ff03-4d4f-badc-91423236e49b - Dave Cliffe (MSFT) suggests that this is a fairly expensive operation and that channels should be reused if possible
romkyns
@Martin: "the overhead of creating and closing a channel ... is negligible". Does that not depend on things like security negotiation? I imagine renegotiating credentials with something like an AD server is far from negligible.
Xiaofu
@romkyns: There's a good MSDN blog on the performance improvements to ClientBase that came with .NET 3.5 here: http://blogs.msdn.com/wenlong/archive/2007/10/27/performance-improvement-of-wcf-client-proxy-creation-and-best-practices.aspx
Xiaofu