Laden...

Problem mit TcpListener

Erstellt von Schattenkanzler vor 18 Jahren Letzter Beitrag vor 18 Jahren 3.167 Views
S
Schattenkanzler Themenstarter:in
238 Beiträge seit 2004
vor 18 Jahren
Problem mit TcpListener

Morgen!

Also, ich habe hier folgendes Problem: Ich habe versucht, mich in den TcpListener und den TcpClient einzuarbeiten.
Dazu habe ich die Beispiele von Microsoft aus der MSDN benutzt.

Client und Server.
Allerdings habe ich sie für eine Winform-Anwendung angepasst, da die Console bei mir gar nicht erst erschienen ist...

So, um nun den Server zu testen, habe ich den TcpListener auf localhost (127.0.0.1) auf Port 13000 horchen lassen.
Der Sender hat dann die Pakete an 127.0.0.1:13000 gesendet.

Solange der Listener nicht läuft, bekomme ich die Fehlermeldung, dass die Verbindung verweigert wurde. Klar soweit.
Sobald ich den Listener starte, meldet mir der Sender, dass er die Daten gesendet und eine Antwort bekommen hätte. Der Server schmiert jedoch (vermeintlich) ab. Das Programm schaltet nun nämlich auf "Keine Rückmeldung" und gibt auch nichts mehr aus.
Erst, wenn man den Sender (der abstürzt, wenn man jetzt noch mal versucht, die Daten zu senden) beendet ("Sofort beenden..."), liefert der Listener das Protokoll (welches auch vollkommen korrekt ist).

Es geht mir darum, dass ich zur Überwachung eines Servers das Syslog an einen Port senden lasse. Den möchte ich nun via TcpListener ständig abhören und bei einem Connect-Event bzw. immer dann, wenn Daten kommen, diese auswerten.

Wie stell ich das an, ohne dass mir jedesmal fast die ganze Kiste abschmiert?

Greets - SK

P.S: Ich weiß, das Thema Sockets/TCP gibt es schon des öfteren, aber bisher habe ich dabei nichts finden können, was mir weitergeholfen hat...

Sagte ich schon Danke? Nein? ...kommt noch...

4.506 Beiträge seit 2004
vor 18 Jahren

Hallo Schattenkanzler!

Wie genau hast Du denn das getestet?

Also ich erklär mal wie ich das gemacht hätte:

  • TCPListener gestartet, der setzt einen Port auf Listen.
  • Client gestartet, der versucht einen Connect
  • Client connected und der Server registriert den Connect
  • Client schickt Daten
  • Server empfängt Daten
  • Client meldet sich ab
  • Client beendet sich
  • Server muss nun SELBSTÄNDIG auch die Verbindung zumachen, und ggf. in einem Loop wieder von vorne anfangen, oder SELBSTÄNDIG beenden.

Hast Du das auch so gemacht?

ansonsten wartet der Server noch auf weitere Daten, die der Client nicht mehr schicken kann, weil er für sich schon alles beendet hat!

Deshalb ist es immens wichtig, dass man bevor Client und Server kommunizieren ein Zeichen festlegt, das die Datenübertragung als beendet signalisiert, oder aber vorher genau weiß wieviel Daten (bytes) kommen und nach erreichen dieser Empfangsbytes automatisch davon ausgehen, dass die Übertragung fertig ist.

Hoffe das hilft Dir ein wenig weiter?

Ciao
Norman-Timo

A: “Wie ist denn das Wetter bei euch?”
B: “Caps Lock.”
A: “Hä?”
B: “Na ja, Shift ohne Ende!”

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo Schattenkanzler,

normalerweise sind doch die MS-Beispiel sofort lauffähig.

da die Console bei mir gar nicht erst erschienen ist

Wie meinst du denn das? Eingabeaufforderung öffnen, Server starten, zweite Eingabeaufforderung öffnen, Client starten. Und das geht nicht?

herbivore

S
Schattenkanzler Themenstarter:in
238 Beiträge seit 2004
vor 18 Jahren

@norman_timo: Habe den Code heute wieder vor mir, werde das mal durchchecken.

@herbivore: Habe das Beispiel so übernommen, wie ich es bei MS gefunden habe. Wurde von mir nur nach Windows-Forms übersetzt (Console.WriteLine ersetzt durch eine Ausgabe in einer Textbox (könnte auch ne ListBox gewesen sein)).

Werd's mir gleich noch mal ansehen und dann hier Antworten geben!

Greets - SK

Sagte ich schon Danke? Nein? ...kommt noch...

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo Schattenkanzler,

man kann auch Windows-Forms-Anwendungen einfach als Konsolen-Applikation übersetzen (/target:exe), um die Ausgabe von Console.WriteLine zu erhalten. Im Beispiel handelte es sich - soweit ich sehe - aber nicht mal um eine Windows-Forms-Anwendung. 🙂

herbivore

F
529 Beiträge seit 2003
vor 18 Jahren

Hast du das mit den Beispiel gemacht, das hinter den Links Client oder Server in deinem ersten Beitrag angeführt ist?

Wenn ja, dann wundert mich das nämlich nicht, da das Warten auf neue Verbindungen und das Warten auf Packete die zu empfangen sind, den derzeitigen Thread so lange blockieren, bis neue Daten zum empfangen oder neuen Verbindungen da sind:
Ich würde das so machen:

Client:


using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace NetworkTest
{
    class Client
    {
        private Socket m_Socket = null;
        private NetworkStream m_Stream = null;
        private Thread m_Thread = null;
        public Client()
        {
            this.m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.m_Thread = new Thread(new ThreadStart(this.Recceive));
        }

        public void Open()
        {
            this.m_Socket.Connect(Dns.GetHostEntry("localhost").AddressList, 13000);
            this.m_Stream = new NetworkStream(this.m_Socket);
            this.m_Thread.Start();
        }

        public void Close()
        {
            this.m_Thread.Abort();
            this.m_Stream.Close();
            this.m_Socket.Close();
        }

        public void Send(string msg)
        {
            try
            {
                byte[] buf = Encoding.Default.GetBytes(msg);
                this.m_Stream.Write(buf, 0, buf.Length);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine(e.StackTrace);
            }
        }

        private void Recceive()
        {
            try
            {
                while (true)
                {
                    byte[] buffer = new byte[10240];
                    this.m_Stream.Read(buffer, 0, buffer.Length);
                    string msg = Encoding.Default.GetString(buffer).TrimEnd((char) 0x00);
                    Console.WriteLine(msg);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message); 
                Console.WriteLine(e.StackTrace);
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Client cli = new Client();
            cli.Open();
            cli.Send("Meldung vom Clienten");
        }
    }
}

Server:


namespace Server
{
    class Connection
    {
        private Thread m_Thread = null;
        private Socket m_Socket = null;
        private NetworkStream m_Stream = null;

        public Connection(Socket s)
        {
            this.m_Socket = s;
            this.m_Stream = new NetworkStream(s);
            this.m_Thread = new Thread(new ThreadStart(this.Recceive));
            this.m_Thread.Start();
        }

        private void Recceive()
        {
            try
            {
                while (true)
                {
                    byte[] buffer = new byte[10240];
                    this.m_Stream.Read(buffer, 0, buffer.Length);
                    Console.WriteLine(System.Text.Encoding.Default.GetString(buffer).TrimEnd((char) 0x00));
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine(e.StackTrace);
            }
        }

        public void Send(string msg)
        {
            try
            {
                byte[] buffer = System.Text.Encoding.Default.GetBytes(msg);
                lock (this.m_Stream)
                {
                    this.m_Stream.Write(buffer, 0, buffer.Length);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }

        public void Close()
        {
            try
            {
                this.m_Thread.Abort();
                this.m_Stream.Close();
                this.m_Socket.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }
    }

    class TCPServer
    {
        TcpListener m_Listener = null;
        Thread m_Thread = null;
        List<Connection> m_Connections = null;

        public TCPServer()
        {
            this.m_Listener = new TcpListener(IPAddress.Any, 13000);
            this.m_Thread = new Thread(new ThreadStart(this.Run));
            this.m_Connections = new List<Connection>();
        }

        public void Start()
        {
            this.m_Listener.Start();
            this.m_Thread.Start();
        }

        public void Stop()
        {
            this.m_Thread.Abort();
            this.m_Listener.Stop();
        }

        private void Run()
        {
            try
            {
                while (true)
                {
                    Socket s = this.m_Listener.AcceptSocket();
                    Connection conn = new Connection(s);
                    this.m_Connections.Add(conn);
                    conn.Send("Hallo vom Server");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            TCPServer svr = new TCPServer();
            svr.Start();
        }
    }
}

Es gibt nur einige Punkte zu beachten:
a) Ich würde anstatt der List<Connection> eine eigene Listen-Klasse verwenden, die Möglichkeiten bietet, per IP-Addresse auf die Verbindung zuzugreifen.
b) Das ganze ist mit der Beta von VS2005 gemacht. Evtl musst du etwas verändern um es mit dem 2003er zu verwenden.
c) Du musst immer die SocketExceptions abfangen und dann die Verbindung trennen.
Nämlich immer wenn ein Socket geschlossen wird, wird auf der Gegenseite eine Socketexception fliegen... (Hauptsächlich in der Methode die auf neue Daten wartet, kann aber auch bei der Senden-Funktion passieren)

Hoffe es hilft dir weiter!

Besuchen sie das VisualC++ - Forum

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo Franknstein,

kleine Anmerkung: für a) braucht man m.E. keine eigene Klasse, sondern kann Dictionary<IPAddress,Connection> benutzen.

herbivore

F
529 Beiträge seit 2003
vor 18 Jahren

Ok, sowas ist natürlich sehr schön. Danke für den Tipp!

Allerdings fällt mir noch ein Punkt d ein:
Du könntest in die Connection- und Client-Klasse noch Events für
-Verbindung hergstellt
-Verbindung geschlossen
-Daten gesendet
-Daten empfangen
-...
einbauen. Damit kannst du einfach andere Aktionen ausführen, wenn etwas passiert ist...

Besuchen sie das VisualC++ - Forum

S
Schattenkanzler Themenstarter:in
238 Beiträge seit 2004
vor 18 Jahren

Ja, diese Beispiele hatte ich benutzt und etwas angepasst. Habe sie nochmal implementiert und siehe da: Es funktioniert mehr oder minder reibungslos.

Dein Code sieht sehr vielversprechend aus, danke erstmal!

Greets - SK

Sagte ich schon Danke? Nein? ...kommt noch...

4.221 Beiträge seit 2005
vor 18 Jahren

Achtung:

AcceptSocket blockt !!

Deshalb vorher Pending des TcpListeners abfragen.

Ein Beispiel habe ich hier mal gepostet (allerdings mit AcceptTcpClient... was allerdings nix zur Sache tut).

TCP/IP Daten senden/empfangen

Suche dort nach .Pending dann findest Du die Codestelle gleich.

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

F
529 Beiträge seit 2003
vor 18 Jahren

Es ist zwar bei diesem Beispiel nicht der Fall, da es sich um eine Konsolenapplikation handelt, aber bei mir bekommt jeder Socket einen eigenen Thread. Der TCPListener ist nichts anderes als ein Socket, und wird daher auch in einem eigenen Thread ausgeführt.

Besuchen sie das VisualC++ - Forum

4.221 Beiträge seit 2005
vor 18 Jahren

Original von Franknstein
Der TCPListener ist nichts anderes als ein Socket, und wird daher auch in einem eigenen Thread ausgeführt.

Dafür hast Du dann einen Thread den Du runterreissen musst weil er blockt....

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

F
529 Beiträge seit 2003
vor 18 Jahren

Er blockt meines Wissens nur, wenn er auf neue Connections wartet. Und wenn das Programm halt beendet wird muss eben eine Threadabortexception geworfen werden um dann eine Socketexception im Thread auszulösen.

Ich hoffe das ist das, was du gemeint hast. Wenn nicht, dann erläutere bitte genauer was du damit sagen willst....

--
mfg
Frankenstein

Besuchen sie das VisualC++ - Forum