Laden...

Konsolenanwendung mit TCPListener läuf als Dienst nicht korrekt

Erstellt von TomRie vor 3 Jahren Letzter Beitrag vor 3 Jahren 954 Views
T
TomRie Themenstarter:in
14 Beiträge seit 2021
vor 3 Jahren
Konsolenanwendung mit TCPListener läuf als Dienst nicht korrekt

Hallo Allerseits

Ich habe eine korrekt funktionierende Konsolenapplikation so umgebaut, dass sie sowohl auf Konsole als auch als WindowsDienst auf einem Server im Hintergrund läuft, bzw. laufen sollte. Als Konsolenanwendung läuft sie nach wie vor, als Dienst jedoch nicht korrekt.

Das Ganze wurde nach dieser Anleitung erstellt: https://www.debuggershub.com/dotnet-console-app-as-a-windows-service/

Ich denke es liegt daran, dass der verwendete DataProcessor über den Timer alle 10 Sec. wiederholt aufgerufen wird. Meine Frage ist also: Wie wird der Aufruf hier korrekt gemacht?


class Program
{
    public class DataProcessor
    {
        private static Thread n_server;
        private static Thread n_send_server;
        private static TcpClient client;
        private static TcpListener listener;
        private static Socket socket;
        private static byte[] bufferReceive = new byte[4096];
        private static byte[] bufferSend = new byte[4096];
        private static string XMLMessage;
        private static string CSVMessage;
        private static string XMLCallback;

