开发者

How to detect if WCF Streaming Client Disconnects Mid-Stream

开发者 https://www.devze.com 2023-02-10 12:21 出处:网络
I have a streaming server that with a contract looking something like this: [ServiceContract] public interface IStreamingService

I have a streaming server that with a contract looking something like this:

[ServiceContract]
public interface IStreamingService
{
    [OperationContract(Action = "StreamingMessageRequest", ReplyAction = "StreamingMessageReply")]
    Message GetStreamingData(Message query);
}

Here's a rudimentary implementation with stuff (like error handling) removed to simplify things:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
[ErrorBehavior(typeof(StreamingServiceErrorHandler))]
public class StreamingService : IStreamingService
{
    public StreamingService()
    {
    }

    public Message GetStreamingData(Message query)
    {
        var dataQuery = query.GetBody<DataQuery>();

        // Hook up events to let us know if the client disconnects so that we can stop the query...
        EventHandler closeAction = (sender, ea) =>
        {
            dataQuery.Stop();
        };
        OperationContext.Current.Channel.Faulted += closeAction;
        OperationContext.Current.Channel.Closed += closeAction;

        Message streamingMessage = Message.CreateMessage(
            MessageVersion.Soap12WSAddressing10,
            "QueryMessageReply",
            new S开发者_如何学编程treamingBodyWriter(QueryMethod(dataQuery));
        return streamingMessage;
    }

    public IEnumerable<object> QueryMethod (DataQuery query)
    {
        // Returns a potentially infinite stream of objects in response to the query
    }
}

This implementation uses a custom BodyWriter to stream results from the QueryMethod:

public class StreamingBodyWriter : BodyWriter
{
    public StreamingBodyWriter(IEnumerable items)
        : base(false) // False should be passed here to avoid buffering the message 
    {
        Items = items;
    }

    internal IEnumerable Items { get; private set; }

    private void SerializeObject(XmlDictionaryWriter writer, object item)
    {
        // Serialize the object to the stream
    }

    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
    {
        foreach (object item in Items)
        {
            SerializeObject(writer, item);
        }
    }
}

The client connects and starts reading the data stream. Something like this:

public IEnumerable<T> GetStreamingData<T>(Message queryMessage)
{
    Message reply = _Server.GetStreamingData(queryMessage);

    // Get a chunckable reader for the streaming reply
    XmlReader reader = reply.GetReaderAtBodyContents();

    // Read the stream of objects, deserializing each one as appropriate
    while (!reader.EOF)
    {
        object item = DeserializeObject(reader);
        if (item == null)
            continue;

        // Yield each item as it's deserialized, resulting in a [potentially] never-ending stream of objects
        yield return (T)item;
    }
}

This works very well and I get a stream of objects back to the client. Very nice. The problem is when the client disconnects mid-stream (either gracefully or ungracefully). In either case, the server doesn't get notified of this disconnect other than as a fault picked up by the service's error handler.

As you can see, I've tried hooking the Faulted and Closed channel events in the service method but they are not firing when the client disconnects.

I suspect that these events don't fire because with a streaming contract the service has already returned from the operation method (GetStreamingData). This method has returned a Message with a custom body-writer and it's this body-write that (lower in the service channel stack) is grabbing results from the calculation thread (via an IEnumerable) and streaming them in to the reply message. Since the operation method has returned then I'm guessing these channel events don't get fire.

The binding is a customized version of net.tcp binding (non-duplex) with only two binding elements: BinaryMessageEncodingBindingElement and TcpTransportBindingElement. No security or anything. Basically just raw net.tcp with binary messages.

The problem is that when a client disconnects, I want the server to stop the background calculation thread that is generating results and feeding them into the IEnumerable being read by the BodyWriter. The body writer has stopped (obviously), but the thread continues to live.

So where can I hook to discover if a client has disconnected mid-stream?

Update:

There are two main disconnection cases: Explicit disconnection, due either to the disposal of the client proxy or termination of the process at the client; Passive disconnection, usually due to a network failure (example: ISP drops the connection, or the network cable gets yanked).

In the first case, there is an explicit connection exception that get's received by the server when it tries to send new data down the stream. No problem.

The second scenario of a broken network pipe is more troublesome. Usually this condition is detected based on the Send timeout, but since this is a streaming interface the send timeout is cranked way up (I have it cranked to several days since it is conceivable that streaming data transfers could last that long). So when a client is disconnected due to a flaky network pipe, the service continues to send data down a connection that does really exist any more.

At this point I don't know of a good way to resolve the second option. Suggestions?


The Faulted event is really the best thing for handling these types of scenarios, but it will only fire if reliableSession is enabled. It is enabled by default in the standard netTcpBinding, but won't be enabled in this case because you are using a custom stripped down version of the binding. Try adding this to your custom binding :)

0

精彩评论

暂无评论...
验证码 换一张
取 消