Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Netzwerke (https://www.delphipraxis.net/14-netzwerke/)
-   -   Delphi Drucken im Netzwerk mit TidLpr: "Socket already in use" (https://www.delphipraxis.net/145616-drucken-im-netzwerk-mit-tidlpr-socket-already-use.html)

skyobserver 5. Jan 2010 09:01


Drucken im Netzwerk mit TidLpr: "Socket already in use&
 
Ich versuche mit der TidLpr Komponente von Indy im Netzwerk zu drucken.
Einen einzelnen Druckauftrag zu senden klappt auch einwandfrei.

Wenn ich aber kurz darauf noch einen Druckauftrag sende erhalte ich
den Fehler "Socket already in use" bei der Methode TidLpr.Connect.
Erst nach 120 Sekunden kann ich wieder drucken.

Wo kann man diesen TimeOut einstellen damit
Ausdrucke in kurzer Folge möglich sind?

Delphi-Quellcode:
procedure PrintLabel;
begin
  idlpr.Host := '192.168.0.100';
  idlpr.Connect;
  if idlpr.Connected then
  begin
    idlpr.PrintFile('C:\Print.tmp');
    idlpr.Disconnect;
    idlpr.DisconnectSocket;
  end
  else ShowMessage('Fehler beim Versenden des Printfiles');
end;

Luckie 5. Jan 2010 09:20

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Wird auch wieder richtig getrennt? Wird das zugehörige Ereigniss ausgelöst? Was passiert, wenn du die Komponente jedes mal dynamisch erstellst und wieder frei gibst?

skyobserver 5. Jan 2010 09:50

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Ich habe nach dem "Disconnecten" idLpr.Disconnected
abgefragt und es ist auch True.

Komponente auflösen und jedesmal neu instanzieren habe
ich noch nicht probiert (sollte bei einer Komponente
eigentlich nicht erforderlich sein...).

Allerdings hilft es auch nichts das ganze Delphi-Programm
zu schließen und erneut zu starten (was einem Destroy/Create
wohl gleichkommen sollte...)

Habe im Internet nach diesem Problem gesucht und gelesen,
daß der Drucker den Port wohl noch eine Weile reserviert
hält um sicherzustellen, daß die Übertragung komplett ist
und nicht noch etwas hinterherkommt.
Dieser TimeOut soll sich angeblich beim herstellen der
Vebindung über LPR-Protokoll angeben lassen...

Assertor 5. Jan 2010 12:53

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Hallo skyobserver,

der Fehler kommt daher, dass der genutzte Port in den FD_WAIT State geht, um dem TCP Stack genügend Zeit zur Beendigung der Connection zu geben. Für TIdLPR und TIdRSH zwingen wir den Client dazu, sich mit einem lokalen Port in einer festen Range zu verbinden, bevor die Connection zum Server hergestellt wird. Dies ist erforderlich.

Intern passiert über die Port-Range von TIdTCPClient.BoundPortMin und .BoundPortMax.

Normalerweise, wenn man eine feste Client IP Adresse vorgibt, wird einfach der nächste freie Port aus der Range genommen. Wenn man jetzt aber die Wildcard-IP 0.0.0.0 nutzt (default), ist das Bind an den ursprünglichen Port erfolgreich, obwohl dieser im FD_WAIT State ist.

Das führt zum "Socket already in use" (10048). Das ist also nicht Indy-spezifisch, sondern vom OS.

Workaround:
- ~ 60 Sekunden warten, bis der lokale Port aus dem FD_WAIT state ist
- TIdTCPClient.BoundIP auf eine lokale IP setzen

.BoundPort, .BoundPortMin und .BoundPortMax sollte man hingegen nur setzen, wenn man einen guten Grund hat.

Bei dem zweiten Workaround gibt es das Problem, dass ein System mehrere IPs haben kann. Dort die richtige für die Connection zum LPR Server auszuwählen erfordert dann etwas Arbeit. Die TIdTCPClient Properties sollten bei TIdLPR über die Vorfahrenklasse aber erreichbar sein.

Tut mir leid, dass es keinen einfacheren Weg gibt, das beste wäre für hochperformante Geschichten den LPR Drucker dem WinSpooler zuzuweisen, der hier automatisch die o.g. Workarounds implementiert. Also direkt gesagt: Nicht das Rad neu erfinden ;)

Viele Grüße,

Assertor

skyobserver 5. Jan 2010 14:44

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Zitat:

~ 60 Sekunden warten, bis der lokale Port aus dem FD_WAIT state ist
Die Wartezeit bis die nächste Connection akzeptiert wird beträgt
bei Windows XP nach meinen Tests genau 120 Sekunden. Das ist für
meine Zwecke viel zu lang - ich muß Labels im Sekundentakt drucken
können.


Zitat:

TIdTCPClient.BoundIP auf eine lokale IP setzen
Habe ich schon probiert - der "Fehler" bleibt...



Einmal "Connecten" und mehrmals Drucken geht auch nicht...

skyobserver 5. Jan 2010 15:24

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Zitat:

das beste wäre für hochperformante Geschichten den LPR Drucker dem WinSpooler zuzuweisen, der
hier automatisch die o.g. Workarounds implementiert. Also direkt gesagt: Nicht das Rad neu erfinden
Ich suche nach einem besseren Rad: Es ist sehr praktisch, wenn man einen Druckjob direkt auf
eine IP-Adresse schicken kann, ohne den Drucker auf einem Server oder einer Workstation
installieren zu müssen. Wenn in einer Firma defekte Rechner getauscht werden müssen ist
immer darauf zu achten das alle Einstellungen bzw. installierte Drucker beim neuen Rechner
auch vorhanden sind. Es ist sehr aufwändig Ersatz-PCs bei gelegentlichen Änderungen ständig
synchron zu halten - vor allem wenn der Betrieb 24 Stunden produziert und man nicht mal 10
Minuten Zeit bekommt um die Festplatte zu clonen.
Wenn man Bereitschaftsdienst hat ist man außerdem sehr dankbar, wenn der Schichtelektriker
Nachts um 04:00 mal eben selbst den PC umstöpselt und man weiterschlafen kann. Ich muß wohl
nicht erwähnen wie "toll" es ist um die Zeit in die Firma zu fahren zu müssen um einen PC zu
tauschen...


Wenn sich die TimeOut-Zeit nicht einstellen läßt dann muß natürlich das "Standard-Rad" herhalten...

...aber der (EDV-)Mensch strebt ja nach Verbesserungen :)

