TCP FileTransfer Performance

  • Aus langeweile habe ich gestern mal wieder ein wenig mit Client Server Geschichten rumgespielt. Die Kommunikation zwischen Client und Server scheint soweit auch zu funktionieren, jedoch sollen nun auch größere Dateien (1GB) per TCP zwischen beiden übertragen werden.
    Dabei gibt es zwei Probleme in meinem Script.

    1. Die Dateigröße der übertragenen Datei weicht vom Original ab. Ich vermute das passiert, beim letzten eingelesenen Part der Datei, aber ich habe keine wirkliche Idee wie ich das beheben kann.

    2. Der Transfer ist verdammt langsam, was für einen Transfer übers Internet zwar noch akzeptabel wäre, aber solange ich client und server auf dem selben Computer betreibe sollten doch eigentlich deutlich höhere Transferraten möglich sein. Ich erreiche mit den aktuellen Scripten lediglich 4-5 MB/s, verzehnfache ich die Größe der Dateiparts im Server und den zugehörigen Lesepuffer im Client, dann erreiche ich immerhin 11 mb/s, was im Vergleich zum normalen Kopieren weniger als ein zehntel der maximalen Geschwindigkeit ist. Ist die Größe der Parts jedoch zu hoch reagiert der Server nicht mehr richtig, weil er relativ lange mit dem Senden eines einzelnen Parts beschäftigt ist, das muss auf jedenfall vermieden werden. Auch sollen später langsame Clients (Internet) nicht ausgebremst werden, weil sie Möglicherweise mehrere Sekunden für einen Dateipart benötigen, bevor sie wieder andere Dinge wie GUI cmds abarbeiten können. Die Partgröße zu erhöhen fällt also schonmal weg um die Performance zu verbessern.


    Vielleicht hat ja jemand von euch Ideen wie man die Performance erhöhen könnte und den Fehler bei der Übertragung behebt.


    Anmerkungen zu den Scripten:


    Der Server sollte per Scite ausgeführt werden um alle Debug Meldungen aus der Console lesen zu können.
    Der Client sollte kompiliert werden.

    Um einen Dateitransfer zu starten muss zuerst der Server, dann der Client gestartet werden, im client dann den connect Button nutzen und im Anschluss den getfile Button.
    Vor dem ersten Testlauf muss im Server noch die globale Variable $testfile angepasst werden, hier sollte eine Datei mit mindestens 500 MB verwendet werden.

    Client
    [autoit]


    #include <ButtonConstants.au3>
    #include <EditConstants.au3>
    #include <GUIConstantsEx.au3>
    #include <WindowsConstants.au3>

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

    TCPStartUp()

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

    Global $IP = "127.0.0.1"
    Global $socket = -1
    Global $datasocket = -1
    Global $peerAliveTimer = TimerInit()
    Global $maxlen = 512000 ; tcprecv buffer
    Global $delim = "$##$"
    Global $alivecounter = 0
    Global $filesize = -1
    Global $retrievedData = 0
    Global $filename = "unknown.dat"
    Global $filehandle = -1

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

    #Region ### START Koda GUI section ### Form=
    $guimain = GUICreate("Client Window", 444, 385, 192, 124)
    $editconsole = GUICtrlCreateEdit("", 24, 104, 369, 249)
    $btngetfile = GUICtrlCreateButton("getfile", 24, 0, 60, 24)
    $btnsend = GUICtrlCreateButton("send", 290, 30, 60, 40, $WS_GROUP)
    $btnconnect = GUICtrlCreateButton("connect", 370, 30, 60, 40, $WS_GROUP)
    $Inputmsg = GUICtrlCreateInput("", 24, 30, 250, 20)
    $inputNick = GUICtrlCreateInput("Nick", 24, 60, 250, 20)
    GUISetState(@SW_SHOW)
    #EndRegion ### END Koda GUI section ###

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

    While 1
    if $socket <> -1 Then
    $data = TCPRecv($socket,$maxlen)
    if $data <> "" Then
    $aTCPmsg=StringSplit($data,$delim,1)
    For $x=1 To $aTCPMsg[0]
    If $aTCPMsg[$x]="" Then ContinueLoop
    Switch $aTCPMsg[$x]
    Case "~!~server forced disconnect~!~"
    GUICtrlSetData($editConsole,"INFO received by server: " & "Sitzung vom Server beendet (kick)" & @CRLF & GUICtrlRead($editConsole))
    TCPCloseSocket($socket)
    $socket = -1
    GUICtrlSetState($btnconnect,$GUI_ENABLE)
    Case "~!~server shutdown~!~"
    GUICtrlSetData($editConsole,"INFO received by server: " & "Sitzung vom Server beendet (shutdown)" & @CRLF & GUICtrlRead($editConsole))
    TCPCloseSocket($socket)
    $socket = -1
    GUICtrlSetState($btnconnect,$GUI_ENABLE)
    Case "~!~Nick registered~!~"
    GUICtrlSetData($editConsole,"INFO received by server: " & "Nickname wurde vom Server registriert" & @CRLF & GUICtrlRead($editConsole))
    Case "~!~Nick changed~!~"
    GUICtrlSetData($editConsole,"INFO received by server: " & "Nickname wurde vom Server geändert" & @CRLF & GUICtrlRead($editConsole))
    Case Else
    GUICtrlSetData($editConsole,"MSG received by server: " & $aTCPMsg[$x] & @CRLF & GUICtrlRead($editConsole))
    EndSwitch
    Next
    EndIf

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

    If TimerDiff($peerAliveTimer) > 10000 Then
    $peerAliveTimer = TimerInit()
    $alivecounter+=1
    $alive = sendmessage($socket,"~!~alive~!~")
    ;WinSetTitle($guimain,"",$alivecounter)
    if $alive = 0 Then
    GUICtrlSetData($editConsole,"TCP Connection lost: " & $socket & @CRLF & GUICtrlRead($editConsole))
    TCPCloseSocket($socket)
    $socket = -1
    GUICtrlSetState($btnconnect,$GUI_ENABLE)
    EndIf
    EndIf
    EndIf

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

    if $datasocket <> -1 And $filehandle <> -1 Then
    $alive = sendmessage($datasocket,"~!~alive~!~")
    if $alive = 0 Then
    TCPCloseSocket($datasocket)
    FileClose($filehandle)
    $datasocket=-1
    $filehandle=-1
    Else
    $binarydata = TCPRecv($datasocket,1024000,1)
    FileWrite($filehandle,$binarydata)
    EndIf
    EndIf

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

    $nMsg = GUIGetMsg()
    Switch $nMsg
    Case $GUI_EVENT_CLOSE
    _terminateClient()
    Exit
    Case $btnsend
    if $socket = -1 Then
    MsgBox(0,"Client Fehler","Server not connected.")
    Else
    sendmessage($socket,GUICtrlRead($Inputmsg))
    GUICtrlSetData($editConsole,"Data send to server: " & GUICtrlRead($Inputmsg) & @CRLF & GUICtrlRead($editConsole))
    GUICtrlSetData($Inputmsg,"")
    EndIf
    Case $btnconnect
    if $socket = -1 Then
    $socket = TCPConnect( $IP, 5555)
    If $socket = -1 Then
    MsgBox(0,"Client-Fehler","Verbindung zum Server fehlgeschlagen")
    Else
    $peerAliveTimer=TimerInit()
    GUICtrlSetState($btnconnect,$GUI_DISABLE)
    sendmessage($socket,"~!~IDENT~!~" & guictrlread($inputNick))
    EndIf
    Else
    MsgBox(0,"Client Fehler","Already connected to server")
    EndIf
    Case $btngetfile
    $datasocket=TCPConnect($ip,5555)
    sendmessage($datasocket,"~!~getFile~!~")
    if Not FileExists(@ScriptDir & "\downloads") Then DirCreate(@ScriptDir & "\downloads")
    $filehandle = FileOpen(@ScriptDir & "\downloads\" & $filename,17)
    ;$filehandle = FileOpen("F:\" & $filename,17)
    EndSwitch

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

    WEnd

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

    Func _terminateClient()
    sendmessage($socket,"~!~disconnect~!~")
    TCPCloseSocket($socket)
    TCPShutdown()
    EndFunc

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

    Func sendmessage($socket,$msg)
    Return TCPSend($socket,$msg & $delim)
    EndFunc

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


    #include <array.au3>
    #include <ButtonConstants.au3>
    #include <ComboConstants.au3>
    #include <EditConstants.au3>
    #include <GUIConstantsEx.au3>
    #include <StaticConstants.au3>
    #include <WindowsConstants.au3>

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

    TCPStartUp()

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

    Global $ServerIP = "127.0.0.1"
    Global $MainSocket = TCPListen($ServerIP, 5555)
    If $MainSocket = -1 Then Exit MsgBox(0,"Server-Fehler","Listen Socket konnte nicht erstellt werden")
    Global $aPeers[1]=[0]
    Global $aDataTransfers[1]=[0]
    Global $peercount = 0
    Global $maxLen = 512000 ; tcprecv buffer
    Global $peerAliveTimer = TimerInit()
    Global $totalTimer = TimerInit()
    Global $aNickNames[1][2]=[["Socket","Nick"]]
    Global $delim = "$##$"
    Global $testfile = "S:\test.dat" ; <---- ÄNDERN !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

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

    #Region ### START Koda GUI section ### Form=
    $guiServer = GUICreate("Server Control", 364, 414, 1328, 195)
    $labelmain = GUICtrlCreateLabel("Server Control Window", 48, 72, 113, 17)
    $labelpeers = GUICtrlCreateLabel("Connected Peers:", 48, 104, 89, 17)
    $labelpeerscount = GUICtrlCreateLabel($peercount, 192, 104, 30, 17)
    $labelNickName = GUICtrlCreateLabel("", 100, 170, 100, 17)
    $peercombo = GUICtrlCreateCombo("", 48, 200, 153, 25)
    $labelPeerList = GUICtrlCreateLabel("Peerlist", 48, 168, 38, 17)
    $btndiscpeer = GUICtrlCreateButton("dissconnect peer", 240, 192, 97, 41, $WS_GROUP)
    $btndiscallpeers = GUICtrlCreateButton("dissconnect all", 239, 246, 97, 41, $WS_GROUP)
    $btnsend = GUICtrlCreateButton("send", 210, 340, 50, 40, $WS_GROUP)
    $btnsendall = GUICtrlCreateButton("send all", 270, 340, 50, 40, $WS_GROUP)
    $inputmsg = GUICtrlCreateInput("", 40, 350, 150, 20)
    GUISetState(@SW_SHOW)
    #EndRegion ### END Koda GUI section ###

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

    While 1

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

    $newPeer = TCPAccept($MainSocket)
    if $newPeer <> -1 Then
    _ArrayAdd($aPeers,$newPeer)
    $aPeers[0] += 1
    ConsoleWrite("New TCP Connection established: " & $newPeer & @CRLF)
    fillPeerCombo()
    EndIf

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

    if $aPeers[0] > 0 Then
    $resetAliveTimer = True
    For $i=1 to $aPeers[0]
    ; check socket alive
    If TimerDiff($peerAliveTimer) > 10000 Then
    $alive = sendmessage($aPeers[$i],"~!~alive~!~")
    if $alive = 0 Then
    ConsoleWrite("TCP Connection lost: " & $aPeers[$i] & @CRLF)
    $resetAliveTimer = False
    removePeer($i)
    ExitLoop
    EndIf
    EndIf
    ; check new data
    $newdata = TCPRecv($aPeers[$i],$maxLen)
    If $newdata <> "" Then
    ;ConsoleWrite("rawData: " & $newdata & @CRLF)
    $aTCPMsg=StringSplit($newdata,$delim,1)
    ;_ArrayDisplay($aTCPMsg)
    For $x=1 To $aTCPMsg[0] ; msg kann mehrere teilnachrichten enthalten
    If $aTCPMsg[$x]="" Then ContinueLoop
    Switch $aTCPMsg[$x]
    Case "~!~disconnect~!~"
    ConsoleWrite("INFO by client (" & $aPeers[$i] & ") received: Session disconnected" & @CRLF)
    removePeer($i)
    ExitLoop 2
    Case "~!~alive~!~"
    ; ignore it
    Case "~!~getFile~!~"
    _ArrayAdd($aDataTransfers,$aPeers[$i])
    Global $transtimer = TimerInit()
    $filehandle = FileOpen($testfile,16)
    $aDataTransfers[0]+=1
    Case Else
    if StringInStr($aTCPMsg[$x],"~!~IDENT~!~",1) Then
    $nick = StringReplace($aTCPMsg[$x],"~!~IDENT~!~","")
    ConsoleWrite("INFO by client (" & $aPeers[$i] & ") received: Identified with nick (" & $nick & ")" & @CRLF)
    $temp = _ArraySearch($aNickNames,$aPeers[$i])
    If $temp = -1 Then
    ReDim $aNickNames[UBound($aNickNames)+1][2]
    $aNickNames[UBound($aNickNames)-1][0]=$aPeers[$i]
    $aNickNames[UBound($aNickNames)-1][1]=$nick
    sendmessage($aPeers[$i],"~!~Nick registered~!~")
    Else
    $aNickNames[$temp][0]=$aPeers[$i]
    $aNickNames[$temp][1]=$nick
    sendmessage($aPeers[$i],"~!~Nick changed~!~")
    EndIf
    GUICtrlSetData($labelNickName,$nick)
    Else
    ConsoleWrite("MSG by client (" & $aPeers[$i] & ") received: " & $aTCPMsg[$x] & @CRLF)
    EndIf
    EndSwitch
    Next
    EndIf
    Next
    If TimerDiff($peerAliveTimer) > 10000 And $resetAliveTimer = True Then $peerAliveTimer = TimerInit()
    EndIf

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

    If $peercount <> $aPeers[0] Then
    $peercount = $aPeers[0]
    GUICtrlSetData($labelpeerscount,$peercount)
    EndIf

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

    If $aDataTransfers[0] > 0 Then
    For $i=1 To $aDataTransfers[0]
    $datapacket=FileRead($filehandle,1024000)
    If @extended > 0 Then TCPSend($aDataTransfers[$i],$datapacket)
    If @error=-1 Then
    TCPCloseSocket($aDataTransfers[$i])
    _ArrayDelete($aDataTransfers,$i)
    $aDataTransfers[0]-=1
    $size = Round(FileGetSize($testfile)/1024/1024,0)
    $time = Round(TimerDiff($transtimer)/1000,0)
    if @Compiled Then
    MsgBox(0,"file transfer complete","FILE TRANSFER Complete: " & $size & " MB" & @TAB & $time & "s" & @TAB & Round($size/$time,2) & "MB/s")
    Else
    ConsoleWrite("FILE TRANSFER Complete: " & $size & " MB" & @TAB & $time & "s" & @TAB & Round($size/$time,2) & "MB/s" & @CRLF)
    EndIf
    ExitLoop
    EndIf
    Next

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

    EndIf

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

    $nMsg = GUIGetMsg()
    Switch $nMsg
    Case $GUI_EVENT_CLOSE
    _terminateserver()
    Exit
    Case $btndiscpeer
    $socket = GUICtrlRead($peercombo)
    sendmessage($socket,"~!~server forced disconnect~!~")
    ConsoleWrite("Manual peer disconnect: " & GUICtrlRead($peercombo) & @CRLF)
    removePeer(_ArraySearch($aPeers,$socket))
    Case $btndiscallpeers
    ConsoleWrite("Manual peer disconnect All: " & @CRLF)
    For $i=1 to $aPeers[0]
    sendmessage($aPeers[$i],"~!~server forced disconnect~!~")
    TCPCloseSocket($aPeers[$i])
    Next
    Global $temp[1]=[0]
    $aPeers=$temp
    fillPeerCombo()
    Case $btnsend
    $socket = GUICtrlRead($peercombo)
    if $socket <> "" Then
    sendmessage($socket,GUICtrlRead($inputmsg))
    Else
    ConsoleWrite("FATALERROR: no peer selected: " & @CRLF)
    EndIf
    Case $btnsendall
    For $i=1 To $aPeers[0]
    sendmessage($aPeers[$i],GUICtrlRead($inputmsg))
    Next
    EndSwitch

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

    WEnd

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

    Func removePeer($peerID)
    if $peerID = -1 Or $peerID = 0 Then Return ConsoleWrite("FATALERROR: peer not found in array: " & $peerID & @CRLF)
    TCPCloseSocket($aPeers[$peerID])
    _ArrayDelete($aPeers,$peerID)
    $aPeers[0] -= 1
    fillPeerCombo()
    EndFunc

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

    Func fillPeerCombo()
    Local $temp = ""
    For $i=1 to $aPeers[0]
    $temp &= $aPeers[$i] & "|"
    Next
    GUICtrlSetData($peercombo,"")
    if $aPeers[0] <> 0 Then GUICtrlSetData($peercombo,StringTrimRight($temp,1),$aPeers[$aPeers[0]])
    EndFunc

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

    Func _terminateserver()
    For $i=1 to $aPeers[0]
    sendmessage($aPeers[$i],"~!~server shutdown~!~")
    TCPCloseSocket($aPeers[$i])
    ConsoleWrite("TCP Connection disconnected by server: " & $aPeers[$i] & @CRLF)
    Next
    TCPCloseSocket($MainSocket)
    ConsoleWrite("Server socket closed: " & $MainSocket & @CRLF)
    TCPShutdown()
    EndFunc

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

    Func sendmessage($socket,$msg)
    Return TCPSend($socket,$msg & $delim)
    EndFunc

    [/autoit] [autoit][/autoit] [autoit][/autoit]
    Code
    FILE TRANSFER Complete: 1485 MB	345s	4.3MB/s