Laden...

Static Funktionen....

Letzter Beitrag vor 18 Jahren 22 Posts 1.916 Views
Static Funktionen....

Hi Leute!

Ich schreibe gerade mit einem Freund ein Chatprogramm. Er schreibt den Client und ich schreibe den Server.

Also ich habe meinen Server so geregelt:

Ich hab mal meine Hauptklasse, die heißt VHCServ in der ich einen Thread laufen hab, indem in einer Endlosschleife Clients akzeptiert werden, wenn einer kommt...

Dann hab ich eine zweite Klasse, die Engine heißt. In dieser Klasse speichere ich alles was ein Client braucht bzw. hat ab. Daher den TcpClient selbst, seinen Stream und einen Clientnamen.

In der VHCServ Klasse existiert ein Array aus Engine das immer einen neuen Client fasst wenn einer Akzeptiert wurde.

Jetzt hab ich allerdings ein Problem mit der userlist...

Für jeden Client hab ich einen Thread laufen indem er die Streams überwacht ob etwas herauszulesen ist und wenn ja dann ließt er.

Wenn jetzt der Befehl /u <Benutzername> kommt, dann schreibt der Client das auf seinen Benutzernamen und ruft aus dem Thread in der Klasse Engine eine static Funktion der Klasse VHCServ auf, in der eine Userlist gemacht werden soll und dann über die Funktion broadcast an alle Client weitergeschickt werden soll....

Der Source der drei Funktionen kommt hier:

Das ist die Funktion für den Thread der die Streams überwacht:

        private void watch ()
        {
            while (client.Connected)
            {
                try
                {
                    byte[] puffer = new byte[9000];
                    stream.Read(puffer, 0, client.ReceiveBufferSize);
                    string s = "";
                    s = Encoding.ASCII.GetString(puffer);
                    if (s[0] == '/')
                    {
                        string[] arr = new string[s.Split().Length];
                        arr = s.Split(' ');
                        clientname = arr[1];
                        VHCServ.make_userlist();
                        stream.Flush();
                    }
                    else
                    {
                        VHCServ.broadcast(puffer);
                        stream.Flush();
                    }
                }
                catch
                {
                    VHCServ.clientanz--;
                }
            }
        }

Das ist die static Funktion die die Userlist baut

        public static void make_userlist()
        {
            string userlist = "/ul";
            foreach (Engine e in ClientArray)
            {
                    if (e != null && e.clientname != "")
                    {
                        userlist += " " + e.clientname;
                    }
            }
            byte[] sendbytes;
            sendbytes = Encoding.ASCII.GetBytes(userlist);
            broadcast(sendbytes);
        }

Und das hier ist die broadcast Funktion

        public static void broadcast(byte[] message)
        {
            foreach (Engine e in ClientArray)
            {
                if (e != null)
                {
                    try
                    {
                        e.stream.Write(message, 0, message.Length);
                    }
                    catch
                    {
                    }
                }
            }
        }

Mein Problem ist nun folgendes:

Ich bekomme also von einem Client /u <Benutzername1> und alle Clients (also nur einer weil mehr noch nicht da sind) bekommen /ul <Benutzername1> zurück, aber wenn jetzt ein zweiter Client kommt und der /u <Benutzername2> eingibt, bekommen beide /ul <Benutername1> zurück....

Liegt dieser Fehler daran, dass make_user_list() eine static Funktion ist? Und wenn ja, wie kann ich das ganze so umschreiben damit es korrekt funktioniert?

Ich hoffe auf eure Hilfe

lg
Blue_Dragon

In Broadcost-Methode schickst du ja die bytes per foreach an alle user in ClientArray, deshalb bekommen es alle.

Dexter

Programmierer sind Maschinen die Koffein in Quellcode umsetzen.

Ich will ja auch das alle die Userlist bekommen, das ist ja der Sinn und Zweck des ganzen...

Mein Problem ist ja das:

wenn zwei clients beim server sind und der erste schickt ein

/u <Clientname1> ab, dann bekommmen alle /ul <Clientname1> was der Client dann in eine Userliste umsetzt.

Wenn jetzt noch der Client2 folgendes schickt: /u <Clientname2> bekommen trotzdem nur

/ul <Clientname1> zurück aber es sollte ja

/ul <Clientname1> <Clientname2> zurückkommen....

lg

Hallo Blue_Dragon,

dann sieht das so aus, als würde der zweite Client nicht in ClientArray eingetragen.

herbivore

Doch, das hab ich mir anfangs auch gedacht... Aber es funktioniert er muss eingetragen sein, den wenn ich normalen Text schicke, bekommt es der andere Client auch, es bekommen ja auch alle die Userlist, nur ist die eben falsch....

Ich komm einfach nicht auf diesen Fehler -.-

Hallo Blue_Dragon,

mit einem Debugger sollte das Rätsel leicht zu lösen sein.

herbivore