Assertor 5. Jan 2010 15:50

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Hallo skyobserver,

Zitat:

Zitat von skyobserver
Ich suche nach einem besseren Rad: Es ist sehr praktisch, wenn man einen Druckjob direkt auf
eine IP-Adresse schicken kann, ohne den Drucker auf einem Server oder einer Workstation
installieren zu müssen. Wenn in einer Firma defekte Rechner getauscht werden müssen ist
immer darauf zu achten das alle Einstellungen bzw. installierte Drucker beim neuen Rechner
auch vorhanden sind. Es ist sehr aufwändig Ersatz-PCs bei gelegentlichen Änderungen ständig
synchron zu halten - vor allem wenn der Betrieb 24 Stunden produziert und man nicht mal 10
Minuten Zeit bekommt um die Festplatte zu clonen.
Wenn man Bereitschaftsdienst hat ist man außerdem sehr dankbar, wenn der Schichtelektriker
Nachts um 04:00 mal eben selbst den PC umstöpselt und man weiterschlafen kann. Ich muß wohl
nicht erwähnen wie "toll" es ist um die Zeit in die Firma zu fahren zu müssen um einen PC zu
tauschen...

:shock: Ich sage mal diplomatisch, dass ist ungünstige Prozessoptimierung ;)

Probier mal folgendes (geht nur, wenn der Rechner nur 1 IP hat):
Delphi-Quellcode:
uses
  ..., IdStack; // IdStack Unit einbinden
...

...
procedure PrintLabel;
begin
  idlpr.BoundIP := GStack.LocalAddress; // holt sich die lokale IP
  idlpr.Host := '192.168.0.100';
  idlpr.Connect;
  if idlpr.Connected then
  begin
    idlpr.PrintFile('C:\Print.tmp');
    idlpr.Disconnect;
    // idlpr.DisconnectSocket; // nicht nötig, gibt es in neueren Indys auch nicht mehr
  end
  else ShowMessage('Fehler beim Versenden des Printfiles');
end;
Du könntest dann noch beim Connect mit einer kleinen Schleife und Timeout dem Port etwas Zeit geben, frei zu werden. Das Problem mit 10048 (WSAADDRINUSE) ist leider MS bekannt und as-designed, da muß so eine Lösung herhalten.

Gruß Assertor

skyobserver 5. Jan 2010 16:21

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Habe ich auch schon probiert...Problem bleibt bestehen...

Hatte die BoundIP zur Sicherheit auch schon "hart kodiert"
aber um die Wartezeit komme ich nicht herum!

Habe auch schon probiert das Printfile über den DOS-Befehl "LPR" (per ShellExecute)
zu versenden. Damit bekomme immerhin acht Drucke versand...

Assertor 5. Jan 2010 16:45

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Hallo skyobserver,

ja, das es mit Shelltools auch nicht klappt ist klar: Die PortRange für LPR ist da auch schnell erschöpft. Ist halt eine RFC Umsetzung von Windows.

