File Transfer with Winsock.
Hey guys, can someone give me an example of what im trying to do?
I want to use a buffer to send and receive the packets from the client, to the server. i want the client to send the file "C:\myFile.exe" to the server, and then i want the server to save it to "C:\Received Files\myFile.exe" again, i want to use a buffer, and i want it to show me the progress, for example,
initially, the progress will be 0 / 0 because no files have been transfered. then, i want it to show, AmountTransfered / TotalSize.
thanks :)
Re: File Transfer with Winsock.
Here's a simple example of one of the many ways this can be done, it's not the most efficient but it works:
Server:
Code:
Option Explicit
Dim intFile As Integer
Private Sub Form_Load()
Winsock1.LocalPort = 8001
Winsock1.Listen
End Sub
Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long)
Winsock1.Close
Winsock1.Accept requestID
End Sub
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
Static strBuffer As String
Static boBinary As Boolean
Static lngSize As Long
Static lngTotalRx As Long
Dim bytReceived() As Byte
Dim lngPos As Long
Dim strData As String
Dim strReceived() As String
'
' boBinary is a flag to indicate whether we're transfering
' String ie Commands(False) or Binary ie the File (True) Data
'
Select Case boBinary
Case False
'
' Application Protocol is:
' Client sends "FILE|" followed by the file size in bytes & vbCr
' Server stores info, opens the output file, flips boBinary,
' and sends OK vbCr to tell Client to start to send
'
Winsock1.GetData strData
strBuffer = strBuffer & strData
lngPos = InStr(strBuffer, vbCr)
If lngPos > 0 Then
strReceived = Split(Mid(strBuffer, 1, lngPos - 1), "|")
Select Case strReceived(0)
Case "FILE"
lngSize = CLng(strReceived(1))
intFile = FreeFile
Open "D:\Received Files\myFile.exe" For Binary As intFile
boBinary = True
Winsock1.SendData "OK" & vbCr
End Select
End If
Case True
'
' Just receive the data and write it to the output file
' until we've received the correct number of bytes
'
Winsock1.GetData bytReceived
Put intFile, , bytReceived
lngTotalRx = lngTotalRx + UBound(bytReceived) + 1
txtReceived.Text = CStr(lngTotalRx) & " / " & CStr(lngSize)
If lngTotalRx = lngSize Then
Close #intFile
boBinary = False
MsgBox "Finished"
End If
End Select
End Sub
Client:
Code:
Option Explicit
Dim intFile As Integer
Dim lngSize As Long
Dim lngBuffer As Long
Dim lngBuffCount As Long
Dim lngPartial As Long
Dim lngSent As Long
Dim bytBuffer() As Byte
Dim boSending As Boolean
Dim boSendComplete As Boolean
Private Sub SendAndWait(Optional boPartial As Boolean = False)
'
' Send a Buffer of data
'
Dim intDo As Integer
If boPartial = True Then
ReDim bytBuffer(lngPartial - 1)
End If
boSendComplete = False
Get intFile, , bytBuffer
Winsock1.SendData bytBuffer
'
' boSendComplete is set by the Winsock SendComplete event
' we loop round waiting for that (but not overdoing the DoEvents
' which is required so the UI can be updated)
'
Do Until boSendComplete = True
intDo = intDo + 1
If intDo Mod 1000 = 0 Then
DoEvents
intDo = 0
End If
Loop
End Sub
Private Sub Command1_Click()
Winsock1.RemoteHost = "localhost"
Winsock1.RemotePort = 8001
Winsock1.Connect
End Sub
Private Sub Form_Load()
lngBuffer = 20 * 1024 '20 Kb Buffer Size
ReDim bytBuffer(lngBuffer - 1)
End Sub
Private Sub Winsock1_Connect()
intFile = FreeFile
'
' Open the input file and determine its size,
' set up the number of complete buffers we're going to send
' and the size of any incomplete buffer.
'
' Send the file size to the server
'
Open "D:\Data.Cab" For Binary As intFile ' I chose a 60Mb file for testing
lngSize = LOF(intFile)
lngBuffCount = lngSize \ lngBuffer
lngPartial = lngSize Mod lngBuffer
Winsock1.SendData "FILE|" & lngSize & vbCr
End Sub
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
Dim strData As String
Dim strReceived() As String
Static strBuffer As String
Static boBinary As Boolean
Dim lngPos As Long
Dim lngI As Long
Winsock1.GetData strData
strBuffer = strBuffer & strData
lngPos = InStr(strBuffer, vbCr)
If lngPos <> 0 Then
strReceived = Split(Mid$(strBuffer, 1, lngPos - 1), vbCr)
Select Case strReceived(0)
Case "OK"
'
' Server is ready to receive
' Send lngBuffCount number of buffers
' waiting for Winsock to complete each SendData
'
lngSent = 0
boSending = True
For lngI = 1 To lngBuffCount
Call SendAndWait
Next lngI
'
' If there was an incomplete buffer then send that
'
If lngPartial <> 0 Then
Call SendAndWait(True)
End If
Close intFile
boSending = False
End Select
strBuffer = ""
End If
End Sub
Private Sub Winsock1_SendComplete()
boSendComplete = True
End Sub
Private Sub Winsock1_SendProgress(ByVal bytesSent As Long, ByVal bytesRemaining As Long)
If boSending = True Then
'
' Show the progress in the TextBox
'
lngSent = lngSent + bytesSent
txtProgress.Text = CStr(lngSent) & " / " & CStr(lngSize)
End If
End Sub
Re: File Transfer with Winsock.
nice. did u put both the string requests and binary requests in there? cuz i was thinking of having the file sending in another winsock, which we can call winsock2, i guess. that way it doesn't block off the original winsock thats sending and receiving string data
EDIT: and whats a simple example of using a buffer. cuz if i knew how to write a buffer, id be able 2 code it myself :). can u teach me where to put the buffer and stuff? and how to write it?
Re: File Transfer with Winsock.
Client:
Code:
Private Sub wskSendFile_DataArrival(ByVal bytesTotal As Long)
Dim strData As String, byteData() As String, xFile As Integer
wskSendFile.GetData strData
If UCase(Mid(strData, 1, 7)) = "GETFILE" Then
xFile = FreeFile
Open Mid(strData, 9) For Binary Access Read As #xFile
ReDim byteData(LOF(xFile))
Get #xFile, , byteData()
Close #xFile
wskSendFile.SendData "FILE" & Len(byteData())
End If
If UCase(strData) = "OK" Then
wskSendFile.SendData byteData()
End If
End Sub
Server:
Code:
Private Sub wskGetFile_DataArrival(ByVal bytesTotal As Long)
Dim strData As String, strBuffer As String, maxSize As Long, bufferSize As Long
wskGetFile.GetData strData
If Mid(strData, 1, 4) = "FILE" Then
maxSize = CLng(Mid(strData, 5))
wskGetFile.SendData "OK"
ElseIf strData = "DONE" Then
'Write the file here
Else
'Receive pieces of the file
strBuffer = strBuffer & strData
bufferSize = CLng(Len(strBuffer))
lblProgress.Caption = bufferSize & " / " & maxSize
End If
End Sub
i think i coded the server right, to accept the buffers.. not so sure, but i dunno how to send the buffers in the client. can u check this out?
Re: File Transfer with Winsock.
There's one or two problems with your Server code.
You're not sending a record terminator with the Commands and you're not performing any Buffering.
Code:
wskGetFile.GetData strData
If Mid(strData, 1, 4) = "FILE" Then
What if you've just received "FI" ? Your code will assume it's part of the file being transferred. As I mentioned in the other Thread, one SendData may or may not cause one or more DataArrival events. IMHO, this is the one single most important thing that anyone working with TCP / IP should learn. It's those protocols that determine how the data is transmitted and received, together with the numerous devices (like routers) that may or may not re-package data as it traverses a network. You and your application have absolutely no control, all you can assume is that all the data will arrive eventually or an error will be raised (if using TCP) and it will arrive in the same order in which it was sent. If I can "teach" you only one thing, let it be that :)
It's most important that you make sure that you have a complete logical record before trying to process it. That's why, in my example, I put a vbCr at the end of each string record sent to either the client or server, then accumulated the data received and checked with InStr whether it had all arrived yet.
This accumulation of data also requires that the Buffer contents are preserved from one DataArrival event to the other and flushed after a record has been processed. I used Static to define the string Buffer, it could just as easily be done by defining the Buffer in the Forms Declarations section.
The protocol you're using is also problematic. It looks as if you start the transfer by sending "OK" and when it's complete you send "DONE", there are at least two problems with this.
(a) What if the characters "DONE" actually appear in the file you're sending and just happen to be at the front of a block of data ? (Your transfer will terminate too ealry and you'll end up with a corrupt file)
(b) What if the characters "DONE" arrive, tagged on to the end of a block of data ? (Your transfer will never terminate)
(a) and (b) may happen at random times, unlikely if you're running Client and Server on the same machine or lightly loaded LAN perhaps, but almost certain when deployed over the Internet.
This is why I separated "Commands" from "Binary" in the Server code example. The idea being that it acts like a state machine. It is in one state (expecting Character Data) or another (expecting Binary Data) that way there's no possibility of either (a) or (b) above.
When transferring Binary data I used Byte Buffers this was to make sure that there'd be no problems with Unicode data which could happen if using String variables.
Because the server knows exctly how many bytes there are to be received it can detect the end of the transfer automatically, by checking the total received so far against the number expected. There's no need for the client to tell the server when it has finished sending. Also, if you look closely, after the transfer has completed I change the state back to "Character", by setting boBinary = False. This is so the client could send another command etc etc.
As far as your client goes, you've adopted a 'blast-it-all-in-one-go' approach. Thiis is fine for fairly small amounts of data but with larger files you could run into the problem of exhausting the internal Winsock Buffers. I used a Buffer size of 20Kb in my example and if you look again you'll see that I used the length of the file to caculate how many full buffers I needed to send and also any residual bytes
Code:
lngSize = LOF(intFile)
lngBuffCount = lngSize \ lngBuffer ' Number of Full Buffers
lngPartial = lngSize Mod lngBuffer ' Residual bytes
Then I used a simple loop to send the data in blocks:
Code:
For lngI = 1 To lngBuffCount
Call SendAndWait
Next lngI
'
' If there was an incomplete buffer then send that
'
If lngPartial <> 0 Then
Call SendAndWait(True)
End If
Close intFile
So, if I had 40,678 bytes to transfer (lngSize)
lngBuffCount = 40678 \ 20480 = 1
and
lngPartial = 40678 Mod 20480 = 20198.
So I send 1 complete block, of 20480, followed by a block of 20198 bytes. (Which equals a total of 20480 + 20198 = 40678)
By blocking the data in this way and waiting for Winsock to confirm that it's been sent (by using the SendComplete event) you're reducing the 'pressure' on resources. It also gives you the opportunity to allow things like the User Interface to be updated.
Basically, it's the understanding of how TCP / IP works and the Application Protocol you use that defines the logic of the C/S. The Application Protocol is the "Rules of Engagment" or "Contract" between the client and the server which defines how they're going to communicate. It's a bit like any other sort of conversation. The one I used goes something like:
How are we going to communicate physically?
1. The server will listen for a connection on Port 8001
2. The client will connect to Port 8001
Is this private or can anyone join in?
3. Once connection has been established, the server will cease to listen for connection requests
Who's going to speak first?
4. The conversation will be started by the client, once connection has been established
How do we know when someone has finished speaking? (akin to "Over" in Radio comms)
5. All command related records / responses to commands transmitted will be terminated by a vbCr
What language and syntax are we going to use to instruct / react to instructions?
6. To start a Transfer, the Client will send "FILE| followed by the length of the file to be transferred.
7. The server will send "OK" when it is ready to receive the data
8. When the client receives "OK" from the server it will send the data
What do I do when I've done what you asked?
9. When all the data has been received the server will wait for another (Character) command from the client.
When developing C/S applications, this, the Application Protocol, is the very first thing that should be designed / developed (IMHO, of course). This might sound like a daft statement, but, if you haven't defined how it's going to work, how will you ever know if it's not working properly?
Sorry to have gone on for so long but you did mention the word "teach". :D Hopefully this diatribe will help you on your way.