Peer-to-Peer Chat (ohne zentralen Server)


#1
Hallo Comunity.
Ich habe vor, als privates Projekt eine Applikation zu entwickeln, die vorerst im heimischen Netz laufen soll. Es soll sich um einen kleinen und kaum Ressourcen fressenden Chat handeln.
Grundsätzlich möchte ich die Applikation in C# schreiben, da ich mich gerade mit dieser Sprache beschäftige und dies als Einstiegsprogramm nutzen möchte. Nun bin ich aber bei meinem Streifzug durchs Web hauptsächlich auf Variationen getroffen, in denen eine Serverapplikation laufen muss. Was ich mir aber vorgestellt hatte, ist ein Chat, in dem mir eine Liste an Benutzern angezeigt wird (durch ihre IP identifiziert) und durch anwählen wird eine direkte Chat-Session gestartet.

Ich wollte fragen, ob sich eventuell einer von euch schon einmal mit etwas ähnlichem beschäftigt hat, sodass ich vielleicht einige Links bekomme. Mein Problem besteht darin, dass ich keine Ahnung habe, was ich zur verwirklichung eines solchen Projektes an Wissen benötige. Muss ich mir das TCP/IP-Protokoll genauer ansehen?

Vielen Dank für die Hilfe.

Mit freundlichen Grüßen

Rente (der eigentlich noch gar nicht soo alt ist.)

PS: Die Informationen dürfen auch gerne sehr allgemein sein und müssen nich auf C# zugeschnitten sein.
 

WorldRacer

Erfahrenes Mitglied
#2
Hallo Rente,

darf ich Fragen, wie weit du in Sachen Netzwerkprogrammierung bist? Ich darf auch mal drauf hinweisen, dass das ganze mit P2P nicht so einfach ist. Da gibts ja noch die dynamischen IPs, die nach jedem Connect wechseln. Da wäre aber noch das Stichwort DynDNS, wobei ich das nicht ganz so toll finden würde, wenn ich für mein Chat-Programm eine DynDNS einrichten müste. Ein dickes Problem würdest du zusätzlich noch mit Leuten bekommen, die hinter einem Router sitzen.

Ich grabe aber auf jeden Fall mal meine Codesnippets für die Netzwerkprogrammierung in C# aus.

Gruß,

WR
 

WorldRacer

Erfahrenes Mitglied
#3
Okay, Snippets finde ich jetzt auf die schnelle, schaue heute Abend mal in Ruhe.

Aber dafür mal ein paar Links und eine kleine Erklärung:

Für die Verbindung zu einem Client:
http://msdn.microsoft.com/de-de/library/system.net.sockets.tcpclient(VS.80).aspx

Für das Abhören auf Clients:
http://msdn.microsoft.com/de-de/library/system.net.sockets.tcplistener(VS.80).aspx

Aaaalso.

Ich hab es immer So gemacht:

Einen neue Klasse erstellt (Bsp Server)

In dieser Klasse werden mehere Events deklariert (OnClientConnected, OnClientDisconnected, OnMessageReceived usw.)
Beispiel: http://msdn.microsoft.com/de-de/library/8627sbea(VS.80).aspx

Methoden deklariert, Bsp SendMessage und StartListening sowie StopListening.

In Klasse kommt ebenfalls ein Thread, der fürs Listening zuständig ist. StartListening startet diesen Thread, StopListening beendet diesen.

http://msdn.microsoft.com/de-de/library/system.threading.thread.aspx

In die Arbeitsmethode des Threads kommt dann:

Code:
// Start listening for client requests.
      server.Start();
         
    

      // Enter the listening loop.
      while(true) 
      {
        // Give the other Threads 1 miliseconds to do their work, else a 100% SPU load is guaranteed.
        System.Threading.Thread.Sleep(1);

        Console.Write("Waiting for a connection... ");
        
        // Perform a blocking call to accept requests.
        // You could also user server.AcceptSocket() here.
        TcpClient client = server.AcceptTcpClient();
An dieser Stelle ruft der Thread das ClientConnected-Event auf und übergibt den Client an einen weiteren Thread, der die Nachricht des Clients abruft.

Code für den Zweiten Thread:

Code:
        Console.WriteLine("Connected!");
        // Buffer for reading data
        Byte[] bytes = new Byte[256];
        String data = null;
        data = null;

        // Get a stream object for reading and writing
        NetworkStream stream = client.GetStream();

        int i;

        // Loop to receive all the data sent by the client.
        while((i = stream.Read(bytes, 0, bytes.Length))!=0) 
        {   
          // Translate data bytes to a ASCII string.
          data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
          Console.WriteLine("Received: {0}", data);
       
          // Process the data sent by the client.
          data = data.ToUpper();

          byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);

          // Send back a response.
          stream.Write(msg, 0, msg.Length);
          Console.WriteLine("Sent: {0}", data);            
        }
So. Dann sollte man nach aufruf des zweiten Threads die Connection zum Client Beenden (sofern keine weiteren Daten oder Vorhaben anstehen, ansonsten muss man das nicht) und die while-Schleife schließen.

Das war so der grobe Aufbau meiner Server. Das geht noch schön verfeinert und viel praktischer, wie ich sie bei mir hab, was du dann heute Abend sehen wirst.

Gruß

WR
 

WorldRacer

Erfahrenes Mitglied
#4
So, habe jetzt mal die Snippets rausgesucht, allerdings nicht ausprobiert. Fehlerlos sind sie, buglos...hm. man weiß nicht.

Im Anhang findest du das komplette Projekt als Klassenbibliothek (DLL)

Client.cs
Code:
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 *  Copyright (C) Marco Klein alias WorldRacer, WorldRacer92, WorldRacer_original  *
 *                                                                                 *
 *   This program is free software: you can redistribute it and/or modify          *
 *   it under the terms of the GNU General Public License as published by          *
 *   the Free Software Foundation, either version 3 of the License, or             *
 *   (at your option) any later version.                                           *
 *                                                                                 *
 *   This program is distributed in the hope that it will be useful,               *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of                *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
 *   GNU General Public License for more details.                                  *
 *                                                                                 *
 *   You should have received a copy of the GNU General Public License             *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.         *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace devNetworks
{
    /// <summary>
    /// This class represents a client
    /// </summary>
    public class Client
    {
        #region Public Attributes
        /// <summary>
        /// Socket. (TcpClient)
        /// </summary> 
        public System.Net.Sockets.TcpClient Socket
        {
            get { return _Socket; }
            set { _Socket = value; }
        }
        #endregion

        #region Private Attributes

        // Thread for handling server Messages
        System.Threading.Thread _ClientThread = null;

        // Socket to handle with
        private System.Net.Sockets.TcpClient _Socket = null;

        // IP-Adress of the endpoint 
        private string _IPAdress = string.Empty;

        // Port to connect at the entpoint
        private int _Port = 0;

        // Contains whether we are connected or not.
        private bool _Connected = false;

        // Bufferlength
        int _BufferLength = 4096;

        // Get a client stream for reading and writing.
        //  Stream stream = client.GetStream();
        System.Net.Sockets.NetworkStream _Stream;

        // How shall the messages be encoded to?
        System.Text.Encoding _EncodingInstance = System.Text.Encoding.ASCII;
        #endregion

        #region Constructor
        /// <summary>
        /// Initializes a Client
        /// </summary>
        /// <param name="IPAdress">IP-Adress of the endpoint</param>
        /// <param name="Port">Port to connect at the entpoint</param>
        /// <param name="ConnectImmediately">Should I connect directly?</param>
        /// <param name="BufferLength">How long shall the received messages be?</param>
        /// <param name="EncodingInstance">How shall the messages be encoded to?</param>        
        public Client(string IPAdress, int Port, System.Text.Encoding EncodingInstance, bool ConnectImmediately = false, int BufferLength = 4096)
        {
            // Save the values
            if (BufferLength > 0)
            {
                _BufferLength = BufferLength;
            }
            else
            {
                _BufferLength = 4096;
            }
            
            _EncodingInstance = EncodingInstance;
            
            // If we shall connect immediately...
            if (ConnectImmediately)
            {
                try
                {
                    // Create a socket which connects immediately
                    _Socket = new System.Net.Sockets.TcpClient(IPAdress, Port);
                    _Connected = _Socket.Connected;
                }
                catch (Exception)
                {

                }
            }
            else
            {
                // Else, initialize a new, not connecting socket
                _Socket = new System.Net.Sockets.TcpClient();
                _IPAdress = IPAdress;
                _Port = Port;
            }            
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Worker Function for client listening
        /// </summary>
        private void _ClientThreadFunc()
        {
            // Enter listening loop
            while (true)
            {
                // Give the other threads 1 miliseconds to do their work, else a 100% CPU load is guaranteed.
                System.Threading.Thread.Sleep(1);

                // Message Bytes
                Byte[] data = new Byte[_BufferLength];

                // String to store the response ASCII representation.
                String responseData = String.Empty;

                // Read the first batch of the TcpServer response bytes.
                Int32 bytes = _Stream.Read(data, 0, data.Length);

                // Placeholder for writing in the recieved data.
                responseData = _EncodingInstance.GetString(data, 0, bytes);

                // Call MessageReceived event, if it has event handlers
                if (MessageRecieved != null)
                {
                    MessageRecieved(this, responseData);
                }
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Sends a message to the Server
        /// </summary>
        /// <param name="Message">Message to send</param>
        public void SendMessage(string Message)
        {
            // Convert string into bytes
            byte[] msg = _EncodingInstance.GetBytes(Message);

            // Send the bytes to the stream.
            _Stream.Write(msg, 0, msg.Length);
        }
        /// <summary>
        /// Connect to the endpoint
        /// </summary>
        /// <returns></returns>
        public bool Connect()
        {            
            // If we have an IP
            if (_IPAdress == string.Empty || _Port == 0)
            {
                // If not, return false
                return false;
            }
            else
            {
                // If there was a socket before
                if (Socket != null)
                {
                    // and if it was connected, disconnect it
                    if (!_Socket.Connected)
                        _Socket.Close();
                }
                else
                {
                    // else create a new one.
                    _Socket = new System.Net.Sockets.TcpClient();
                }

                // If everything's fine, connect it.
                _Socket.Connect(_IPAdress, _Port);
                
                // If the socket is connected
                if (_Socket.Connected)
                {
                    // Retrieve a stream
                    _Stream = _Socket.GetStream();

                    // And start the listening thread
                    _ClientThread = new System.Threading.Thread(new System.Threading.ThreadStart(_ClientThreadFunc));
                    _ClientThread.Start();                    
                }

                // Last but not least, return success or fail message
                return _Socket.Connected;
            }
        }        
        #endregion

        #region Events
        /// <summary>
        /// Called when the client received a message
        /// </summary>
        /// <param name="sender">This ServerClient</param>
        /// <param name="Message">Received message</param>
        public delegate void MessageRecievedDelegate(object sender, string Message);

        /// <summary>
        /// Called when the client received a message
        /// </summary>
        public event MessageRecievedDelegate MessageRecieved;
        #endregion

    }
}
Server.cs
Code:
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 *  Copyright (C) Marco Klein alias WorldRacer, WorldRacer92, WorldRacer_original  *
 *                                                                                 *
 *   This program is free software: you can redistribute it and/or modify          *
 *   it under the terms of the GNU General Public License as published by          *
 *   the Free Software Foundation, either version 3 of the License, or             *
 *   (at your option) any later version.                                           *
 *                                                                                 *
 *   This program is distributed in the hope that it will be useful,               *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of                *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
 *   GNU General Public License for more details.                                  *
 *                                                                                 *
 *   You should have received a copy of the GNU General Public License             *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.         *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace devNetworks
{
    /// <summary>
    /// This class represents a multi-client server
    /// </summary>
    public class Server
    {
        #region Public Attributes
        /// <summary>
        /// Socket. (TcpClient)
        /// </summary> 
        public System.Net.Sockets.TcpListener Socket
        {
            get { return _Listener; }
            set { _Listener = value; }
        }
        #endregion

        #region Private Attributes
        // Listener.
        private System.Net.Sockets.TcpListener _Listener = null;

        // List of connected clients.
        private List<ServerClient> _ConnectedClients = new List<ServerClient>();

        // Bufferlength
        int _BufferLength = 4096;

        // Get a client stream for reading and writing.       
        System.Net.Sockets.NetworkStream _Stream;

        // How shall the messages be encoded to?
        System.Text.Encoding _EncodingInstance = System.Text.Encoding.ASCII;
        
        // Thread for handling server Messages
        System.Threading.Thread _ListenThread = null;

        #endregion

        #region Constructor
        /// <summary>
        /// Initializes a new Multiple-Connection-Server
        /// </summary>
        /// <param name="Port">Port to listen on</param>
        /// <param name="LocalEndPoint">IPAdress to bind</param>
        /// <param name="EncodingInstance">How shall the messages be encoded to?</param>
        public Server(int Port, System.Net.IPAddress LocalEndPoint, Encoding EncodingInstance)
        {
            _EncodingInstance = EncodingInstance;
            _Listener = new System.Net.Sockets.TcpListener(LocalEndPoint, Port);
        }        
        #endregion

        #region Private Methods
        /// <summary>
        /// Thread to handle a client
        /// </summary>
        /// <param name="ClientObject">TcpClient. Client to handle</param>
        private void _AddClient(object ClientObject)
        {
            // Perform a blocking call to accept requests.
            // You could also user server.AcceptSocket() here.                
            ServerClient Client = new ServerClient(((System.Net.Sockets.TcpClient)ClientObject), this._EncodingInstance);
            Client.MessageRecieved += new ServerClient.MessageRecievedDelegate(_Client_MessageRecieved);
            _ConnectedClients.Add(Client);
            ClientConnected(this, _ConnectedClients[_ConnectedClients.Count - 1]);            
        }

        /// <summary>
        /// Overridable function, for child classes. Here you can define what to do with recieved messages
        /// </summary>
        /// <param name="sender">The Server class</param>
        /// <param name="Message">Received Message</param>
        protected virtual void _Client_MessageRecieved(object sender, string Message)
        {
            
        }
        /// <summary>
        /// Worker function for the listening thread
        /// </summary>
        private void _ListenFunc()
        {
            // Give the other threads 1 miliseconds to do their work, else a 100% CPU load is guaranteed.
            System.Threading.Thread.Sleep(1);

            // Start listening
            _Listener.Start();

            // Buffer for reading data
            Byte[] bytes = new Byte[256];
           
            // Enter the listening loop
            while (true)
            {
                // Give the other threads 1 miliseconds to do their work, else a 100% CPU load is guaranteed.
                System.Threading.Thread.Sleep(1);

                // Start a new thread to handle the client in a asynchronous whay
                (new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(_AddClient))).Start(_Listener.AcceptTcpClient());
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Start Listening for Clients
        /// </summary>
        public void StartListening()
        {
            // If there was already a listening thread...
            if (_ListenThread != null)
            {
                // ...check if listening thread is running. If so, abort it.
                if (_ListenThread.ThreadState == System.Threading.ThreadState.Running)
                    _ListenThread.Abort();
            }

            // Create a new listening thread
            _ListenThread = new System.Threading.Thread(new System.Threading.ThreadStart(_ListenFunc));            
        }

        public void StopListening()
        {
            // If there was already a listening thread...
            if (_ListenThread != null)
            {
                // ...check if listening thread is running. If so, abort it.
                if (_ListenThread.ThreadState == System.Threading.ThreadState.Running)
                    _ListenThread.Abort();
            }
        }
        #endregion

        #region Events
        /// <summary>
        /// Called when a client has connected
        /// </summary>
        /// <param name="sender">The devNetworks.Server object</param>
        /// <param name="Client"></param>
        public delegate void ClientConnectedDelegate(object sender, ServerClient Client);

        /// <summary>
        /// Called when a client has connected
        /// </summary>
        public event ClientConnectedDelegate ClientConnected;
        #endregion
    }
}
ServerClient.cs
Code:
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
 *  Copyright (C) Marco Klein alias WorldRacer, WorldRacer92, WorldRacer_original  *
 *                                                                                 *
 *   This program is free software: you can redistribute it and/or modify          *
 *   it under the terms of the GNU General Public License as published by          *
 *   the Free Software Foundation, either version 3 of the License, or             *
 *   (at your option) any later version.                                           *
 *                                                                                 *
 *   This program is distributed in the hope that it will be useful,               *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of                *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
 *   GNU General Public License for more details.                                  *
 *                                                                                 *
 *   You should have received a copy of the GNU General Public License             *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.         *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace devNetworks
{
    /// <summary>
    /// This class represents a client which has connected to a devNetworks.Server
    /// </summary>
    public class ServerClient
    {
        #region Public Attributes
        /// <summary>
        /// Socket. (TcpClient)
        /// </summary> 
        public System.Net.Sockets.TcpClient Socket
        {
            get { return _Socket; }
            set { _Socket = value; }
        }
        #endregion

        #region Private Attributes
        // Socket to handle with
        private System.Net.Sockets.TcpClient _Socket = null;

        // Thread for handling server Messages
        System.Threading.Thread _ClientThread = null;

        // IP-Adress of the endpoint 
        private string _IPAdress = string.Empty;

        // Port to connect at the entpoint
        private int _Port = 0;

        // Contains whether we are connected or not.
        private bool _Connected = false;

        // Bufferlength
        int _BufferLength = 4096;

        // Get a client stream for reading and writing.
        //  Stream stream = client.GetStream();
        System.Net.Sockets.NetworkStream _Stream;

        // How shall the messages be encoded to?
        System.Text.Encoding _EncodingInstance = System.Text.Encoding.ASCII;
        #endregion

        #region Constructor
        /// <summary>
        /// Initializes a client connected to a devNetworks.Server
        /// </summary>
        /// <param name="Socket">TcpClient</param>
        /// <param name="EncodingInstance">How shall the messages be encoded to?</param>
        public ServerClient(System.Net.Sockets.TcpClient Socket, Encoding EncodingInstance)
        {
            // Save the values
            _EncodingInstance = EncodingInstance;
            _Socket = Socket; 
           
            // Retrieve a stream
            _Stream = _Socket.GetStream();

            // Start a listening stream to listen on clients messages
            _ClientThread = new System.Threading.Thread(new System.Threading.ThreadStart(_ClientThreadFunc));
            _ClientThread.Start();
        }        
        #endregion

        #region Private Methods
        /// <summary>
        /// Worker Function for client listening
        /// </summary>
        private void _ClientThreadFunc()
        {
            // Enter listening loop
            while (true)
            {
                // Give the other threads 1 miliseconds to do their work, else a 100% CPU load is guaranteed.
                System.Threading.Thread.Sleep(1);

                // Message Bytes
                Byte[] data = new Byte[_BufferLength];

                // String to store the response ASCII representation.
                String responseData = String.Empty;

                // Read the first batch of the TcpServer response bytes.
                Int32 bytes = _Stream.Read(data, 0, data.Length);

                // Placeholder for writing in the recieved data.
                responseData = _EncodingInstance.GetString(data, 0, bytes);

                // Call MessageReceived event, if it has event handlers
                if (MessageRecieved != null)
                {
                    MessageRecieved(this, responseData);
                }
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Sends a message to the Client
        /// </summary>
        /// <param name="Message">Message to send</param>
        public void SendMessage(string Message)
        {
            // Convert string into bytes
            byte[] msg = _EncodingInstance.GetBytes(Message);

            // Send the bytes to the stream.
            _Stream.Write(msg, 0, msg.Length);
        }
        #endregion

        #region Events
        /// <summary>
        /// Called when the server received a message
        /// </summary>
        /// <param name="sender">This ServerClient</param>
        /// <param name="Message">Received message</param>
        public delegate void MessageRecievedDelegate(object sender, string Message);

        /// <summary>
        /// Called when the server received a message
        /// </summary>
        public event MessageRecievedDelegate MessageRecieved;
        #endregion
    }
}
 

Anhänge

#5
Wow, ich danke dir schon mal im vorraus. Ich bin beeindruckt für dein Engagement. Ich muss gestehen, dass ich in der Netzwerkprogrammierung noch keine Erfahrung habe. Ist mir zwar ein bisschen peinlich, aber jeder hat ja schließlich mal angefangen, nicht? Und ich dachte mir, dass so ein Chat auf jeden Fall eine tolle Möglichkeit darstellt.
Ich werde mir jetzt mal alles genauestens angucken und danke dir natürlich herzlich für den Aufwand und die Sorgfalt. Dieser Post wird hoffentlich nicht nur mir von Nutzen sein. Bei eventuellen Fragen melde ich mich.

Alles Liebe,

Rente
 
#7
Ich muss gestehen, dass ich noch zu wenige Kenntnisse in C# habe, um alles wirklich nachvollziehen zu können. Ich habe mir die Daten aber mal gespeichert, damit ich sie mir zu einem späteren Zeitpunkt mal ansehen kann.
Das größte Problem sehe ich zwar nicht im Verstehen, aber vielmehr im Anwenden, also wie ich die ganzen Sachen später gezielt und vor allem auch richtig einsetzen kann. Deswegen *****e ich hier momentan ne Menge kleinkram zusammen, weil das mit der Netzwerkprogammierung doch noch ne Stufe zu hoch ist. Aber ich bin dir echt sehr dankbar, weil der Code sehr gut strukturiert ist.
Wenn was ist, melde ich mich.
 

WorldRacer

Erfahrenes Mitglied
#12
Da das Tutorial vor meiner Registrierung hier entstanden ist, gibt es das hier noch nicht. Werde mich aber bei Zeiten bemühen, es hier einzustellen ;)
 

Neue Beiträge