Probier es doch mal andersherum:

Delphi-Quellcode:
procedure PrintLabel;
begin
  // das auskommentierte mal irgendwo allgemein festlegen (Konstruktor o.ä.)
  // idlpr.BoundIP := GStack.LocalAddress; // holt sich die lokale IP
  // idlpr.Host := '192.168.0.100';
  if not idlpr.Connected then
    idlpr.Connect;
  idlpr.PrintFile('C:\Print.tmp');
end;
So wird nicht immer ein neuer Port geöffnet, sondern nur wenn keine Connection besteht. Vorteil dürfte sein, dass wenn der Druck schnell hintereinander erfolgt, der vorhandene Socket genutzt wird. Wenn der Socket nicht offen ist, wird eben ein neuer geöffnet.

Nach RFC dürften ca. 10 Sockets für LPR da sein (iirc 721-731). Wenn Du jetzt jedesmal den Socket schließt (+ Timeout von Windows OS, überlicherweise 60-120 sek) bist Du sonst schnell am Limit.

Mit o.g. dürfte es aber keine Raise Conditions mehr geben.

Fehler kannst Du übrigens - so wie auch den Status - über den IdLPR.OnLPRStatus Event handeln.

Gruß Assertor

skyobserver 6. Jan 2010 07:44

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Die Idee hatte ich auch schon...dann bekomme ich beim zweiten
Druck zwar keine Fehlermeldung aber auch keinen Ausdruck und
das Programm friert ein...

Assertor 9. Jan 2010 12:26

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Hallo skyobserver,

Zitat:

Zitat von skyobserver
Die Idee hatte ich auch schon...dann bekomme ich beim zweiten
Druck zwar keine Fehlermeldung aber auch keinen Ausdruck und
das Programm friert ein...

Hmm, ich habe das mal zur Prüfung weitergeleitet, vielleicht hat jemand noch eine Idee. Auf jeden Fall ist es bekannt: Das auch Dein Dos-Tool schnell fehlschlägt zeigt ja das grundsätzliche Problem aber auf, also ist es nicht Indy-Spezifisch.

Gruß Assertor

Assertor 9. Jan 2010 17:04

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Hallo skyobserver,

probier mal bitte folgendes, um die Beschränkung der RFC 1179 zu umgehen:

Delphi-Quellcode:
IdLPR1.Host := 'brn_906b9b';
IdLPR1.Queue := 'BINARY_P1';

IdLPR1.BoundPortMin := 512;
IdLPR1.BoundPortMax := 1024;
IdLPR1.BoundPort := IdLPR1.BoundPortMin;

while (not IdLPR1.Connected) and (IdLPR1.BoundPort <= IdLPR1.BoundPortMax) do
begin
  try
    IdLPR1.Connect;
  except
    On E: EIdSocketError do
    begin
      if E.LastError = 10048 then // Socket already in use...
        IdLPR1.BoundPort := IdLPR1.BoundPort + 1
      else
        raise;
    end;
    On E: Exception do
      raise;
  end;
end;

if IdLPR1.Connected then
begin
  IdLPR1.PrintFile('D:\temp.prn');
  IdLPR1.Disconnect;
end
else
  ShowMessage('Fehler beim Versenden des Printfiles');
Das ist zwar nicht schön und nicht schnell bei vielen Druckaufträgen, sollte aber funktionieren. Sobald dann die ersten Ports in der Range wieder frei werden (die 60-120 Sekunden), wird beim nächsten Auftrag wieder ein kleinerer Port genommen.

Eine Abbruchmöglichkeit und Fehlerprüfung solltest Du noch einbauen, aber Du hast schonmal eine Idee davon.

Edit: Die Exception wirst Du in der IDE trotzdem noch sehen, also bitte entsprechend steuern & testen.

Gruß Assertor

Assertor 10. Jan 2010 14:33

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Hallo skyobserver,

Zitat:

Zitat von Assertor
probier mal bitte folgendes, um die Beschränkung der RFC 1179 zu umgehen

Mir ist noch was eingefallen, da die 10048 nicht im Bind sondern im Connect geraist wird.

Überlass mal Windows die lokale Port Zuweisung, dann solltest Du nicht so schnell als Limit kommen:

Delphi-Quellcode:
IdLPR1.Host := 'brn_906b9b';
IdLPR1.Queue := 'BINARY_P1';

IdLPR1.BoundPortMin := 0;
IdLPR1.BoundPortMax := 0;
IdLPR1.BoundPort := 0;

IdLPR1.Connect;
try
  IdLPR1.PrintFile('meinfile.prn');
finally
  IdLPR1.Disconnect;
end;
Gerade testest, geht auch schnell hintereinander. Ohne die langsamen Exceptions.

