Может ли клиент дуплексной службы WCF (привязка TCP) отправлять и получать одновременно? - PullRequest
0 голосов
/ 22 октября 2011

Мой код на данный момент выглядит так:
Сторона сервера:

#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

Клиентская сторона в данный момент

#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

На данный момент мое приложение работает так: После успешного входа в систему каждый клиент отправляет WcfElements (который содержит связку списка с идентификаторами и версиями элементов). Затем он отправляет ReadyToReceive одностороннее сообщение, которое после входа в систему выполняет метод sync. Этот метод подготавливает данные для клиента и отправляет первый из них, используя метод одностороннего приема. Если есть более одного wcfelement для отправки, только последний помечается как последний. Клиент отвечает ReadyToReceive после каждого успешного получения от Сервера. Все до этого момента работает довольно хорошо. Проблема начинается позже. В основном пакеты теряются (метод receiveWcfElement). Сервер отметил, что клиент получает и, возможно, обрабатывает сообщение и ожидает готового пакета, который никогда не будет отправлен из-за потерянного элемента.

Я сделал это так, потому что, насколько я знаю, клиент не может отправлять и получать одновременно. Я попробовал это и получил эту проблему: Если клиент отправил wcfElement с помощью метода SendWcfElement, а сервер из-за обработки этого элемента создал другой элемент, который должен был быть возвращен клиенту, то клиент отказал прокси-серверу, если обратный вызов был отправлен до того, как sendWcfElement вернул true, указывая, что метод был завершен.

Теперь мне интересно, можно ли клиенту отправлять и получать одновременно двумя способами?

1 Ответ

1 голос
/ 13 апреля 2012

Я закончил с услугами (два соединения). Один для подключения от клиента к серверу, а другой с обратным вызовом, который обрабатывает соединение с сервера на клиент.

...