Laden...

Forenbeiträge von Solid96 Ingesamt 18 Beiträge

28.08.2014 - 16:16 Uhr

Noch ein Nachtrag von mir:

würde man Fertigteile auch nur als Produkt definieren, dann käme man auch mit 2 Tabellen aus...

Tabelle "Produkte"
-(PK) ID
-Name

Tabelle "ProduktKomponenten"
-(PK) ProduktID (Fremdschlüssel auf Produkt)
-(PK) KomponentenID (Fremdschlüssel auf Produkt)
-Stückzahl

Beispielbefüllung:

Tabelle Produkte:
1, Rad
2, Fahrrad
3, Auto
99, Schraube
101, Unterlegscheibe
999, Griff zum Wegwerfen

Tabelle ProduktKomponenten:
2, 1, 2 #(Fahrrad hat 2 Räder)
3, 1, 4 #(Auto hat 4 Räder)
1, 99, 10 #(Rad hat 10 Schrauben)
1, 101, 5 #(Rad hat 5 Unterlegscheiben)
3, 999, 1 #(Auto hat 1 Griff zum Wegwerfen)

28.08.2014 - 11:42 Uhr

Da wir hier DB-First nutzen... hier mal der Datenbank-Ansatz evtl. hilft dir das auch weiter:

4 Tabellen (PK = Primary Key)

Tabelle "Produkte"
-(PK) ID
-Name

Tabelle "Fertigteile"
-(PK) ID
-Name

Tabelle "ProduktFertigteile"
-(PK) ProduktID (Fremdschlüssel auf Produkt)
-(PK) FertigteilID (Fremdschlüssel auf Fertigteil)
-Stückzahl

Tabelle "ProduktKomponenten"
-(PK) ProduktID (Fremdschlüssel auf Produkt)
-(PK) KomponentenID (Fremdschlüssel auf Produkt)
-Stückzahl

Beispielbefüllung:

Tabelle Fertigteile:
1, Schraube

Tabelle Produkte:
1, Rad
2, Fahrrad
3, Auto

Tabelle ProduktFertigteile:
1, 1, 10 #(Rad hat 10 Schrauben)

Tabelle ProduktKomponenten:
2, 1, 2 #(Fahrrad hat 2 Räder)
3, 1, 4 #(Auto hat 4 Räder)

Das ganze ergibt dann zwangsläufig eine Baumstruktur, schreit also bei den Berechnungen nach Rekursion.

-edit: Name natürlich kein PK

11.07.2014 - 10:59 Uhr

Dies MSDN sagt (unter anderem) folgendes zu HttpWebRequest.GetResponse():

When using the POST method, you must get the request stream, write the data to be posted, and close the stream. This method blocks waiting for content to post; if there is no time-out set and you do not provide content, the calling thread blocks indefinitely.

Das hört sich für mich stark danach an, dass der Request tatsächlich erst getätigt wird (die Daten gesendet werden), wenn GetResponse() aufgerufen wird.

07.01.2013 - 12:31 Uhr

Also erstmal vorweg:

  • Session-Ende nicht unbedingt gleich Logout (bei dir schon, da du im Logout Session.Abandon() aufrufst, soweit so gut)
  • Session-Start nicht unbedingt gleich Login -> Knackpunkt.

Lösung:

Zähler nicht im Session_Start-Event erhöhen, sondern, nach dem tatsächlichen erfolgreichen Login.

13.09.2010 - 23:42 Uhr

Vielleicht hilft dir ja das hier weiter, so mache ich es im Moment mit einer GridView bei der AutoGenerateColumns auf "true" gestellt ist:



    protected void DL_List_RowDataBound( object sender, GridViewRowEventArgs e ) {
      GridViewRow row = e.Row;

      if( row.RowType == DataControlRowType.DataRow ) {
        DataRowView rowView = row.DataItem as DataRowView;

        object[] iArray = rowView.Row.ItemArray;
        for( int i = 0; i < iArray.Length; ++i ) {
          if( iArray[ i ] is DateTime ) {
            DateTime dt = ( DateTime )iArray[ i ];
            row.Cells[ i ].Text = dt.ToShortDateString();
          }
        }
      }

im Markup dann irgendwo:

                        <asp:GridView ID="DL_List" runat="server"
-->                                 OnRowDataBound="DL_List_RowDataBound"



Wie schon jemand sagte: schöner wäre es, wenn man im Code-Behind an das BoundField rankäme, dann könnte man HtmlEncode auf "false" setzen und DataFormatString entsprechend und alles wär (eventuell) gut. 😉

26.01.2010 - 12:50 Uhr

Moin,

ich habe mich nicht auf hebivore bezogen, sondern auf einige Aussagen drüber, wo glaube ich sinngemäß gefragt wurde, wann das endlich geändert wird.

Der Sinn könnte der sein, es real existierender Hardware zu ermöglichen ein Bitshift schnellstmöglich umzusetzen? Siehe Intel-Spec.

Ich werde weiterforschen,

gez. Holmes.

😉

26.01.2010 - 12:24 Uhr

Meine Güte, es ist, wie schon oben erwähnt Definitionssache.

Das ist kein Fehlverhalten und da muss auch nichts dran korrigiert werden.

Wie schon erwähnt, ist das nicht nur in C# so definiert, sondern auch in: C, C++, Java, VB und ich könnte wetten, dass man noch mehr Sprachen finden kann, bei denen es genau so definiert ist.

Wie ebenfalls erwähnt ist das auf Intel-Hardware (für andere hab ich nicht gesucht), also dort auch für Assembler, auch so definiert:

IA-32 Architecture Compatibility
The 8086 does not mask the shift count. However, all other IA-32 processors
(starting with the Intel 286 processor) do mask the shift count to 5 bits, resulting in
a maximum count of 31. This masking is done in all operating modes (including the
virtual-8086 mode) to reduce the maximum execution time of the instructions.

Wie man nun darauf kommen kann, dass da irgendwas falsch dran wäre und etwas korrigiert werden müsste ist mir schleierhaft. ?(

25.01.2010 - 12:49 Uhr

http://www.velocityreviews.com/forums/t283343-shifting-bits-shift-32-bits-on-32-bit-int.html

Zusammengefasst: Shiften um eine Anzahl an Bits, die größer oder gleich als die Anzahl der Bits ist, die der Datentyp hat, ist nicht vorgesehen... sowohl in C#/C/C++, als auch auf Intel-Hardware...

Auszug:

I looked up the Intel spec [1]. The result is /not/ undefined. The Intel spec reads literally:

"The count is masked to 5 bits (or 6 bits if in 64-bit mode and REX.W is used). The count range is limited to 0 to 31 (or 63 if 64-bit mode and REX.W is used)."

Kind regards
//Herbert Glarner

[1] "IA-32 Intel® Architecture Software Developer's Manual, Volume 2B: Instruction Set Reference, N-Z", page 243"

16.12.2009 - 19:11 Uhr

Hallo,

erstmal vielen Dank für die zahlreichen Antworten.

Aufgrund eurer Vorschläge habe ich nun folgende Änderungen vorgesehen bzw. schon implementiert:

1.) Benutzername wird nicht mehr in der .ini Datei gespeichert.
2.) Der TripleDES-Key wird nun aus SHA1 gehashtem und dann verschlüsseltem Benutzernamen und Passwort gebildet ( PasswordDeriveBytes.CryptDeriveKey ).

3.) Ich speichere immer noch den IV RSA-verschlüsselt (RSA-Schlüsselpaar in Schlüsselcontainer) in der .ini Datei ab. Ich frage mich noch warum. 😉

Alle Texte, die ich mir bisher zu dem Thema durchgelesen habe, sind der Meinung, der IV könnte auch ruhig bekannt sein. Ich frage mich warum auf eine weitere Sicherheitsstufe verzichten? Hängt vielleicht damit zusammen, dass sich diese Texte bisher alle auf eine Kommunikation über ein Medium (z.B. Internet) bezogen, wo davon ausgegangen wird, dass der IV und der Key oft gewechselt werden?

Ich habe mir überlegt, dass mit verschlüsseltem IV, sowohl mein Benutzername, als auch mein Passwort bekannt sein dürften, als auch jemand ruhig die .ini Datei mit sämlichen Einstellungen und Daten (einschließlich verschlüsseltem IV) in die Hände bekommen dürfte. Nutzt ihm noch nix, da der IV verschlüsselt ist und an das Schlüsselpaar im Container kommt er nur, wenn er sich unter meinem Namen anmeldet (Windows-Anmeldung).

