WCF Broadcast do wszystkich klientów

0

Cześć.

Przy użyciu WCF stworzyłem serwis. Teraz usiłuje zaimplementować metodę, która zaktualizuje dane we wszystkich klientach poprzez wywołanie metody callback.

Serwis wystawia metodę, dzięki której klient "rejestruje się" w usłudze:

 
private static Dictionary<Guid, ICallbacks> clients = new Dictionary<Guid, ICallbacks>();

public void RegisterInService(Guid clientID)
{
            ICallbacks callback = OperationContext.Current.GetCallbackChannel<ICallbacks>();

            lock (clients)
            {
                if (!clients.ContainsKey(clientID))
                {
                    clients.Add(clientID, callback);
                }
            }
}

public void UnregisterFromService(Guid clientID)
{
            lock(clients)
            {
                if (clients.ContainsKey(clientID))
                    clients.Remove(clientID);
            }
}

Opcje serwisu zostały zostały zdefiniowane w ten sposób:

 
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]

Serwis posiada również metodę, której zadaniem jest wykonanie bardziej złożonych operacji, a następnie wywołanie kolejnej metody, której zadaniem jest rozesłanie wyników do wszystkich "zarejestrowanych" klientów:

public void MainProcessActions()
{

            SendMessageToAllClients("Roześlij do wszystkich");
}

private void SendMessageToAllClients(string data)
{
            StreamWriter file = new System.IO.StreamWriter(@"E:\Temp\ClientsInvokingEvidence.txt", true);

            lock (clients)
            {
                foreach (KeyValuePair<Guid, ICallbacks> client in clients)
                {

                    client.Value.UpdateClientData(data);

                    file.WriteLine("Invoking method for client ID : {0}", client.Key);

                }
            }

            file.Close();

}
 

Problem w tym, że metoda callback u klienta:

 
public interface ICallbacks
{
        [OperationContract(IsOneWay = true)]
        void UpdateClientData(string data);
}

, wywoływana jest tylko dla instancji aplikacji na rzecz której wywołana została metoda MainProcessActions(), a docelowo chciałbym aby została wywołana dla każdej uruchomionej instancji aplikacji. Co w teorii powinno być realizowane przez metodę SendMessageToAllClients() - dla każdego elementu słownika. Poszczególni klienci rejestrują się poprawnie w serwisie. Podczas wykonywania metody SendMessageToAllClients w słowniku istnieje tyle wpisów co uruchomionych aplikacji klienckich.

#edit

Okazuje się, że metoda callback jest jednak prawidłowo wywoływana dla każdego klienta... Problem leży zupełnie gdzie indziej. Otóż, dane otrzymane z serwisu używane są do ustawiania tekstu w kontrolce TextBox w Windows Forms. W klasie, w której implementuję metodę callback stworzyłem property, które jest ustawiane właśnie przez tą metodę. Następnie w klasie głównej formy używam instancji klasy z metodą callback aby ustawić tekst w kontrolce. Niestety, tak jak wspomniałem wcześniej tekst jest ustawiany tylko dla instancji klienta na rzecz którego zainicjalizowane zostało wywołanie metody MainProcessActtions() po stronie serwisu.

Czy nie ma to przypadkiem czegoś wspólnego z Synchronization Context?

Jeżeli do klasy, w której implementuje metodę callback przekażę referencję do obiektu kontrolki TextBox, wówczas jestem w stanie ustawić tekst dla każdego klienta równocześnie - tylko nie wiem czy jest to dobre rozwiązanie (jest to raczej sprzeczne z modelem View / Controler).

Macie jakiś pomysł?

Możecie mi dać jakąś wskazówkę, która naprowadzi mnie na rozwiązanie tego problemu?

Pozdrawiam

0

Witam,

Możesz mi powiedzieć na jakiej podstawie końcówka która nie wywołała funkcję WCF-a wie o tym że stan który został zwrócony do innej końcówki się zmienił? Zakładając zasadę żądanie/odpowiedź (wywołania funkcji callback) nie miało miejsca dla końcówki która takiej operacji nie przeprowadziła. Musisz napisać sobie funkcję która będzie wysyłała "ping" z zapytaniem czy ma odświeżyć stan (np w ping-u jest data ostatniej aktualizacji). Możesz to robić np za pomocą timer-a.

Pozdrawiam,

mr-owl

0
Krwawy Kot napisał(a):

Witam,

Możesz mi powiedzieć na jakiej podstawie końcówka która nie wywołała funkcję WCF-a wie o tym że stan który został zwrócony do innej końcówki się zmienił? Zakładając zasadę żądanie/odpowiedź (wywołania funkcji callback) nie miało miejsca dla końcówki która takiej operacji nie przeprowadziła. Musisz napisać sobie funkcję która będzie wysyłała "ping" z zapytaniem czy ma odświeżyć stan (np w ping-u jest data ostatniej aktualizacji). Możesz to robić np za pomocą timer-a.

Pozdrawiam,

mr-owl

Na tej podstawie, że każda instancja klienta "rejestruje" się w serwisie podczas uruchamiania - metoda RegisterInService przy użyciu GetCallbackChannel. Nie w tym rzecz. W aktualizacji postu napisałem, że jednak metoda callback jest uruchamiana na rzecz każdego klienta - dla pewności przesyłam do niej unikalne ID przy użyciu którego klient rejestrował się w serwisie, dla weryfikacji czy dane dotarły do właściwego klienta.

Problem pojawia się z przekazaniem "dalej" danych otrzymanych z serwisu.

Poniżej uproszczona wersja klasy odpowiedzialnej za komunikację z serwisem.

 
[CallbackBehavior(UseSynchronizationContext = false)]
class WCFServiceCommunication : ICallbacks
{
    public string DataFromService;

    ...

    public WCFServiceCommunication()
    {
        binding = new NetNamedPipeBinding();

        pipeFactory = new DuplexChannelFactory<IGeneralContract>(this, binding, new EndpointAddress("net.pipe://localhost/SomeName"));

        pipeProxy = pipeFactory.CreateChannel();

        ClientID = Guid.NewGuid();
    }

    public void ExecuteOnServiceSide()
    {
        pipeProxy.MainProcessActions();
    }

    public void RegisterClient()
    {
        pipeProxy.RegisterInService(ClientID);
    }

    public void UnregisterClient()
    {
        pipeProxy.UnregisterFromService(ClientID);
    }

    public void UpdateClientData(string data)
    {
        // here I'm able to verify that this method actually has been invoked by service for each instance of the client

        DataFromService = data;
    }

}

Następnie, w klasie Formy tworzę instancje tej klasy:

 
class MainForm : Form
{
    private WCFServiceCommunication wcfCommunication;
    ....

    public MainForm()
    {
        InitalizeComponent();

        wcfCommunication = new WCFServiceCommunication();

        wcfCommunication.RegisterClient();

    }

    ...

    private void btnCheckForUpdates_Click(object sender, EventArgs e)
    {
        // here, on service side method MainProcessActions() is called
        // in this method I'm calling broadcast method
        wcfCommunication.ExecuteOnServiceSide();

    }
}

Powtarzam, komunikacja pomiędzy procesami zachodzi poprawnie i dla każdej instancji klienta otrzymuje odpowiednie dane w metodzie callback (UpdateClientData(string data)).

Problem w tym, że gdy po wykonaniu metody "pod przyciskiem" chciałbym później użyć pola:

public string DataFromService;

, zdefiniowanego w klasie:

WCFServiceCommunication

, to jestem w stanie to zrobić tylko na rzecz klienta który zainicjalizował wywołanie metody:

 MainProcessActions() 

, po stronie serwisu.

Innymi słowy, każdy z klientów "wewnątrz" obiektu klasy WCFServiceCommunication posiada unikalną wartość pola DataFromService otrzymaną z serwisu, ale gdy dochodzi do użycia tego pola poza klasą w której zostało zdefiniowane (klasa obsługi formy), wówczas wartość ta jest dostępna tylko dla jednego klienta.

1 użytkowników online, w tym zalogowanych: 0, gości: 1