Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Konsolenanwendung mit TCPListener läuf als Dienst nicht korrekt
TomRie
myCSharp.de - Member



Dabei seit:
Beiträge: 14
Herkunft: Schweiz

Themenstarter:

Konsolenanwendung mit TCPListener läuf als Dienst nicht korrekt

beantworten | zitieren | melden

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:
Fehler
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()







private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1429
Herkunft: Düsseldorf

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 3984

beantworten | zitieren | melden

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)...
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Th69 am .
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15618
Herkunft: BW

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
TomRie
myCSharp.de - Member



Dabei seit:
Beiträge: 14
Herkunft: Schweiz

Themenstarter:

beantworten | zitieren | melden

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?
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von TomRie am .
private Nachricht | Beiträge des Benutzers
TomRie
myCSharp.de - Member



Dabei seit:
Beiträge: 14
Herkunft: Schweiz

Themenstarter:

beantworten | zitieren | melden

@Abt, sieht interessant aus! Das werde ich mir vornehmen, bin allerdings punkto Dienste und Worker noch ziemlich grün hinter den Ohren.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von TomRie am .
private Nachricht | Beiträge des Benutzers
TomRie
myCSharp.de - Member



Dabei seit:
Beiträge: 14
Herkunft: Schweiz

Themenstarter:

beantworten | zitieren | melden

@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.
private Nachricht | Beiträge des Benutzers
inflames2k
myCSharp.de - Experte

Avatar #AARsmmPEUMee0tQa2JoB.png


Dabei seit:
Beiträge: 2339

beantworten | zitieren | melden

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 | Spielkartenbibliothek
private Nachricht | Beiträge des Benutzers
TomRie
myCSharp.de - Member



Dabei seit:
Beiträge: 14
Herkunft: Schweiz

Themenstarter:

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
TomRie
myCSharp.de - Member



Dabei seit:
Beiträge: 14
Herkunft: Schweiz

Themenstarter:

beantworten | zitieren | melden

@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 ...
private Nachricht | Beiträge des Benutzers
inflames2k
myCSharp.de - Experte

Avatar #AARsmmPEUMee0tQa2JoB.png


Dabei seit:
Beiträge: 2339

beantworten | zitieren | melden

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.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von inflames2k am .
Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager | Spielkartenbibliothek
private Nachricht | Beiträge des Benutzers
TomRie
myCSharp.de - Member



Dabei seit:
Beiträge: 14
Herkunft: Schweiz

Themenstarter:

beantworten | zitieren | melden

@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.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von TomRie am .
private Nachricht | Beiträge des Benutzers
dannoe
myCSharp.de - Member



Dabei seit:
Beiträge: 152

beantworten | zitieren | melden

Zitat von TomRie
@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();
    }
Zitat von TomRie
Ü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.
private Nachricht | Beiträge des Benutzers
inflames2k
myCSharp.de - Experte

Avatar #AARsmmPEUMee0tQa2JoB.png


Dabei seit:
Beiträge: 2339

beantworten | zitieren | melden

Zitat von TomRie
@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.
Zitat von TomRie
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.
Zitat von TomRie
Ü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.
Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von inflames2k am .
Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager | Spielkartenbibliothek
private Nachricht | Beiträge des Benutzers