1. Diese Seite verwendet Cookies. Wenn du dich weiterhin auf dieser Seite aufhältst, akzeptierst du unseren Einsatz von Cookies. Weitere Informationen

Socket EventHandler für verschiedene Pakettypen und Best-Practices

Dieses Thema im Forum "Support & Discussion" wurde erstellt von 3HMonkey, 12. Januar 2018.

  1. 3HMonkey

    3HMonkey Administrator Mitarbeiter Administrator

    Registriert seit:
    28. Juni 2017
    Beiträge:
    77
    Zustimmungen:
    41
    Punkte für Erfolge:
    18
    Geschlecht:
    männlich
    Hallo Leute,

    Im Rahmen meiner Recherche und dem Selbststudium zum Thema Netzwerklayer, Sockets, TCP, UDP uvm. , habe ich nun einen meiner ersten asynchronen Socket-Server geschrieben. Ursprünglich habe ich damit in C++ angefangen, kam auch zu Ergebnissen, bin aber doch lieber wieder auf C# geswitched, weil es mir momentan einfach besser liegt. Da ich euch nun nciht mit vielen Informationen langweilen möchte, bzw. mit zu vielen Informationen verwirre, erkläre ich euch nur knapp, was bereits steht und wobei ich die ein oder andere Idee gebrauchen könnte. Wahrscheinlich werde ich daraus auch ein Tutorial machen, wobei ihr das Erlernte dann in die Tat umsetzen könnt, um euch euren eigenen Server für Lizenzsysteme, Datenbankverbindungen, Spiele etc. zu schreiben.

    Eckdaten:
    • Asynchroner Socket-Server
    • Dynamischer Socket-Buffer (Minimalster Netzwerktraffic)
    • benutzt System.Net.Sockets
    • Packethandler, PacketReader, PacketWriter besteht bereits
    Nun zu meinem konkreten Punkt.
    Der PacketHandler hat eine Handle-Funktion, welche das Paket und den Socket entgegennimmt. Innerhalb der Funktion werden die ersten 4 Byte des Paketes gelesen, welche Informationen über Paketlänge, sowie den Pakettyp bereitstellen. Je nach geparsten Typ, wird dann innerhalb des Switch-Cases entschieden, was bei erhalten der Nachricht geschehen soll. Dies könnte z.B. eine Textausgabe oder z.B. ein spezieller Funktionsaufrauf sein. In meinem Fall ist es eine spezielle Funktionalität. Dabei erstelle ich für jeden Pakettypen eine Klasse, welche von meiner MainPaket-Klasse erbt. Die Idee dazu stammt aus einem ziemlich alten Tutorial, funktioniert auch, gefällt mir aber nciht, wegen dem immer größer werdenen Switch.

    Code (C#):
    public static class PacketHandler
        {
                     

            public static void Handle(byte[] packet, Socket clientSocket)
            {
                ushort packetLenght = BitConverter.ToUInt16(packet, 0);
                ushort packetType = BitConverter.ToUInt16(packet, 2);
           
                Console.WriteLine("Packet received! Length: {0} | Type: {1}", packetLenght, packetType);

           

                switch (packetType)
                {
                    case 1000:
                        Packets.Message msg = new Packets.Message(packet);
                        Console.WriteLine(msg.Text);
                   
                        break;
                    default:
                        break;
                }
            }

        }
    Momentan stelle ich mir sowas wie einen Enum pro Pakettypen vor.

    Code (C#):
    public enum PacketID : UInt16
        {
            TEXTMESSAGE = 1000,
            TEST,
            ANOTHERONE

        }
    Je nach Enum soll dann ein Event gefeuert werden. C# bietet hier viele Möglichkeiten dazu (Events, Delegate usw.). Trotzdem fehlt es mir dabei an einer konkreten Umsetzungsidee, bzw. möchte ich einfach mal eure Meinung dazu wissen, wie ihr das umsetzen würdet oder was eure "best practices" dafür sind/wären?

    UPDATE:
    Eine Idee von mir wäre z.B. ein Dictionairy im PacketHandler anzulegen, welcher den Packettype als Enum und die Methode beinhaltet:
    Code (C#):
    public static Dictionary<PacketID, Func<byte[],bool>> handler = new Dictionary<PacketID, Func<byte[],bool>>
            {
                {PacketID.TEXTMESSAGE,  OnTextmessageReceived},
                {PacketID.TEST,  OnTextmessageReceived},
                {PacketID.ANOTHERONE,  OnTextmessageReceived}

            };
    Die Handlermethode könnte dann sowas sein:

    Code (C#):
    private static bool OnTextmessageReceived(byte[] packet)
            {
                Packets.Message msg = new Packets.Message(packet);
                if(msg != null)
                {
                    Console.WriteLine(msg.Text);
                    return true;
                }
                return false;
             
            }

    Jetzt muss das Ganze nur noch in der Hauptmethode gefeuert werden:

    Code (C#):
    public static void Handle(byte[] packet, Socket clientSocket)
            {
                ushort packetLenght = BitConverter.ToUInt16(packet, 0);
                ushort packetType = BitConverter.ToUInt16(packet, 2);
             
                Console.WriteLine("Packet received! Length: {0} | ID: {1} | Type: {2}", packetLenght, packetType, (PacketID)packetType);

                Func<byte[],bool> OnPacketTypeHandler =  handler[(PacketID)packetType];
                OnPacketTypeHandler(packet);

            }

    Vielen Dank schonmal für die Ideen und eure Zeit.

    MFG 3HMonkey
     
    Zuletzt bearbeitet: 12. Januar 2018
  2. CptVince

    CptVince New Member

    Registriert seit:
    1. Juli 2017
    Beiträge:
    15
    Zustimmungen:
    11
    Punkte für Erfolge:
    3
    Muss der PacketHandler static sein, was fuer einen Vorteil bringt dir das?

    1) Der PacketHandler koennte jeweils ein Event feuern, auf das sich registriert wird.

    Wenn es nicht viele Event-Typen gibt, bzw. diese starr sind und nicht oft erweitert werden, koennte ueber eine feste implementierung an PacketHandler gedacht werden.

    Dadurch hat man sehr konkrete Events, auf die sich registriert wird, und die Klasse kann (fast) nicht mehr falsch verwendet werden.

    Beispiel fuer TextMessage:
    Code (C#):

    namespace SocketEvents
    {
        using System;
        using System.Net.Sockets;

        public class TextMessageEventArgs : EventArgs
        {
            public byte[] PacketData { get; }

            public TextMessageEventArgs(byte[] packetData)
            {
                this.PacketData = packetData;
            }
        }

        public class PacketHandler
        {
            public event EventHandler<TextMessageEventArgs> TextMessageReceived;

            protected virtual void OnTextMessageReceived(TextMessageEventArgs e)
            {
                this.TextMessageReceived?.Invoke(this, e);
            }

            public void Handle(byte[] packet, Socket clientSocket)
            {
                ushort packetLenght = BitConverter.ToUInt16(packet, 0);
                ushort packetType = BitConverter.ToUInt16(packet, 2);

                Console.WriteLine("Packet received! Length: {0} | Type: {1}", packetLenght, packetType);

                switch (packetType)
                {
                    case 1000:
                        this.OnTextMessageReceived(new TextMessageEventArgs(packet));

                        break;
                    default:
                        break;
                }
            }
        }
    }
     
    Die Registrierung hast du dann an der PacketHandler-Instance:
    Code (C#):

    namespace SocketEvents
    {
        using System;
        using System.Net.Sockets;

        public class Program
        {
            public static void Main(string[] args)
            {
                PacketHandler handler = new PacketHandler();

                handler.TextMessageReceived += Program.Handler_TextMessageReceived;

                handler.Handle(/* byte Array for TextMessage */ new byte[1], /* Socket */ new Socket())
            }

            private static void Handler_TextMessageReceived(object sender, TextMessageEventArgs e)
            {
                Console.WriteLine($"Yeah we received a new text with data: ${e.PacketData.ToString()}");
            }
        }
    }
     


    2) Anderer Ansatz, wenn es viele Typen gibt oder diese erweitert werden koennen:

    Annahme: PacketID enthaelt mehr als 3 Werte/wird um mehr Werte erweitert werden.
    Da es noch mehr Typen geben wird und dann nicht jedes mal die PacketHandler Klasse angepasst werden soll, koennte die Unterscheidung auf die handler des Events verlagert werden.

    Code (C#):

    namespace SocketEvents
    {
        using System;
        using System.Net.Sockets;

        public class PacketReceivedEventArgs : EventArgs
        {
            public byte[] PacketData { get; }

            public PacketID Type { get; }

            public PacketReceivedEventArgs(PacketID packetType, byte[] packetData)
            {
                this.Type = packetType;
                this.PacketData = packetData;
            }
        }

        public class PacketHandler
        {
            public event EventHandler<PacketReceivedEventArgs> PacketReceived;

            protected virtual void OnPacketReceived(PacketReceivedEventArgs e)
            {
                this.PacketReceived?.Invoke(this, e);
            }

            public void Handle(byte[] packet, Socket clientSocket)
            {
                ushort packetLenght = BitConverter.ToUInt16(packet, 0);
                ushort packetType = BitConverter.ToUInt16(packet, 2);

                Console.WriteLine("Packet received! Length: {0} | Type: {1}", packetLenght, packetType);

                this.OnPacketReceived(new PacketReceivedEventArgs((PacketID)packetType, packet));
            }
        }
    }
     
    Und der dazugehoerige Aufruf und die Registrierung:
    Code (C#):

    namespace SocketEvents
    {
        using System;
        using System.Net.Sockets;

        public class Program
        {
            public static void Main(string[] args)
            {
                PacketHandler handler = new PacketHandler();

                handler.PacketReceived += Program.Handler_CallAlways;
                handler.PacketReceived += Program.Handler_TestOrAnotherReceived;
                handler.PacketReceived += Program.Handler_TextMessageReceived;
                handler.PacketReceived += Program.Handler_TestReceived;

                handler.Handle(/* byte Array for TextMessage */ new byte[1], /* Socket */ new Socket());

                handler.Handle(/* byte Array for Test */ new byte[1], /* Socket */ new Socket());
            }

            private static void Handler_CallAlways(object sender, PacketReceivedEventArgs e)
            {
                Console.WriteLine($"Handling Packet ${e.Type} with data: ${e.PacketData.ToString()}");
            }

            private static void Handler_TestOrAnotherReceived(object sender, PacketReceivedEventArgs e)
            {
                if (!e.Type.HasFlag(PacketID.TEST | PacketID.ANOTHERONE)
                {
                    return;
                }

                Console.WriteLine($"Received data: ${e.PacketData.ToString()}");
            }
           
            private static void Handler_TextMessageReceived(object sender, PacketReceivedEventArgs e)
            {
                if (!e.Type.HasFlag(PacketID.TEXTMESSAGE))
                {
                    return;
                }

                Console.WriteLine($"${nameof(PacketID.TEXTMESSAGE}: Yeah we received a text with data: ${e.PacketData.ToString()}");
            }

            private static void Handler_TestReceived(object sender, PacketReceivedEventArgs e)
            {
                if (!e.Type.HasFlag(PacketID.TEST))
                {
                    return;
                }

                Console.WriteLine($"${nameof(PacketID.TEST}: Yeah we received a test with data: ${e.PacketData.ToString()}");
            }
        }
    }
     
    Vorteile von 1:
    • Benamung ist einfach super
    • Leicht nachvollziehbare Implementierung (sehr explizit)

    Vorteile von 2:
    • Weniger schreibaufwand
    • Eine Nachricht kann von mehreren gehandelt werden
    • PacketHandler (switch) muss nicht erweitert werden, wenn ein neuer Typ hinzugefuegt wird

    * Hab alles hier ohne IDE geschrieben hoffe es kompiliert alles :D
    LG Vince
     
    3HMonkey gefällt das.
  3. 3HMonkey

    3HMonkey Administrator Mitarbeiter Administrator

    Registriert seit:
    28. Juni 2017
    Beiträge:
    77
    Zustimmungen:
    41
    Punkte für Erfolge:
    18
    Geschlecht:
    männlich
    Vielen vielen Dank, da sind ein paar super Ansätze dabei! Jetzt muss ich doch schon aus dem Bett aufstehen und das ganze ausprobieren:)
     
  4. CptVince

    CptVince New Member

    Registriert seit:
    1. Juli 2017
    Beiträge:
    15
    Zustimmungen:
    11
    Punkte für Erfolge:
    3
    Mir ist noch die Idee gekommen, die Objekt creation in eine Factory auszulagern. Ubersteigt ja laut SRP die Aufgabe vom PacketHandler.

    Die Factory ist dann dafuer zustaendig die anhand des Parameters (PacketID) die Konkrete Klassen-Instanz zu liefern.

    Der Handler oder die konkreten Handler muessen dann noch um Verhalten erweitert werden. Im Beispiel sind diese nur minimal implementiert.
     

    Anhänge:

    3HMonkey gefällt das.
  5. 3HMonkey

    3HMonkey Administrator Mitarbeiter Administrator

    Registriert seit:
    28. Juni 2017
    Beiträge:
    77
    Zustimmungen:
    41
    Punkte für Erfolge:
    18
    Geschlecht:
    männlich
    Hey hey und danke für die tollen Antworten. Ich habe jetzt so ne Kombination daraus gemacht, da ich das Dictionary nutzen wollte. Nach meinen Funktions- und Belastungstests denke ich, dass ich daraus ein Tutorial machen werde.
     
    CptVince gefällt das.

Diese Seite empfehlen

Die Seite wird geladen...