Ошибка дуплексной TCP-связи WCF

У меня есть пример службы для тестирования связи WCF net.tcp. Это очень простая служба, и все, что она делает, — это подписывает клиента на службу, а затем вызывает канал обратного вызова, чтобы уведомить всех подключенных клиентов о широковещательном сообщении. Служба размещена внутри IIS 7.5.

Вот сервисный код и тестовый клиент для его проверки.

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

Вот реализация сервиса:

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

Я не могу понять поведение, которое я получаю при запуске. После запуска первого клиента все работает нормально, но когда я закрываю и открываю тестовое клиентское приложение, оно выдает исключение, уведомляющее о том, что канал не может использоваться для связи, поскольку он находится в состоянии сбоя.

Это пример тестового клиента:

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

Ситуация становится еще хуже, когда я запускаю 5 клиентов. Ни один из них не отправляет и не получает сообщения.


person Rati_Ge    schedule 29.12.2011    source источник


Ответы (1)


Когда клиент вызывает SubcribeToService, вы добавляете контекст его операции в список с именем JoinedClien.

Когда вы вызываете Broadcast на своем сервере, вы вызываете метод NotifyClient для всех собранных контекстов операций для каждого клиента, который когда-либо подключался.

Проблема в том, что отключенный клиент не будет удален из вашего JoinedClien списка. Когда вы пытаетесь вызвать метод операции в отключенном контексте операции, вы получаете сообщение об ошибке канал находится в состоянии сбоя.

Чтобы обойти это, вы должны подписаться на события Channel_Closed и Channel_Faulted, а также перехватить CommunicationException при обратном вызове своих клиентов и удалить контекст операции отказавших клиентов:

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

При добавлении новых клиентов вы также должны заблокировать эту операцию:

var subscriber = OperationContext.Current.GetCallbackChannel<ISampleServiceCallBack>();
lock(JoinedClien) 
{
    if (!JoinedClien.Contains(subscriber))
    {
        JoinedClien.Add(subscriber);
    }
}
person Jan    schedule 29.12.2011
comment
не могли бы вы предоставить дополнительную информацию о блокировке? я имею в виду, что я изменил службу в соответствии с вашим советом, я изменил ее, чтобы у нее был код проверки состояния неисправности - person Rati_Ge; 29.12.2011
comment
Я обновил свой ответ, чтобы показать синхронизированный доступ к вашим общим данным. - person Jan; 30.12.2011
comment
и последний вопрос? Можете ли вы предоставить какие-либо предложения или замечания относительно того, как я реализовал это уведомление и службу на основе push? это правильный способ вести список клиентов и отправлять уведомления? как TCP привязка используется с сессией? - person Rati_Ge; 30.12.2011