Laden...

Wie mache ich einen Server Multitaskingfähig?

Letzter Beitrag vor 3 Jahren 11 Posts 1.395 Views
Wie mache ich einen Server Multitaskingfähig?

Hallo liebe Mitentwickler,

ich dabei einen Server zu entwickeln, welcher anfragen von Clients aufnimmt und weiterleitet sowie die antwort an den anfragenden Client zurückgibt. So als eine Art "Router".

Der Server selber im Grundgerüst (als Dienst funktioniert)

Anbei etwas Code: Das Auskommentierte kann ignoriert werden.

Code der Dienststeuerung (Start und Stopp)


namespace Server
{
    public partial class ServerService : ServiceBase
    {
        #region ServerControlls
        public ServerService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            StartUP();
        }

        protected override void OnStop()
        {
            
        }
        #endregion

        #region Backend
        internal void StartUP()
        {
            ServerEngine serverEngine = new ServerEngine(9090);

            // WorkerTasks.IncomingRequest(serverEngine);
            //Thread iRT = new Thread(WorkerThreads.IncomingRequestThread);
            //iRT.Start(serverEngine);

            //Thread cWT = new Thread(WorkerThreads.ClientWorkingThread);
            //cWT.Start();

            //Thread rT = new Thread(WorkerThreads.RoutingThread);
            //rT.Start();

        }
        #endregion
    }
}

Code der Server Engine (Das eigentliche Serverprogramm)


namespace Server
{
    /// <summary>
    /// Beinhaltet alle Methoden für die Serververbindung
    /// </summary>
   public class ServerEngine
    {
        #region Parameter
        private int _port;
        public TcpListener _tcpServerListener;
        public static TcpClient _tcpServerClient;
        public IPEndPoint _serverIPEndPoint;
        public Socket _serverSocket;
        public IPAddress _serverIPAdress;
        public IPHostEntry _serverIPHost;
        public static NetworkStream _networkStream;
        public static ClientLister _cl;
        public static string _data;
        #endregion

        #region Konstruktor
        public ServerEngine(int port)
        {
            int maxUserPerServer = 1000;
            _port = port;
            _serverIPHost = Dns.GetHostEntry(Dns.GetHostName());
            _serverIPAdress = _serverIPHost.AddressList[3];
            _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _serverIPEndPoint = new IPEndPoint(_serverIPAdress, _port);
            _tcpServerListener = new TcpListener(_serverIPEndPoint);
            
            _tcpServerListener.Start();

            //_tcpServerListener.Server.Bind(_serverIPEndPoint);
            //_tcpServerListener.Server.Listen(maxUserPerServer);
            
            
            _cl = new ClientLister();
        }
        #endregion

        #region public Methods
        /// <summary>
        /// Empfängt alle Anfragen an den Server und Speichert diese in einer Liste.
        /// </summary>
        public static void Receiver(object server)
        {
            _tcpServerClient = new TcpClient();
            if (((ServerEngine)server)._tcpServerListener.Pending()) // Prüft ob eingehende Anfragen vorliegen
            {
                _tcpServerClient = ((ServerEngine)server)._tcpServerListener.AcceptTcpClient(); // Nimmt die eingehende Anfrage an!

                // Interne Methodenvariablen //

                string[] saveIncomeData;
                string substrg;
                byte[] _receiverPuffer = new byte[_tcpServerClient.Available];
                //////////////////////////////////

                if (_receiverPuffer.Length < 1) // prüft ob Daten empfangen wurden; wenn nicht löse fehler aus!
                {
                    Protocoll.Write($"Es wurden keinen Daten von { _tcpServerClient.Client.RemoteEndPoint } übergeben! ");
                }
                else
                {
                    _networkStream = _tcpServerClient.GetStream();// Ruft den Netzwerkstream ab

                    ////// Empfängt Daten vom Client
                    _networkStream.Read(_receiverPuffer, 0, _tcpServerClient.Available);
                    _data = Encoding.ASCII.GetString(_receiverPuffer);

                    _cl.AddIncomingRequest(_tcpServerClient.Client.RemoteEndPoint, _data); // Speichert eingehende Verbindung
                    Protocoll.Write($"{_tcpServerClient.Client.RemoteEndPoint},,, {_data}");
                    Protocoll.Write($"{_cl.CountIncomingRequest()}");
                }
            }
        }
        /// <summary>
        /// Prüft ob sich Clients am Server angemeldet haben
        /// </summary>
        /// <returns></returns>
        public static bool IsContainedValues()
        {
            if(_cl.CountIncomingRequest()>0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        
        public static bool Handshake()
        {
            try
            {
                if(_data != null)
                {
                    string[] splitData = _data.Split(':');
                    string[] splitData2 = splitData[0].Split('-');
                    
                }
                return true;
            }
            catch(Exception Err)
            {
                return false;
            }
        }
        #endregion



    }
}


Der Kostruktor beinhaltet den eigentlichen Server. Die Methode "Receiver()" ist für den Datenempfang zuständig.

Nun mein Problem:

Der Server soll nun ständig auf anfragen warten und sobald eine eingeht soll diese in einem seperaten Thread ausgelagert werden und dort weiterverarbeitet werden, sodass der server sofort für die nächste Anfrage bereit ist.

Ich habe versucht den Thread in der SartUP Medhode des ServerService zu implementieren:


Thread iRT = new Thread(WorkerThreads.IncomingRequestThread);
iRT.Start(serverEngine);

zur Kontrolle habe ich eine Protokollmethode geschrieben die mir die Zwischenergebnisse zeigt. Leider ohne erfolg. Auch habe ich versucht den eigentlichen Speichervorgang (für die Clients) in einem Task auszulagern. Siehe wie Folgt:


  public static class WorkerThreads
    {
        public static void IncomingRequestThread(object serverEngine)
        { 
                WorkerTasks.IncomingRequest(serverEngine);  
        }
    }


   public static class WorkerTasks
    {
        #region Parameter
       
        #endregion
        public static void IncomingRequest(object serverEngine)
        {
            Task _workTask = Task.Run(() =>
            {
                Protocoll.Write($"Incoming tread 1"); // Kontrollpunkt für mich selbst
                ServerEngine.Receiver(serverEngine);
            });                                                                                                                                                             
        }
      }

Leider war auch das nicht von erfolg gekrönt.

Ich weiss das es viel Code ist und ich habe versucht es übersichtlich und verständlich wie möglich zu formulieren.

Der wichtigste Faktor ist, dass Du Deinen Code asynchron gestaltest - und dass Du verstehst, was Du da tust, wonach es nicht unbedingt aussieht 🙂
Nur so kannst Du mit denen Ressourcen, die Dir Windows / die Hardware zur Verfügung stellt, effizient nutzen. Mit blockierenden Methoden, wie Du sie verwendest, verschenkst Du Ressourcen.
Sofern Du diesen Faktor erfüllen willst, was ratsam ist, musst Du aber ehrlicherweise fast Deinen kompletten Code umwerfen, weil Deiner in der Form nicht async-fähig ist und nicht wirklich skaliert.

Der weitere Faktor hier wäre, dass man so wenig allokiert wie möglich, weil auch das Zeit und Ressourcen kostet.
Auch dass Du alles mit statischen Methoden und untypisierten Parametern umsetzt ist alles andere als förderlich für die Qualität Deines Quellcodes.

Ein asynchrones Server Beispiel findest Du einfach in den Docs:
Asynchroner Serversocket, Beispiel
Denk dran, dass dies ein grundlegendes Beispiel für die Veranschaulichung der Funktionsweise ist - mehr nicht.

PS: kurzer Reminder, dass Du das alles nicht brauchst, wenn Du nicht unbedingt das Rad neu erfinden willst.
-> Eigenen Auth-Handshake über TCP entwickeln

In der Tat habe ich mir auch schon die Async Variante angesehen. Allerdings auf der MS Doc seite Asynchrone Programmierung in C#
.

Soweit ich das verstanden habe sorgt async dafür, dass vorgänge Paralel abgearbeitet werden und bei sync wird jede Anfrage nacheinander abgearbeitet.

Nö. Asynchrone Programmierung ist asynchrone Programmierung und keine parallele Programmierung.
Und synchrone Programmierung ist eben synchron.

Drei verschiedene Konzepte, alle mit ihren jeweiligen Vorteilen und Anwendungsfällen, die man auch miteinander mixen kann.
Parallel Processing, Concurrency, and Async Programming in .NET

Denke, daß es mit asynchronen Methoden allein nicht besser wird. Der ganze Code ergibt irgendwie keinen Sinn. Warum hast du denn z.B. die gesamte "ServerEngine"-Klasse mit statischen Methoden implementiert?


public class ServerEngine
{
	public ServerEngine(int port)
	{
		//...
	}


