# libnodave - closePort



## Vbxler (5 Juni 2006)

Hallo an alle.

Ich weis, dieses Verhalten wurde schon mal berichtet, aber ich möchte nachfragen 
ob jemand eine Lösung kennt, da ich keine gefunden habe.

Folgende Situation:
Der User wählt eine Anlage aus, es wird eine Verbindung über libnodave - ISO
TCP zu einer S7-300 aufgebaut. 
Daten werden ausgelese und die Verbindung wieder beende damit, 
der User eine andere Anlage anwählen kann um Daten zu lesen.
Das ganze erfolgt ca. 3-4 mal am Tag manuell durch den Bediener.

Beim Abbau der Verbindung mit 'lRetval = closePort(MydaveType.hPort)'
ist der Inhalt von Retval = 1. Nach dem Abbau der Verbindung ist eine
Glückssache, dass eine Verbindung erneut aufgebaut werden kann oder nicht.
Manchmal kann man das 1 oder 2 mal machen, aber dann hängt die Applikation und man muss sie abwürgen.
Es kommt auch kein Timeout. Man muss das Programm über den Taskmanger beenden.

Nachdem das Programm neu gestartet wurde, kann man das Spiel von neuem beginnen.

Wenn dieses Verhalten auftritt fällt auf, das der Port geöffnet und das
Interface initialisiert wird, aber bei der Herstellung der Verbindung hängt es dann.

Das Ganze lässt sich sowohl bei einer S7-300 mit CP343-1 als auch bei einer VIPA 315SB beobachten.

Eine andere Möglichkeit wäre natürlich, wenn zu mehreren Anlagen gleichzeitig eine Verbindung aufgebaut werden könnte
und diese offen blieben. Aber das habe ich noch nicht durchschaut wie das geht. :???:
Vieleicht kann mir jemand mit einem Beispiel zeigen wie man Verbindungen zu mehreren Steuerungen herstellt.


Danke für jede Hilfe 


Vbxler


----------



## afk (5 Juni 2006)

Seltsames Verhalten, habe ich bei mir nicht, und ich arbeite viel mit libnodave, allerdings entweder mit VIPA, mit einem CP443 oder mit einem NetLink.


Rufst Du beim Schließen der Verbindung daveDisconnectPLC auf ?
Welche Version von lbnodave verwendest Du ?
In welcher Programmiersprache hast Du das Programm erstellt ?
Zeig doch mal ein paar Quellcode-Auszüge der entsprechenden Programmteile.



			
				Vbxler schrieb:
			
		

> Eine andere Möglichkeit wäre natürlich, wenn zu mehreren Anlagen gleichzeitig eine Verbindung aufgebaut werden könnte
> und diese offen blieben. Aber das habe ich noch nicht durchschaut wie das geht. :???:
> Vieleicht kann mir jemand mit einem Beispiel zeigen wie man Verbindungen zu mehreren Steuerungen herstellt.


Das ist eigentlich ganz einfach, Du mußt nur für jede Verbindung daveNewConnection und daveConnectPLC und beim Schließen der Verbindungen daveDisconnectPLC und daveFree für jede Verbindung aufrufen.

Von daveNewConnection bekommst Du als Ergebnis die jeweilige Verbindungsstruktur, die Du dann dementsprechend bei daveRead... und daveWrite... usw. mit übergeben mußt.

Ich hoffe ich habe nichts vergessen, kann es hier Zuhause leider nicht nachprüfen ...


Schönen Feiertag noch.
Gruß Axel


----------



## Vbxler (5 Juni 2006)

Hi afk!

1) verwende libnodave-0.8.2. 

2) die Verbindung wird so abgebaut:
	
	



