Freitag, 1. Oktober 2010

Datenaustausch im Internet und Netzwerk über TCP / IP

Um 2 PCs mit C# entweder über das Internet oder das lokale Netzwerk zu verbinden, gibt es unzählige Möglichkeiten.
Die meisten Möglichkeiten werden jedoch über die Verwendung von Sockets und über das TCP - Protokoll führen. Heute möchte ich euch eine davon zeigen.
Es gibt im Netz sicherlich sehr viele gute Tutorials, die das gleiche Thema behandeln wie dieser Post und sehr detailliert auf die damit verbundene Technik eingehen.
Im generellen Stil dieses Blogs werde ich daher nur relativ knapp auf den Code eingehen und die Theorie anderen überlassen.
Einleitend aber doch ein paar Satz dazu: Zur Kommunikation über ein Netzwerk (das Internet einbezogen) brauchen die beteiligten Computer ein gemeinsames Protokoll zur Datenübertragung, dass alle verstehen. Ein solches Protokoll ist zum Beispiel das hier verwendete TCP/IP Protokoll. Diese Abkürzung bedeutet Transmission Control Protocol / Internet Protocoll und ist eigentlich eine Sammlung von Protokollen zur Kommunikation über das Internet.
Das TCP/IP Protokoll nutzt zur Kommunikation Sockets. Diese sind Endpunkte, zwischen denen die Datenübertragung abläuft, man kann sie als äußere Schnittstellen zwischen den Kommunikationspartnern ansehen. Die Kommunikation läuft weiterhin über einen festgelegten Port. Ports sind eine Methode, die eingehenden Daten auf einem Rechner zu sortieren. Jede Anwendung, die Daten über das Internet / lokale Netzwerk sendet und empfängt, tut dies über einen bestimmten Port. So weiß der Computer, wohin er welche Daten leiten soll.

So, nun aber wie versprochen zur Praxis:

Wenn wir mehrere PCs vernetzen wollen, muss ein PC die Rolle des Servers übernehmen, an den sich ein oder mehrere Clients anmelden können. In diesem Beispiel gehen wir von 2 PCs aus, zwischen denen Daten ausgetauscht werden sollen.
Der Server wird durch die System.Net.Sockets.TcpListener dargestellt, der Client durch die Klasse System.Net.Sockets.TcpClient.
Jetzt zuerst das rudimentäre Gerüst des Server - Codes:

  • Das Server - Objekt vom Typ TcpListener erstellen, im Konstruktor den Port übergeben, über den die Kommunikation laufen soll.
  • Server starten mittels Start().
  • Die Methode AcceptTcpClient() wartet dann solange, bis ein Client sich angemeldet hat und gibt diesen als TcpClient zurück.
  • Über die Methode GetStream() des Clients kann dann ein Stream erstellt werden, über den dann Daten gesendet und empfangen werden können.
  • Nach getaner Arbeit den Client mittels Close() schließen und den Server beenden: Stop()


Nun das Gerüst des Client - Codes:

  • Das Client - Objekt vom Typ TcpClient erstellen.
  • Mittels der Methode Connect() auf den Server verbinden, hierbei muss die IP - Adresse dieses sowie der Port angegeben werden, auf dem das Server - Programm "lauscht".
  • Über die Methode GetStream() des Clients kann dann ein Stream erstellt werden, über den dann Daten gesendet und empfangen werden können.


Der Datenaustausch wurde bei diesem Beispiel zu Demozwecken nur sehr simpel realisiert, der Server läuft in einer Endlosschleife und wartet auf Nachrichten vom Client.
Die Nachrichtenabfrage kann aber auch über Ereignisse o.ä. realisiert werden, da eine Endlosschleife ressourcentechnisch nicht sehr günstig ist.
Die Streams wurden einfach als Instanzen der Basisklasse Stream angelegt, welche Daten als byte - Arrays senden und empfangen kann.
Die byte - Arrays werden mit Instanzen der Klasse ASCIIEncoding in Strings konvertiert und umgekehrt.
In der Nachrichtenschleife des Servers wird über die Methode Read() des Streams, versucht, die nächsten Bytes aus dem Stream zu lesen.
Diese Methode blockiert den Programmablauf solange, wie nicht Daten im Stream vorhanden sind, die ausgelesen werden können. Die Schleife wird also nur so oft durchlaufen, wie Nachrichten ankommen.
Wird die Verbindung zum Client unterbrochen, blockiert die Methode nicht mehr, es werden 0 Bytes ausgelesen, die Schleife kann nun verlassen werden.

