Fast buffered file transfer (new code, finished)

Share your advanced PureBasic knowledge/code with the community.
Bonne_den_kule
Addict
Addict
Posts: 841
Joined: Mon Jun 07, 2004 7:10 pm

Fast buffered file transfer (new code, finished)

Post by Bonne_den_kule »

Code updated For 5.20+

Here is a buffered file transfer routine I have made. It is much more advance then Inf0Byt3 idea, and works on both internet and LAN. The code is made to ensure that all data is transfered and not corrupted.
You can specifie size of buffers and enable/disable threads in the inlcude file (for both client and server) to optimize for the code's purpose and best speed. Run the server first and then the client.

Edit: Some bugs removed

Edit2:
Added possible to resume downloading. Just choose the directory where the file to continue downloading to is. To test it, quit the client while uploading, and then restart server and client. Download the same file and choose the same directory.

Edit3:
User/password authentication added and some bugs removed.

Server:

Code: Select all

; File send/receive example with progress(bar). Fast and reliable, works on lan and over internet (both tested)
; The server, receives the file
; By bonne_den_kule - Aug,2006


EnableExplicit
IncludeFile "Include.pb"
EnableGraphicalConsole(1)
OpenConsole()
ConsoleTitle("Server")

Define Quit.b, DataReceived.l, Buffer.l=AllocateMemory(#BufferSize), FileSize.q, FileOffset.q, FileName.s, Reserved.l
Define Path.s, TotaltDataReceived.q, DataInBuffer.l, ClientID.l, StartTime.l, Timer.l, TempDataReceived.l, DataTimer.l
Define Username.s, Password.s
If #Threaded
  Define Buffer2.l=AllocateMemory(#BufferSize), BufferData.DataInfo, WriteThread.l
EndIf

If InitNetwork()
  PrintN("Init network done")
  If CreateNetworkServer(#Server, #Server_Port)
    PrintN("Server created on "+Hostname())
    
    Repeat
    
      Select NetworkServerEvent()
        
        Case #PB_NetworkEvent_Connect
          PrintN("Client connected, ip/hostname: "+IPString(GetClientIP(EventClient())))
        
        Case #PB_NetworkEvent_Disconnect
          PrintN("Client disconnected")
          Quit=1
        
        Case #PB_NetworkEvent_Data
          ClientID=EventClient()
          DataReceived=ReceiveNetworkDataEx(ClientID, Buffer)
          If DataReceived
            PrintN("Valid data received")
            Select PeekB(Buffer+4) ;Reading network message
              
              Case #SendFile ;A message sent from the client
                FileSize=PeekQ(Buffer+5) ;Length of file
                Reserved=PeekL(Buffer+13) ;Reserved space in the packet for other info (to be used in the future maybe)
                FileName=PeekS(Buffer+17) ;File name
                Username=PeekS(Buffer+18+Len(FileName))
                Password=PeekS(Buffer+19+Len(FileName)+Len(Username), DataReceived-19)
                
                If CheckFilename(FileName) And FileSize<>0 And ValidateUser(Username, Password)
                  PrintN("Name: "+FileName+", size of file: "+Str(Int(FileSize/1024))+"kB")
                  Path.s=PathRequester("Where to save "+FileName, "")
                  If Path
                    If OpenFile(#File, Path+FileName)
                      FileBuffersSize(#File, #FileBufferSize)
                      ;Making a packet to send to the client to say that the request is accepted
                      PokeL(Buffer, 13) ;The size of the packet
                      PokeB(Buffer+4, #RequestAccepted) ;The message
                      PokeQ(Buffer+5, Lof(#File)) ;Message to client to read from this offset
                      FileSize-Lof(#File) ;Bytes of data to download
                      FileOffset=Lof(#File) ;Continue to download from last time if filesize<>0
                      
                      If SendNetworkDataEx(ClientID, Buffer) And FileSize>Lof(#File)
                        PrintN("File transfer request accepted")
                        DataTimer=ElapsedMilliseconds() 
                        Repeat ;Wait for data
                          Delay(1)
                          If ElapsedMilliseconds()-DataTimer>#TimeOut Or NetworkServerEvent()=#PB_NetworkEvent_Disconnect
                            Quit=1
                            Break
                          EndIf
                        Until NetworkServerEvent()=#PB_NetworkEvent_Data
                        
                        If Not Quit
                     
                          PrintN("Receiving the file")
                          If FileOffset
                            PrintN("Will resume downloading from last time")
                          EndIf 
                          FileSeek(#File, FileOffset)
                          StartTime=ElapsedMilliseconds()
                          Timer.l=ElapsedMilliseconds()
                        
                          ;The file receive loop
                          Repeat
                        
                            DataTimer=ElapsedMilliseconds() 
                            Repeat ;Wait for data
                              Delay(1)
                              If ElapsedMilliseconds()-DataTimer>#TimeOut Or NetworkServerEvent()=#PB_NetworkEvent_Disconnect
                                Quit=1
                                Break
                              EndIf
                            Until NetworkServerEvent()=#PB_NetworkEvent_Data
                        
                            If Not Quit  
                              
                              DataReceived=ReceiveNetworkData(ClientID, Buffer+DataInBuffer, Min(#BufferSize-DataInBuffer, FileSize-TotaltDataReceived))
                              If DataReceived<=-1
                                PrintN("Connection error")
                                If #Threaded
                                  If IsThread(WriteThread)
                                    WaitThread(WriteThread)
                                  EndIf
                                  BufferData\Buffer=Buffer
                                  BufferData\DataInBuffer=DataInBuffer
                                  WriteThread=CreateThread(@WriteThread(), @BufferData)
                                Else  
                                  WriteData(#File, Buffer, DataInBuffer)
                                EndIf
                                DataInBuffer=0
                                Break
                              EndIf
                              TempDataReceived+DataReceived
                              TotaltDataReceived+DataReceived
                              DataInBuffer+DataReceived
                          
                              If DataInBuffer>Int(#BufferSize*0.9) Or TotaltDataReceived=FileSize
                                If #Threaded
                                  If IsThread(WriteThread)
                                    WaitThread(WriteThread)
                                  EndIf
                                  BufferData\Buffer=Buffer
                                  BufferData\DataInBuffer=DataInBuffer
                                  WriteThread=CreateThread(@WriteThread(), @BufferData)
                                  Swap Buffer, Buffer2
                                Else
                                  WriteData(#File, Buffer, DataInBuffer)
                                EndIf
                                DataInBuffer=0
                              EndIf
                          
                              If ElapsedMilliseconds()-Timer>1000
                                PrintN("Progress: "+StrD(((TotaltDataReceived+FileOffset)/(FileSize+FileOffset))*100, 1)+"%, speed: "+StrD((TempDataReceived/1024)/((ElapsedMilliseconds()-Timer)/1000), 2)+"kB/s")
                                Timer=ElapsedMilliseconds()
                                TempDataReceived=0
                              EndIf
                              
                            Else
                              PrintN("Network timeout or client lost.")
                              ;CloseNetworkConnection(ClientID) Crashes the server if the client has disconnected from before
                              Quit=1
                              DataReceived=-1
                              Break
                            EndIf
                                                
                          Until TotaltDataReceived=FileSize 
                          
                          If #Threaded
                            If IsThread(WriteThread)
                              WaitThread(WriteThread)
                            EndIf
                          EndIf
                          
                          If DataReceived>-1
                            PrintN("File received")
                            PrintN("Average speed: "+StrD((FileSize/1024)/((ElapsedMilliseconds()-StartTime)/1000), 2)+"kB/s, time used: "+FormatTime(Int(ElapsedMilliseconds()-StartTime)/1000))
                          EndIf
                          
                        Else
                          PrintN("Network timeout or client lost.")
                          ;CloseNetworkConnection(ClientID) Crashes the server if the client has disconnected from before
                          Quit=1
                        EndIf                          
                      Else
                        If Not (FileSize>Lof(#File))
                          PrintN("The file is already finished downloaded")
                          CloseNetworkConnection(ClientID)
                        Else
                          PrintN("Could not send request to client")
                        EndIf
                        Quit=1
                      
                      
                      EndIf
                        
                    Else
                      PrintN("Cant create or open file")
                      PokeL(Buffer, 5) ;The size of the packet
                      PokeB(Buffer+4, #RequestDenied)
                      SendNetworkDataEx(ClientID, Buffer)
                      CloseNetworkConnection(ClientID)
                      Quit=1
                    EndIf
                    
                  Else
                    PrintN("Wrong or no specified path")
                    PokeL(Buffer, 5) ;The size of the packet
                    PokeB(Buffer+4, #RequestDenied)
                    SendNetworkDataEx(ClientID, Buffer)
                    CloseNetworkConnection(ClientID)
                    Quit=1
                  EndIf
                  
                Else
                  If Not ValidateUser(Username, Password)
                    PrintN("Wrong username and/or password")
                  Else
                    PrintN("Wrong file name or size")
                  EndIf
                  PokeL(Buffer, 5) ;The size of the packet
                  PokeB(Buffer+4, #RequestDenied)
                  SendNetworkDataEx(ClientID, Buffer)
                  CloseNetworkConnection(ClientID)
                  Quit=1
                EndIf
                
            EndSelect
          Else
            PrintN("Garbarge received from client")
            CloseNetworkConnection(ClientID)
            Quit=1
          EndIf
          
      EndSelect    
      Delay(1)
    Until Quit=1
    
  Else
    PrintN("Error creating server")
  EndIf
Else
  PrintN("Error initing network")
EndIf

If #Threaded
  If IsThread(WriteThread)
    WaitThread(WriteThread)
  EndIf
EndIf

If IsFile(#File)
  CloseFile(#File)
EndIf

CloseNetworkServer(#Server)
;FreeMemory(-1)
PrintN("Server closed")
Input()
Client:

Code: Select all

; File send/receive example with progress(bar). Fast and reliable, works on lan and over internet (both tested)
; The client, sends the file
; By bonne_den_kule - Aug,2006

EnableExplicit
IncludeFile "Include.pb"
EnableGraphicalConsole(1)
OpenConsole()
ConsoleTitle("Client")

Define ConnectionID, Buffer.l=AllocateMemory(#BufferSize), File.s, DataRead.l, DataSent.l, Quit.l, FileSize.l
Define StartTime.l, Timer.l, TempDataSent.l, TotaltDataSent.l, TotalBufferSent.q, FileOffset.q, DataTimer.l
If #Threaded
  Define  ReadThread.l, Buffer2.l=AllocateMemory(#BufferSize)
EndIf

If InitNetwork()
  PrintN("Init network done")
  ConnectionID=OpenNetworkConnection(#HostName, #Server_Port)
  
  If ConnectionID
    PrintN("Connected to server")
    File=OpenFileRequester("File to send", "", "", 0)
    
    If FileSize(File)>0 And ReadFile(#File, File)
      FileBuffersSize(#File, #FileBufferSize)
      ;A packet to send to server:
      FileSize=FileSize(File)
      PokeL(Buffer, Len(GetFilePart(File))+19+Len(#Username)+Len(#Password))
      PokeB(Buffer+4, #SendFile)
      PokeQ(Buffer+5, FileSize) ;Length of file to send
      PokeL(Buffer+13, 0) ;Reserved for use in the future
      PokeS(Buffer+17, GetFilePart(File))
      PokeS(Buffer+18+Len(GetFilePart(File)), #Username)
      PokeS(Buffer+19+Len(GetFilePart(File))+Len(#Username), #Password)
      SendNetworkDataEx(ConnectionID, Buffer)
      
      DataTimer=ElapsedMilliseconds()
      
      Repeat
        Delay(1)
        If NetworkClientEvent(ConnectionID)=#PB_NetworkEvent_Disconnect; Or ElapsedMilliseconds()-DataTimer>#TimeOut*3
          Quit=1
          Break
        EndIf
      Until NetworkClientEvent(ConnectionID)=#PB_NetworkEvent_Data
      
      If Not Quit
        If ReceiveNetworkDataEx(ConnectionID, Buffer)=13 And PeekB(Buffer+4)=#RequestAccepted And PeekQ(Buffer+5)<FileSize
          PrintN("Server accepted the request")
          FileOffset=PeekQ(Buffer+5)
          FileSize-FileOffset
                    
          PrintN("Starting sending the file, name: "+GetFilePart(File)+", file size: "+Str(Int((FileSize+FileOffset)/1024))+"kB")
          If FileOffset
            PrintN("Will resume sending from last time")
          EndIf
          StartTime=ElapsedMilliseconds()
          Timer=ElapsedMilliseconds()
        
          FileSeek(#File, FileOffset)
          If #Threaded
            ReadThread=CreateThread(@ReadThread(), Buffer)
          EndIf
          
          ;The file send loop
          Repeat
            If #Threaded
              If IsThread(ReadThread)
                WaitThread(ReadThread)
              EndIf
              DataRead=GlobalDataRead
              ReadThread=CreateThread(@ReadThread(), Buffer2)
            Else
              DataRead=ReadData(#File, Buffer, #BufferSize) 
            EndIf
            If DataRead=0 And TotaltDataSent<>FileSize
              PrintN("Can't read file")
              DataSent=-1
              Break
            EndIf
            TotalBufferSent=0 

            Repeat
              DataSent=SendNetworkData(ConnectionID, Buffer, DataRead-TotalBufferSent)
              If DataSent<=-1
                PrintN("Connection error")
                Break 2
              EndIf
              TotalBufferSent+DataSent
              TempDataSent+DataSent
            
              If ElapsedMilliseconds()-Timer>1000
                PrintN("Progress: "+StrD(((TotaltDataSent+FileOffset)/(FileSize+FileOffset))*100, 1)+"%, speed: "+StrD((TempDataSent/1024)/((ElapsedMilliseconds()-Timer)/1000), 2)+"kB/s")
                Timer=ElapsedMilliseconds()
                TempDataSent=0
              EndIf
            
            Until DataRead=TotalBufferSent
            TotaltDataSent+TotalBufferSent
            If #Threaded
              Swap Buffer, Buffer2
            EndIf
            
          Until TotaltDataSent=FileSize
          
          If #Threaded
            If IsThread(ReadThread)
              WaitThread(ReadThread)
            EndIf
          EndIf
          If DataSent>-1
            PrintN("File sent to server")
            PrintN("Average speed: "+StrD((FileSize/1024)/((ElapsedMilliseconds()-StartTime)/1000), 2)+"kB/s, time used: "+FormatTime(Int(ElapsedMilliseconds()-StartTime)/1000))
          EndIf
          
        Else
          If PeekQ(Buffer+5)>=FileSize
            PrintN("The file is already finished uploaded to the server")
          EndIf
          PrintN("The client could not upload the file")
        EndIf
        
      Else
        PrintN("Connection timeout or server lost")
      EndIf
                
    Else
      PrintN("Wrong file, empty file or no file specified")
    EndIf 
       
  Else
    PrintN("Error connecting to server")
  EndIf
  
Else
  PrintN("Error initing network")
EndIf

If ConnectionID
  CloseNetworkConnection(ConnectionID)
EndIf
If IsFile(#File)
  CloseFile(#File)
EndIf

;FreeMemory(-1)
PrintN("Client closed")
Input()
Include file:

Code: Select all

; File send/receive example with progress(bar). Fast and reliable, works on lan and over internet (both tested)
; The include file for both server and client
; By bonne_den_kule - Aug,2006

; The constants can be different on the client and server (expect the server port)
#Server_Port=2586
#HostName="localhost"
#BufferSize=1024*100 ;The main buffer for reading/sending and writing/receiving, Higher buffer, better speed, but more memory usage.
#FileBufferSize=#BufferSize ;The PureBasic internal file buffer, can be turned off by setting it to 0.
#TimeOut=7000 ; The connection time out period.
#Threaded=#True ; The code send and read or receive and write datas at the same time if turned on. Effective if #filebuffersize is set to 0
#Username="Fred" ;Username for the client (edit ValidateUser() for customize login for server)
#Password="Berikco" ;Password for the client (edit ValidateUser() for customize login for server)


Enumeration
  #Server
  #File
  #SendFile
  #RequestAccepted
  #RequestDenied
EndEnumeration

If #Threaded
  Global GlobalDataRead.l
EndIf

Structure DataInfo
  Buffer.l
  DataInBuffer.l
EndStructure

Procedure ReceiveNetworkDataEx(ConnectionID.l, Buffer.l) ;Function to prevent some kinds of buffer overflow's and ensure that all data is received
  Define DataRead.l, TotaltDataRead.l, Length.l=4, StartTime.l=ElapsedMilliseconds()
  Repeat
  
    DataRead=ReceiveNetworkData(ConnectionID, Buffer+TotaltDataRead, Length-TotaltDataRead)
    TotaltDataRead+DataRead
    
    If TotaltDataRead>=4 ;Get the length of the packet as fast as possible
      Length=PeekL(Buffer)
    EndIf
    If DataRead<=-1 Or Length>#BufferSize Or (ElapsedMilliseconds()-StartTime>#TimeOut And TotaltDataRead<=4)
      ProcedureReturn 0
    EndIf
    
    Delay(1)
  Until TotaltDataRead=Length And Length<>0
  ProcedureReturn TotaltDataRead
EndProcedure
Procedure SendNetworkDataEx(ConnectionID.l, Buffer.l) ;Function to ensure that all data is sent
  Define DataSent.l, TotaltDataSent.l, StartTime.l=ElapsedMilliseconds(), Length=PeekL(Buffer) ;Number of bytes to send
  Repeat
  
    DataSent.l=SendNetworkData(ConnectionID, Buffer+TotaltDataSent, Length-TotaltDataSent)
    TotaltDataSent+DataSent
    
    If DataSent<=-1  Or (ElapsedMilliseconds()-StartTime>#TimeOut And TotaltDataSent=0) Or Length=0
      ProcedureReturn 0
    EndIf
    
    Delay(1)      
  Until TotaltDataSent=Length And TotaltDataSent>0
  ProcedureReturn 1
EndProcedure

Procedure.q Min(val1.q, val2.q) ;Returns the lowest value
  If val1<val2
    ProcedureReturn val1
  Else
    ProcedureReturn val2
  EndIf
EndProcedure
Procedure.s FormatTime(seconds.l)
  Define Date.s=FormatDate("%hhh %iim %sss", AddDate(0, #PB_Date_Second, seconds))
  Date=RemoveString(Date, "00h")
  Date=RemoveString(Date, "00m")
  Date=RemoveString(Date, "00s")
  Date=Trim(Date)
  If Left(Date, 1)="0"
    Date=Right(Date, Len(Date)-1)
  EndIf
  If Trim(Date)=""
    Date="less than 1s"
  EndIf
  ProcedureReturn Date
EndProcedure

Procedure ReadThread(Buffer)
  Define DataRead.l
  If Not Eof(#File)
    DataRead.l=ReadData(#File, Buffer, #BufferSize)
    GlobalDataRead=DataRead
  EndIf
EndProcedure
Procedure WriteThread(*strct.DataInfo)
  WriteData(#File, *strct\Buffer, *strct\DataInBuffer)
EndProcedure

Procedure ValidateUser(Username.s, Password.s)
  If Username=#Username And Password=#Password
    ProcedureReturn 1
  Else
    ProcedureReturn 0
  EndIf
EndProcedure
Last edited by Bonne_den_kule on Thu Aug 17, 2006 7:27 pm, edited 4 times in total.
Dare
Addict
Addict
Posts: 1965
Joined: Mon May 29, 2006 1:01 am
Location: Outback

Post by Dare »

Thank you, this is really nice.
Dare2 cut down to size
dell_jockey
Enthusiast
Enthusiast
Posts: 767
Joined: Sat Jan 24, 2004 6:56 pm

Post by dell_jockey »

Very nice indeed, thanks a lot Bonne!
cheers,
dell_jockey
________
http://blog.forex-trading-ideas.com
Inf0Byt3
PureBasic Fanatic
PureBasic Fanatic
Posts: 2236
Joined: Fri Dec 09, 2005 12:15 pm
Location: Elbonia

Post by Inf0Byt3 »

Works perfectly! Thanks a lot !
None are more hopelessly enslaved than those who falsely believe they are free. (Goethe)
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Looks very good here. Fine work and thanks for sharing!
BERESHEIT
User avatar
Paul
PureBasic Expert
PureBasic Expert
Posts: 1282
Joined: Fri Apr 25, 2003 4:34 pm
Location: Canada
Contact:

Post by Paul »

Nice work.

How about adding a Resume feature?
If the file exists on the host, the size is sent back to the client and the client will continue sending from that point on (and append the data to existing file) if told to do so.
Image Image
Bonne_den_kule
Addict
Addict
Posts: 841
Joined: Mon Jun 07, 2004 7:10 pm

Post by Bonne_den_kule »

Paul wrote:Nice work.

How about adding a Resume feature?
If the file exists on the host, the size is sent back to the client and the client will continue sending from that point on (and append the data to existing file) if told to do so.
Nice idea. I will add it later. I have just began at school again.
Last edited by Bonne_den_kule on Thu Aug 17, 2006 6:27 am, edited 1 time in total.
CherokeeStalker
User
User
Posts: 66
Joined: Fri Oct 17, 2003 2:42 am

Post by CherokeeStalker »

Awesome! I'm working on adding user validation ( UserID & Password ) to
this. Thanks!
Pantcho!!
Enthusiast
Enthusiast
Posts: 538
Joined: Tue Feb 24, 2004 3:43 am
Location: Israel
Contact:

Post by Pantcho!! »

Great code!
at last we got a decent networking functions that work well.
and yes resume will make it perfect.
Bonne_den_kule
Addict
Addict
Posts: 841
Joined: Mon Jun 07, 2004 7:10 pm

Post by Bonne_den_kule »

CherokeeStalker wrote:Awesome! I'm working on adding user validation ( UserID & Password ) to
this. Thanks!
I will add that too.
Bonne_den_kule
Addict
Addict
Posts: 841
Joined: Mon Jun 07, 2004 7:10 pm

Post by Bonne_den_kule »

New update. Added a resume feature.
User avatar
Paul
PureBasic Expert
PureBasic Expert
Posts: 1282
Joined: Fri Apr 25, 2003 4:34 pm
Location: Canada
Contact:

Post by Paul »

Resume feature is nice but there is a little problem.

If the file already exists on the server and is complete, the client side will crash since there is no check for this.


Looking forward to a user/password authentication :)
Great work!
Image Image
Bonne_den_kule
Addict
Addict
Posts: 841
Joined: Mon Jun 07, 2004 7:10 pm

Post by Bonne_den_kule »

User/password authentication added.
Inf0Byt3
PureBasic Fanatic
PureBasic Fanatic
Posts: 2236
Joined: Fri Dec 09, 2005 12:15 pm
Location: Elbonia

Post by Inf0Byt3 »

Great work Bonne!
None are more hopelessly enslaved than those who falsely believe they are free. (Goethe)
User avatar
NoahPhense
Addict
Addict
Posts: 1999
Joined: Thu Oct 16, 2003 8:30 pm
Location: North Florida

Re: Fast buffered file transfer (new code, finished)

Post by NoahPhense »

Can you create a zip or rar of this code?

Bonne .. freakin nice code man. Thanks for the contribution!

- np
Post Reply