# libnodave gibt nicht alle Handles wieder frei



## Manni01 (20 Mai 2009)

Hallo Forum,

ich habe eine Anwendung mit libnodave (0.8.4.4) in VB.NET 2008 geschrieben, mit der ich in einem Thread die Verbindung immer wieder öffnen und schliessen möchte, um bei Verbindungsabbruch ein automatisches Neuverbinden zu ermöglichen. Beim Connect schicke ich vorher ein Ping um die Verbindung zu testen (die Windows Timeouts dauern mir zu lange).

Das Problem: Mit jedem neuen Connect steigt die Anzahl der verwendeten Handles bis auf dem Rechner nichts mehr geht. Bei der momentan eingestellten Zeit von 1 Sec. dauert das schon etwas, aber später soll das Ganze schneller laufen. Bei 10 ms sind es nur ein paar Minuten bis das Netzwerk zusammenbricht.

Folgendes ist mir noch aufgefallen:

Wenn ich die libnodave.net.dll über einen Verweis einbinde, steigen die verwendeten IO-Bytes langsam an, bis das LAN nicht mehr funktioniert, um dann wieder fast auf 0 abzufallen (siehe angehängte Grafik).
Erstelle ich jedoch eine eigene libnodave.dll in meinem Projektordner mit der Code aus der libnodave.net.cs, dann zeigt sich dieses Verhalten nicht.

Die Menge der verwendeten IO-Bytes ist umso höher, je kürzer die Zeit zwischen Trennen und neuem Verbinden ist.

Ich hoffe, es hat jemand einen Tipp für mich, wie ich dieses Problem lösen kann; vielen Dank jedenfalls schonmal für Eure Bemühungen....

Viele Grüße Manni

Hier noch der wesentliche Code dazu:

'Endlosschleife zum Lesen der Stati aus der SPS
While True
'Alle 10 Sekunden versuchen die Verbindung herzustellen, wenn ok dann weiter
While Me.Connect = False
Threading.Thread.Sleep(10000)
End While
'Lese den Status der Stationen
Me.daveResult = Me.daveConn.readBytes(libnodave.daveDB, 1, 0, Me.ReadBufferStati.Length, Me.ReadBufferStati)
If Me.daveResult <> 0 Then
Me.bgw1.ReportProgress(1, "Fehler beim Lesen der Stati aus der SPS! Error: " + Me.daveResult.ToString)
Else
Me.ConvertStati()
End If
DisConnect()
'Den anderen Threads ein bisschen Zeit geben
Application.DoEvents()
Threading.Thread.Sleep(1000)
End While

Hier die Function Connect:
If My.Computer.Network.Ping(IpAdresse, 100) = False Then
Me.bgw1.ReportProgress(1, "Die TCP-Verbindung zur SPS " + IpAdresse + " kann nicht hergestellt werden!")
Return False
End If
Me.daveFds.rfd = libnodave.openSocket(102, IpAdresse)
If Me.daveFds.rfd <= 0 Then
ReturnFalse
EndIf
Me.daveFds.wfd = Me.daveFds.rfd
Me.daveInterface = New libnodave.daveInterface(Me.daveFds, "SPS1", 0, libnodave.daveProtoISOTCP, libnodave.daveSpeed187k)
Me.daveInterface.setTimeout(1000000)
Me.daveConn = New libnodave.daveConnection(Me.daveInterface, 0, Rack, Slot)
Me.daveResult = Me.daveConn.connectPLC
IfMe.daveResult <> 0 Then
DisConnect()
ReturnFalse
EndIf
Return True

Und hier die Function Disconnect:
Me.daveConn.disconnectPLC()
libnodave.closePort(Me.daveFds.rfd)
libnodave.closePort(Me.daveFds.wfd)
Me.daveInterface.disconnectAdapter()


----------



## Ralle (20 Mai 2009)

Ich hatte ein ähnliches Problem bei einer Verbindung über S7Online. daraufhn hat AFK ein Handle in die Funktion (open, close) eingebaut, so daß beim Schließen einer Verbindung auch dieses Handle richtig behandelt wurde. Internas weiß ich nicht mehr, vielleicht schreibt er ja noch etwas dazu.

Hast du das gelesen? : http://www.sps-forum.de/showthread.php?t=18724&highlight=Handle

Aber insgesamt ist es auch aus Performancegründen keine gute Idee, die Verbindung ständig zu öffnen und zu schließen.


----------



## Manni01 (20 Mai 2009)

Danke für die schnelle Antwort, Ralle. Den Thread hatte ich noch nicht gefunden; interessant. Wegen der Performance hast Du natürlich Recht, so richtig glücklich bin ich mit der 'Lösung' auch nicht; hatte bisher jedoch noch keine bessere Idee.

Es soll eine robuste Anwendung werden, die auch nach Verbindungsabbrüchen (Kabel ziehen usw.) nach möglichst kurzer Zeit wieder zu sich findet (ohne Neustart oder dgl.).

Grundsätzlich sollte es doch möglich sein, die Verbindung zu schließen und alle Ressourcen wieder freizugeben. Gibt's da vielleicht 'ne andere Möglichkeit? Ich muss mit der maximal möglichen Anzahl von Verbindungen auf die SPS zugreifen, dabei werden ständig irgendwelche Verbindungen geöffnet und geschlossen; irgendwann, und sei es nach Wochen, tritt das Problem sonst wieder auf.

Ich würde mich über weitere Antworten freuen....

Viele Grüße, Manni


----------



## Question_mark (20 Mai 2009)

*Verbindungen*

Hallo,



			
				Ralle schrieb:
			
		

> Aber insgesamt ist es auch aus Performancegründen keine gute Idee, die Verbindung ständig zu öffnen und zu schließen.



*ACK*

Verbindungen werden bei mir beim Start der Applikation aufgebaut und erst beim Beenden der App abgebaut. Einmal aufgebaute logische Verbindungen können zwar z.B. durch Unterbrechungen im Netzwerk physikalisch unterbrochen werden, aber niemals logisch beendet werden..
Professionelle Kommunikationstreiber haben eigentlich mit sowas kein Problem.

Ansonsten ein Livebit zwischen App und SPS rumtoggeln lassen und nur bei Problemen die Verbindung ab- bzw. wieder aufbauen.

Gruß

Question_mark


----------



## Ralle (21 Mai 2009)

Vielleicht mußt du die Sockets per Hand schließen, irgendwas war da noch, allerdings hab ich da mit Delphi7 kein Problem.

Ich lasse die Verbindung offen, lese allerdings auch fortlaufend in einem Thread Daten aus der SPS. Wird das Kabel gezogen, bekomme ich eigentlich sofort beim Lesen einen Fehler zurück. Daraufhin schließe ich die Verbindung und baue sie nach 5 oder 10 Sekunden wieder auf. Entweder ich bekomme dabei gleich einen Fehler oder spätestens beim ersten Leseversuch. Dann geht es wieder von vorn los. Das funktioniert problemlos, bisher konnte ich nicht feststellen, daß sich Speicher oder Handles dabei aufbauen. Bis auf das Problem, daß im genannten Link schon beschrieben wurde.


----------



## Manni01 (21 Mai 2009)

Danke für die Anregungen. Vielleicht eine kurze Beschreibung der Anwendung: In meinem Anwendungsfall muss ich mit über 20 Stationen die über Profibus mit einer Zentral-SPS verbunden sind, sprechen. Jede Station hat je 100 Byte Sende- und Empfangsdaten, zusätzlich zu einem allgemeinen Statusdatenbereich. Insgesamt habe ich ca. 3,2 KByte zu lesen und 3,2 KByte zu senden. Das dauert ca. 200 ms. Jede Station kann bis zu 15 versch. Kommandos auslösen, worauf meine Anwendung teilweise recht komplexe Aktionen auf einer SQL-Datenbank ausführt und die Ergebnisse zurückmeldet.

Wenn nun ein Kommando von einer Station gesetzt wird, sind die ersten 200 ms vergangen bevor ich es merke. Hinzu kommen 200-500 ms Datenbankaktionen. Wenn die Ergebnisse vorliegen, schreibe ich sie in meinen Sendepuffer. Dann können nochmal 200 ms vergehen bevor die Daten in der SPS sind. Alles in allem muss ich im worst-case-Fall mit ca. 1 sec. Verzögerung rechnen.

Da mir das eigentlich zuviel ist, wollte ich von Anfang an eine Anwendung schreiben, die ereignisgesteuert auf die Kommandos aus den Stationen reagiert: Also einen Thread, der nur den Kommando-Datenbereich pollt (ca. 100 Byte = 10 ms) und für jede Station einen weiteren Thread, der die Kommandos ausführt, wenn welche anstehen. Da die CPU 315-2DP/PN nur 14 ISO-Verbindungen gleichzeitig akzeptiert, bin ich gezwungen, ereignisgesteuert die Verbindungen auf- und auch wieder abzubauen, um die Ressourcen für die nächsten Verbindungen freizugeben.

Sollte einmal kein Verbindungsaufbau möglich sein, weil die Verbindungsressourcen CPU-seitig erschöpft sind, versucht der entsprechende Thread es eben nach 1 sec. nocheinmal. Ich hätte somit ein dynamisches, sich selbst regulierendes, schnelles Kommunikationsverhalten mit den Stationen.

Soweit die Theorie.

Du, Ralle und auch afk in dem anderen Thread, schreibt, dass es da irgendwo evtl. ein Problem mit dem Schliessen der Sockets gibt, und dass man die von Hand schliessen sollte. Hättet ihr da einen Tipp für mich, wie ich das anstellen kann? Das würde ich gerne nochmal ausprobieren.

Besten Dank für Eure Hilfe....

Gruß, Manni


----------



## Rainer Hönle (21 Mai 2009)

Sind alle SPSen mit dieser Zentrale verbunden? Liegen dann auf der Zentral-SPS alle Daten? Warum sind die Resourcen der 315er das Problem? Über eine Verbindung können doch beliebig viele unterschiedliche Daten gelesen und geschrieben werden. Oder habe ich mal wieder die Aufgabenstellung nicht verstanden?


----------



## - chris - (21 Mai 2009)

Zum schließen der Sockets gibt es die Befehle aus der Windows API:
closesocket
WSACleanup

http://msdn.microsoft.com/en-us/library/ms737582(VS.85).aspx
http://msdn.microsoft.com/en-us/library/ms741549(VS.85).aspx

Bei mir (PureBasic) sieht es dann so aus:


```
Procedure cleanUp()

Protected res.l

If dc <> 0
  res = daveDisconnectPLC(dc)
  daveFree(dc)
  dc = 0
EndIf

If di <> 0
  res = daveDisconnectAdapter(di)
  daveFree(di)
  di = 0
EndIf

If ph <> 0
  res = closePort(ph)
  closesocket_(ph)
  WSACleanup_()
  ph = 0
EndIf

EndProcedure
```


----------



## Manni01 (21 Mai 2009)

Ja, alle SPS'en (200er) sind mit der Zentrale, also mit der 315-2DP/PN über Profibus verbunden. Das Problem ist die parallele Verarbeitung der Anfragen, da gleichzeitig mehr als 20 Anfragen abgearbeitet werden müssen, die CPU aber nur 14 gleichzeitige Verbindungen unterstützt.

Würde ich das alles über eine Verbindung, die in einem Thread läuft, machen, muss ich alle Daten, alles Stationen ständig übertragen. So läuft es ereignisgesteuert, und es werden nur die Daten übertragen, die gerade benötigt werden.

Zusätzlich habe ich keine Wartezeiten, durch den Parallelisierungseffekt der verschiedenen Threads.

Grüße, Manni


----------



## Manni01 (21 Mai 2009)

@ -chris- Danke für den Tipp. Ich arbeite mit VB.NET 2008 und der Wrapper-DLL libnodave.net.dll. Dort gibt es kein 'davefree' und auch die disconnect-Funktionen akzeptieren keine Parameter. So wie ich es verstanden habe, ist das auch gar nicht nötig.

Aber wie kann ich dann den Socket freigeben??

Gruß Manni


----------



## - chris - (21 Mai 2009)

Um den Socket freizugeben dienen diese Befehle
aus der Windows API:

closesocket
WSACleanup

http://msdn.microsoft.com/en-us/library/ms737582(VS.85).aspx
http://msdn.microsoft.com/en-us/library/ms741549(VS.85).aspx

In PureBasic können die Befehle direkt verwendet werden.


----------



## Manni01 (21 Mai 2009)

Das kann ich so in .NET leider nicht verwenden. .NET hat da einen anderen Ansatz. Trotzdem danke für Deinen Tipp.

Hab's jetzt nochmal so probiert:
Die Funktion davefree über den Wrapper (C#) zugänglich gemacht:

[DllImport("libnodave.dll"/*, PreserveSig=false*/)]
public static extern int daveFree(IntPtr p);

Die Funktion dann so aufgerufen:

libnodave.daveFree(Me.daveConn.pointer)
libnodave.daveFree(Me.daveInterface.pointer)
 
Hat nichts verändert, war auch nur ein eher halbherziger Versuch.

Gruß, Manni


----------



## Rainer Hönle (21 Mai 2009)

Manni01 schrieb:


> Ja, alle SPS'en (200er) sind mit der Zentrale, also mit der 315-2DP/PN über Profibus verbunden. Das Problem ist die parallele Verarbeitung der Anfragen, da gleichzeitig mehr als 20 Anfragen abgearbeitet werden müssen, die CPU aber nur 14 gleichzeitige Verbindungen unterstützt.
> 
> Würde ich das alles über eine Verbindung, die in einem Thread läuft, machen, muss ich alle Daten, alles Stationen ständig übertragen. So läuft es ereignisgesteuert, und es werden nur die Daten übertragen, die gerade benötigt werden.
> 
> ...


D.h. es soll über 20 parallele Verbindungen gepollt werden? Wo ist die Ereignissteuerung?


----------



## Manni01 (21 Mai 2009)

Nein, es wird nur über eine Verbindung gepollt, die anderen (20) werden dann aufgrund der Daten aus der ständig pollenden Verbindung, ereignisgesteuert auf- und wieder abgebaut.


----------



## - chris - (21 Mai 2009)

Die Befehle:

closesocket
WSACleanup

lassen sich nicht einbinden?

Diese Befehle befinden sich beide, laut MSDN, in dieser DLL:

Ws2_32.dll


----------



## - chris - (21 Mai 2009)

Hier nochmal ein kleines Beispiel:

Das Problem ist der Befehl 'closePort', der Befehl
alleine schliesst die Verbindung nicht.

Wenn ich die Befehle closesocket und WSACleanup
entferne wird die Verbindung erst abgebaut wenn das
Programm beendet wird.

Beobachtet habe ich das mit dem Programm TCPView.


```
peer$ = "192.168.1.111"

; Verbindung aufbauen
ph = openSocket(102, peer$)

; Wartezeit
Delay(2000)

; Verbindung schliessen
res = closePort(ph)

closesocket_(ph)

WSACleanup_()

; Wartezeit
Delay(10000)

; Programmende
End
```


----------



## Manni01 (21 Mai 2009)

@ -chris-
Du hast natürlich Recht. Habe die Befehle nun folgendermaßen eingebunden und aufgerufen:

DeclareFunction WSACleanup Lib"Ws2_32.dll" () AsInteger
DeclareFunction closesocket Lib"Ws2_32.dll" (ByVal Socket AsInteger) AsInteger

closesocket(Me.daveFds.rfd)
closesocket(Me.daveFds.wfd)
WSACleanup()

Scheint jetzt zu funktionieren. Ich lass es mal bis morgen durchlaufen, mal sehen was passiert. Werde mich dann nochmal dazu melden.

In jedem Fall erstmal ein herzliches Dankeschön an alle, und noch einen schönen Vatertag (bzw. was davon übrig ist).

Gruß, Manni


----------



## Manni01 (21 Mai 2009)

Kurzer Zwischenstand:

Leider wütet da immer noch irgendwo ein Speicherfresser. Nicht mehr so extrem wie vorher, aber er ist immer noch da. Habe es jetzt ca. 2 Std. laufen lassen, und der Prozess benötigt jetzt ca. 70 MB mehr Speicher als vorher.

Ich denke, so komme ich nicht weiter. Wenn ich libnodave nutzen will, muss ich mich von dem ständigen 'Verbinden und Trennen' verabschieden; Schade.

Entweder ich baue das Programm anders auf, oder ich programmiere das Lesen und Schreiben von Daten per ISO on TCP in VB.NET nach. Hätte da jemand eine Doku, wie die Telegramme aussehen müssen? Kenne mich mit C leider nicht aus, so das ich die Info's nicht aus dem libnodave-Code extrahieren kann (würde zu lange dauern).

Wenn das nicht zuviel Aufwand ist, würde ich es angehen und die Klasse dann zur Verfügung stellen.

Viele Grüße,

Manni


----------



## Rainer Hönle (21 Mai 2009)

Wenn du dich nicht vor einer kommerziellen Lösung scheust, schicke mir einfach mal genauere Infos (welche Operanden müssen wann gelesen werden und was soll dann wann passieren etc.). Ich kann dir dann sagen (schreiben) wie ich es mit AGLink (auch in VB.net) realisieren würde. Kommen eigentlich mehrere Threads zum Einsatz oder handelst du alles im Hauptthread ab?


----------



## Manni01 (22 Mai 2009)

Danke für dein Angebot. Damit habe ich grundsätzlich kein Problem, wenn der Kunde es bezahlt. Ich habe auch schon mit unterschiedlichen externen, kommerziellen Lösungen gearbeitet. Ich möchte nur gerne eine eigene, schlanke Lösung haben, um DB's zu lesen und zu beschreiben.

Bei anderen Protokollen, z.B. Modbus-TCP/UDP, habe ich das ebenfalls, mit dem Ergebnis nicht auf externe Komponenten angewiesen zu sein, deutlich schnellere Kommunikation sowie volle Eingriffmöglichkeiten zu haben, falls Sonderlösungen gefragt sind. Nur sind diese Protokolle eben öffentlich zugänglich, was, nebenbei bemerkt, m.E. den Erfolg derselben ausmacht.

Ich werde, falls niemand die Info's hat oder zur Verfügung stellt, nochmal einen Versuch unternehmen, den Datenverkehr selbst aufzuzeichnen und zu entschlüsseln. Falls mir das zu lange dauert, werde ich libnodave.net.dll nutzen und mein Programm anders aufbauen (ich sehe da noch ein paar Optimierungsmöglichkeiten).

Viele Grüße, Manni


----------



## marcengbarth (22 Mai 2009)

Bau doch eine TCP-Verbindung von der SPS zu deiner Applikation auf, dann kannst du dir die Daten aus dem SPS-Programm heraus schicken lassen und sparst dir die Zeit immer wieder die selben Daten anzufragen.

In der SPS kannst du dann mit AGSEND / AGRECV arbeiten.


----------



## Manni01 (22 Mai 2009)

Ist auch eine Variante, über die ich nachgedacht habe. Es muss dann aber bei Änderungen/Anpassungen auch das SPS-Programm angepasst werden. Dies ist vom Kunden nicht so gewünscht. Und bei diesem Projekt ist es so, dass das SPS-Programm jemand anders schreibt.


----------



## Ruud (28 Mai 2009)

*Thread, ivoke*

Hallo,

Ich denke das deine applikation nicht in die selbe threat zuruck komt.

du kannst vielleicht die funktion "Invoke" benutzen.

Hier is ein link fur was extra information.

http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx


gruss Ruud


----------



## Zottel (28 Mai 2009)

Manni01 schrieb:


> Das kann ich so in .NET leider nicht verwenden. .NET hat da einen anderen Ansatz. Trotzdem danke für Deinen Tipp.
> 
> Hab's jetzt nochmal so probiert:
> Die Funktion davefree über den Wrapper (C#) zugänglich gemacht:
> ...


Die Funktion daveFree sollte nichts bringen und ist daher auch nicht in libnodave.NET.dll enthalten. Der Grund ist, daß diese Funktion automatisch im Destruktor von Pseudopointer, der Basisklasse von daveInterface und daveConnection, aufgerufen wird.


----------



## Zottel (28 Mai 2009)

- chris - schrieb:


> Um den Socket freizugeben dienen diese Befehle
> aus der Windows API:
> 
> closesocket
> ...



So habe ich es mir ursprünglich gedacht. Schaut euch aber mal diesen Thread an. Der Themenstarter hatte auch Probleme, handles wieder zu schließen. Ich habe ihm daher eine Testversion gemacht, in der closesocket aus Libnodave aufgerufen wird. Mein Verdacht ist das winsock.dll irgendwie Buch führt, aus welchem Kontext handles geöffnet wurden und es daher wichtig ist, sie aus demselben Kontext wieder freizugeben.

http://www.sps-forum.de/showthread.php?t=26040&highlight=libnodave&page=2

Eine erweiterte Version von libnodave.dll findet sich dort im angehängten Archiv.


----------