	public static void Receiver(object server)
	{
		if ((ServerEngine)server) // ... 
	}
}

Hier fehlt es dir einfach an den Basics bzw. Grundlagen von objektorientierter Programmierung und C#.
Ich würde empfehlen, erstmal damit anzufangen, und sich dann im Bereich Web-Entwicklung zu belesen, und DANN überlegen, wie (und ob) man einen Webserver implementiert!

Weeks of programming can save you hours of planning

Wieso überhaupt selber bauen?

Tutorial: Erstellen eines gRPC-Clients und -Servers in ASP.NET Core

Da hast du das gleiche mit ungefähr 5 Zeilen Code.

Wieso überhaupt selber bauen?

Hab ich auch gefragt. Antwort siehe Eigenen Auth-Handshake über TCP entwickeln

Ich arbeite als Softwareentwickler C# und habe den Auftrag erhalten einen Handshake zu entwickeln. Leider darf ich auf die Details nicht eingehen aber soviel kann ich sagen.
Es wird keine Websache entwickelt. Allgeimein gesprochen soll ein Serverdienst laufen (läuft auch) welcher auf Clients zum anmelden wartet. Der Verbindungsaufbau an sich funktioniert und auch der Datenaustausch.

Ich sehe das absolut wie MrSparkle: hier fehlen enorm viele Grundlagen, sei es jetzt von C# selbst wie auch von Protokollen.
Aber manchmal muss man einfach selbst sehr harte, schmerzvolle Fehler machen, um daraus zu lernen 🙂

Ich bin da völligst bei Abt, Grundlagen sind das Problem.

Die Basis ist hier ja Netzwerk. TCP/IP funktioniert über Ports & Sockets. Dabei gilt, ein Port kann dabei nur einen Socket aufnehmen, diese Verbindung ist ein Socket. Der Socket ist immer eine Kombination aus lokaler IP+Port und Remote IP+Port.
Wenn du möchtest, dass sich mehrere Clients auf einen Server Port verbinden können, musst du nach der Verbindung den Socket wieder vom Port trennen.

Grundsätzlich kannst du das natürlich gerne selber implementieren - aber es gibt so viele fertige Lösung, die du verwenden könntest und die dir immense Vorteile bringen.

Auch "Websachen" basieren auf eben diesen Verbindungen und "Websachen" sind für Datenaustausch gemacht - auch low-level Verbindungen können darüber realisiert werden.
mit ASP.NET Core hat Microsoft hier etwas weiter gedacht, denn das Framework bietet auch die Möglichkeit andere Endpunkte als Web Endpunkte zu nutzen. Es gibt dazu auch ein Beispiel repository von einem Microsoft Mitarbeiter - leider finde ich es auf die schnelle nicht.
Allerdings denke ich, dass du es dir wesentlich einfacher machen würdest, wenn du viel höher in der Schicht ansetzt (sofern es natürlich die Anforderungen zulassen) und beispielsweise gRPC, oder ähnliches verwendest.

P.S.: Über Anforderungen lässt sich immer diskutieren. Die Anforderung sollte dabei aber nicht sein "Entwickel einen Handshake", sondern sollte sich mehr auf "was soll denn eigentlich passieren" funktionieren.

Es gibt dazu auch ein Beispiel repository von einem Microsoft Mitarbeiter - leider finde ich es auf die schnelle nicht.

Meinst Du Glenn? Er hat uns (MVPs) vor 3 Jahren den frühen Stand der Prototypen dazu gezeigt.
glennc/mvp

wenn du viel höher in der Schicht ansetzt (sofern es natürlich die Anforderungen zulassen) und beispielsweise gRPC, oder ähnliches verwendest.

👍

Ich dachte es wäre David Fowler gewesen, finde bei ihm aber kein passendes Repository - vielleicht hat er es aber auch gelöscht, weil immer wieder Leute dort issues aufgemacht haben.

Edit: habs gefunden
davidfowl/MultiProtocolAspNetCore

hat sich erledigt. Danke für die Hilfe 🙂