        internal void Execute()
        {
            try
            {
                n_server = new Thread(new ThreadStart(Server));
                n_server.IsBackground = true;
                n_server.Start();
                Console.WriteLine("MessageReceiver started");
                TimerTranslate.TimerTranslateStart();
            }
            catch (Exception ex)
            {
                StreamWriter Filelog = new StreamWriter(ConfigurationManager.AppSettings.Get("logs") + "\\log.txt", append: true);
                Filelog.Write(DateTime.Now + " *** " + ex.Message + Environment.NewLine);
                Filelog.Close();
            }

            public static void Server()
            {
                listener = new TcpListener(IPAddress.Any, Convert.ToInt32(ConfigurationManager.AppSettings.Get("port")));
                listener.Start();

                try
                {
                    socket = listener.AcceptSocket();
                    if (socket.Connected)
                    {
                        Console.WriteLine("Client connected on: " + socket.RemoteEndPoint.ToString());
                    }
                    while (true)
                    {
                        int length = socket.Receive(bufferReceive);
                        if (length > 0)
                        {
                            XMLMessage = (Encoding.UTF8.GetString(bufferReceive, 0, length));
                            Console.WriteLine("XML_Message received successfully");
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("A client has disconnected");

                    StreamWriter Filelog = new StreamWriter(ConfigurationManager.AppSettings.Get("logs") + "\\log.txt", append: true);
                    Filelog.Write(DateTime.Now + " " + ex.Message + Environment.NewLine);
                    Filelog.Close();
                }
                finally
                {
                    listener.Stop();
                    n_server = new Thread(new ThreadStart(Server));
                    n_server.IsBackground = true;
                    n_server.Abort();

                    if (socket != null)
                    {
                        n_server = new Thread(new ThreadStart(Server));
                        n_server.IsBackground = true;
                        n_server.Start();

                        Console.Clear();
                        Console.WriteLine("MessageReceiver started, waitig for next connection...");
                    }
                }
            }

            private static void send()
            {
                if (socket != null)
                {
                    bufferSend = Encoding.UTF8.GetBytes(XMLCallback);
                    socket.Send(bufferSend);
                }
                else
                {
                    try
                    {
                        if (client.Connected)
                        {
                            bufferSend = Encoding.UTF8.GetBytes(XMLMessage);
                            NetworkStream nts = client.GetStream();
                            if (nts.CanWrite)
                            {
                                nts.Write(bufferSend, 0, bufferSend.Length);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error : " + ex.Message);

                        StreamWriter File = new StreamWriter(ConfigurationManager.AppSettings.Get("logs") + "\\log.txt", append: true);
                        File.Write(DateTime.Now + " *** " + ex.Message + Environment.NewLine);
                        File.Close();
                    }
                    finally
                    {
                        //client.Close();                    
                    }
                }
            }
        }
    }
}

Die MainClass


public class MainClass
{
    static void Main(string[] args)
    {            
        if ((!Environment.UserInteractive))
        {
            MainClass.RunAsAService();
        }
        else
        {
            if (args != null && args.Length > 0)
            {
                if (args[0].Equals("-i", StringComparison.OrdinalIgnoreCase))
                {
                    SelfInstaller.InstallMe();
                }
                else
                {
                    if (args[0].Equals("-u", StringComparison.OrdinalIgnoreCase))
                    {
                        SelfInstaller.UninstallMe();
                    }
                    else
                    {
                        Console.WriteLine("Invalid argument!");
                    }
                }
            }
            else
            {
                MainClass.RunAsAConsole();
            }
        }
    }

    static void RunAsAConsole()
    {
        DataProcessor dataProcessor = new DataProcessor();
        dataProcessor.Execute();
    }

    static void RunAsAService()
    {
        ServiceBase[] servicesToRun = new ServiceBase[]
        {
            new MessageReceiverService()
        };
        ServiceBase.Run(servicesToRun);
    }            
}

Die ServiceBase hier wird über den Timer gestartet. Wenn ich hier dataProcessor.Execute(); direkt verwende läuft das Programm etwas weiten, zeigt aber die gleichen Fehler wie unten beschrieben.


partial class MessageReceiverService : ServiceBase
{
    private static Timer aTimer;

    public MessageReceiverService()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        aTimer = new Timer(10000); 
        aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
        aTimer.Enabled = true;
    }

    private static void OnTimedEvent(object source, ElapsedEventArgs e)
    {
        DataProcessor dataProcessor = new DataProcessor();
        dataProcessor.Execute();
    }

    protected override void OnStop()
    {
        aTimer.Stop();
    }
}

Die Fehlermeldungen bei Start ist "The handle is invalid" anstatt "MessageReceiver started"

In den Logs findet sich dann folgendes:

Fehlermeldung:
Application: MessageReceiver.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Net.Sockets.SocketException
at System.Net.Sockets.Socket.DoBind(System.Net.EndPoint, System.Net.SocketAddress)
at System.Net.Sockets.Socket.Bind(System.Net.EndPoint)
at System.Net.Sockets.TcpListener.Start(Int32)
at MessageReceiver.DataProcessor.Server()
at System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
at System.Threading.ThreadHelper.ThreadStart()

2.078 Beiträge seit 2012
vor 3 Jahren

Dein LINK hat keine URL hinterlegt.
Schau mal, ob Du das noch bearbeiten kannst, ansonsten muss das ein Admin machen.

Und dein DataProcessor macht einen Thread auf, wartet aber nicht auf dessen Beendigung.
Das aus einem Timer heraus halte ich für riskant, da solltest Du dir nochmal genau anschauen, ob die Threads sich wirklich so verhalten, wie Du das möchtest. VisualStudio bietet übrigens ein paar nützliche Features zum Beeinflussen der Threads in einer Debug-Session.

Ansonsten wäre die Exception-Message spannend.
Soweit ich mich erinnere, muss man Port und Pfad im Windows mit Admin-Rechten registrieren, damit man darauf horchen kann. Was der Installer genau tut, weiß ich nicht, damit hab ich bisher nie gearbeitet, aber wenn dein Dienst als User ohne Admin-Rechte startet und dann versucht, alles einzurichten, wird das vermutlich schief gehen.

Und ohne diese Registrierung, funktioniert das Horchen auf den Port nicht.
Ich weiß nicht, ob dabei auch eine SocketException raus kommt, die Message könnte Klarheit schaffen.

Du solltest dir aber auch ein sinnvolles Logging überlegen (selber bauen ist selten eine gute Idee) und den problematischen Aufruf auch ins Try legen und nicht davor.

4.931 Beiträge seit 2008
vor 3 Jahren

Hallo,

da ist noch einiges logisch falsch an deinem Code.

Warum verwendest du Thread?
Warum einen Timer in der Service-Klasse?
Warum ist DataProcessor.Execute() nicht-statisch, während die Klasse selbst nur statische Member hat (und dazu noch viele unnötig davon als Member)?
Und in der Server-Methode startest du jeweils wieder einen (bzw. sogar zweifach?!?) neuen Thread (welche selbst in einem Thread läuft): hier wäre eine Schleife viel angebrachter!

Das ganze Laufzeitverhalten von deinem Code paßt also nicht (man kann hier also nicht von "korrekt funktionierende Konsolenapplikation" sprechen)...

16.807 Beiträge seit 2008
vor 3 Jahren

Solch einen Service schreibt man mittlerweile mit der IHostedService Klasse.
Visual Studio hat hier auch ein Projekttemplate, das sich Backgroundservice bzw. Worker Services nennt.

Siehe auch Doku
Implementieren von Hintergrundtasks in Microservices mit IHostedService und der BackgroundService-Klasse

T
TomRie Themenstarter:in
14 Beiträge seit 2021
vor 3 Jahren

Hallo,

erst mal Danke für die Hinweise. Es ist so, dass das näturlich nicht der ganze Code ist. Dabei handelt es sich nur um einen kleinen Ausschnitt. Ansonsten wäre das zu unübersichtlich. Es geht nur um die grundlegende Starterei als Service, weniger um den Inhalt von DataProcessor, aber ja, da gibt es sicher Verbesserungspotenzial. Ich habe das noch mit Always Up getestet und da läuft das Ganze problemlos, zumindest lokal. Wie es sich jetzt über Always Up auf einem WinServer2019 verhält wird sich in Kürze zeigen. @Palladin007, ich könnte mir vorstellen, dass das mit dem "Port und Pfad im Windows mit Admin-Rechten registrieren" das Problem sein könnte, falls es nun auf dem Server nicht korrekt laufen sollte. Wo und wie müssten diese Rechte eingetragen werden? Registry?

T
TomRie Themenstarter:in
14 Beiträge seit 2021
vor 3 Jahren

@Abt, sieht interessant aus! Das werde ich mir vornehmen, bin allerdings punkto Dienste und Worker noch ziemlich grün hinter den Ohren.

T
TomRie Themenstarter:in
14 Beiträge seit 2021
vor 3 Jahren

@Th69, danke für die Analyse. Der Timer in der Service-Klasse mach im reduzierten Code hier natürlich keinen Sinn, hingegen im Orginal schon. Sinn und Zweck der App ist es, empfangene Deposit informationen die als XML daherkommen in CSV zu übersetzen und in definierbaren Pfaden abzulegen. Dabei wird nach erfolgreicher Übersetzung auch eine Bestätigung an den Client gesendet. Da alles automatisch ablaufen muss, sind die Thread's hier auch etwas verwirrend. Ich fand leider keine Lösung die besser funktioniert hat als diese. Ich kann Dir den gesamten Code auch gerne mal zum durchchecken übergeben. Bestimmt kan man den wesentlich verbessern.

2.298 Beiträge seit 2010
vor 3 Jahren

Nach der Beschreibung macht deine MessageReceiverService -Klasse speziell mit dem Timer noch weniger Sinn.

Du erzeugst alle 10 Sekunden einen neuen DataProcessor mit der darunter liegenden Logik. - Wenn ich ins blaue raten sollte knallt dir die Laufzeit dann beim 2. Start deiner Server-Methode die Exception raus, da der Port bereits verwendet wird.

Im Allgemeinen wäre es wohl besser einen Listener-Thread zu verwenden und nicht zyklisch neu den Listener aufzubauen.

Was du im Code machst ist:

  1. Server Port öffnen
  2. Auf Verbindung warten
  3. Kommunikation mit Client

Solang dieser Ablauf jetzt durch rennt, hast du das Problem, dass alle 10 Sekunden ein neuer Listener erstellt werden soll und das ganze noch mal starten soll. Du kannst aber nur einmal auf den Port horchen.

Geschickter wäre es also mit den Asynchronen-Methoden der Netzwerkklassen zu arbeiten und genau eine Listener Instanz zu verwenden.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

T
TomRie Themenstarter:in
14 Beiträge seit 2021
vor 3 Jahren

Jetzt habe ich vom Admin erfahren, dass die Konsolenapplikation über Always Up problemlos auf den Servern läuft. Also die Variante ohne Dienst was also bedeutet, dass gegenwärtig nur dieser Dienst das Problem ist. Wie bereits gesagt, nehme ich an, dass die Timer darin das Problem verursachen. Daher schon zu Begin die Frage, wie der aussehen muss damit eben nicht alle 10 sec. ein neuer DataProzessor gestartet wird.

T
TomRie Themenstarter:in
14 Beiträge seit 2021
vor 3 Jahren

@inflames2k, ja, das entspricht genau meiner Vermutung. Da ich mich mit Diensten noch ziemlich schlecht auskenne, kann ich nur auffindbare Beispiele heranziehen und sie testen. Leider findet sich kaum eins das genau zur Anwendung passt, so dass herumpröbeln und testen angesagt ist, daher also besser mal die Profis fragen ...

2.298 Beiträge seit 2010
vor 3 Jahren

Dein Problem hat nichts. Aber auch gar nichts mit Diensten an sich zu tun. Wenn du das in der Service-Klasse gezeigte in einer Konsole ausführst, wirst du auf genau die gleichen Probleme stoßen.

Der riesen Unterschied den du eben fabriziert hast ist:

  • deine Konsole initialisiert den DataProcessor und führt ihn aus
  • dein Dienst initialisiert alle 10 Sekunden einen DataProcessor und führt ihn aus

Wenn du jetzt noch verstehst, was ich versuche dir zu sagen, solltest du der Lösung für dein Problem schon näher kommen.

Wenn es bei Ausführung ohne den Timer zu Fehlern kommt, dann poste diesen doch mal. - Aber der Timer macht ohnehin wie schon von anderer Stelle angemerkt keinen Sinn.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

T
TomRie Themenstarter:in
14 Beiträge seit 2021
vor 3 Jahren

@inflames2k, in der Konsolenapplikation ohne Service gibt es doch noch gar keinen DataProcessor. Ich denke da verstehst Du etwas ganz und gar falsch. Dass der Service "und nur der Service (inkl. DataProzessor)" nicht für diese Art Anwendung geeignet ist sage ich ja schon lange, daher muss ja etwas anderes her.

Übrigens wenn das gezeigte nur als Konsole ausgeführt wird, kommt der Timer entgegen deiner Ansicht gar nicht zum Zug, nur beim auführen des Services. Das erkennt man kurzerhand im Beispiel über den Link.

D
261 Beiträge seit 2015
vor 3 Jahren

@inflames2k, in der Konsolenapplikation ohne Service gibt es doch noch gar keinen DataProcessor. Ich denke da verstehst Du etwas ganz und gar falsch.

Was ist dann das?


    static void RunAsAConsole()
    {
        DataProcessor dataProcessor = new DataProcessor();
        dataProcessor.Execute();
    }

Übrigens wenn das gezeigte nur als Konsole ausgeführt wird, kommt der Timer entgegen deiner Ansicht gar nicht zum Zug, nur beim auführen des Services. Das erkennt man kurzerhand im Beispiel über den Link.

Und genau das ist dein Problem. Der Timer versucht ständig (alle 10 Sekunden) neue TCP Listener mit dem gleichen Port zu erzeugen, das funktioniert aber nicht.

2.298 Beiträge seit 2010
vor 3 Jahren

@inflames2k, in der Konsolenapplikation ohne Service gibt es doch noch gar keinen DataProcessor.

Nun, wenn du deinen eigenen Code nicht liest. Dann verstehe ich die Wurzel der Probleme.

Dass der Service "und nur der Service (inkl. DataProzessor)" nicht für diese Art Anwendung geeignet ist sage ich ja schon lange, daher muss ja etwas anderes her.

Ja genau. - Nämlich die korrekte Variante ohne Timer. - Was passiert denn wenn du deinen DataProcessor direkt ausführst statt über den Timer? - Dort sollte man ansetzen den Fehler zu suchen und nicht irgendetwas Wild mit Timern dahinter zu Frickeln damit überhaupt was startet.

Übrigens wenn das gezeigte nur als Konsole ausgeführt wird, kommt der Timer entgegen deiner Ansicht gar nicht zum Zug, nur beim auführen des Services. Das erkennt man kurzerhand im Beispiel über den Link.

Das habe ich auch nie behauptet. - Im Gegenteil. Ich habe dich drauf hingewiesen, dass er ja dort - wo dein Programm funktioniert - eben nicht zum Einsatz kommt.

Nun weiß ich auch, wo das ganze Problem her kommt. - Du schreibst leider nur ab, was in der Anleitung vorgegeben ist ohne dir weitere Gedanken darüber zu machen. Für den in der Anleitung verwendeten Anwendungsfall [der auch nur ein Beispiel ist] macht der Timer natürlich sinn. - Für deinen Fall aber nicht. - Also als erstes Weg mit dem Timer. Danach können wir uns der weiteren Probleme annehmen.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |