Avatar billede Droa Seniormester
14. april 2016 - 15:11 Der er 13 kommentarer

Designvalg til complex protocol

Hej Eksperter

Jeg har mange gange siddet og rodet rundt i min egen kode, grundet dårligt design, som har gjort man bare føler man vil nuke det hele og starte forfra.

Jeg sidder lige pt med et projekt i design fasen.

projektet er et server-client framework, der skal kunne mange ting, protocollen er text-basseret i en træ-struktur der ligner Xml der bliver streamet, basseret på xmpp.

xmpp starter med et stream tag, og derefter kan den sende 3 forskellige undertags message/iq/presence, disse 3 tags er utroligt dynamiske i hvad data der indeholder, alt efter hvad namespace de de arver fra.

IQ-tagget har rigtigt mange namespaces og vælger fra, da det er data-udvekslings delen af protocollen.

jeg kommer her med min ide til design, og ville høre om dette er dumt eller ok og gøre.

ServerSide:

Jeg vælger og pharse indkommende data igennem en xml parser.
Xml-data vil derefter blive simplificeret i mit eget xml objekt (XmppElement)
XmppElement vil derefter blive kaldt til onDataRecieve med en EventArg der har 2 data (XmppElement og et kontrol id)
onDataRecieve har 78-109 hooks efter namespace af iq/message/presence taggende.

er det en god ide og lave så mange events på et event der bliver kaldt så tit, eller skulle man f.eks opdele det så et event med (iq/message/presence) og derefter opdele dem i sub-events derfra?

eller har jeg rodet mig ud i noget jeg bare burde lave om?

jeg håber nogen har ideer :)

på forhånd tak.
Avatar billede arne_v Ekspert
14. april 2016 - 15:55 #1
Jeg ville gribe det helt anderledes an.

:-)

Jeg ville lave:

1) En OO model for informationen som er uafhaengig af wire protocol. Denne model vil formentligt indeholder en del arve-hirakier idet faelles information puttes i super-klasses mens specifik information puttes i sub-klasser.

2) Core business logic som processer incoming objects og afsender outgoing objects.

3) Transport layer. En wrapper omkring socket eller HTTP client eller hcad du nu bruger.

4) En converter mellem XML og data objekter.

5) Interface definitioner for #2, 3 og 4

6) Application som:
  - via konfig loader kode fra #2, 3 og 4 via interfaces fra #5
  - laeser fra transport layer, konverterer message og kalder business logic
Avatar billede Droa Seniormester
14. april 2016 - 16:17 #2
Det var noget lignende jeg gjorde i starten i min prototype, men jeg havde svært ved og designe en god logic så det var til og finde rundt i, kender du noget godt læsestof til hvordan man laver god logic til et dynamisk protokol design?
Avatar billede arne_v Ekspert
14. april 2016 - 17:53 #3
Hvad var problemet? At du endte op en million if og switch statements?
Avatar billede Droa Seniormester
14. april 2016 - 20:44 #4
mit problem ligger tit i at jeg tænker at jeg ender med en masse Klasse Typer jeg kun bruger meget lidt.

Jeg lavede min struktur med min pharser indbygget.

Type til og læse et IQ Tag


class XmppIQ : XmppElement
{

  //Kun en af følgende tag bliver brugt, skulle måske være samme abstract Type med en Extension istedet.
  public IQQuery Query {get; private set;} //Query Tag under IQ tag
  public IQError Error {get; private set;} //Error Tag under IQ tag

  //Andre IQ specifikke variabler
  public string Type {
      get{
          if(this.Attributes.ContainsKey("type")){
              return this.Attributes["type"];
          }
          return null;
      }
  }
 
  //der er mange flere, men jeg holder eksemplet kort.

  private XmppIQ(){} //brug Read til og fylde objekt


  public XmppIQ Read(XmppElement ele){
    //parsing af xmppelement magic
    return new XmppIQ();
  }
}


--------------------------------------


logikken bestod af en masse if-statements.


if(ele is XmppIQ){
    XmppIQ iq = (XmppIQ)ele;
    if(iq.Query != null){
        switch(iq.Query.Namespace){
            case "jabber:iq:auth":
                jabber_iq_auth(iq);
            break;
            case "urn:xmpp:ping":
                jabber_ping(iq);
            break;
            case "http://jabber.org/protocol/disco#info":
                jabber_disco(iq);
            break;
            //også videre....
        }
    }else if(iq.Error != null){
        switch(iq.Error.Namespace){
            //og så videre.
        }
    }...
}...




min data bliver derefter gemt i et hukommelses matrix hvor jeg har en form for State, der fortæller hvor langt protokollen er nået... f.eks om man har logget ind, og om forbindelsen køre TLS, og om man har brugt SASL eller None-SASL login.


men jeg følte bare hele hoved logikken blev så utroligt lang, selvom jeg lagde resten i methoder..
Avatar billede Droa Seniormester
14. april 2016 - 20:49 #5
Read er static, glemte jeg of skrive
Avatar billede arne_v Ekspert
22. april 2016 - 03:16 #6
Jeg tror at loesningen er at lave det mere objekt-orienteret.

:-)

Det er nemmere sagt end gjordt.

Men lad mig proeve med et eksempel.
Avatar billede Droa Seniormester
22. april 2016 - 10:19 #7
Det ville jeg gerne se, for det som jeg gør nu har så mange logic segmenter at de fylder næsten 7000 linjer for at holde alle reglerne som ISO dokumentet diktere :)

Syntes det er rimeligt mange If statements I en methods
Avatar billede arne_v Ekspert
26. april 2016 - 04:40 #8
250 linier og ikke halvfaerdig endnu.

Men naesten ingen if saetninger!
Avatar billede arne_v Ekspert
28. april 2016 - 04:01 #9
Min ide til strukturering er at lave et lille helt separat modul til haandtering af hver message type.

Jeg har et eksempel med parsing, processing og formatering.

Det daekker ikke transport. Men jeg har forstaaet at det er messages somer problemet og i transport er det bare string ind og string ud.
Avatar billede arne_v Ekspert
28. april 2016 - 04:01 #10
Foerst frameworket.

Framework.cs


using System;
using System.Collections.Generic;
using System.Linq;

namespace Chat.Framework.Protocol
{
    public abstract class Message
    {
        public string ClientID { get; set; }
    }
    public abstract class RequestMessage : Message
    {
    }
    public abstract class ResponseMessage : Message
    {
        public bool Status { get; set; }
    }
}

namespace Chat.Framework.Provider
{
    using Chat.Framework.Protocol;
    using Chat.Framework.Core;
    public interface ISerializer
    {
        bool Accept(string strmsg);
        RequestMessage Parse(string strmsg, string id);
        bool Accept(ResponseMessage objmsg);
        string Format(ResponseMessage objmsg);
    }
    public interface IProcessor
    {
        bool Accept(RequestMessage reqmsg);
        Tuple<ClientState,ResponseMessage> Process(ClientState prevstate, RequestMessage reqmsg, Server cntx);
    }
    public interface IProvider
    {
        ISerializer GetSerializer();
        IProcessor GetProcessor();
    }
    public class SerializerBase : ISerializer
    {
        private string msgtyp;
        private Type reqtyp;
        private Type resptyp;
        public SerializerBase(string msgtyp, Type reqtyp, Type resptyp)
        {
            this.msgtyp = msgtyp;
            this.reqtyp = reqtyp;
            this.resptyp = resptyp;
        }
        public virtual bool Accept(string strmsg)
        {
            return strmsg.StartsWith(string.Format("<{0}-request>", msgtyp)) && strmsg.EndsWith(string.Format("</{0}-request>", msgtyp));
        }
        public virtual RequestMessage Parse(string strmsg, string id)
        {
            RequestMessage objmsg = (RequestMessage)Activator.CreateInstance(reqtyp);
            objmsg.ClientID = id;
            return objmsg;
        }
        public virtual bool Accept(ResponseMessage objmsg)
        {
            return objmsg.GetType() == resptyp;
        }
        public virtual string Format(ResponseMessage objmsg)
        {
            return string.Format("<{0}-response><status>{1}</status></{0}-response>", msgtyp, objmsg.Status);
        }
    }
    public class ProcessorBase : IProcessor
    {
        private Type reqtyp;
        private Type resptyp;
        public ProcessorBase(Type reqtyp, Type resptyp)
        {
            this.reqtyp = reqtyp;
            this.resptyp = resptyp;
        }
        public virtual bool Accept(RequestMessage reqmsg)
        {
            return reqmsg.GetType() == reqtyp;
        }
        public virtual Tuple<ClientState,ResponseMessage> Process(ClientState prevstate, RequestMessage reqmsg, Server cntx)
        {
            ResponseMessage objmsg = (ResponseMessage)Activator.CreateInstance(resptyp);
            objmsg.Status = true;
            return Tuple.Create(prevstate, objmsg);
        }
    }
}

namespace Chat.Framework.Core
{
    using Chat.Framework.Protocol;
    using Chat.Framework.Provider;
    public class ChatException : Exception
    {
        public ChatException(string descrip) : base(descrip)
        {
        }
    }
    public class UnknownMessageChatException : ChatException
    {
        public UnknownMessageChatException() : base("Unknown message type")
        {
        }
    }
    public class IllegalStateChatException : ChatException
    {
        public IllegalStateChatException() : base("Illegal state for message type")
        {
        }
    }
    public static class ProviderRepository
    {
        private static List<IProvider> repo = new List<IProvider>();
        private static bool dirty = false;
        private static List<ISerializer> ser = new List<ISerializer>();
        private static List<IProcessor> proc = new List<IProcessor>();
        public static void RegisterProvideer(IProvider provider)
        {
            lock(repo)
            {
                repo.Add(provider);
                dirty = true;
            }
        }
        public static List<ISerializer> GetSerializers()
        {
            lock(repo)
            {
                if(dirty)
                {
                    ser = repo.Select(f => f.GetSerializer()).ToList();
                }
                return ser;
            }
        }
        public static List<IProcessor> GetProcessors()
        {
            lock(repo)
            {
                if(dirty)
                {
                    proc = repo.Select(f => f.GetProcessor()).ToList();
                }
                return proc;
            }
        }
    }
    public class Server
    {
        private Dictionary<string, Client> clients = new Dictionary<string, Client>();
        public void AddClient(string id)
        {
            clients.Add(id, new Client(id, this));
        }
        public void RemoveClient(string id)
        {
            clients.Remove(id);
        }
        public string Process(string id, string reqmsg)
        {
            return clients[id].Process(reqmsg);
        }
        public void Send(string id, string onetext)
        {
            clients[id].Send(onetext);
        }
        public List<string> Read(string id)
        {
            return clients[id].Read();
        }
    }
    public enum ClientState { SIGNEDIN, NOT_SIGNEDIN }
    public class Client
    {
        private string id;
        private Server cntx;
        private ClientState state;
        private List<string> text;
        public Client(string id, Server cntx)
        {
            this.id = id;
            this.cntx = cntx;
            state = ClientState.NOT_SIGNEDIN;
            text = new List<string>();
        }
        public string Process(string strmsg)
        {
            foreach(ISerializer s in ProviderRepository.GetSerializers())
            {
                if(s.Accept(strmsg))
                {
                    return s.Format(Process(s.Parse(strmsg, id)));
                }
            }
            throw new UnknownMessageChatException();
        }
        public ResponseMessage Process(RequestMessage reqmsg)
        {
            foreach(IProcessor p in ProviderRepository.GetProcessors())
            {
                if(p.Accept(reqmsg))
                {
                    Tuple<ClientState,ResponseMessage> res = p.Process(state, reqmsg, cntx);
                    state = res.Item1;
                    return res.Item2;
                }
            }
            throw new UnknownMessageChatException();
        }
        public void Send(string onetext)
        {
            text.Add(onetext);
        }
        public List<string> Read()
        {
            List<string> res = text;
            text = new List<string>();
            return res;
        }
    }
}
Avatar billede arne_v Ekspert
28. april 2016 - 04:03 #11
Og saa moduler for 4 message types.

Login.cs


using System;
using System.Xml;

namespace Chat.Module.Login
{
    using Chat.Framework.Protocol;
    using Chat.Framework.Provider;
    using Chat.Framework.Core;
    public class LoginRequestMessage : RequestMessage
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
    public class LoginResponseMessage : ResponseMessage
    {
    }
    public class LoginSerializer : SerializerBase
    {
        public LoginSerializer() : base("login", typeof(LoginRequestMessage), typeof(LoginResponseMessage))
        {
        }
        public override RequestMessage Parse(string strmsg, string id)
        {
            LoginRequestMessage objmsg = new LoginRequestMessage();
            objmsg.ClientID = id;
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(strmsg);
            objmsg.Username = doc.SelectSingleNode("//login-request/username/text()").Value;
            objmsg.Password = doc.SelectSingleNode("//login-request/password/text()").Value;
            return objmsg;           
        }
    }
    public class LoginProcessor : ProcessorBase
    {
        public LoginProcessor() : base(typeof(LoginRequestMessage), typeof(LoginResponseMessage))
        {
        }
        public override Tuple<ClientState,ResponseMessage> Process(ClientState prevstate, RequestMessage reqmsg, Server cntx)
        {
            if(prevstate == ClientState.NOT_SIGNEDIN)
            {
                ClientState nextstate;
                LoginResponseMessage respmsg = new LoginResponseMessage();
                if(((LoginRequestMessage)reqmsg).Username == "test" && ((LoginRequestMessage)reqmsg).Password == "hemmeligt")
                {
                    nextstate = ClientState.SIGNEDIN;
                    respmsg.Status = true;
                }
                else
                {
                    nextstate = prevstate;
                    respmsg.Status = false;
                }
                return Tuple.Create(nextstate, (ResponseMessage)respmsg);
            }
            else
            {
                throw new IllegalStateChatException();
            }
        }
    }
    public class LoginProvider : IProvider
    {
        public ISerializer GetSerializer()
        {
            return new LoginSerializer();
        }
        public IProcessor GetProcessor()
        {
            return new LoginProcessor();
        }
    }
}


Send.cs


using System;
using System.Xml;

namespace Chat.Module.Send
{
    using Chat.Framework.Protocol;
    using Chat.Framework.Provider;
    using Chat.Framework.Core;
    public class SendRequestMessage : RequestMessage
    {
        public string Receiver { get; set; }
        public string Text { get; set; }
    }
    public class SendResponseMessage : ResponseMessage
    {
    }
    public class SendSerializer : SerializerBase
    {
        public SendSerializer() : base("send", typeof(SendRequestMessage), typeof(SendResponseMessage))
        {
        }
        public override RequestMessage Parse(string strmsg, string id)
        {
            SendRequestMessage objmsg = new SendRequestMessage();
            objmsg.ClientID = id;
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(strmsg);
            objmsg.Receiver = doc.SelectSingleNode("//send-request/receiver/text()").Value;
            objmsg.Text = doc.SelectSingleNode("//send-request/text/text()").Value;
            return objmsg;           
        }
    }
    public class SendProcessor : ProcessorBase
    {
        public SendProcessor() : base(typeof(SendRequestMessage), typeof(SendResponseMessage))
        {
        }
        public override Tuple<ClientState,ResponseMessage> Process(ClientState prevstate, RequestMessage reqmsg, Server cntx)
        {
            if(prevstate == ClientState.SIGNEDIN)
            {
                SendRequestMessage reqmsg2 = (SendRequestMessage)reqmsg;
                cntx.Send(reqmsg2.Receiver, reqmsg2.Text);
                ClientState nextstate = prevstate;
                SendResponseMessage respmsg = new SendResponseMessage();
                respmsg.Status = true;
                return Tuple.Create(nextstate, (ResponseMessage)respmsg);
            }
            else
            {
                throw new IllegalStateChatException();
            }
        }
    }
    public class SendProvider : IProvider
    {
        public ISerializer GetSerializer()
        {
            return new SendSerializer();
        }
        public IProcessor GetProcessor()
        {
            return new SendProcessor();
        }
    }
}


Read.cs


using System;
using System.Collections.Generic;
using System.Text;

namespace Chat.Module.Read
{
    using Chat.Framework.Protocol;
    using Chat.Framework.Provider;
    using Chat.Framework.Core;
    public class ReadRequestMessage : RequestMessage
    {
    }
    public class ReadResponseMessage : ResponseMessage
    {
        public List<string> Text { get; set; }
    }
    public class ReadSerializer : SerializerBase
    {
        public ReadSerializer() : base("read", typeof(ReadRequestMessage), typeof(ReadResponseMessage))
        {
        }
        public override string Format(ResponseMessage objmsg)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<read-response>");
            sb.Append(string.Format("<status>{0}</status>", objmsg.Status));
            foreach(string onetext in ((ReadResponseMessage)objmsg).Text)
            {
                sb.Append(string.Format("<text>{0}</text>", onetext));
            }
            sb.Append("</read-response>");
            return sb.ToString();
        }
    }
    public class ReadProcessor : ProcessorBase
    {
        public ReadProcessor() : base(typeof(ReadRequestMessage), typeof(ReadResponseMessage))
        {
        }
        public override Tuple<ClientState,ResponseMessage> Process(ClientState prevstate, RequestMessage reqmsg, Server cntx)
        {
            if(prevstate == ClientState.SIGNEDIN)
            {
                ClientState nextstate = prevstate;
                ReadResponseMessage respmsg = new ReadResponseMessage();
                respmsg.Status = true;
                respmsg.Text = cntx.Read(reqmsg.ClientID);
                return Tuple.Create(nextstate, (ResponseMessage)respmsg);
            }
            else
            {
                throw new IllegalStateChatException();
            }
        }
    }
    public class ReadProvider : IProvider
    {
        public ISerializer GetSerializer()
        {
            return new ReadSerializer();
        }
        public IProcessor GetProcessor()
        {
            return new ReadProcessor();
        }
    }
}


Logout.cs


using System;

namespace Chat.Module.Logout
{
    using Chat.Framework.Protocol;
    using Chat.Framework.Provider;
    using Chat.Framework.Core;
    public class LogoutRequestMessage : RequestMessage
    {
    }
    public class LogoutResponseMessage : ResponseMessage
    {
    }
    public class LogoutSerializer : SerializerBase
    {
        public LogoutSerializer() : base("logout", typeof(LogoutRequestMessage), typeof(LogoutResponseMessage))
        {
        }
    }
    public class LogoutProcessor : ProcessorBase
    {
        public LogoutProcessor() : base(typeof(LogoutRequestMessage), typeof(LogoutResponseMessage))
        {
        }
        public override Tuple<ClientState,ResponseMessage> Process(ClientState prevstate, RequestMessage reqmsg, Server cntx)
        {
            if(prevstate == ClientState.SIGNEDIN)
            {
                ClientState nextstate = ClientState.NOT_SIGNEDIN;
                LogoutResponseMessage respmsg = new LogoutResponseMessage();
                respmsg.Status = true;
                return Tuple.Create(nextstate, (ResponseMessage)respmsg);
            }
            else
            {
                throw new IllegalStateChatException();
            }
        }
    }
    public class LogoutProvider : IProvider
    {
        public ISerializer GetSerializer()
        {
            return new LogoutSerializer();
        }
        public IProcessor GetProcessor()
        {
            return new LogoutProcessor();
        }
    }
}
Avatar billede arne_v Ekspert
28. april 2016 - 04:04 #12
Og saa et test program som loader de 4 moduler og sender lidt requests og printer responses.


using System;

namespace E
{
    using Chat.Framework.Core;
    public class Program
    {
        public static void Main(string[] args)
        {
            ProviderRepository.RegisterProvideer(new Chat.Module.Login.LoginProvider());
            ProviderRepository.RegisterProvideer(new Chat.Module.Send.SendProvider());
            ProviderRepository.RegisterProvideer(new Chat.Module.Logout.LogoutProvider());
            ProviderRepository.RegisterProvideer(new Chat.Module.Read.ReadProvider());
            Server srv = new Server();
            srv.AddClient("1.2.3.4");
            srv.AddClient("4.3.2.1");
            Console.WriteLine(srv.Process("1.2.3.4", "<login-request><username>test</username><password>test</password></login-request>"));
            Console.WriteLine(srv.Process("1.2.3.4", "<login-request><username>test</username><password>hemmeligt</password></login-request>"));
            Console.WriteLine(srv.Process("1.2.3.4", "<send-request><receiver>4.3.2.1</receiver><text>This is a test</text></send-request>"));
            Console.WriteLine(srv.Process("1.2.3.4", "<send-request><receiver>4.3.2.1</receiver><text>This is another test</text></send-request>"));
            Console.WriteLine(srv.Process("1.2.3.4", "<logout-request></logout-request>"));
            Console.WriteLine(srv.Process("4.3.2.1", "<login-request><username>test</username><password>hemmeligt</password></login-request>"));
            Console.WriteLine(srv.Process("4.3.2.1", "<read-request></read-request>"));
            Console.WriteLine(srv.Process("4.3.2.1", "<read-request></read-request>"));
            Console.WriteLine(srv.Process("4.3.2.1", "<logout-request></logout-request>"));
            Console.ReadKey();
        }
    }
}
Avatar billede arne_v Ekspert
28. april 2016 - 04:06 #13
Og det blev lidt omfattende. Men nogle ting er bare svaere at illustrere med 10 linier kode.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester