Limit download speed, Dateinamen herausfinden (InetGet, WinHttp, TCP?)

  • Hey!
    Ich programmiere grade einen Downloader. Momentan downloade ich die Dateien mit InetGet nachdem ich den geneauen Link mit WinHttp herausgefunden habe.
    Das funktioniert auch alles, nur möchte ich jetzt die Geschwindigkeit begrenzen. Es handelt sich um größere dateien und ich möchte in der Zeit auch noch im Internet surfen usw. können.
    Gibt es eine Möglichkeit? Ich könnte auch stadt InetGet WinHttp oder TCP zum download benutzen.
    Noch ein Problem ist der Dateiname. Wie kann ich einstellen, dass ich die Datei in einen bestimmten Ordner mit dem original Dateinamen laden will?
    Danke schonmal im Vorraus!

  • Ich halte es für unwahrscheinlich, dass es mit InetGet geht. Mit WinHTTP eventuell. Und mit TCP bestimmt. Aber ich weiß leider nicht, ob da irdgenwo die Daten in einem Buffer gespeichert werden. Wenn das der Fall ist, dann könnte ich dir nur sagen, wie man die Geschwindigkeit mit der die Daten aus diesem Zwischenspeicher in eine Datei kopiert werden reguliert. Die Downloadgeschwindigkeit würde dann gleich bleiben.
    Ich hab mal ein Beispiel für WinHTTP geschrieben. Das scheint zu funktionieren.

    Spoiler anzeigen
    [autoit]

    #include <WinHTTP.au3>
    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    ; -Author: name22(http://www.autoit.de)

    [/autoit] [autoit][/autoit] [autoit]

    $nSpeedLimit = 300 ;Bytes per Second
    $iBytesPerStep = 8192 ;Number of Bytes read every iteration - does not affect speed
    $iSleepTime = Round($iBytesPerStep / $nSpeedLimit)

    [/autoit] [autoit][/autoit] [autoit]

    $sURL_Download = 'http://translation.autoit.de/autoitinfo/hilfedateien/AutoIt-Hilfe-Deutsch-3.3.6.1-Stand-27_05_11.zip'
    $sPathDst = @DesktopDir

    [/autoit] [autoit][/autoit] [autoit]

    $vNTdll = DllOpen("ntdll.dll")

    [/autoit] [autoit][/autoit] [autoit]

    $tPrecSleep = DllStructCreate("int64 time;")
    $pPrecSleep = DllStructGetPtr($tPrecSleep)

    [/autoit] [autoit][/autoit] [autoit]

    $aURL_Split = _WinHttpCrackUrl($sURL_Download)

    [/autoit] [autoit][/autoit] [autoit]

    $hOpen = _WinHttpOpen()
    $hConnect = _WinHttpConnect($hOpen, $aURL_Split[2])
    $hRequest = _WinHttpOpenRequest($hConnect, "GET", $aURL_Split[6])

    [/autoit] [autoit][/autoit] [autoit]

    _WinHttpSendRequest($hRequest)
    _WinHttpReceiveResponse($hRequest)

    [/autoit] [autoit][/autoit] [autoit]

    $iSizeBytes = _WinHttpQueryHeaders($hRequest, $WINHTTP_QUERY_CONTENT_LENGTH)

    [/autoit] [autoit][/autoit] [autoit]

    $nT2 = TimerInit()
    Switch _WinHttpQueryDataAvailable($hRequest)
    Case True
    $vData = Binary("")
    $nT = TimerInit()
    Do
    DllStructSetData($tPrecSleep, "time", -10000 * ($iSleepTime - TimerDiff($nT)))
    DllCall($vNTdll, "dword", "ZwDelayExecution", "int", 0, "ptr", $pPrecSleep)
    $nT = TimerInit()
    $vData &= _WinHttpReadData($hRequest, 2, $iBytesPerStep)
    Until @error

    [/autoit] [autoit][/autoit] [autoit]

    _WinHttpCloseHandle($hRequest)
    _WinHttpCloseHandle($hConnect)
    _WinHttpCloseHandle($hOpen)

    [/autoit] [autoit][/autoit] [autoit]

    $hFile = FileOpen($sPathDst & "\" & StringTrimLeft($aURL_Split[6], StringInStr($aURL_Split[6], "/", 0, -1)), 18)
    FileWrite($hFile, $vData)
    FileClose($hFile)

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(64, "Info", "Download completed." & @CRLF & "Time: " & Int(TimerDiff($nT2)) & @CRLF & "Speed: " & $iSizeBytes / Int(TimerDiff($nT2)))
    Case False
    MsgBox(16, "Error", "No Data available.")
    EndSwitch

    [/autoit] [autoit][/autoit] [autoit]

    DllClose($vNTdll)

    [/autoit]
  • Danke für die Antwort erstmal.
    Das sieht gut aus und scheint auch funktionieren. Aber:
    1. Ich verstehe das nicht. Kannst dus mir erklären? Die WinHttp sachen gehen ja noch bzw. kann ich in der dokumentation nachlesen aber die dllcalls geben mir den Rest :D
    2. Ich habe versucht eine 100Mb Testdatei (http://speedtest.qsc.de/100MB.qsc) mit 1Mb/s (1024*1024 Bytes) herunterzuladen. Es passiert aber nichts (Habs schon ca. 5 Minuten laufen lassen, hab ne DSL 16000 leitung) und das Script hat einen extrem hohen CPU verbrauch.

  • Der extrem hohe CPU Verbrauch kommt von AutoIts schlechten Performanceverhältnissen. AutoIt ist nunmal eine Interpretersprache und stößt bei stark geschwindigkeitsabhängigen Anwendungen schnell an die Grenzen.
    Ein niedrigeres Speedlimit schont die CPU eher, weil hier der Prozessor größtenteils im leerlauf ist (wegen den langen Sleep-zeiten).
    AutoIt dürfte aus oben genannten Gründen vielleicht eher nicht für deine Zwecke geeignet sein (wenn du ein Limit über 500 anstrebst).
    Falls dir das nichts ausmacht, hab ich hier noch eine korrigierte Version (wenn AutoIt nicht schneller als das Downloadlimit gearbeitet hat, wurde eine negative Zahl an die Sleep Funktion gesendet). Außerdem hab ich den Code kommentiert.

    Spoiler anzeigen
    [autoit]

    #include <WinHTTP.au3>
    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    ; -Author: name22(http://www.autoit.de)

    [/autoit] [autoit][/autoit] [autoit]

    $nSpeedLimit = 1024 * 100 ;Bytes per Second
    $iBytesPerStep = 8192 ;Number of Bytes read every iteration - does not affect speed
    $iSleepTime = Round($iBytesPerStep / $nSpeedLimit * 1000) ;Die Zeit die jeder Durchgang minimal benötigen muss um die Geschwindigkeit nicht zu überschreiten

    [/autoit] [autoit][/autoit] [autoit]

    $sURL_Download = 'http://speedtest.qsc.de/100MB.qsc'
    $sPathDst = @DesktopDir

    [/autoit] [autoit][/autoit] [autoit]

    $vNTdll = DllOpen("ntdll.dll") ;Öffnet die NTDll die eine Funktion zum pausieren eines Prozesses enthält

    [/autoit] [autoit][/autoit] [autoit]

    $tPrecSleep = DllStructCreate("int64 time;")
    $pPrecSleep = DllStructGetPtr($tPrecSleep)

    [/autoit] [autoit][/autoit] [autoit]

    $aURL_Split = _WinHttpCrackUrl($sURL_Download) ;URL in Bestandteile zerlegen

    [/autoit] [autoit][/autoit] [autoit]

    $hOpen = _WinHttpOpen() ;WinHTTP session starten
    $hConnect = _WinHttpConnect($hOpen, $aURL_Split[2]) ;Mit Server vebinden
    $hRequest = _WinHttpOpenRequest($hConnect, "GET", $aURL_Split[6]); GET Request für Datei erstellen

    [/autoit] [autoit][/autoit] [autoit]

    _WinHttpSendRequest($hRequest) ;Request senden
    _WinHttpReceiveResponse($hRequest) ;Antwort abwarten

    [/autoit] [autoit][/autoit] [autoit]

    $iSizeBytes = _WinHttpQueryHeaders($hRequest, $WINHTTP_QUERY_CONTENT_LENGTH) ;Content-Length (Dateigröße in diesem Fall) aus Responseheader auslesen

    [/autoit] [autoit][/autoit] [autoit]

    $nT2 = TimerInit()
    Switch _WinHttpQueryDataAvailable($hRequest) ;Wenn Daten verfügbar sind...
    Case True
    $vData = Binary("") ;Leeren Binärstring initialisieren
    $nT = TimerInit()
    Do
    $iSleepTemp = ($iSleepTime - TimerDiff($nT))
    If $iSleepTemp > 0 Then ;Damit die Funktion keine negative Zeit als Parameter empfängt (ich bin mir nicht sicher wie die Funktion reagiert, da sie kaum dokumentiert ist...)
    DllStructSetData($tPrecSleep, "time", -10000 * $iSleepTemp) ;Legt die Zeit die gewartet werden soll fest
    DllCall($vNTdll, "dword", "ZwDelayExecution", "int", 0, "ptr", $pPrecSleep) ;Pausiert den Prozess für eine bestimmte Zeit
    EndIf
    $nT = TimerInit() ;Timer um zu beobachten wie lange der DLL Call benötigt.
    $vData &= _WinHttpReadData($hRequest, 2, $iBytesPerStep) ;Liest ein Datenpaket der angegebenen Größe aus

    [/autoit] [autoit][/autoit] [autoit]

    Until @error ;Download fertig, keine Daten mehr auslesbar

    [/autoit] [autoit][/autoit] [autoit]

    $hFile = FileOpen($sPathDst & "\" & StringTrimLeft($aURL_Split[6], StringInStr($aURL_Split[6], "/", 0, -1)), 18) ;Datei wird im Binärmodus erstellt/geöffnet und nach Originaldatei auf dem Server benannt
    FileWrite($hFile, $vData) ;Daten in Datei schreiben
    FileClose($hFile)

    [/autoit] [autoit][/autoit] [autoit]

    _WinHttpCloseHandle($hRequest)
    _WinHttpCloseHandle($hConnect)
    _WinHttpCloseHandle($hOpen)

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(64, "Info", "Download completed." & @CRLF & "Time: " & Int(TimerDiff($nT2)) & @CRLF & "Speed: " & Round($iSizeBytes / Int(TimerDiff($nT2))))
    Case False
    MsgBox(16, "Error", "No Data available.")
    EndSwitch

    [/autoit] [autoit][/autoit] [autoit]

    DllClose($vNTdll) ;Dll schließen

    [/autoit]


    Hier hab ich die Geschwindigkeit auf 100KB/s festgelegt. Laut meiner Firewall hält das Programm diese Grenze auch relativ genau ein, aber mit nur 100KB/s dauert das natürlich ewig ^^.
    Es könnte bei größeren Dateien aber ein weiteres Problem auftreten... Die gesamten heruntergeladenen Daten werden zuerst im Arbeitsspeicher gesammelt und dann in eine Datei geschrieben. Ich hab zwar 8GB Arbeitsspeicher, aber wenn ich jetzt eine Datei runterladen würde die größer als 8 GB ist verlangsamt das den ganzen PC (dann wird nämlich in die Auslagerungsdatei geschrieben... Von allen Prozessen...). Das lässt sich relativ leicht ändern, indem man ein RAM Limit festlegt nach dem die Daten erstmal in die Datei geschrieben und aus dem RAM gelöscht werden. Aber da ich nicht weiß ob du überhaupt noch interesse hast, warte ich lieber bevor ich jetzt mein Beispiel umschreibe.

  • Jetzt versteh ich das ganze. Aber warum benutzt du nicht einfach sleep statt dem Dllcall?
    Ich hab die 100mb datei auch mal länger laufen lassen:
    Es war auf ein Limit von 1mb/s ausgelegt, hat aber nur mit 100kb/s geladen. Lag denk ich am Prozessor der zu fast 100% ausgelastet war.
    Ein Problem an dem Script wäre auch das der Dateiname nicht im Link enhalten ist (das ganze läuft über eine ID).
    Aber wenn es nicht mit hohen Geschwindigkeiten zurecht kommt ist das sowiso nicht wichtig. Wie bereits erwähnt soll das ein Downloader sein, der in der Geschwindigkeit zwar verstellbar, aber trotzdem auf wunsch noch so schnell wie möglich sein soll.
    Im Notfall muss ich mich dann auf InetGet beschränken. Dann kann ich aber kaum noch surfen. Kennst du vielleicht einen über Parameter steuerbaren Downloader der in einer effektiveren Programmiersprache geschrieben ist?

    Immerhin hab ich schonmal einiges gelernt :thumbup:

  • Zitat

    Aber warum benutzt du nicht einfach sleep statt dem Dllcall?


    Sleep ist ungenauer und bei allem unter 10ms erst recht.

    Zitat

    Es war auf ein Limit von 1mb/s ausgelegt, hat aber nur mit 100kb/s geladen


    Was hast du denn als Zahl für das Limit eingegeben? Und was hast du für einen Prozessor? Bei meiner 4GHz CPU sind 500 als Limit kein Ding, obwohl AutoIt nur einen der 4 Kerne nutzen kann.

    Zitat

    Ein Problem an dem Script wäre auch das der Dateiname nicht im Link enhalten ist (das ganze läuft über eine ID).


    Den kannst du ja selbst festlegen.

    Zitat

    Kennst du vielleicht einen über Parameter steuerbaren Downloader der in einer effektiveren Programmiersprache geschrieben ist?


    Da fällt mir nur noch das Firefox Add-On DownThemAll ein. Da kannst du die Downloadgeschwindigkeit begrenzen.
    Aber sowas gibt es bestimmt, befrag mal Google. Falls die nichts wissen kann dir vielleicht noch jemand anders aus dem Forum helfen ;).

  • Ich hab 1024*1024 als speed angegeben. Also 1b * 1024 = 1kb 1kb *1024 = 1mb?!
    Ich hab dual core 2,1Ghz.
    Ich brauche aber den Original dateinamen. Ist mir auch ein Rätsel wo andere Downloader den hernemen. Google werd ich morgen mal in ruhe fragen. Hab nur jetzt erstmal hier gefragt falls du zufällig schon ne perfekte lösung kennst, am besten was das nur über die Konsole läuft und sich garnicht zeigt ^^
    Bin dann erstmal weg bis morgen.

  • Also bei mir ist 740KB/s maximum wenn ich 1024*1024 angebe. Vielleicht sind 100KB/s dann dein maximum.... An was genau das jetzt liegt weiß ich nicht. Meine CPU ist doppelt so schnell, aber das kann nicht der einzige Grund sein. Versuch mal die Zeilen mit dem Sleep und $nT etc. auszukommentieren und schau dir dann mal die Werte an.

  • Hi,

    Erst mal kannst du die URL auch per _WinHttpCrackUrl in die Einzelteile zerlegen, statt eigene Regex und Stringfunktionen zu verwenden.
    Bei großen Dateien ist außerdem das sofortige Schreiben in eine Datei sinnvoll statt die Daten im Arbeitsspeicher zusammenzubauen.
    Der Dateiname wird manchmal im Content-Disposition Header per filename-Parameter mitgeliefert.

  • Zitat

    Der Dateiname wird manchmal im Content-Disposition Header per filename-Parameter mitgeliefert.


    Cool, das wusste ich noch nicht :).

    Zitat

    Erst mal kannst du die URL auch per _WinHttpCrackUrl in die Einzelteile zerlegen, statt eigene Regex und Stringfunktionen zu verwenden.


    Mach ich ja auch... Das mit dem Dateinamen hab ich nur schnell zusammengebastelt. Wenn er da was anderes haben will kann er es ja so machen. ;)

    Zitat

    Bei großen Dateien ist außerdem das sofortige Schreiben in eine Datei sinnvoll statt die Daten im Arbeitsspeicher zusammenzubauen.


    Das ist mir auch klar, aber das Problem ist wieder einmal AutoIt. Wenn ich jedes 8192KB Datenpaket direkt in die Datei schreiben lasse, dann wird der Download abnormal verlangsamt (bei mir teilweise auf weit unter 100KB/s obwohl das Limit höher ist. Ich hab halt keine SSD ^^).
    Man könnte jetzt entweder mit größeren Datenpaketen experimentieren (das könnte aber auch eisfreaks Bandbreite für längere Zeit beanspruchen), oder man könnte die Downloadpakete kleiner machen als die Pakete die in die Datei geschrieben werden (also FileWrite nicht bei jedem Schleifendurchgang aufrufen).

  • Man könnte jetzt entweder mit größeren Datenpaketen experimentieren (das könnte aber auch eisfreaks Bandbreite für längere Zeit beanspruchen), oder man könnte die Downloadpakete kleiner machen als die Pakete die in die Datei geschrieben werden (also FileWrite nicht bei jedem Schleifendurchgang aufrufen).


    Am besten wäre wohl ein asynchroner Schreibvorgang mit WinAPI-Funktionen.


  • Mag sein. Aber wie stellst du dir das vor? Kannst du vielleicht die nötigen Funktionen nennen? Ich würde gerne versuchen das mal einzubauen.


    Erstelle die Datei mit CreateFile als OVERLAPPED. Dann einen DLLStruct-Buffer von z.B. 10MB, Download 10MB an Daten, dann setze das in die DLLStruct und starte mit WriteFile den Schreibvorgang. Lade weiter Daten runter. Mit GetOverlappedResult prüfst du, ob der Schreibvorgang fertig ist und wenn ja, dann starte einen neuen Schreibvorgang. Wenn du die 10MB geladen hast, aber noch nicht schreiben kannst, warte so lange bis es geht.

  • Aber das löst ja alles nicht das Problem der hohen CPU auslastung :(
    Ich hab auch bisher keinen Downloader gefunden der sich über Kommandozeilenparameter ordentlich steuern lässt. Ich mach es momentan so, dass die Links am Ende in einer HTML datei gespeichert werden, so dass man mit DownThemAll nurnoch alle Markieren und hinzufügen muss. Ich hoffe ich finde noch eine bessere Lösung.

    Jetzt ein anderes Problem:
    Ich möchte das ganze mit dlcs (Link containern) kompatibel machen. Das ist eigentlich kein Problem. Ich benutze dcrypt.it zum entschluesseln der Container.
    Aber einige Seiten geben keinen "richtigen" downloadlink für den Container (mit .dlc am Ende), sondern einen der IDs benutzt (z.B. http://linkcrypt.ws/container/1gQY…IreQjQEs7ngcYy6). Die müsste ich dann runterladen und auf dcrypt.it wieder hochladen. Das runterladen ist kein Problem, das werd ich wahrscheinlich mit WinHttp machen da ich dann keine Temporäre datei erstellen muss und das alles über den RAM laufen kann. Das uploaden werd ich denk ich auch irgentwie hinkriegen. Das Problem ist, dass dcrypt. it den container als beschädigt ansieht wenn ich ihn mit WinHttp oder InetGet herunterlade. Wenn ich ihn manuell herunterlade funktioniert alles.
    Hier ist ne beispiel Seite mit nem Container (Nur zum Test erstellt, der Downloadlink da funktioniert nicht wirklich): linkcrypt.ws/container/1gQYYH3Byk60rrmjskEleSEkzmuR0eZhSxJ70qPG2c68C8mwXIreQjQEs7ngcYy6
    Hat einer ne Idee warum das nicht funktioniert und wie es funktionieren könnte?

  • Kenne ich. Aber mir geht es darum, das mit diesen Links gearbeitet werden soll. Ich weiss nicht ob dieser Service legal ist also Poste ich hier nicht die Seite, aber die Links sollen einem Premium Link generator übergeben und die Premium Links dann heruntergeladen werden.
    Und über parameter steuerbar ist Jdownloader soweit ich weiss auch nicht? Aber selbst wenn, er kommt mit den Premium links nicht zurecht.

  • ...
    Jetzt ein anderes Problem:
    Ich möchte das ganze mit dlcs (Link containern) kompatibel machen. Das ist eigentlich kein Problem. Ich benutze dcrypt.it zum entschluesseln der Container.
    Aber einige Seiten geben keinen "richtigen" downloadlink für den Container (mit .dlc am Ende), sondern einen der IDs benutzt (z.B. http://linkcrypt.ws/container/1gQY…IreQjQEs7ngcYy6). ... Das Problem ist, dass dcrypt. it den container als beschädigt ansieht wenn ich ihn mit WinHttp oder InetGet herunterlade. Wenn ich ihn manuell herunterlade funktioniert alles.
    Hier ist ne beispiel Seite mit nem Container (Nur zum Test erstellt, der Downloadlink da funktioniert nicht wirklich): linkcrypt.ws/container/1gQYYH3Byk60rrmjskEleSEkzmuR0eZhSxJ70qPG2c68C8mwXIreQjQEs7ngcYy6
    Hat einer ne Idee warum das nicht funktioniert und wie es funktionieren könnte?

    Darum geht es ja auch eigentlich.

  • Es hat keine AGB deshalb würde ich es als erlaubt sehen. Es macht ja nichts anderes als der Browser. Es schickt ein package mit den Links und wertet die Antwort aus.