Socket vs. Modbus

SY50

Level-1
Beiträge
271
Reaktionspunkte
1
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo,

ich habe gerade eine Socketkommunikationnzwishcne PC und Steuerung aufgebaut.

was sagt ihr denn zur Kommunikation zweier Steuerungen untereinander?
Ist da ein Socket auch Ok, öde lieber Modbus nutzen?
Was genau ist der Unterschied? Modbus basiert ja darauf einen Socket zu nutzen.
Gehe ich recht in der Annahme, dass es wie eine Socketverbindung ist, nur mit unterlagertem Modbusprotokoll?
 
Kommt darauf an was du machen willst. Wenn du eine steuerungsunabhängige Lösung willst (du willst ja mit C# kommunizieren) dann wäre eine Socketverbindung in deinem Fall UDP oder TCP sicherlich eine gute Lösung. Du musst dann allerdings in Codesys einiges selbst entwickeln bis das stabil läuft. In C# sind das nur ein paar wenige Zeilen Code. Ich kenne mich mit Modbus nicht gut aus, mit TCP/IP und UDP/IP habe ich allerdings schon viel gemacht. Um da zwei Systeme miteinander zu verbinden geht das eigentlich recht flott. Was später Schwierigkeiten machen sind:

1. Sicherstellung, dass große Pakete sauber übertragen werden (Grenze von TCP ist typischerweise 1500Bytes) wenn das mehr sind verschickt das TCP automatisch in mehreren Paketen und setzt diese auch wieder sauber zusammen. Du musst dann halt einen ausreichend großen Zwischenpuffer haben den du immer sofort wieder leerst damit die Gegenstelle wieder Daten schicken kann. Dann musst du die Daten selbst wieder zusammensetzen und bei erreichen deiner gesamten Paketlänge kannst du das dann intern weiterverarbeiten sonst hast du inkonsistente Daten.

2. Ich hatte immer sehr große Probleme festzustellen wenn z.B. das Kabel ausgesteckt wird. Das musst du selbst überwachen. TCP/IP sendet ja nicht zyklisch per se, sondern nur nach Anforderung. D.h. Wenn du das Kabel aussteckst kannst es die Steuerung erstmal nicht feststellen ob die Verbindung noch steht da ja u.U. ein paar Minuten nix geschickt wird und deshalb hier keine Möglichkeit besteht das zu erkennen. Abhilfe schafft dann das KeepAlive (sorry ist schon eine Weile her, Google da besser mal nach genauen Infos). Manche Steuerung können das leider auch nicht. Ich habe dann immer meine Pakete dauernd geschickt auch wenn sich geändert hat um die Verbindung zu überwachen.

3. Wenn du keine feste Paketlänge hast musst du eine Terminierung definieren die den Abschluss eines Paketes definieren. Mit ioctl kannst du prüfen ob Daten zum Abholen bereit stehen und wieviele Bytes es sind. Diese dann abholen und nach der Terminierung durchsuchen. Macht man normalerweise wenn man nicht mit Binärdaten arbeitet, sondern z.B. ASCII over TCP. Die meisten Kameras (Cognex, Festo, Keyence,..) arbeiten so. Senden: "Trigger$R$N" Empfangen: "OK$R$N" oder so in der Art. Die Pakete werden dann natürlich größer für solche Kommunikationen ist es dennoch gut so zu arbeiten da direkt alles im Klartext zu loggen ist.


Welche Steuerung hast du? Codesys V2 oder V3,... oder BEckhoff?


EDIT: Achso, für Taktzeitkritische Signale wie z.B. ein Sollwert an einen Servoantrieb würde ich auf keinen Fall TCP/IP verwenden. Manchmal gibt es hier Pakete die länger oder kürzer "unterwegs" sind. TCP/IP ist nicht echtzeitfähig. UDP/IP ist schneller aber Pakete die verlorern gehen bleiben verloren,... echtzeitfähig ist beides nicht.
EDIT2: Bei Beckhoff ist die ADS Kommunikation am besten (je nach Anforderung!), da brauchst dann auch nicht den TCP Client zu kaufen
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich habe eine CodesysV3 Steuerung.
Also ich mache es so:
wenn in meineWindows Form ein Button gedrückt wird, dann sende ich eine bestimmte Date an die Steuerung. die Steuerung sendet mir dann eine Quittung.
im nächsten Zyklus (Timer ruft alle 100ms im C# Programm das senden auf) sende ich nur eine Anforderung, dass die Steuerung mir allgemeine Daten sendet.
jetzt sendet die Steuerung mir auf diese Anfrage Positionen und Geschwindigkwiten von Antriben zurück.
das funktioniert sehr gut. Bin bis jetzt echt zufrieden.
mein einziger Punkt den ich eben nicht schön finde, ist, dass ich mir quasi ein eigenes Protokoll aufbauen muss und wissen muss wo welche Date steht. Sicherlich könnte man das durch entsprechende deklarierungen ändern, aber das würde auch wieder ein zu hohen Overhead liefern.
zugriff über Symbolkonfiguration wäre schon genial, aber der socket funktioniert so auch ganz gut.
ps. Die Socketkommunikation auf der Steuerung läuft einwandfrei. Haben das schon öfter gemacht. Auch mit diversen Fehler und abbruchreaktionen. Startet schön selbst wieder in allen lagen :)
 
TCP oder UDP ist nicht echtzeitfähig, aber wenn man nur einen Switch zwischen den Teilnehmern hat, ist der Jitter recht klein. Ich habe mit Python auf Windows einen Jitter von 1ms auf 50ms Scanzeit erreicht. Damit lässt sich schon etwas machen. Servos würde ich damit trotzdem nicht steuern.
 
Nein ich will auch keine Servos damit ansteuern.
Die Daten dienen rein zur Visualisierung auf dem Windows Rechner.
Im Moment habe ich es auch so aufgebaut, dass ich in den ersten 10 Byte immer verschiedene Befehle (Ansteuerbefehle) sende. (Bis jetzt immer nur 1 Byte lang.
Je nachdem welche Befehl gesendet wird, werden verschiedene Funktionen dadurch in der Steuerung aktiviert. (Simuliert bspw. einen Button in der realen HMI).
Als Antwort sendet mir die Steuerung genau diesen Befehl auch wieder zurück und ab dem 11. Byte sendet die Steuerung noch weitere Daten wie Position einer Achse, Geschwindigkeit, Fehler usw. zurück.

Die Daten wie Position Geschwindigkeit usw. hole ich mir in einem 100ms Zyklus ab.
Meine Frage jetzt... wenn ich mal andere Anwendungen habe, bei denen nicht zyklisch etwas abgeholt werden muss, sondern nur auf ein bestimmtes Ereignis hin.
Bspw. von einer Kamera oder so .... sagen wir mal jede Minute oder alle 30s einmal... Würdet Ihr dann nach dem Abholen bzw. empfangen der Daten den Socket wieder schließen, oder würdet Ihr ihn offen lassen und
nur wieder zu gegebener Zeit einen Schreibbefehl vom Client an den Server senden?
 
Zuviel Werbung?
-> Hier kostenlos registrieren
offen lassen... stört ja nicht. Bei Kameras lese ich in den Pausenzeiten immer irgendwas aus um die Kommunikation zu prüfen wie z.B. die Version oder sonst was. Dann weiß man alles OK
 
OK, danke. Dachte ich mir schon... mache ich ja jetzt im Prinzip auch so. lese eben einfach die Achspositionen ein.

Wenn die Steuerung als Server agiert, dann wird nachdem der Client sich connected hat ein Receive() ausgeführt.
wird etwas anfangen, dann habe ich es so programmiert, dass die Daten zwar in dem RxBuffer landen, jedoch zusätzlich ein Callback aufgerufen wird und die Daten verarbeitet werden, bzw. die TxDaten aufbereitet werden können. Im gleichen Zyklus wird dann direkt das Senden des TxBuffer angestoßen.
ist dies passiert, so wird im nächsten Zyklus wieder ein Recieve() ausgeführt. ..... Wird nun für eine Timeoutzeit lang nichts empfangen (sprich de Client sendet nichts), so gibt es einen Timeoutzeit und der Server wartet auf ein erneutes Connect vom Client.

Dadurch habe aber ich den Client natürlich so ausgeführt, dass dieser auch wenn keine speziellen Befehle dauerhaft gesendet werden sollen doch einen Dummywert sendet und Daten zurückbekommt.
Somit können diese beiden grundprogramme schonmal miteinander reden.
 
Zuletzt bearbeitet:
Server öffnet manchmal den Socket nicht.
gerade wenn ich Änderungen mache und einen Download einspiele, dann öffnet die Win V3 den Socket nicht mehr und liefert als Result den Code 519 "erfror bond 519".
durch wechseln des Ports oder beenden der Wien V3 und Neustart dieser geht es dann meistens wieder. Was kann das sein und gibt's da irgendwo ne fehlerbeschreibung?
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Habe jetzt raus gefunden, woran es liegt.
der Fehler tritt auf, wenn die Steuerung in Stopp geht (beim Download) und der Socket noch geöffnet ist.
Schließe ich ihn vorher, so passiert es nicht. Nachdem die Steuerung wieder startet, ist der handle allerdings nicht mehr gültig und die Steuerung kann den Socket weder auf noch zu machen.
was kann ich dagegen tun? Kann man irgendwie abfragen, welche handle noch den Port auf hat?
 
ja das ist immer das Problem bei den Codesyssteuerungen. Die Sockets werden insbesondere beim Onlinechange irgendwo "vergraben" im System und sind nicht mehr gültig.

Es gibt nur eine Möglichkeit die sinnvoll ist. Erstelle eine Callbackfunktion die auf das Event Onlinechange, Download, Reset,... eingehängt wird. Dort schließst du den Socket dann beim entsprechenden Event und nach dem Onlinechange öffnest du einen neuen.

Das ist übrigens nicht nur beim Server so, sondern auch beim Client.

SysCallbackRegister(INDEXOF(callbackCloseSocket), EVENT_BEFORE_RESET);
SysCallbackRegister(INDEXOF(callbackCloseSocket), EVENT_BEFORE_DOWNLOAD);
SysCallbackRegister(INDEXOF(callbackCloseSocket), EVENT_SHUTDOWN);
SysCallbackRegister(INDEXOF(callbackCloseSocket), EVENT_ONLINE_CHANGE);


Kann sein,dass die bei V3 etwas anders heißen.

In der FC callbackCloseSocket dann:


Code:
FUNCTION callbackCloseSocket : BOOL
VAR_INPUT
 dwEvent:DWORD;
 dwFilter:DWORD;
 dwOwner:DWORD;
END_VAR

SysSockShutdown(dwSocket,2);
SysSockClose(dwSocket);


Shutdown sollte eigentlich nicht notwendig sein vor Close. Stören tut es aber auch nicht also was solls...

dwSocket dann natürlich global deklarieren oder sonst irgendwie drauf zugreifen.
 
Danke,
funktioniert super. Beim Client ging es komischerweise ohne, aber habe es generell in meinem FB drin.
Schade, dass man bei den SysCallbacks keine Adresse von einer Methode des FBs angeben kann,
dann wäre alles in einer Instanz. Habe schon einen Callback über ein Interface, wenn Daten gelesen, bzw. geschrieben werden. So kann ch nicht nur stumpf auf den Rx & TxBuffer schauen, sondern kann direkt wenn Daten beim Server empfangen werden drauf reagieren und direkt eine passend verarbeitete Antwort im gleichen Zyklus zurücksenden.
Die Adresse der Callbackfunktion wird als Eingangsparameter übergeben und auf ungleich 0 geprüft.
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
Habe noch folgenden Trick angewendet:
Habe den Callback auf eine Funktion gesetzt, welche dann eine Aktion meiner Bausteininstanz aufruft ;-)
So habe ich wenigstens das kompletter Handling der Socket in dem FB.
 
Bist auf einem guten Weg, ich sehe das schon. Wie ich anfangs schon gesagt habe. Das die beiden Teilnehmer mal mit einander kommunizieren geht schnell. Dann kommt der riesen Rattenschwanz an zusätzlichen Dingen die man beachten muss wie dieses Systemverhalten beim Onlinechange,... Wenn man überlegt wie simpel das Ganze in C# ist.

Hast du bei der Socket Erstellung auch div. Optionen gesetzt? Ich habe mich damals mal eingearbeitet und einen eigenen SysSockcreat geschrieben der auch die Options setzt. Ich poste dir einfach mal meinen Code. Der ist nicht fertig. Habe irgendwann keinen Bock mehr gehabt aber es sind viele Kommentare drin ich ich mir aus div. C++ Dokus zusammengesucht habe. Vielleicht ist der eine oder andere Tipp drin der dir noch hilft. Geht um die Optionen: SOCKET_SO_SNDBUF, SOCKET_SO_RCVBUF, SOCKET_SO_REUSEPORT, SOCKET_SO_KEEPALIVE und SOCKET_SO_KEEPALIVE

Achso: Hier habe ich das mit den Async FBs aufgezogen. Mit den synchronen FCs hatte ich irgendwann wegen irgendwas Probleme und habe alles auf FB umgestellt. Kannst du natürlich verwenden wie du willst, also auch bei FC

Code:
FUNCTION_BLOCK FB_SocketCreate
VAR_INPUT
    xExecute                    : BOOL                     := FALSE;
    bServer                        : BOOL                     := FALSE;
    eProtocolType                : ENUM_EthProtocolType    := ENUM_TCP;
    nEthComInstNo                : INT                     := 0;
END_VAR
VAR_OUTPUT
    xDone                        : BOOL                     := FALSE;
    xError                        : BOOL                     := FALSE;
    wErrorID                    : WORD                     := 16#0;
END_VAR
VAR
    nState                        : INT                     := 0;
    (* Create Socket *)
    lOptLinger                    : SOCKET_LINGER;
    dwValue                        : DWORD                    := 1;
    nValue                        : DINT                     := 1;
    (* Verwendete FBs aus SysSockAsync.lib *)
    mySysSockCreateAsync        : SysSockCreateAsync;
    mySysSockSetOptionAsync        : SysSockSetOptionAsync;


END_VAR




IF NOT xExecute THEN    nState := 0;
END_IF;




CASE nState OF
0:
    xDone := FALSE;
    xError := FALSE;
    wErrorID := 16#0;
    nState := 10;


10:
    IF xExecute THEN
        nState :=20;
    END_IF;
(* ---------------------------------------------------------- *)
20:(* create socket *)
    mySysSockCreateAsync(bEnable := FALSE);
    IF NOT mySysSockCreateAsync.bDone THEN
        nState := nState + 10;
    END_IF;
30:
    IF eProtocolType = ENUM_TCP THEN
        mySysSockCreateAsync.diType := SOCKET_STREAM;
        mySysSockCreateAsync.diProtocol := SOCKET_IPPROTO_IP;
    ELSE
        mySysSockCreateAsync.diType := SOCKET_DGRAM;
        mySysSockCreateAsync.diProtocol := SOCKET_IPPROTO_UDP;
    END_IF;


    FUN_EthComRegisterCallbacks();


    mySysSockCreateAsync(
        bEnable                := TRUE,
        diAddressFamily        := SOCKET_AF_INET,    (* AF_INET: IPv4 basierete Internet-Protokolle. TCP und UDP sind allgemein gebräuchliche Protokolle dieser Familie *)
        diType                := ,(* SOCK_STREAM: Stellt zuverlässige, sequentielle verbindungsorientierte Byte-Streams im Vollduplexmodus zur Verfügung.
                                Auch bandexterne Mechanismen zur Datenübertragung können unterstützt werden. Das TCP-Protokoll beruht auf diesem Socket.Typ.  *)
        diProtocol            := ,(* IPPROTO_IP is for raw IP packets. IPPROTO_TCP and IPPROTO_UDP are protocols based on IP. If you open an raw IP socket,
                                you're responsible for assembling all the IP bytes yourself. You may need privileged access to open a raw IP socket. *)
        sSocket                => my_ST_EthCommunication[nEthComInstNo].diSocket (* erzeugten Socket in gobale Instanz übernehmen *)
    );


    IF         mySysSockCreateAsync.bDone
        AND NOT mySysSockCreateAsync.bError
    THEN
        (* Die Callbacks werden benötigt um den Socket bei Reset oder Onlinechange zu schließen sonst kann es zum Crash der PLC führen *)


        nState := nState + 10;


    ELSIF     mySysSockCreateAsync.bError
        AND mySysSockCreateAsync.bDone
    THEN
        mySysSockCreateAsync(
        bEnable                := FALSE);(*nState := 1000 + nState + 2; (* Socket konnte nicht erzeugt werden *)*)
    END_IF;
40:
    (* Callback Funktionen registrieren und Socket Optionen setzen *)
    (*
    SO_DEBUG         Gibt an, ob Debug-Informationen mitgeschrieben wurden
    SO_BROADCAST     Gibt an, ob die Übermittlung von Rundsende-Nachrichten unterstützt wird.
    SO_REUSEADDR     Gibt an, ob die lokale Adresse wiederverwendet werden kann.
    SO_KEEPALIVE     Gibt an, ob Verbindungen durch periodische Aussendungen von Nachrichten aufrecht erhalten werden.
                    Falls der verbundene Socket nicht auf diese Nachrichten antworten kann, wird die Verbindung unterbrochen
                    und Prozesse, die auf diesen Socket schreiben, erhalten ein SIGPIPE Signal.
    SO_LINGER         Gibt an, ob der socket einen Aufruf von socket_close() verzögert, falls noch Daten
                    vorhanden sind. In der Standardeinstellung versucht ein Socket vor dem Schließen, alle noch nicht
                    gesendeten Daten zu übermitteln. Im Falle eines verbindungsorientierten Sockets, wartet socket_close() auf die
                    Empfangsbestätigung seiner Gegenstelle. Falls l_onoff von 0 verschieden und l_linger ist 0, werden bei einem
                    verbindungsorientierten Socket alle noch nicht gesendeten Daten verworfen und das Signal RST (Reset) an
                    die Gegenstelle gesendet. Andererseits, falls sowohl l_onoff als auch l_linger von 0 verschieden sind,
                    blockiert der Aufruf von socket_close() den Socket so lange, bis entweder alle Daten gesendet sind oder
                    das Zeitlimit erreicht ist, das in l_linger gesetzt wurde. Wenn der Socket auf nicht-blockieren gesetzt ist,
                    schläGT socket_close() fehl und gibt einen Fehler zurück. ARRAY. Das ARRAY enthäLT zwei Schlüssel: l_onoff AND l_linger.
    SO_OOBINLINE     Gibt an, ob socket die Übertragung von bandexternen Daten in der Reihenfolge überträGT, wie sie auftreten.
    SO_SNDBUF         Gibt die Grösse des Sendepuffers zurück.
    SO_RCVBUF         Gibt die Grösse des Empfangspuffers zurück.
    SO_ERROR         Gibt den Fehlerstatus zurück und löscht ihn. INT (dieser Wert kann nicht von socket_set_option() gesetzt werden.)
    SO_TYPE         Gibt den Typ von socket zurück (z. B. SOCK_STREAM). INT (dieser Wert kann nicht von socket_set_option() gesetzt werden.)
    SO_DONTROUTE     Gibt an, ob ausgehende Nachrichten das standardmässige Routing umgehen.
    SO_RCVLOWAT     Gibt die Mindestanzahl Bytes an, die socket bei einer Empfangsoperation verarbeiten soll.
                    (Der Vorgabewert ist 1.)
    SO_RCVTIMEO     Gibt den Wert der Zeitspanne bis zu einer Zeitüberschreitung bei Empfangsoperationen an. ARRAY. Das ARRAY
                    enthäLT zwei Schlüssel: sec, den Sekunden-Anteil der Zeitspanne und usec, den Mikrosekunden-Anteil der Zeitspanne
                    bis zu einer Zeitüberschreitung. SO_SNDTIMEO Gibt die Zeitspanne an, nach der eine Zeitüberschreitung bei
                    Sendeoperationen ausgelöST wird. Eine Ausgabefunktion darf während der angegebenen Zeitspanne blockieren,
                    falls die Ablaufsteuerung den Versand der Daten verhindert. ARRAY. Das ARRAY enthäLT zwei Schlüssel: sec
                    den Sekunden-Anteil der Zeitspanne und usec den Mikrosekunden-Anteil der Zeitspanne bis zu einer Zeitüberschreitung.
    SO_SNDLOWAT     Gibt die Mindestanzahl Bytes an, die socket bei einer Ausgabeoperation verarbeiten soll.
    *)


    mySysSockSetOptionAsync(bEnable := FALSE);
    IF NOT mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    END_IF;


50: (* Socketoption setzen: Sendepuffergröße einstellen *)
    mySysSockSetOptionAsync(
        bEnable            := TRUE,
        diSocket        := my_ST_EthCommunication[nEthComInstNo].diSocket,     (* Socket übergeben *)
        diLevel            := SOCKET_SOL,                (* Der Parameter level gibt die Protokollebene an, auf der diese Option gilt. Um beispielsweise Optionen auf dem Socket-Level abzufragen
                                                                würde man den Parameter level auf den Wert SOL_SOCKET setzen. Andere Ebenen, wie etwa TCP können benutzt werden, indem man die 
                                                                Protokollnummer dieser Ebene benutzt. *)
        diOption        := SOCKET_SO_SNDBUF,        (* Setzt die Größe des Sendepuffers fest *)
        pOptionValue    := ADR(ETH_COM_MAX_DATA_SIZE_SEND),
        diOptionLength    := SIZEOF(ETH_COM_MAX_DATA_SIZE_SEND)
    );
    IF mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    ELSIF mySysSockSetOptionAsync.bError THEN
        nState := 1000 + nState + 2;
    END_IF;


60:
    mySysSockSetOptionAsync(bEnable := FALSE);
    IF NOT mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    END_IF;


70: (* Socketoption setzen: Empfangspuffergröße einstellen *)
    mySysSockSetOptionAsync(
        bEnable            := TRUE,
        diSocket        := my_ST_EthCommunication[nEthComInstNo].diSocket,     (* Socket übergeben *)
        diLevel            := SOCKET_SOL,                (* Der Parameter level gibt die Protokollebene an, auf der diese Option gilt. Um beispielsweise Optionen auf dem Socket-Level abzufragen
                                                                würde man den Parameter level auf den Wert SOL_SOCKET setzen. Andere Ebenen, wie etwa TCP können benutzt werden, indem man die 
                                                                Protokollnummer dieser Ebene benutzt. *)
        diOption        := SOCKET_SO_RCVBUF,        (* Setzt die Größe des Sendepuffers fest *)
        pOptionValue    := ADR(ETH_COM_MAX_DATA_SIZE_RECV),
        diOptionLength    := SIZEOF(ETH_COM_MAX_DATA_SIZE_RECV)
    );
    IF mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    ELSIF mySysSockSetOptionAsync.bError THEN
        nState := 1000 + nState + 2;
    END_IF;
80:
    mySysSockSetOptionAsync(bEnable := FALSE);
    IF NOT mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    END_IF;
90: (* Socketoption setzen: Adresse darf wiederverwendet werden nach Schließen des Sockets *)
    dwValue := 1;
    mySysSockSetOptionAsync(
        bEnable            := TRUE,
        diSocket        := my_ST_EthCommunication[nEthComInstNo].diSocket,     (* Socket übergeben *)
        diLevel            := SOCKET_SOL,                (* Der Parameter level gibt die Protokollebene an, auf der diese Option gilt. Um beispielsweise Optionen auf dem Socket-Level abzufragen
                                                                würde man den Parameter level auf den Wert SOL_SOCKET setzen. Andere Ebenen, wie etwa TCP können benutzt werden, indem man die 
                                                                Protokollnummer dieser Ebene benutzt. *)
        diOption        := SOCKET_SO_REUSEADDR,        (* Gibt an, dass Adresse wieder genutzt werden darf nach Schließen des Sockets! *)
        pOptionValue    := ADR(dwValue),
        diOptionLength    := SIZEOF(dwValue)
    );
    IF mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    ELSIF mySysSockSetOptionAsync.bError THEN
        nState := 1000 + nState + 2;
    END_IF;


100: mySysSockSetOptionAsync(bEnable := FALSE);
    IF NOT mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    END_IF;


110: (* Socketoption setzen: Beim Schließen des Sockets kann durch Linger gewartet werden falls noch Daten im Sendepuffer vorliegen ==> Deaktivieren *)
    lOptLinger.l_onoff    := 0; (* Lingerfunktion deaktivieren! ==> Socket schließen bei SysSockClose auch wenn noch Daten im Ausgangspuffer vorliegen!  *)
    lOptLinger.l_linger    := 0; (* Kein Linger nutzten! *)


    mySysSockSetOptionAsync(
        bEnable            := TRUE,
        diSocket        := my_ST_EthCommunication[nEthComInstNo].diSocket,     (* Socket übergeben *)
        diLevel            := SOCKET_SOL,                (* Der Parameter level gibt die Protokollebene an, auf der diese Option gilt. Um beispielsweise Optionen auf dem Socket-Level abzufragen
                                                                würde man den Parameter level auf den Wert SOL_SOCKET setzen. Andere Ebenen, wie etwa TCP können benutzt werden, indem man die 
                                                                Protokollnummer dieser Ebene benutzt. *)
        diOption        := SOCKET_SO_LINGER,        (* Gibt an, dass Adresse wieder genutzt werden darf nach Schließen des Sockets! *)
        pOptionValue    := ADR(lOptLinger),
        diOptionLength    := SIZEOF(lOptLinger)
    );
    IF mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    ELSIF mySysSockSetOptionAsync.bError THEN
        nState := 1000 + nState + 2;
    END_IF;


120:
    mySysSockSetOptionAsync(bEnable := FALSE);
    IF NOT mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    END_IF;


130:
    nValue := 1;
    mySysSockSetOptionAsync(
        bEnable            := TRUE,
        diSocket        := my_ST_EthCommunication[nEthComInstNo].diSocket,     (* Socket übergeben *)
        diLevel            := SOCKET_SOL,        (* Gibt an, ob Verbindungen durch periodische Aussendungen von Nachrichten aufrecht erhalten werden.
                                                    Falls der verbundene Socket nicht auf diese Nachrichten antworten kann, wird die Verbindung unterbrochen
                                                    und Prozesse, die auf diesen Socket schreiben, erhalten ein SIGPIPE Signal. *)
        diOption        := SOCKET_SO_KEEPALIVE,        (* Gibt an, dass Adresse wieder genutzt werden darf nach Schließen des Sockets! *)
        pOptionValue    := ADR(nValue),
        diOptionLength    := SIZEOF(nValue)
    );
    IF mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    ELSIF mySysSockSetOptionAsync.bError THEN
        nState := 1000 + nState + 2;
    END_IF;


140:
    mySysSockSetOptionAsync(bEnable := FALSE);
    IF NOT mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    END_IF;


150:
    dwValue := 1;
    mySysSockSetOptionAsync(
        bEnable            := TRUE,
        diSocket        := my_ST_EthCommunication[nEthComInstNo].diSocket,     (* Socket übergeben *)
        diLevel            := SOCKET_SOL,        (* Gibt an, ob Verbindungen durch periodische Aussendungen von Nachrichten aufrecht erhalten werden.
                                                    Falls der verbundene Socket nicht auf diese Nachrichten antworten kann, wird die Verbindung unterbrochen
                                                    und Prozesse, die auf diesen Socket schreiben, erhalten ein SIGPIPE Signal. *)
        diOption        := SOCKET_SO_REUSEPORT,        (* Gibt an, dass Adresse wieder genutzt werden darf nach Schließen des Sockets! *)
        pOptionValue    := ADR(dwValue),
        diOptionLength    := SIZEOF(dwValue)
    );


    IF mySysSockSetOptionAsync.bDone THEN
        nState := nState + 10;
    ELSIF mySysSockSetOptionAsync.bError THEN
        nState := 1000 + nState + 2;
    END_IF;


160:
    xDone := TRUE;
1000..1999:
    ;
END_CASE;

PS: Endlich mal wieder ein interessantes Thema und nicht nur irgendwer der einen Syntaxfehler im Programm hat :cool:
 
Zuletzt bearbeitet:
Wieso muss man denn mit so kruden Callbacks hantieren, wenn einem die Fuktionen in ihrem Rückgabewert sagen, dass der Handle ungültig ist? Damit muss man übrigens immer rechnen.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Die Fkt sagt zwar, dass der Handle gültig ist wenn man den Socket erzeugt. Anschließend gibt es allerdings keine Fkt die einem prüft ob der Handle noch gültig ist. Der Wert des Handles wird ja nur einmalig geschrieben von SysSockCreate. Anschließend prüft man diesen. Wenn man aber einen Onlinechange macht reagiert Codesys hier so, dass der Handle nach wie vor gültig ist zumindest das was man prüfen kann. Ist ja nur ein DWORD. Dieser wird aber beim Onlinechange nicht auf 0 gesetzt o.ä. wenn man das nicht über SocketClose tut. Das heißt: In der Applikation kann man nach dem Onlinechange nicht herausfinden ob der Socket noch zu verwenden ist. Einzige Lösung: den Socket VOR dem Onlinechange schließen. Dies kann man machen in dem man immer vorher von Hand den Socket schließt durch "Verbindungsabbau" oder einfach automatisiert über CALLBACKs.

Selbst wenn man nach dem Onlinechange prüfen könnte ob der Handle noch gültig ist kann man damit nix mehr machen also auch kein nachträgliches schließen denn das erfordert natürlich einen gültigen Socket. Ungültige Sockets/Handles kann man nicht schließen.

Meiner Meinung nach sollten die Sockets Onlinechangesicher von 3s gestaltet werden. Ich habe keine Ahnung wieso sie das nicht sind.
 
Mit einem ungültigen Handle kann ich auch bei Windows nichts mehr machen außer als diesen wegzuwerfen, und ggf. die eigenen Ressourcen aufzuräumen.
Was machen denn die Codesys-Funktionen zum Lesen und Schreiben über einen Socket, wenn der Handle ungültig ist? Da muss es doch eine Fehlermeldung geben. Und wenn nicht, dann ist es ein Fehler in den Codesys-Bibliotheken.
 
Also mein Problem bestand nicht beim onlinechange.
Ich hatte das Problem nur nach einem Download. Und zwar folgendes:
Wenn ich einen Socket geöffnet hatte und dann einen Programmdownload gemacht habe, dann war nach dem Neustart der Steuerung (Nur bei der Win V3) kein öffnen des Sockets auf dem gleichen Port mehr möglich, weil der wohl noch geöffnet war. Das Programm kannte aber ja den Handle nicht mehr, deswegen konnte ich ihn nicht mehr benutzen oder schließen. Mit dem callback wird einfach alles vor dem Download geschlossen und somit kann ich danach den Socket wieder öffnen.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Also für mich ist das eindeutig ein Fehler in Codesys, wenn da irgendwas im Hintergrund unkontrolliert vor sich hin vagabundiert. Wenn ich Systemressourcen anfordere, muss ich diese kontrollieren können. Wenn bei einem Online-Change irgendwas zurückgesetzt werden muss, dann muss Codesys dafür sorgen dass die Dienste die davon beeinflusst werden zurückgesetzt werden. Ansonsten ist es kein Online-Change.

Ich verstehe nicht warum die Codesys-Anwender solche Bugs einfach so hinnehmen. Bei Siemens würde ich umgehend einen Fall aufmachen, und ich bin mir sicher, dass Siemens so etwas (zumindest irgendwann) korrigieren würde.
 
Es ist wie es ist,.. war schon in V2 so und wird sich auch nicht ändern, denken ich. Eine Grundsatzdiskussion S7 vs Codesys fange ich jetzt nicht an - das ist müssig. Vielleicht kann HausSPSler hier ein abschließenden Kommentar geben.
 
Also nochmal beim Onlinechange passiert garnichts. Die Verbindung läuft einwandfrei weiter.
Naja und zu dem Kommentar mit SIEMENS kann ich nur sagen, dass SIEMENS sich in Sachen Support einen Sch... um die Kunden kümmert ;-) naja egal.
 
Zurück
Oben