In wie fern mit einem Debugger? Ich kenn mich mit den Einstellungen nicht gut genug aus...

aber ich hab schon etwas herausgefunden:

wenn ich zwei clients verbinde und dem den ich als zweites verbunden habe einen username zuweise, bekomme ich folgendes:

/ul --- username2

wenn ich jetzt den namen von dem zuerst verbundenen ändere bekomme ich

/ul username1

hm, er hört anscheinend immer nachdem client auf, der die make_user_list() funktion gestartet hat, obwohl das aber meiner meinung nach nicht sein kann....

aber ich weiß überhaupt nicht wo ich den fehler habe

Hallo Blue_Dragon,

also wenn du keinen Debugger benutzt, ist es natürlich echt schwierig. Aber was soll ich da sagen. Debugger ist halt genauso wichtig wie einen Editor.

Was ist denn überhaupt ClientArray und wo wird es gesetzt?

herbivore

Ich benutze schon einen Debugger ich arbeite ja mit VS 2005....

Also, ich hab meine Hauptklasse, und eine kleinere Klasse Engine....

In einer Engine wird ein ganzer client erfasst..... mit seinem TcpClient seinem stream usw....

in dieser Engine klasse rennt außerdem ein Thread der überprüft ob durch den Stream von diesem Client etwas zum Server geschickt wird... das ist eben genau diese watch Funktion die ich geschrieben habe....

Und in meiner Hauptklasse hab ich mir ein EngineArray gemacht, was immer einen neuen Client dazu nimmt sobald der Listener einen Client akzeptiert....

Ich hoffe das war verständlich

Versteht ihr wo das Problem ist?

Hallo Blue_Dragon,

ja und nein. Lass und mal Nägel mit Köpfen machen. Häng mal den ganzen Code an.

herbivore

Also, das ist meine Hauptklasse:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.IO;
using System.Threading;

namespace Virtual_Hell_Chat_Server
{
    public partial class VHCServ : Form
    {
        public VHCServ()
        {
            InitializeComponent();
        }

        #region "Variablen"

        static Engine[] ClientArray = new Engine[100];
        private delegate void uidelegate();
        private delegate void uidelegatei(int i);
        private Thread wait;
        TcpListener Listener;
        public static int clientanz;

        #endregion

        #region "Start/Stop Server"

        private void StartServer_Click(object sender, EventArgs e)
        {
            try
            {
                clientanz = 0;
                txtport.ReadOnly = true;
                txtStatus.Text = DateTime.Now.ToLongTimeString().ToString() + " Server gestartet..." + Environment.NewLine;
                wait = new Thread(new ThreadStart(waitforclient));
                wait.IsBackground = true;
                wait.Start();
                checker.Start();
            }
            catch (Exception err)
            {
                MessageBox.Show(err.ToString());
            }
        }

        private void StopServer_Click(object sender, EventArgs e)
        {
            try
            {
                wait.Abort();
                Listener.Stop();
                txtStatus.Text = "";
                checker.Stop();
                txtClientAnzahl.Text = "0";
                clientanz = 0;
                foreach (Engine eng in ClientArray)
                {

                    if (eng != null)
                    {
                        eng.disconnect();
                    }

                }
            }
            catch (Exception error)
            {
                MessageBox.Show(error.Message.ToString());
            }
        }

        #endregion

        #region "Methoden"

        public static void broadcast(byte[] message)
        {
            foreach (Engine e in ClientArray)
            {
                if (e != null)
                {
                    try
                    {
                        e.stream.Write(message, 0, message.Length);
                    }
                    catch
                    {
                    }
                }
            }
        }

        public static void make_userlist()
        {
            string ul = "/ul";
            byte[] sendbytes;
            foreach(Engine e in ClientArray)
            {
                if(e != null)
                {
                    ul += " " + e.Clientname;
                }
            }
            sendbytes = Encoding.ASCII.GetBytes(ul);
            broadcast(sendbytes);
        }

        #endregion

        #region "Thread Funktionen"

        private void waitforclient()
        {
            Listener = new TcpListener(System.Net.IPAddress.Any, Convert.ToInt32(txtport.Text));
            Listener.Start();
            this.txtStatus.Invoke(new uidelegate(listener_started));
            //Server auf warten für Clients stellen
            this.txtStatus.Invoke(new uidelegate(wait_for_clients));
            int i = 0;
            while (true)
            {
                //Client akzeptieren
                ClientArray[i] = new Engine(Listener.AcceptTcpClient());
                this.txtStatus.Invoke(new uidelegate(client_accapted));
                i++;
                clientanz++;
            }
        }
        #endregion

        #region "Timer Tick"

        private void checker_Tick(object sender, EventArgs e)
        {
            if (Int32.Parse(txtClientAnzahl.Text) > clientanz)
            {
                txtStatus.Text += DateTime.Now.ToLongTimeString().ToString() + " Ein Client wurde beendet..." + Environment.NewLine;
            }
            txtClientAnzahl.Text = clientanz.ToString();
        }

