wcfduplexsimultaneousnet.tcptwo-way

Can Client of a WCF Duplex Service(TCP binding) send and recive at the same time?


My code at the moment looks like this:
Server side:

#region IClientCallback interface
interface IClientCallback
{
    [OperationContract(IsOneWay = true)]
    void ReceiveWcfElement(WcfElement wcfElement);
}
#endregion

#region IService interface
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IClientCallback))]
interface IService
{
    [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
    void ReadyToReceive(string userName, int source, string ostatniTypWiadomosci);

    [OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = false)]
    bool SendWcfElement(WcfElement wcfElement);

    [OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]
    List<int> Login(Client name, string password, bool isAuto, bool isSuperMode);
}
#endregion

#region Public enums/event args
public delegate void WcfElementsReceivedFromClientEventHandler(object sender, WcfElementsReceivedFromClientEventArgs e);
public class WcfElementsReceivedFromClientEventArgs : EventArgs
{
    public string UserName;
}

public class ServiceEventArgs : EventArgs
{
    public WcfElement WcfElement;
    public Client Person;
}
#endregion

#region Service
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service : IService
{        
    #region Instance fields
    //thread sync lock object
    private static readonly Object SyncObj = new Object();
    //callback interface for clients
    IClientCallback _callback;
    //delegate used for BroadcastEvent
    public delegate void ChatEventHandler(object sender, ServiceEventArgs e);
    public static event ChatEventHandler ChatEvent;
    private ChatEventHandler _myEventHandler;
    //holds a list of clients, and a delegate to allow the BroadcastEvent to work
    //out which chatter delegate to invoke
    static readonly Dictionary<Client, ChatEventHandler> Clients = new Dictionary<Client, ChatEventHandler>();
    //current person 
    private Client _client;
    #endregion
    #region Helpers
    private bool CheckIfPersonExists(string name)
    {
        return Clients.Keys.Any(p => p.UserName.Equals(name, StringComparison.OrdinalIgnoreCase));
    }

    private ChatEventHandler getPersonHandler(string name)
    {
        foreach (var c in Clients.Keys.Where(c => c.UserName.Equals(name, StringComparison.OrdinalIgnoreCase)))
        {
            ChatEventHandler chatTo;
            Clients.TryGetValue(c, out chatTo);
            return chatTo;
        }
        return null;
    }

    private Client GetPerson(string name)
    {
        return Clients.Keys.FirstOrDefault(c => c.UserName.Equals(name, StringComparison.OrdinalIgnoreCase));
    }

    #endregion

    #region IService implementation
    public List<int> Login(Client client, string password, bool isAuto, bool isSuperMode)
    {
        if (client.ElementsVersions == null)
        {
            client.ElementsVersions = new WcfElement(WcfElement.RodzajWiadomosci.VersionControl, client.UserName);
        }
        //create a new ChatEventHandler delegate, pointing to the MyEventHandler() method
        _myEventHandler = MyEventHandler;

        lock (SyncObj)
        {
            if (!CheckIfPersonExists(client.UserName))
            {
                _client = client;
                Clients.Add(client, _myEventHandler);
            }
            else
            {
                _client = client;
                foreach (var c in Clients.Keys.Where(c => c.UserName.Equals(client.UserName)))
                {
                    ChatEvent -= Clients[c];
                    Clients.Remove(c);
                    break;
                }
                Clients[client] = _myEventHandler;
            }
            _client.LockObj = new object();
        }

        _callback = OperationContext.Current.GetCallbackChannel<IClientCallback>();
        ChatEvent += _myEventHandler;
        var rValue = isAuto ? bazaDanych.Login(client.UserName, isSuperMode) : bazaDanych.Login(client.UserName, password);
        return rValue;
    }

    public void PerformDataSync(Client c)
    {
        WcfElement wcfDelete = null;
        WcfElement wcfUpdate = null;
        //...
        //this method prepares elements for client
        //when done it adds them to clients queue (List<WcfElement)

        try
        {
            var counter = 0;
            if (wcfDelete != null)
            {
                foreach (var wcf in WcfElement.SplitWcfElement(wcfDelete, false))//split message into small ones
                {
                    c.AddElementToQueue(wcf, counter++);
                }
            }
            if (wcfUpdate != null)
            {
                foreach (var wcf in WcfElement.SplitWcfElement(wcfUpdate, true))
                {
                    c.AddElementToQueue(wcf, counter++);
                }
            }
            SendMessageToGui(string.Format("Wstępna synchronizacja użytkownika {0} zakończona.", c.UserName));
            c.IsSynchronized = true;
        }
        catch (Exception e)
        {
        }
    }

    private void SendMessageToClient(object sender, EventArgs e)
    {
        var c = (Client) sender;
        if (c.IsReceiving || c.IsSending)
        {
            return;
        }
        c.IsReceiving = true;
        var wcfElement = c.GetFirstElementFromQueue();
        if (wcfElement == null)
        {
            c.IsReceiving = false;
            return;
        }
        Clients[c].Invoke(this, new ServiceEventArgs { Person = c, WcfElement = wcfElement });
    }

    public void ReadyToReceive(string userName)
    {
        var c = GetPerson(userName);
        c.IsSending = false;
        c.IsReceiving = false;
        if (c.IsSynchronized)
        {
            SendMessageToClient(c, null);
        }
        else
        {
            PerformDataSync(c);
        }
    }

    public bool SendWcfElement(WcfElement wcfElement)
    {
        var cl = GetPerson(wcfElement.UserName);
        cl.IsSending = true;
        if (wcfElement.WcfElementVersion != bazaDanych.WcfElementVersion) return false;

        //method processes messages and if needed creates creates WcfElements which are added to every clients queue 
        return ifSuccess;
    }
    #endregion
    #region private methods
    private void MyEventHandler(object sender, ServiceEventArgs e)
    {
        try
        {
            _callback.ReceiveWcfElement(e.WcfElement);
        }
        catch (Exception ex)
        {
        }
    }
    #endregion
}
#endregion

Client side in a moment

#region Client class
[DataContract]
public class Client
{
    #region Instance Fields

    /// <summary>
    /// The UserName
    /// </summary>
    [DataMember]
    public string UserName { get; set; }

    [DataMember]
    public WcfElement ElementsVersions { get; set; }

    private bool _isSynchronized;
    public bool IsSynchronized
    {
        get { return _isSynchronized; }
        set 
        { 
            _isSynchronized = value;
        }
    }

    public bool IsSending { get; set; }
    public bool IsReceiving { get; set; }

    private List<WcfElement> ElementsQueue { get; set; }
    public object LockObj { get; set; }

    public void AddElementToQueue(WcfElement wcfElement, int position = -1)
    {
        try
        {
            lock (LockObj)
            {
                if (ElementsQueue == null) ElementsQueue = new List<WcfElement>();
                if (position != -1 && position <= ElementsQueue.Count)
                {
                    try
                    {
                        ElementsQueue.Insert(position, wcfElement);
                    }
                    catch (Exception e)
                    {
                    }
                }
                else
                {
                    try
                    {
                        //dodaje na koncu
                        ElementsQueue.Add(wcfElement);
                    }
                    catch (Exception e)
                    {
                    }
                }
            }
        }
        catch (Exception e)
        {
        }
    }

    public WcfElement GetFirstElementFromQueue()
    {
        if (ElementsQueue == null) return null;
        if (ElementsQueue.Count > 0)
        {
            var tmp = ElementsQueue[0];
            ElementsQueue.RemoveAt(0);
            return tmp;
        }
        return null;
    }

    #endregion
    #region Ctors
    /// <summary>
    /// Assign constructor
    /// </summary>
    /// <param name="userName">The userName to use for this client</param>
    public Client(string userName)
    {
        UserName = userName;
    }
    #endregion
}
#endregion

ProxySingletion:

[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
public sealed class ProxySingleton : IClientCallback
{
    #region Instance Fields

    private static ProxySingleton _singleton;
    public static bool IsConnected;
    private static readonly object SingletonLock = new object();
    private ServiceProxy _proxy;
    private Client _myPerson;

    private delegate void HandleDelegate(Client[] list);

    private delegate void HandleErrorDelegate();

    //main proxy event
    public delegate void ProxyEventHandler(object sender, ProxyEventArgs e);

    public static event ProxyEventHandler ProxyEvent;
    //callback proxy event
    public delegate void ProxyCallBackEventHandler(object sender, ProxyCallBackEventArgs e);

    public static event ProxyCallBackEventHandler ProxyCallBackEvent;

    #endregion

    #region Ctor

    /// <summary>
    /// Blank constructor
    /// </summary>
    private ProxySingleton()
    {
    }

    #endregion

    #region Public Methods

    #region IClientCallback implementation
    public void ReceiveWcfElement(WcfElement wcfElement)
    {
        //process received data
        //...
        ReadyToReceive();
    }
    #endregion

    public void ReadyToReceive()
    {
        try
        {
            if (bazaDanych.Dane.Client.IsSending) return;
            var w = bazaDanych.Dane.Client.GetFirstElementFromQueue();
            if (w != null)
            {
                SendWcfElement(w);
                return;
            }
            _proxy.ReadyToReceive(bazaDanych.Dane.Client.UserName, source, ostatniTypWiadomosci);
        }
        catch (Exception)
        {
            IsConnected = false;
        }
    }

    public static WcfElement CurrentWcfElement;
    public bool SendWcfElement(WcfElement wcfElement)
    {
        if (bazaDanych.Dane.Client.IsReceiving)
        {
            bazaDanych.Dane.Client.AddElementToQueue(wcfElement);
            return true;
        }
        bazaDanych.Dane.Client.IsSending = true;
        foreach (var wcfElementSplited in WcfElement.SplitWcfElement(wcfElement, true))
        {
            CurrentWcfElement = wcfElementSplited;
            try
            {
                var r = _proxy.SendWcfElement(wcfElementSplited);
                CurrentWcfElement = null;
            }
            catch (Exception e)
            {
                IsConnected = false;
                return false;
            }
        }
        bazaDanych.Dane.Client.IsSending = false;
        ReadyToReceive();
        return true;
    }

    public void ListenForConnectOrReconnect(EventArgs e)
    {
        SendWcfElement(WcfElement.GetVersionElement());//send wcfelement for perform PerformDataSync
        ReadyToReceive();
    }

    public static bool IsReconnecting;
    public bool ConnectOrReconnect(bool shouldRaiseEvent = true)
    {
        if (IsReconnecting)
        {
            return IsConnected;
        }
        if (IsConnected) return true;
        IsReconnecting = true;
        bazaDanych.Dane.Client.IsReceiving = false;
        bazaDanych.Dane.Client.IsSending = false;
        bazaDanych.Dane.Client.IsSynchronized = false;
        try
        {
            var site = new InstanceContext(this);
            _proxy = new ServiceProxy(site);
            var list = _proxy.Login(bazaDanych.Dane.Client, bazaDanych.Dane.UserPassword, bazaDanych.Dane.UserIsAuto, bazaDanych.Dane.UserIsSuperMode);
            bazaDanych.Dane.UserRights.Clear();
            bazaDanych.Dane.UserRights.AddRange(list);
            IsConnected = true;
            if (shouldRaiseEvent) ConnectOrReconnectEvent(null);
        }
        catch (Exception e)
        {
            IsConnected = false;
        }
        IsReconnecting = false;

        return IsConnected;
    }
}
#endregion

At the moment my app works like this: After successful login every client sends WcfElements(which contains bunch of list with ids and versions of elements). Then it sends ReadyToReceive one way message which after login fires performsync method. That method prepares data for client and sends first of them using one way receive method. IF there is more than one wcfelement to send then only last one is marked as last. Client responds with ReadyToReceive after every successful receive from Server. All up to this point works quite well. Problem starts later. Mostly packages are lost (method receiveWcfElement). Server has marked that client is receiving and maybe processing message and is waitng for readytoreceive packet, which will never be send because of lost element.

I've made it like this because as far as I know client can't send and receive at the same time. I've tried this and got this problem: If client send wcfElement with SendWcfElement method and server due to processing this element created another element which was supposed to be ssend back to client then client whoud have faulted proxy if callback was send before sendWcfElement returned true indicating that method was completed.

Now I wonder if it is possible for client to send and receive at the same time using two way methods ?


Solution

  • I ended up with to services(two connections). One for connection from client to server and another with callback which handles connection from server to client.