Im folgenden Beispiel meldet sich der Client am Server an, schickt eine Nachricht an diesen, wartet 10 Sekunden, schickt die Nachricht dann nochmal und beendet sich.
Beide Programme, der Client und der Server, sind als Konsolenanwendungen realisiert und benötigen die using - Direktiven:

using System.IO;
using System.Net.Sockets;

Und jetzt der Quellcode:

Server:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;
using System.Net.Sockets;
using System.Net;

namespace TCPExample
{
    class TCPServer
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starte Server ...");

            TcpListener Server = new TcpListener(5550);
            Server.Start();
            Console.WriteLine("Server gestartet.");
            
            Console.WriteLine("Warte auf Verbindung ...");
            TcpClient Client = Server.AcceptTcpClient();
            Console.WriteLine("Verbindung hergestellt");

            Stream MessageStream = Client.GetStream();

            while (true)
            {
                byte[] message = new byte[4096];
                int bytesRead;
                bytesRead = MessageStream.Read(message, 0, 4096);

                if (bytesRead == 0)
                {
                    // Client beendet
                    break;
                }

                ASCIIEncoding encoder = new ASCIIEncoding();
                Console.WriteLine(encoder.GetString(message, 0, bytesRead));
            }

            // wenn die Schleife irgendwann beendet wird, Programm beenden
            Client.Close();
            Server.Stop();
        }
    }
}

Client:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;
using System.Net.Sockets;

namespace TCPExample
{
    class TCPClient
    {
        static void Main(string[] args)
        {
            System.Net.IPAddress IP = System.Net.IPAddress.Parse("192.168.2.110");
            TcpClient Client = new TcpClient();
            Client.Connect(IP, 5550);
            Stream MessageStream = Client.GetStream();

            ASCIIEncoding encoder = new ASCIIEncoding();

            byte[] buffer = encoder.GetBytes("Hello Server!");

            MessageStream.Write(buffer, 0, buffer.Length);
            MessageStream.Flush();

            System.Threading.Thread.Sleep(10000);

            MessageStream.Write(buffer, 0, buffer.Length);
            MessageStream.Flush();

            Client.Close();
        }
    }
}


Wie welche Arten von IP - Adressen (im lokalen Netzwerk und globale über das Internet) funktionieren, ist im nächsten Post erläutert.

Kommentare:

  1. Wow, Klasse Post. Der hat mir echt geholfen, als es darum ging mal nen bisschen in die Socketprogrammierung reinzuschnuppern. Ein weiterer Blogeintrag mit Multithreading oder gar ein paar events wären super.

    AntwortenLöschen
  2. Hallo,
    super sache, jedoch möchte ich einen solchen Chat übers internet realisieren.. Mein problem ist, wie kann ich eine dyndnsadresse in enine ip adresse umwandeln via c# damit die clients dem home server connecten können??

    gruss

    AntwortenLöschen
  3. Hallo,
    du möchtest also diesen Chat, statt über das lokale Netzwerk über das Internet betreiben?
    Hast du schon den nächsten Post (http://csharp-tricks.blogspot.com/2010/10/wahl-der-ip-adressen-zur-kommunikation.html) gelesen? Da sollte eigentlich deine Frage nach den Online IP Adressen beantwortet werden.
    Grüße
    Oliver

    AntwortenLöschen
  4. Hammer , das was Ich gerade brauche.
    Danke dir vielmals.

    AntwortenLöschen
  5. Cool, ich find das richtig toll, aber habe eine Frage: könntest du das so um schreiben das sich die Programme nicht selbst beendet sondern das man beim Server oder Client einen Befehl eingeben muss damit alles Beendet wird?

    AntwortenLöschen
  6. Hallo,
    diese Funktionalität lässt sich leicht einbauen.
    Baue einfach beim Client eine Schleife ein, die solange durchlaufen wird wie nicht der Beenden Befehl kommt, und entferne das "break" beim Server. Auch dieses soll nur aufgerufen werden, wenn der Beenden Befehl kommt.

    AntwortenLöschen
    Antworten
    1. Hi erstmal danke für die antwort,
      ich habe das jetzt so gemacht das wenn im Client "exit" eingegeben wird sich der Client beendet, daraufhin beendet sich auch der Server. Ich habe jetzt den Server Code in eine Windows-Form getan. Wenn ich aber jetzt "exit" eingebe, beendet sich nur der Client, und der Server läuft weiter.
      Was ist da jetzt denn falsch. Liegt das an der Windows-Form? Denn bei einer Konsolen-Applikation klappt das. Am Code habe ich aber nix großartiges verändert.

      Löschen
    2. Hi, das ist jetzt schwierig zu sagen, ich kenne deinen Code nicht. An Windows Forms bzw. Konsolenanwendungen wird es nicht liegne, wenn du weißt wie du diese richtig beendest (Windows Forms Anwendungen über System.Windows.Forms.Application.Exit()).
      Du musst natürlich auf die Synchronisation achten, d.h. wenn der Client sich beendet und der Server dies auch soll, muss ihm dieses irgendwie über eine Nachrichte mitgeteilt werden und andersherum.

      Löschen
    3. Ok, doch wie erkennt den der Server das ich den Client beende? Der Server springt bei der Konsolenanwendung in die "If bytesRead == 0" Abfrage wenn man den Client beendet und bei der Windows Form nicht. Ich wollte dann nämlich bei der Windows Form das machen das sich der Server nicht beendet sonder wieder nach oben springt (im Code) und auf eine Verbindung wartet sodass man den Server nicht neustarten muss.

      Löschen
  7. "Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte"

    Was mache ich jetzt?

    AntwortenLöschen
    Antworten
    1. Uff, das kann viele Ursachen haben, die wahrscheinlich gar nichts mit C# zu tun haben. Steht die Verbindung zwischen den Rechnern, erlaubt die Firewall diese? Probier mal ein Ping aus.

      Löschen
  8. Kann man es auch so programmieren, dass eine Datei (z.B. eine *.txt Datei) an einen anderen Rechner übers Internet gesendet wird? Ich möchte möglichst kein anderes Toll dabei ausführen (z.B. Skype). Wenn es möglich ist, würde ich mich über ein Beispiel freuen.

    PS: Klasse Blog!!!

    AntwortenLöschen
    Antworten
    1. Hi, danke für dein Lob erstmal.
      Leider bin ich im Moment zu sehr beschäftigt um ein Beispiel zu schreiben, dieses kannst du jedoch auch versuchen dir mit obigen Mitteln selber zu basteln!
      Ob man jetzt einzelne Bytes wie oben verschickt oder eine ganze Datei, macht für das Programm ja keinen Unterschied - es sind alles nur Bytefolgen.
      Also "einfach" die Datei in ein Byte Array umwandeln und verschicken.

      Löschen
    2. Danke!

      Kann man eigendlich irgendwo einen Wünsch abgeben, was als nächstes kommen soll bzw. was man gerne sehen würde?

      Löschen
    3. Gerne! Wie du wahrscheinlich schon gemerkt hast, habe ich leider momentan recht wenig Zeit, aber schreib deine Wünsche einfach in die Kommentare oder per Email!

      Löschen
  9. Hey,

    Ist es irgenwie möglich, gegenseitig Nachrichten auszutauschen, ohne dass 2 Ports benötigt werden? Kann ein server auch Nachrichten an den Client schicken?

    Danke für deine Antwort

    AntwortenLöschen
    Antworten
    1. Hmm ... ich finde mit 2 Ports ist es echt einfacher. Was stört dich denn an 2 Ports?

      Löschen
  10. Mich würde auch interessieren wie ich mit 2 Clients Nachrichten an den Server schicke, und der Server wiederum die Nachrichten an die Clients verteilt. Quasi wie ein "Chat" ( alles Lokal ) Ich habe das oben stehende Programm in WPF realisiert und es etwas verändert, ich brauche nur eine grobe Richtung wie es funktionieren könnte.

    AntwortenLöschen
    Antworten
    1. Dazu würde ich auf den Clients einen Server auf einem neuen Port einrichten, und auf dem Server einen Client, welcher dann die jeweiligen Nachrichten an die richtige Komibination aus IP / Port schickt.

      Löschen
  11. Gerade in Anbetracht der vorigen Fragen könnte folgende Seite einige hier interessieren:
    http://physudo.blogspot.de/2013/05/panzerkampf.html
    Dort wird "gleichzeitige" Kommunikation zwischen Client und Server benutzt.

    AntwortenLöschen
  12. Leider klappt ein Zugriff über Internet nicht :( was mache ich nur falsch Ports habe ich freigegeben

    AntwortenLöschen
  13. Hallo, ich habe ein klein chat mit hilfe deiner snippets geschrieben aber wenn ich eine andere IP als 127.0.0.1 verwende findet er den server nicht :( (es funktioniert nur auf dem selben gerät aber auch selbst dort nicht mit 192.168...etc sondern nur 127.0.0.1)

    AntwortenLöschen