        #endregion

        #region "Delegate Funktionen"


        private void client_accapted()
        {
            txtStatus.Text += DateTime.Now.ToLongTimeString().ToString() + " Neuer Client akzeptiert..." + Environment.NewLine;
        }

        private void listener_started()
        {
            txtStatus.Text += DateTime.Now.ToLongTimeString().ToString() + " Listener wurde gestartet..." + Environment.NewLine;
        }

        private void wait_for_clients()
        {
            txtStatus.Text += DateTime.Now.ToLongTimeString().ToString() + " Warte auf Clients..." + Environment.NewLine;
        }

        #endregion

        
    }
}

Und das hier ist meine Engine Klasse:

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

namespace Virtual_Hell_Chat_Server
{
    class Engine
    {

        #region "Variablen"

        private TcpClient client;
        public Stream stream;
        private Thread T;
        public string clientname = "---";
        public bool fertig = false;
        public delegate void mydelegate();

        #endregion

        #region "Konstruktor"

        public Engine(TcpClient c)
        {
            client = c;
            stream = client.GetStream();
            T = new Thread(new ThreadStart(watch));
            T.IsBackground = true;
            T.Start();
        }

        #endregion

        public string Clientname
        {
            set
            {
                clientname = value;
            }
            get
            {
                return clientname;
            }
        }

        #region "Thread Funktionen"

        private void watch ()
        {
            while (client.Connected)
            {
                try
                {
                    byte[] puffer = new byte[9000];
                    stream.Read(puffer, 0, client.ReceiveBufferSize);
                    string s = "";
                    s = Encoding.ASCII.GetString(puffer);
                    if (s[0] == '/' && s[1] == 'u' && s[2] == ' ')
                    {
                        string[] arr = new string[s.Split().Length];
                        arr = s.Split(' ');
                        clientname = arr[1];
                        VHCServ.make_userlist();
                    }
                    else
                    {
                        VHCServ.broadcast(puffer);
                        stream.Flush();
                    }
                }
                catch
                {
                    VHCServ.clientanz--;
                }
            }
        }

        #endregion

        #region "Methoden"

        public void disconnect()
        {
            T.Interrupt();
            T.Abort();
            client.Close();
            stream = null;
        }

        #endregion
    }
}

Hallo Blue_Dragon,

beim schnellen Drübergucken, denke ich mal, dass dein Problem hier liegt:


ClientArray[i] = new Engine(Listener.AcceptTcpClient());

Die neue Engine wird erst nach der Erstellung der neuen Engine in die Liste ClientArray aufgenommen, aber während der Erstellung der Engine wird schon foreach ClientArray durchgeführt. Da ist die neue Engine dann noch nicht enthalten.

herbivore

Hm, ich hab mir das auch schon gedacht aber den gedanken wieder verworfen, da das broadcasten mit normalen text ja ohne probleme funktioniert...

Ich kann dir auch einfach mal meine Solution als zip per mail schicken wenn du es dir mal ganz ansehen willst...

Hallo Blue_Dragon,

wenn du bei der Fehlersuche nicht weiter kommst, ist es nicht sinvoll, Möglichkeiten zu verwerfen. Dass das Broadcasten bei normalen Texten funktioniert, ist ja auch kein Wunder, da die fragliche Zeile ja nur beim Erstellen eines neuen Clients ausgeführt wird. Ich denke es ist diese Stelle und daher brauche ich erstmal auch keinen weiteren Code. Mit einer Solution kann ich ohnehin nichts anfangen, weil ich keine IDE einsetze.

herbivore

Hm, okay ich denke ich hab verstanden was du meinst... Aber was kann ich dann dagegen tun?

lg

Hallo Blue_Dragon,

früher zuweisen oder später Broadcasten. 🙂

herbivore

und wie sieht die technische realisierung aus?

Hallo Blue_Dragon,

naja, die Klasse Engine könnte selber das ClientArray enthalten. Dann könntest du als erstes im Konstruktor von Engine zuweisen:


ClientArray[i++] = this;

herbivore

Das hab ich mir auch schon überlegt das ich das ClientArray in die Engine Klasse schreibe, aber ich wusste nicht wie ich das mache das die VHCServ Klasse dann auch noch darauf zugriff hat....

Hallo Blue_Dragon,

hm, wenn ich mir deine Nachfragen so angucke, meinst du nicht, dass so ein Client nicht eine zu große Aufgabe für dich ist? Immerhin geht es hier um Multithreading und die Synchronisation von Zugriffen. Darüber haben wir ja noch gar nicht gesprochen. Aber deine Threads greifen ja lustig auf dieselben Daten zu. Da kannst du dir schnell Probleme einhandeln.

Aber zu deiner Frage: Im einfachsten Fall schreibst du dir eine static Property.

herbiviore