```
'getrennt hConnection
If IsTrue(MydaveType.hConnection) Then
    lRetval = daveDisconnectPLC(MydaveType.hConnection)  
    
    'prüfen ob ein Fehler aufgetreten
    If lRetval Then sMessage = sMessage & "Fehler beim Trennen der PLC-Verbindung aufgetreten:" & $CrLf & daveStrError(lRetval) & $CrLf

    Call daveFree(MydaveType.hConnection)
    MydaveType.hConnection = 0
End If

'getrennt hInterface
If IsTrue(MydaveType.hInterface) Then
    lRetval = daveDisconnectAdapter(MydaveType.hInterface)
    
    'prüfen ob ein Fehler aufgetreten
    If lRetval Then sMessage = sMessage & "Fehler beim Trennen des Interface aufgetreten:" & $CrLf & daveStrError(lRetval) & $CrLf

    Call daveFree(MydaveType.hInterface)
    MydaveType.hInterface = 0
End If

'getrennt MydaveType.hPort
If IsTrue(MydaveType.hPort) Then
    lRetval = closePort(MydaveType.hPort)

'            'prüfen ob ein Fehler aufgetreten
'            hier tritt der Fehler mit lRetVal = 1 auf
                  
    MydaveType.hPort = 0
End If
```

3) Das Programm ist in PowerBasic 8.01 erstellt.


Servus

Vbxler


----------



## afk (5 Juni 2006)

Warum läßt Du nicht einfach das Interface offen und benutzt für alle Verbindungen dieses eine Interface immer wieder in daveNewConnection ?
Das müßte dann auch deutlich schneller sein.


Gruß Axel


----------



## Vbxler (5 Juni 2006)

Ich habe folgendes versucht:

Button01 - Herstellen der Verbindung  (funktioniert)

```
'Socketverbindung herstellen
'----------------------------
MydaveType.hPort = openSocket(102, MydaveType.sIpAdresse)    ' for ISO over TCP

'wenn kein Fehler beim öffnen des Socket dann verbinden
If IsTrue(MydaveType.hPort) Then
    Control Set Text CbHndl, %IDC_lblhPortData01, Str$(MydaveType.hPort)

    'Interface anmelden
    '-------------------
    MydaveType.hInterface = daveNewInterface(MydaveType.hPort, MydaveType.hPort, "IF1", 0, %daveProtoISOTCP, 0)
    lRetval = daveInitAdapter(MydaveType.hInterface)

    If lRetval Then
        MsgBox "Fehler anmelden Interface " & $CrLf & daveStrError(lRetval)
        Exit Function
    Else
        Control Set Text CbHndl, %IDC_lblhInterfaceData01, Str$(MydaveType.hInterface)
        Call daveSetTimeout(MydaveType.hInterface, 5000)   'Timeout auf 5 sekunden        
    End If

    'Verbindung anmelden
    '--------------------------
    MydaveType.hConnection = daveNewConnection(MydaveType.hInterface, MydaveType.lMpiAdresse, MydaveType.lRack, MydaveType.lSlot)
    lRetval = daveConnectPLC(MydaveType.hConnection)

    If lRetval Then
        MsgBox "Fehler anmelden Verbindung " & $CrLf & daveStrError(lRetval)
        Exit Function
    Else

        Control Set Text CbHndl, %IDC_lblhConnectionData01, Str$(MydaveType.hConnection)
        lVerbunden01 = %TRUE

        'Bestellnummer ermitteln und anzeigen
        If daveGetOrderCode(MydaveType.hConnection, buffer(0)) Then
            Control Set Text CbHndl, %IDC_lblBestellData01, "Fehler Bestnr. ermitteln"
        Else
            'zusammensetzen der Bestellnummer
            For lZeiger = 0 To %daveOrderCodeSize
                sTempStr = sTempStr + Chr$(buffer(lZeiger))
            Next
            'ausgabe Bestellnummer
            Control Set Text CbHndl, %IDC_lblBestellData01, sTempStr
        End If

    End If
Else
    MsgBox "Fehler beim öffnen des Port's aufgetreten"
    Exit Function
End If
```
Button02 - Trenner nur der Connection (keine Fehlermeldung von libnodave)

```
'getrennt hConnection
If IsTrue(MydaveType.hConnection) Then
    lRetval = daveDisconnectPLC(MydaveType.hConnection)

    If lRetval Then MsgBox "daveDisconnectPLC" & $CrLf & daveStrError(lRetval)

    Call daveFree(MydaveType.hConnection)
    MydaveType.hConnection = 0
End If
```
Button03 - Erneutes herstellen der Connection (Fehlermeldung von libnodave -1)  

```
'Verbindung anmelden
'--------------------------
MydaveType.hConnection = daveNewConnection(MydaveType.hInterface, MydaveType.lMpiAdresse, MydaveType.lRack, MydaveType.lSlot)
lRetval = daveConnectPLC(MydaveType.hConnection)

If lRetval Then
    MsgBox "Fehler anmelden Verbindung " & $CrLf & daveStrError(lRetval)
    Exit Function
Else

    Control Set Text CbHndl, %IDC_lblhConnectionData02, Str$(MydaveType.hConnection)

    'Bestellnummer ermitteln und anzeigen
    If daveGetOrderCode(MydaveType.hConnection, buffer(0)) Then
        Control Set Text CbHndl, %IDC_lblBestellData01, "Fehler Bestnr. ermitteln"
    Else
        'zusammensetzen der Bestellnummer
        For lZeiger = 0 To %daveOrderCodeSize
            sTempStr = sTempStr + Chr$(buffer(lZeiger))
        Next
        'ausgabe Bestellnummer
        Control Set Text CbHndl, %IDC_lblBestellData02, sTempStr
    End If

End If
```

Hast Du das so gemeint?


Servus


Vbxler


----------



## afk (5 Juni 2006)

Ja, so hatte ich das gemeint. Was passiert, wenn Du versuchst, eine zweite Verbindung zu öffnen, bevor Du die erste Verbindung schließt ?


Gruß Axel


----------



## Vbxler (6 Juni 2006)

Ich werde das nächstes Wochenende austesten.
Bin unter der Woche nicht zu Hause.

Schöne Arbeitswoche 


Vbxler


----------



## Ralle (6 Juni 2006)

Also ich hab auch bei WLAN-TCPIP-Verbindungen das Problem, daß eine Verbindung, ohne Fehlermeldung von Libnodave, keine Daten mehr liefert und die Verbindung nicht korrekt geschlossen werden kann. Dadurch, daß keine Fehlermeldung kommt, kann ich auch keine korrekte Fehlerbehandlung durchführen, was dazu führt, daß beim Beenden der "Libnodave-Thread" einfriert und das Programm nicht mal mehr per Task-Manager beendet werden kann. (Delphi-Programm) Das Verhalten tritt aber nicht bei allen Verbindungsabbrüchen auf, bin da noch am suchen. Muß auch nicht direkt an Libnodave liegen, könnte auch ein Problem in der Software "darunter" sein.


----------



## afk (6 Juni 2006)

Ralle schrieb:
			
		

> Dadurch, daß keine Fehlermeldung kommt, kann ich auch keine korrekte Fehlerbehandlung durchführen, was dazu führt, daß beim Beenden der "Libnodave-Thread" einfriert und das Programm nicht mal mehr per Task-Manager beendet werden kann. (Delphi-Programm)


Ich nehme mal an, Du arbeitest mit meiner Komponente, oder ? Welcher Thread bleibt denn hängen ?

Meine Komponente erzeugt einen Thread für den Verbindungsaufbau, der einmal versucht, die Verbindung aufzubauen, uns sich selbst danach beendet, und einen weiteren Thread für das zyklische Lesen, der beendet wird, sobald die Verbindung geschlossen wird.

Der Thread für den Verbindungsaufbau soll eigentlich nur verhindern, daß das Programm blockiert ist, während die WinSocks-Bibliothek von Windows versucht, die TCP-Verbindung aufzubauen, weil das ziemlich lange dauern kann. Bei fehlgeschlagenen Verbindungsversuchen habe ich auf PCs bei uns schon bis zu 120 Sek. gemessen. Wenn es dieser Thread ist, der hängenbleibt, und sich das auch nach mehreren Minuten (> 5) nicht heilt, dann hängt der Task in irgendeiner Betriebssystem-Routine vom TCP/IP-Stack, was auch erklären würde, warum Du ihn nicht mal mehr per Taskman abschießen kannst.

Der Thread für das zyklische Lesen erkennt selbständig, wenn Fehler auftreten und schließt dann die Verbindung, und beendet sich damit indirekt auch selbst. Der einzige Fehlercode, der dabei ignoriert wird, ist der von Vbxler bereits erwähnte Code -1. Der ist bei meinen Tests auch sporadisch mal vorgekommen, hatte aber bei mir nie einen kompletten Verbindungsausfall zur Folge. Und da dieser Fehlercode auch in libnodave nicht weiter beschrieben ist (kein Text zu diesem Code, keine symbolische Konstante), ignoriert meine Komponente diesen Fehlercode und läßt den Lese-Thread weiter laufen. Er müßte allerdings auch bei permanent auftretendem Fehlercode -1 ganz normal beendet werden, sobald die Verbindung geschlossen wird. 

Seit Version 0.8.2 arbeitet die Komponente allerdings beim Lesen mit der max. PDU-Größe der jeweiligen CPU, falls die in Folge des Fehlers von libnodave auf 0 gesetzt wird, dann kommt es zu einer Endlos-Schleife im Lese-Thread. 

Ersetze in der NoDaveComponent.pas ganz am Ende doch mal die Methode GetMaxPDUData durch diese Version:


```
//Return the max. datasize in a single read-request.
//~result maximal datasize.
function TNoDave.GetMaxPDUData: Integer;
begin
  Result:=0;
  If Assigned(DaveConn) then
  begin
    Result:=DaveConn^.maxPDUlength - 18;
    If Protocol in [daveProtoIBH, daveProtoIBH_PPI] then Dec(Result, 22);
  end;
  if Result <= 0 then Result:=200;
end;
```
Dadurch wird die Netto-Übertragungsgröße beim Lesen auf mindestens 200 Byte festgelegt, und damit die beschriebene Endlosschleife verhindert. Wäre interessant, ob das was hilft. 

Das erklärt aber nicht, warum Du den Task nicht im Taskman abschießen kannst. Ist dein Delphi-Programm ein Systemdienst ?

Hol Dir doch mal den Process Explorer von SysInternals, damit kann man auch Systemdienste abschießen.


Gruß Axel


----------



## Ralle (6 Juni 2006)

@afk

Ich arbeite mit der nodave.pas und habe mir (zugegebner maßen :-D ) aus deiner Komponente ein paar Typedefinitionen und den Editor mitgenommen. Den Lesethread hatte ich schon mit Prodave und einer AS511.dll in meinem Programm, da man dadurch das Lesen der Daten und die Bedienoberfläche am Besten entkoppeln konnte. Deinen Vermutungen schließe ich mich an, die Verbindungsauf/abbaugeschichte muß ich mir nochmal ansehen, ich hatte immer wieder mit dem Thread Probleme (Beenden klappte nicht korrekt). Das kann natürlich eine Folgeerscheinung der Verbindungsabbauprozedur sein.

Ich hab dein Testprogramm für die Komponente trotzdem mal getestet. Ich bekomme nur neue Daten angezeigt, wenn ich mit dem Mauszeiger über dem Fenster herumfahre (WinXP SP2). Allerdings war ich nicht mit dem Debugger dran, daher weiß ich nicht, ob nur die Aktualisierung des Fensters ein Problem hat, oder der Datenthread.

Bei der S7Online-Verbindung über Libnodave per MPI (CP5511) funktioniert die Verbindung zu einer SPS. Bei der selben Verbindung über TCP kommt nur Datensalat an (nur ein Hinweis, hat nichts mit der Komponente zu tun, ob mit libnodave, entzieht sich meiner Kenntnis.)

PS: Nein, kein Systemdienst.


----------



## Vbxler (19 Juni 2006)

Also ich habe mich jetzt nochmals einen Tag mit Libnodave beschäftigt
und bin der Meinung, dass in der DLL beim schliessen der Verbindung 
eine kleinigkeit nicht in ordnung sein dürfte. Sobald eine Verbindung
abgebaut und eine neue aufgebaut wird, es eine Glückssache das es
funktioniert. 
Bleibt die Verbindung immer aufrecht, läuft alles ohne Probleme. 

Ich weis nicht ob hier im Forum einer der 'Väter' von Libnodave bzw. der
DLL ist, aber es wäre super wenn sich das jemand den Verbindungsabbau ansehen könnte.

Servus


Vbxler


----------



## Rainer Hönle (19 Juni 2006)

Vbxler schrieb:
			
		

> Ich weis nicht ob hier im Forum einer der 'Väter' von Libnodave bzw. der DLL ist, aber es wäre super wenn sich das jemand den Verbindungsabbau ansehen könnte.


Aber selbstverständlich ist der hier. Es ist Zottel.


----------



## Gast (20 Juni 2006)

Hallo,
kann das Problem nicht am WinDoof liegen?
Wenn ich einer TCP/IP Verbindung über Winsocket 2.0 schließe und diese über den gleichen Port wieder öffnen will, muss ich ca. 180 Sek. warten. Winsocket hält den Port für diese Zeit noch offen. Es kommt folgende Fehler:

10048 -- WSAEADDRINUSE
Address already in use. Under normal circumstances, only one socket is permitted to use each socket address. (For example, an IP socket address consists of the local IP address and port number.) This error is usually associated with the bind, connect, and WSAConnect functions. The socket option SO_REUSEADDR can be set with the setsockopt function to 
allow multiple sockets access to the same local IP address and port.


----------



## afk (20 Juni 2006)

Vbxler schrieb:
			
		

> es wäre super wenn sich das jemand den Verbindungsabbau ansehen könnte.


Dazu wäre es für Zottel sicherlich hilfreich, wenn Du die Debug-Ausgaben von libnodave einschaltest und mal postest, das Problem ist kein allgemeiner Fehler, denn bei mir funktioniert das einwandfrei.


Gruß Axel


----------



## afk (20 Juni 2006)

Gast schrieb:
			
		

> Wenn ich einer TCP/IP Verbindung über Winsocket 2.0 schließe und diese über den gleichen Port wieder öffnen will, muss ich ca. 180 Sek. warten. Winsocket hält den Port für diese Zeit noch offen.


Das kann eigentlich nicht das Problem sein, da das den lokalen Port betrifft, und der wird nicht per Bind an einen bestimmten Port gebunden, daher müßte der dynamisch von WinSocks zugewiesen werden, und zwar bei jedem Connect ein neuer (freier) Port.


Gruß Axel


----------



## Vbxler (23 Juni 2006)

Ich habe das Problem jetzt ein wenig umgangen,
indem ich den Dialog vom Hauptprogramm getrennt habe
und als eigenständigen Process augelagert habe.

Im Hauptprgramm rufe ich über 'ShellExecute' den externen 
Process auf - lese Daten aus der SPS und schliesse die Verbindung
und den externen Process. Dadurch ergibt sich beim nächsten start
kein Problem.

Die Datenübertragung läuft mit Libnodave recht flott ab.
Lese 1500 byte aus und das geht so schnell, dass ich 
APISleep einbauen musste, da sonst der Progressbar nicht
schön ausgeschaut hätte.  

Danke für die Hilfe

Vbxler


----------