Hmmm... evtl. übertreibe ich da ein wenig?

4.) Der Hinweis auf SecureString war auch sehr hilfreich; weiß jemand, ob Key und IV im CryptoServiceProvider-Objekt vor unbefugten Speicherzugriffen geschützt sind? Oder muss ich zusehen, dass dieses Objekt auch so schnell wie möglich gecleared und disposed wird?

Hmm... ja, nochmal vielen Dank für die Denkanstöße und falls ich noch was übersehen haben sollte, bitte macht mich darauf aufmerksam.

Bis denne,

Markus.

15.12.2009 - 20:43 Uhr

Hallo,

ich schreibe gerade ein kleines Tool zuhause für mich selbst, das meine Passwörter verwalten soll. Ich hätte gerne gewusst für wie sicher ihr folgendes Konzept haltet bzw. ob ich irgendwo auf dem Holzweg bin...

  1. Beim ersten Start des Tools

1.1 Es wird ein Login-Benutzername und ein Login-Passwort abgefragt.
1.2 Es wird ein RSA-Schlüsselpaar in einem Schlüsselcontainer mit festem Namen für den Benutzer erzeugt.
1.3 Aus dem Login-Passwort wird ein MD5-Hash erstellt.
1.4 Dieser Passwort-Hash ist von nun an mein TripleDES-Key
1.5 Es wird ein TripleDES-Initialisierungsvektor (IV) erstellt
1.6 Dieser IV wird mit dem öfftenlichen RSA-Schlüssel verschlüsselt und in einer frei zugänglichen .ini Datei gespeichert.
1.7. Der Benutzername wird per TripleDES verschlüsselt und ebenfalls in der frei zugänglichen .ini Datei gespeichert.

  1. Beim zwei + nten Start des Tools

2.1 Benutzername + Passwort werden abgefragt.
2.2 RSA-Schlüsselpaar wird aus Container genommen und der IV aus der .ini Datei mit dem privaten Schlüssel entschlüsselt und dient fortan als TripleDES-IV
2.3 Benutzerpasswort wird MD5 gehasht und dient fortan als TripleDES-Key
2.4 Benutzername wird aus der .ini Datei gelesen, entschlüsselt und mit dem eingegebenen Benutzernamen verglichen

Alle sonstigen Daten ( Passwörter etc. ) werden mit obigen IV und Key per TripleDES verschlüsselt und in der .ini Datei gespeichert.

Seht ihr da evtl. auf Anhieb schon irgendwelche schwerwiegenden Lücken, bin ich evtl. sowieso schon auf dem Holzweg? Alternativen?

Freue mich über jeden Kommentar und Verbesserungsvorschlag. 😃

Bis denn,

Markus.

29.07.2009 - 09:06 Uhr

Hallo Christian,

super, danke dir für's Ausprobieren. 😃

Dann habe ich einen Verdacht, dass das mit SP1 gefixt wurde, ich habe das Servicepack hier noch nicht aufgespielt.

Ich nehme an, du verwendest VS2008 SP1, oder? (bitte sag ja g)

Danke nochmals, dann mach ich mich mal an's Installieren.

27.07.2009 - 15:43 Uhr

Hallo zusammen,

kann jemand von euch evtl. folgendes seltsame Verhalten nachvollziehen?

Erstmal was Code...


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;

namespace GenTestWPF {
  /// <summary>
  /// Interaktionslogik für Window1.xaml
  /// </summary>
  public partial class Window1 : Window {

    private TestClass p_Test = new TestClass();

    public Window1() {
      DataContext = p_Test;

      InitializeComponent();
    }
  }

  public class TestClass : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;

    private SolidColorBrush p_BorderBrush = new SolidColorBrush( Colors.Blue );
    private Color p_BorderColor = Colors.Blue;
    private Color p_BackgroundColor = Colors.Green;

    public SolidColorBrush BorderBrush {
      get { return p_BorderBrush; }
      set {
        p_BorderBrush = value;
        OnPropertyChanged( "BorderBrush" );
      }
    }
    public Color BackgroundColor {
      get { return p_BackgroundColor; }
      set {
        p_BackgroundColor = value;
        OnPropertyChanged( "BackgroundColor" );
      }
    }
    public Color BorderColor {
      get { return p_BorderColor; }
      set {
        p_BorderColor = value;
        OnPropertyChanged( "BorderColor" );
      }
    }

    private void OnPropertyChanged( string propertyName ) {
      PropertyChangedEventHandler tmp = PropertyChanged;

      if( tmp != null ) {
        tmp( this, new PropertyChangedEventArgs( propertyName ) );
      }
    }

  }
}


<Window x:Class="GenTestWPF.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <Border BorderThickness="8">
            <Border.BorderBrush>
                <SolidColorBrush Color="{Binding BorderColor}" />
            </Border.BorderBrush>
            <Border.Background>
                <SolidColorBrush Color="{Binding BackgroundColor}" />
            </Border.Background>
        </Border>
    </Grid>
</Window>

Seltsames Verhalten: Der Borderhintergrund wird korrekt angezeigt (grün), die Umrandung, also der Borderbrush aber nicht, der bleibt transparent.

Folgendes klappt allerdings korrekterweise:


[...]
        <Border BorderThickness="8" BorderBrush="{Binding BorderBrush}">
            <Border.Background>
                <SolidColorBrush Color="{Binding BackgroundColor}" />
            </Border.Background>
        </Border>
[...]

Bug, Feature, oder wie meißt: Denkfehler?

Hoffe ihr könnt mir da ein wenig auf die Sprüng helfen.

10.06.2009 - 08:58 Uhr

Hallo herbivore,

ich glaube was kleines_eichhoernchen sagen wollte ist, dass alle Tests, die man durchführen kann, nur eine begrenzte Aussagekraft haben, weil es keine feststehende Spezifikation des Verhaltens gibt, sondern sich dieses schon in der nächsten Version oder sogar schon im nächsten Servicepack ändern kann. Man muss also sowieso vom worse case ausgehen und gegen diesen robust programmieren.

Ok, dann hab ich das wohl falsch verstanden. Hörte sich für mich alles ein wenig nach "es kann nicht sein, was nicht sein darf" an. Ja, man müsste eigentlich in beiden Fällen (System.Timers.Timer und System.Threading.Timer) in dem Beispielszenario erwarten, dass die Timer frühzeitig aufgeräumt werden. Auch klar ist, dass ein Dispose beide Fälle korrekt löst.

da der GC in der Lage ist, zyklische Verweise zu erkennen und zu ignorieren, ist das zumindest sehr merkwürdig.

Es ging ja noch weiter... ich schrieb danach noch:

Daraus folgt, dass dieser Verweis (und der auf den Callback) noch in einem anderen Objekt gespeichert wird.

Und eben das dürfte dann der Grund sein, warum der Timer in dem Fall überhaupt nicht mehr vom GC angefasst wird.

Man kann sich also auch nicht darauf verlassen, dass ein Timer überhaupt aufgeräumt wird. Beide implementieren nicht ohne Grund Dispose, ich denke da sind wir uns dann wieder einig. 😃

09.06.2009 - 14:38 Uhr

@kleines_eichhörnchen

Sorry, aber PUNKT ist in meinen Augen kein Argument - ich gehe den Sachen ganz gerne auf den Grund. Klar ist, dass System.Timers.Timer NIE von selbst aufgeräumt wird (sobald intern das System.Threading.Timer Objekt erstellt wurde), System.Threading.Timer dann nicht, wenn als state Objekt ein Verweis auf sich selbst geliefert wird.

Daraus folgt, dass dieser Verweis (und der auf den Callback) noch in einem anderen Objekt gespeichert wird. Ob das eine statische Liste oder was auch immer ist, kann evtl. jemand mit mehr Zeit eruieren, jedenfalls ist das anscheinend der Grund, warum System.Timers.Timer gar nicht (Verweis auf Callback), und System.Threading.Timer beim Erstellen per Konstruktur mit einem Parameter nicht aufgeräumt wird (Verweis auf sich selbst).

Das hat jetzt weniger was mit Optimierung, als mit der ganz simplen Tatsache zu tun, dass ein Objekt nicht aufgeräumt werden kann, wenn noch Verweise darauf existieren.

Wenn du es natürlich GENAU weißt, dann immer her mit der Info. Ich weiß zumindest nun, dass sich System.Timers.Timer und System.Threading.Timer sehr unterschiedlich verhalten, was das "frühzeitige Aufräumen" angeht. 😉