Hinweis: Das klappt nur, wenn der LPR keine bestimmte Client-Port-Range voraussetzt, wobei dies seit vielen Jahr überlicherweise kein Problem mehr ist.

Gruß Assertor

jus 18. Jan 2010 22:32

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Zitat:

Zitat von Assertor
Hallo skyobserver,

Zitat:

Zitat von Assertor
probier mal bitte folgendes, um die Beschränkung der RFC 1179 zu umgehen

Mir ist noch was eingefallen, da die 10048 nicht im Bind sondern im Connect geraist wird.

Überlass mal Windows die lokale Port Zuweisung, dann solltest Du nicht so schnell als Limit kommen:

Delphi-Quellcode:
IdLPR1.Host := 'brn_906b9b';
IdLPR1.Queue := 'BINARY_P1';

IdLPR1.BoundPortMin := 0;
IdLPR1.BoundPortMax := 0;
IdLPR1.BoundPort := 0;

IdLPR1.Connect;
try
  IdLPR1.PrintFile('meinfile.prn');
finally
  IdLPR1.Disconnect;
end;
Gerade testest, geht auch schnell hintereinander. Ohne die langsamen Exceptions.

Hinweis: Das klappt nur, wenn der LPR keine bestimmte Client-Port-Range voraussetzt, wobei dies seit vielen Jahr überlicherweise kein Problem mehr ist.

Gruß Assertor


Hallo,

ich habe bisher noch nie was mit der Indy Komponente gearbeitet. Ich habe bei mir die IdLPR Komponente auf Form geklatscht. Bei den Versuch den obigen Code unter Delphi 2007 auszuführen kennt mein Delphi einige Befehle wie z.B."IdLPR1.Host" nicht.

Benötigt man dafür noch spezielle Units?

Lg,
jus

jus 19. Jan 2010 19:06

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Hallo,

für diejenigen, wie ich, die verzweifelt diesen Code unter Delphi 2007 nicht zum Laufen gebracht haben, weil die mitgelieferte Indy Komponente viele Befehle wie z.B. "IdLPR1.Host",.... nicht kennt. :wall:
Den entscheidenden Hinweis brachte erst nach einer mühsamen Internet Recherche folgendes Forum:
https://forums.codegear.com/message....ssageID=148883

Ich habe erst zum Laufen gebracht, nachdem ich die alte Indy deinstalliert habe und die neueste Indy Version in Delphi installiert habe. Ich habe mir die "IndyTiburon.zip" runtergladen.

:)

Lg,
jus

Assertor 19. Jan 2010 19:47

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Hallo jus,

Zitat:

Zitat von jus
Hallo,

für diejenigen, wie ich, die verzweifelt diesen Code unter Delphi 2007 nicht zum Laufen gebracht haben, weil die mitgelieferte Indy Komponente viele Befehle wie z.B. "IdLPR1.Host",.... nicht kennt. :wall:
Den entscheidenden Hinweis brachte erst nach einer mühsamen Internet Recherche folgendes Forum:
https://forums.codegear.com/message....ssageID=148883

Ich habe erst zum Laufen gebracht, nachdem ich die alte Indy deinstalliert habe und die neueste Indy Version in Delphi installiert habe. Ich habe mir die "IndyTiburon.zip" runtergladen

Danke für die Aufklärung! Ich habe nicht bedacht, das dieser Thread auch von Indy-Neulingen gelesen werden würde - und war nicht schnell genug Deinen Post rechtzeitig zu lesen und Dir zu helfen...

Der Vollständigkeit halber: Unter der alten Indy Version gab es m.E. TIdLPR.Connect('Host'), das hätte die Delphi Code-Vervollständigung der IDE anzeigen müssen...

Gruß Assertor

jus 20. Jan 2010 09:19

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Hallo Assertor,

trotzdem vielen Dank fürs antworten! :)

Lg,
jus

Assertor 9. Mär 2010 21:01

Re: Drucken im Netzwerk mit TidLpr: "Socket already in
 
Hallo,

nachdem ich hier seinerzeit eine Lösung präsentiert hatte, habe ich den Punkt immer mal wieder aufgegriffen und nun zusammen mit Remy Lebeau eine allgemeine Lösung direkt in Indy implementiert. O.g. Lösungsansatz (BoundPortMin und Max auf 0) ist mit neuem SVN also nicht mehr nötig.

Problemursache ist, dass trotz Verzicht auf eine Wildcard-IP (BoundIP ist gesetzt) der Windows Socket nicht im Bind(), sondern im Connect() einen Fehler (WSAEADDRINUSE) meldet. Dies entspricht nicht dem Dokumentierten Verhalten aus dem MSDN und ist mal wieder ein Windows-spezifischer Sonderfall :roll:

Gruß,
Assertor


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:40 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz