Search code examples
wcfduplexnet.tcp

WCF duplex TCP communication error


I have a sample service to test WCF net.tcp communication. It is very simple service and all it does is subscribing a client to the service and then calls callbackchannel to notify all connected clients about broadcasted message. The service is hosted inside IIS 7.5.

Here is service code and test client to test it.

[ServiceContract(CallbackContract = typeof(ISampleServiceCallBack), SessionMode = SessionMode.Required)]
public interface ISampleCuratioService
{
    [OperationContract(IsOneWay = true)]
    void SubcribeToService(string sub);

    [OperationContract]
    string GetData(int value);

    [OperationContract(IsOneWay = true)]
    void Broadcast(string message);
}

public interface ISampleServiceCallBack
{
    [OperationContract(IsOneWay = true)]
    void NotifyClient(string message);
}

Here is the service implementation:

[ServiceBehavior(Name = "CuratioCSMService", InstanceContextMode = InstanceContextMode.PerSession)]
public class Service1 : ISampleCuratioService
{
    private static List<ISampleServiceCallBack> JoinedClien = new List<ISampleServiceCallBack>();

    public void SubcribeToService(string sub)
    {
        var subscriber = OperationContext.Current.GetCallbackChannel<ISampleServiceCallBack>();
        if (!JoinedClien.Contains(subscriber))
        {
            JoinedClien.Add(subscriber);
        }
    }

    public string GetData(int value)
    {
        return string.Format("You entered: {0}", value);
    }

    public void Broadcast(string message)
    {
        JoinedClien.ForEach(c => c.NotifyClient("message was received " + message));
    }
}

I can not understand the behavior I get when running it. After the first client runs everything works fine but as I close and open test client app, it throws exception notifying that channel can not be used for communication as it is in fault state.

This is sample test client:

    static void Main(string[] args)
    {
        var callneckclient = new ServiceClientProxy();
        var client = new SampleCuratioServiceClient(new InstanceContext(callneckclient));
        client.SubcribeToService("me");
        Console.ReadLine();

        for (int i = 0; i < 15; i++)
        {
            Console.WriteLine(client.GetData(5));
            client.Broadcast("this is from client me");
        }
        client.Close();
        Console.Read();
    }

    public class ServiceClientProxy : ISampleCuratioServiceCallback, IDisposable
    {
        public void NotifyClient(string message)
        {
            Console.WriteLine(message);
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }
    }

The situation gets even buggy when I run 5 clients. Non of those send or receive messages.


Solution

  • When a client calls SubcribeToService you add its operation context to a List called JoinedClien.

    When you call Broadcast in your server, you call the method NotifyClient on all collected operation contexts for every client that has ever connected.

    The problem is, that a disconnected client won't get removed from your JoinedClien list. When you try to call an operation method on a disconnected operation context, you get the channel is in faulted state error.

    To work around, you should subscribe to the Channel_Closed and Channel_Faulted events and also catch the CommunicationException when calling back into your clients and remove the operation context of the faulted clients:

    public void Broadcast(string message)
    {
        // copy list of clients
        List<OperationContext> clientsCopy = new List<OperationContext>();
        lock(JoinedClien) {
            clientsCopy.AddRange(JoinedClien);
        }
    
        // send message and collect faulted clients in separate list
        List<OperationContext> clientsToRemove = new List<OperationContext>();
        foreach (var c in JoinedClien) 
        { 
            try {
                c.NotifyClient("message was received " + message));
            }
            catch (CommunicationException ex) {
                clientsToRemove.Add(c);
            }
        }
    
        foreach (var c in clientsToRemove)
        {
            lock(JoinedClien) {
                if(JoinedClien.Contains(c))
                    JoinedClien.Remove(c);
            }
        }
    }
    

    When adding new clients you have to lock that operation, too:

    var subscriber = OperationContext.Current.GetCallbackChannel<ISampleServiceCallBack>();
    lock(JoinedClien) 
    {
        if (!JoinedClien.Contains(subscriber))
        {
            JoinedClien.Add(subscriber);
        }
    }