09.06.2009 - 12:51 Uhr

Hallo,

also mal der Reihe nach...

@herbivore

Der Tip mit den verschiedenen Timern und Reflector war schonmal sehr gut. g

System.Timers.Timer verwendet intern ein System.Threading.Timer Objekt. Der Knackpunkt liegt dann auch bei der System.Threading.Timer Implementation. Dazu später mehr...

@kleines_eichhörnchen

Doch, es geht ohne Dispose.

Ersetze bei deinem Beispiel die Zeile "Timer tmr = new Timer(Callback, null, 0, 1000);" durch diese beiden Zeilen:


Timer tmr = new Timer( Callback );
tmr.Change( 0, 1000 );

Was wirst du festellen? Richtig. Der Timer wird nicht aufgeräumt und läuft schön weiter.

So, und nun zum Knackpunkt: nämlich dem state-objekt bzw. der Referenz darauf.

Wird dem System.Threading.Timer Konstruktur als state-objekt ein Verweis auf sich selbst gegeben (z.B. wie oben indem man den Konstruktor mit nur einem Parameter verwendet), wird er nicht vom GC aufgeräumt, andernfalls wird er ganz normal vom GC aufgeräumt, wie man es erwarten würde.

Tiefer bin ich nicht eingestiegen, aber ich denke es wird irgendwo statisch einen "echten" Timer geben, der dann einfach eine Menge Einträge ala Callback/StateObjekt/dueTime/Intervall enthält. Und diese Einträge beinhalten dann halt auch evtl. Verweise auf die System.Threading.Timer Objekt selbst (state Objekt oder callback). Dieser "echte" Timer wird nie aufgeräumt. D.h. die Einträge werden nur entfernt, wenn die "übergeordneten" Timer-Objekte entweder von Hand disposed werden, oder aber durch den GC (sofern der entsprechende Eintrag im "echten" Timer keinen Verweis auf das abzuräumende Timer-Objekt enthält).

Abschließend nochmal zu kleines_Eichhörnchens Bemerkung in meinem Script würde der GC nicht ausgeführt: doch das wird er, sogar auf Tastendruck. g

Es ist nur so: System.Timers.Timer Objekte werden NIE (eher bekommst du eine OutOfMemory-Exception) vom GC aufgeräumt, wenn sie nicht von Hand gestoppt (System.Timers.Timer.Stop() - was gleichbedeutend mit Enabled = false ist - was gleichbedeutend mit einem Dispose auf das interne System.Threading.Timer Objekt ist) werden. Dies ist wahrscheinlich deshalb so, weil System.Timers.Timer ein System.Threading.Timer Objekt instanziiert und zwar mit einem Verweis auf seine eigene Callback-Funktion (die ja dann erst das Elapsed-Event feuert).

Naja, zumindest weiß ich jetzt, dass das frühzeitige Aufräumen auf keinen Fall ein Mythos ist... aber auch, dass es nicht mit allen Timern geschieht, und vor allem WARUM es geschieht - oder eben manchmal auch nicht. 😉

08.06.2009 - 16:02 Uhr

Zur ersten Frage: Release-Build.

Um das ganze noch ein wenig auf die Spitze zu treiben:


using System;
using System.Collections.Generic;
using System.Text;
using System.Timers;

namespace GenTest {
  class MyTimer : Timer {

    public int[] Mem = null;
    public string Name = "";

    public MyTimer( int msecs, string name ) : base( msecs ) {
      Name = name;
      Mem = new int[ 1024 * 1024 * 100 ];

      for( int i = 0; i < Mem.Length; ++i ) {
        Mem[ i ] = i;
      }
    }

    ~MyTimer() {
      Console.WriteLine( "Destructed " + Name + "!" );
    }
  }

  class Program {
    static int p_ElapsedNr = 0;

    static bool GetKey() {
      ConsoleKeyInfo keyInfo = Console.ReadKey();

      if( keyInfo.Key == ConsoleKey.C ) {
        Console.WriteLine( "Collecting..." );
        GC.Collect();
      }
      else if( keyInfo.Key == ConsoleKey.Z ) {
        return false;
      }

      return true;
    }

    static void Test1() {
      MyTimer tmr = new MyTimer( 5000, "T1" );
      tmr.Elapsed += ElapsedHandler;
      tmr.AutoReset = true;
      tmr.Enabled = true;

      while( GetKey() ) ;

      tmr.Elapsed -= ElapsedHandler;
      tmr.Stop(); // Holla, erst das Stoppen des Timers gibt den Timer anscheinend zum Aufräumen frei
    }

    static void Test2() {
      MyTimer tmr = new MyTimer( 5000, "T2" );
      tmr.Elapsed += ElapsedHandler;
      tmr.AutoReset = true;
      tmr.Enabled = true;

      while( GetKey() ) ;

      tmr.Elapsed -= ElapsedHandler;
      tmr.Stop(); // Dasselbe hier
    }

    static void Main( string[] args ) {
      Test1();
      Test2();

      while( GetKey() ) ;
    }

    static void ElapsedHandler( object obj, ElapsedEventArgs e ) {
      Console.WriteLine( "Elapsed #" + p_ElapsedNr++ + "  " + (obj as MyTimer).Name );
    }
  }
}

Wenn man die Zeilen "tmr.Stop();" auskommentiert, werden die beiden Timer-Instanzen nie aufgeräumt, ob nun der Event abgehangen wurde oder nicht. Erst, wenn man den Timer stoppt, fasst der GC ihn an.

Ich denke mal, das ist so ziemlich das Gegenteil von "frühzeitigem Aufräumen" und evtl. die Reaktion von MS auf ein früheres Fehlverhalten?

08.06.2009 - 12:26 Uhr

Bei Timern ist es nicht nur wegen der sofortigen Ressourcenfreigabe wichtig Dispose aufzurufen, sondern auch damit der GC diesen Timer nicht vorzeitig wegräumt.
Beispiel:

  
public static void Main() {  
  //Führe alle x-Sekunden eine bestimmte Funktion aus, bis eine Taste gedrückt wird  
  Timer tmr = new Timer(...);  
  Console.ReadKey();  
  tmr.Dispose();  
}  
  

Ohne Dispose würde der GC beim nächsten Collect auch fälschlicherweise die Tmr-Instanz wegräumen, da der GC erkennt, dass die Variable nicht mehr gebraucht wird. (Ein setzen auf null würde auch nicht helfen!)

Hmmm... hat das mal jemand getestet?

Folgender Code:


using System;
using System.Collections.Generic;
using System.Text;
using System.Timers;

namespace GenTest {
  class Program {
    static int p_ElapsedNr = 0;

    static bool GetKey() {
      ConsoleKeyInfo keyInfo = Console.ReadKey();

      if( keyInfo.Key == ConsoleKey.C ) {
        Console.WriteLine( "Collecting..." );
        GC.Collect();
      }
      else if( keyInfo.Key == ConsoleKey.Z ) {
        return false;
      }

      return true;
    }

    static void Main( string[] args ) {
      Timer tmr = new Timer( 5000 );
      tmr.Elapsed += ElapsedHandler;
      tmr.AutoReset = true;
      tmr.Enabled = true;

      while( GetKey() ) ;
    }

    static void ElapsedHandler( object obj, ElapsedEventArgs e ) {
      Console.WriteLine( "Elapsed #" + p_ElapsedNr++ );
    }
  }
}

Das läuft bei mir munter den ganzen Tag weiter und feuert Elapsed-Events, egal wie oft ich "c" drücke. Hab ich da einen Denkfehler oder ist das "frühzeitige Aufräumen" ein Mythos?

PS: das läuft auch munter eventsfeuernd weiter, wenn ich ein "tmr = null;" vor die while-Schleife setze.

26.02.2009 - 18:10 Uhr

Hi Ares,

Evtl. würde ich's so versuchen...

noch ein weiteres Notify einbauen: ServerStatusNotify. Der Server teilt darin ganz einfach seinen jetzigen Status mit.

Im Konstruktor den Observer beim Server anmelden --> Client-Objekt ist dann halt eine Weile ohne Status (wartet auf aktuellen Status).

Den Server, bei Anmeldung eines neuen Observers immer den jetztigen Serverstatus senden lassen --> landet dann beim Client im ServerStatusNotify und da wird dann der Status gesetzt.

D.h. Statusänderungen gehen auf diese Weise NUR vom Server aus...