Laden...

TwoWay - TCP Kommunikation (Request/Response)

Erstellt von snupi vor 11 Jahren Letzter Beitrag vor 11 Jahren 4.822 Views
S
snupi Themenstarter:in
357 Beiträge seit 2007
vor 11 Jahren
TwoWay - TCP Kommunikation (Request/Response)

Ich habe einen Client und einen Server, welche über TCP kommunizieren müssen. Es handelt sich um ein Request/Response-Protokoll (Format: JSON, inkl. Message-End-Indikator); d.h. der Client kann einen Request senden, aber auch der Server.
Ein Request wird in den meisten Fällen mit einer ACK-Response (ResultCode=0) quittiert.

Um Request und Response zuordnen zu können, ist eine messageId vorhanden.

Bei der Überlegung zur möglichen Implementierung ist mir folgendes eingefallen:

Da ja beide Parteien einen Request initiieren können, muss in der Implementierung darauf geschaut werden, dass es beim 'Warten' auf die Response nicht zu einem DeadLock kommt (Beide Parteien senden gleichzeitig einen Request und warten auf die Response ...). Ein Timeout ist hier natürlich auch vorhanden, welcher obige Situation entschärfen soll...

Vielleicht hat der ein oder andere noch einige hilfreiche Inputs zum Design der Kommunikation (queues, request-objekte welche sich automatisch resenden im falle eines timeouts) ... ?

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo snupi,

wenn du mehrere Threads verwendest, kann ein Prozess gleichzeitig einen Request initiieren und auf einen anderen Request warten. Ein Deadlock kann dann gar nicht erst entstehen. Anschließend können sogar beide Verbindungen gleichzeitig genutzt werden.

herbivore

S
snupi Themenstarter:in
357 Beiträge seit 2007
vor 11 Jahren

hi - ja daran dachte ich auch für Senden/Empfangen eigene Threads zu verwenden - nur die Synchronisation zwischen den beiden muss ich mir noch durchdenken...denn es kann ja zb. vor einem OK noch ein neuer Request reinkommen...

ich frage mich auch, ob ein Request/Response-Protokoll für eine TwoWay-TCP Kommunikation eine ausnahme darstellt, oder ob dies doch öfters zum einsatz kommt ?

W
872 Beiträge seit 2005
vor 11 Jahren

Folgende Punkte wuerde ich Dir empfehlen:*Im Protokoll zwischen Client und Server wuerde ich einen Kontext oder eine Request-Nummer einfuegen, so dass der Client anhand des Kontext den zugehoerigen Request zu der Response wiederfinden kann. *Im Client sollte die Kommunikation in einem Singleton erfolgen. Das Singleton synchronisiert dann die Kommunikation. *Wenn Du viel parallel/asychron arbeitet, dann wuerde ich mit Delegates/Events arbeiten, so dass nach dem Empfang der Antwort auf einem anderen Thread weitergearbeitet wird, ohne dass im Singelton fuer die Kommunikation Applikations-Logik enthalten ist. Die Callbacks lassen sich so auch leichter testen...

S
snupi Themenstarter:in
357 Beiträge seit 2007
vor 11 Jahren

Folgende Punkte wuerde ich Dir empfehlen:
Im Protokoll zwischen Client und Server wuerde ich einen Kontext oder eine Request-Nummer einfuegen, so dass der Client anhand des Kontext den zugehoerigen Request zu der Response wiederfinden kann.

@weismat: das ist bereits mit der messageId vorgesehen 😉

S
snupi Themenstarter:in
357 Beiträge seit 2007
vor 11 Jahren

Ich habe nun eine erste Version, welche mehr oder weniger gut funktioniert:

Am Server gibt es pro verbundenem Client eine Instanz von AppClient, welche den zugehörigen TcpClient (_client, aus TcpListener.AcceptTcpClient()) erhält.

AppClient kann mittels SendMessage Daten senden:


        void SendMessage(string msg) {
            try {
                //lock (_client.GetStream()) {
                    NetworkStream ns = _client.GetStream();
                    byte[] bytesToSend = Encoding.ASCII.GetBytes(msg);
                    ns.Write(bytesToSend, 0, bytesToSend.Length);
                    ns.Flush();
                //}
            } catch (Exception) {
            }
        }

Das Empfange von Nachrichten erfolgt asynchron mittels BeginRead:


        void ReadCallback(IAsyncResult ar) {
            int bytesRead;
            try {
                //lock (_client.GetStream()) {
                    bytesRead = _client.GetStream().EndRead(ar);
                    //}
                if (bytesRead < 1) {
                    return;
                } else {
                    string msg;
                    int i = 0;
                    int start = 0;
                    while (i + 1 <= bytesRead) {
                        if (_buffer[i] == '\n') {
                            msg = _partialStr + Encoding.ASCII.GetString(_buffer, start, i - start);
                            _partialStr = String.Empty;
                            
                            //..do stuff
                            start = i + 1;
                        }
                        i += 1;
                    }

                    if (start != i) {
                        _partialStr = Encoding.ASCII.GetString(_buffer, start, i - start);
                    }
                }
                //lock (_client.GetStream()) {
                    _client.GetStream().BeginRead(_buffer, 0, _buffer.Length, ReadCallback, null);
                //}
            } catch (Exception) {
            }
        }

In meinen Test habe ich auch Stresstest gemacht, welche alle 10ms Daten senden, empfangen und wieder zurücksenden...

Dabei ist mir aufgefallen, wenn ich die einkommentierten lock-Anweisungen verwende, steht das System irgendwann still (ich nehme an, ein DeadLock).

Ohne den lock-Anweisungen funktioniert es...

Sind die lock-Anweisungen überhaupt notwendig ?

laut msdn:

Lese- und Schreibvorgänge können gleichzeitig in einer Instanz der NetworkStream-Klasse ausgeführt werden, ohne dass eine Synchronisierung erforderlich ist.Unter der Voraussetzung, dass ein eindeutiger Thread für Schreibvorgänge und ein eindeutiger Thread für Lesevorgänge vorhanden sind, überschneiden sich die Lese- und Schreibthreads nicht, und es ist keine Synchronisierung erforderlich.

J
641 Beiträge seit 2007
vor 11 Jahren

Für die Basis TCP Kommunikation hab Ich mal eine Klasse realisiert. Vielleicht kannst du die ja gebrauchen: DotNetSiemensPLCToolBoxLibrary: TCPFunctionsAsync.cs

cSharp Projekte : https://github.com/jogibear9988

S
snupi Themenstarter:in
357 Beiträge seit 2007
vor 11 Jahren

@jogibear9988: nach erster durchsicht: du verwendest hier keinen lock oder ?

W
872 Beiträge seit 2005
vor 11 Jahren

Der Lock sollte ein eigenstaendiges object sein. Ansonsten synchronisierst Du nicht.
Laut Doku sind Instanzen von TcpCLient sind nicht threadsicher, also solltest Du schon synchronisieren, obwohl an sich Win-Sockets threadsicher sind.

S
snupi Themenstarter:in
357 Beiträge seit 2007
vor 11 Jahren

Ich kann mir ja bei der Instanzierung auch gleich den NetworkStream speichern und dann nur mehr den verwenden... :

Im Konstruktor:


...
_ns = _client.GetStream();
...

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo weismat,

Der Lock sollte ein eigenstaendiges object sein.

das ist die Empfehlung von Microsoft, richtig. Warum ich denke, dass man diese nicht einhalten muss bzw. es sogar besser ist, dieser nicht zu folgen, steht in Sollte man lock this vermeiden? [==> Microsoft sagt ja, herbivore sagt nein]. Ich empfehle, möglichst das Objekt fürs lock zu verwenden, auf das der gemeinsame Zugriff erfolgen soll.

Hallo snupi,

innerhalb eines locks eine Operation zu verwenden, die beliebig lange blockieren kann, ist immer gefährlich. Wenn diese Operation dann auch noch von einem anderen Thread abhängt, der auf das lock wartet, gibt es - wie du vermutet hast - einen Deadlock. Allerdings sehe ich keinen Grund, warum mehrere Threads gleichzeitig auf dasselbe Client-Objekt zugreifen müssen. Wenn das nicht der Fall ist bzw. du dein Programm entsprechend umstellst, brauchst du (dafür) auch kein lock.

herbivore

J
641 Beiträge seit 2007
vor 11 Jahren

@jogibear9988: nach erster durchsicht: du verwendest hier keinen lock oder ?

Nö, die Klasse ist ja auch nicht Threadsafe, sie dient nur zum wrappen des verbindungsaufbaus, Reconnect, Keepalive, etc...

cSharp Projekte : https://github.com/jogibear9988

S
snupi Themenstarter:in
357 Beiträge seit 2007
vor 11 Jahren

Hi herbivore

innerhalb eines locks eine Operation zu verwenden, die beliebig lange blockieren kann, ist immer gefährlich. Wenn diese Operation dann auch noch von einem anderen Thread abhängt, der auf das lock wartet, gibt es - wie du vermutet hast - einen Deadlock.

Das wäre in meinem Code ja aber nicht der Fall (Die Operation hängt nicht von einem anderen Thread ab - oder sehe ich hier was falsch) ?

SendMessage kann von mehreren Threads aufgerufen werden, da es eine BroadCast-Funktion gibt, welche an alle verbundenen Client was schicken kann.
Dies habe ich nun mittels ein SafeQueue gelöst, dh. das SendMessage stellt die Messages nur mehr in die Queue, ein Thread in AppClient kümmert sich dann um das Senden der Messages aud dieser Queue.

So wie ich das dann aus der MSDN (NetworkStream) verstehe, brauche ich dann keinen lock mehr (SenderThread, ReadCallback), da Read/Write auf einen NetworkStream gleichzeitig ausgeführt werden darf.

W
872 Beiträge seit 2005
vor 11 Jahren

Zu Snupi:

NetworkStream muss schon synchronisiert werden.

Alle öffentlichen statischen (Shared in Visual Basic) Member dieses Typs sind threadsicher. Bei Instanzmembern ist die Threadsicherheit nicht gewährleistet.

Wenn Du nicht synchronisieren willst, musst Du mit Socket arbeiten (Instanzen dieser Klasse sind threadsicher.)

S
snupi Themenstarter:in
357 Beiträge seit 2007
vor 11 Jahren

@weismat:

MSDN sagt folgendes:

Lese- und Schreibvorgänge können gleichzeitig in einer Instanz der NetworkStream-Klasse ausgeführt werden, ohne dass eine Synchronisierung erforderlich ist.Unter der Voraussetzung, dass ein eindeutiger Thread für Schreibvorgänge und ein eindeutiger Thread für Lesevorgänge vorhanden sind, überschneiden sich die Lese- und Schreibthreads nicht, und es ist keine Synchronisierung erforderlich.

soviel ich sehe, habe ich keine Überschneidung von Lese- und Schreibthreads

S
snupi Themenstarter:in
357 Beiträge seit 2007
vor 11 Jahren

Verwende nun das asynchrone Muster mit NetworkStream.BeginRead/NetworkStream.BeginWrite Ohne Synchronisierung - konnte in meinen Dauertests (heftiges, gleichzeitiges Lesen/Schreiben) bis dato keine Probleme feststellen.

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo snupi,

Testen ist zur Ermittlung, ob ein Konstrukt threadsicher ist, nur von sehr begrenztem Wert, siehe dazu auch die Überlegungen in SyncQueue <T> - Eine praktische Job-Queue und Das Programmier-Spiel: nette Übungsaufgaben für zwischendurch. Gerade die vertrackten Konstellationen lassen sich durch Testen kaum erreichen (es sei denn man konstruiert solche Tests gezielt für diese Konstellationen, wozu man sie aber vorher eben doch mit anderen Maßnahmen identifiziert haben muss). Leider liefert das schwierige Auffinden solcher Fehler durch Tests keine Garantie, dass sie sich in einer anderen Umgebung nicht doch störend bemerkbar machen.

herbivore

W
872 Beiträge seit 2005
vor 11 Jahren

Wenn Du an einer Stelle nur liest und einer anderen Stelle nur schreibst, dann ist das alles kein Problem...
Problematisch wird es, wenn Du zwei Stellen hast - z.B. wenn regelmaessig ein echo an den Server ueber einen Background-Thread geschickt wird und das GUI an anderer Stelle Applikationsnachrichten schickt.

S
snupi Themenstarter:in
357 Beiträge seit 2007
vor 11 Jahren

@herbivore : ja das ist mir klar 😃

Ich habe mir 2 StateObjecte jeweils für Lesen und Schreiben erstellt, welche ich jeweils in BeginRead/BeginWrite mitsende.

Da MSDN folgendes sagt:

Lese- und Schreibvorgänge können gleichzeitig in einer Instanz der NetworkStream-Klasse ausgeführt werden, ohne dass eine Synchronisierung erforderlich ist.

hat jedes StateObject jeweils den selben NetworkStream, welches es zum Lesen bzw. Schreiben verwenden kann...

@weismat : genau für diesen Grund synche ich dann 'ausserhalb' mittels einer Write(Snyc)-Queue.