[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 5022
  • Last Modified:

How to reset VB winsock receive buffer flag or clear buffer through code?

Hi,
My  application (VB6)  uses winsock with TCP/IP protocol to transfer large amount of data (1MBytes and up) to another system.
When receiving data using slow computer speed (like 1.7GHz with windows XP) - data is lost.
I payed attention that one DataArrival event "chases" another and the operation doesn't return to calling function (from DoEvents command) by program debugging.
When program runs on 3GHz PC the problem is less serious (1 of ~180 data receiving operation fails).
The goal is to create code which will work on any platform, no matter what is the computer speed.
I suspect that winsock is forced to handle with next 8KBytes (size of receive buffer) chunk before it resets internal receive buffer flag or clears buffer (be prepared for next packet).
If i insert DoEvents command in the end of DataArrival event sub the winsock succeed to perform these actions (the data receiving completed successfully),  but the next event is fired from this command and so on and nesting is created. For large files "Stack out of space error" occurs, which terminates the transfer.
Am i right about understanding the socket receive process?
There is VB method or API  commad of winsock to do this through code at the end of DataArrival event?
I appreciate any help.
Thanks.
0
oren_amat
Asked:
oren_amat
  • 8
  • 5
  • 3
1 Solution
 
PaulHewsCommented:
When transferring data, break the data into small chunks.  Send the chunks one at a time, and when you receive them, send back an acknowledgment.  Wait until you receive an acknowledgment for one chunk before sending the next.

Simple example follows

Sample client:
Option Explicit
Private mstrMyStrings(3) As String
Private intPos As Integer

Private Sub Command1_Click()
    Winsock1.RemoteHost = "127.0.0.1"
    Winsock1.RemotePort = 5000
    Winsock1.Connect
   
   
End Sub

Private Sub Form_Load()
    mstrMyStrings(0) = "This is a test" & vbCrLf
    mstrMyStrings(1) = "of the emergency broadcast system" & vbCrLf
    mstrMyStrings(2) = "I will understand" & vbCrLf
    mstrMyStrings(3) = "if you choose to ignore it." & vbCrLf
End Sub

Private Sub Winsock1_Connect()
    Winsock1.SendData mstrMyStrings(0)

End Sub

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
    Debug.Print "DataArrival"
    Static strReceive As String
    Dim strTemp As String
    Winsock1.GetData strTemp
    strReceive = strReceive & strTemp
    Debug.Print strReceive
    If CInt(strReceive) = Len(mstrMyStrings(intPos)) Then
        strReceive = ""
        intPos = intPos + 1
        If intPos = UBound(mstrMyStrings) + 1 Then
            Winsock1.Close
            Exit Sub
        End If
        Debug.Print mstrMyStrings(intPos)
        Winsock1.SendData mstrMyStrings(intPos)
    End If
End Sub

Server example:
Option Explicit
Private strArrive As String
Private Sub Form_Load()
   
    Winsock1.LocalPort = 5000
    Winsock1.Listen
End Sub

Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long)
    If Winsock1.State <> sckClosed Then
        Winsock1.Close
    End If
    Winsock1.Accept requestID

End Sub

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
    Debug.Print "DataArrival"
    Dim strTemp As String
    Dim strEnd As String
    strEnd = vbCrLf
    Winsock1.GetData strTemp
    strArrive = strArrive & strTemp
    If InStr(1, strArrive, strEnd) Then
        Winsock1.SendData CStr(Len(strArrive))  'Send length of string as acknowledgment
        Debug.Print strArrive
        strArrive = ""
    End If
End Sub
0
 
oren_amatAuthor Commented:
Thank you for your answer Paul, but can it be done without changing the sending side software (without acknowledge every chunk)?
0
 
PaulHewsCommented:
Actually I'm not sure how you are losing data.  Seems to me that if the receiving buffer overflows, you get an error on the sender side.  

That aside, in my experience the most reliable way to transfer data is to send -> acknowledge receipt -> send

If your receiving procedure is slow enough that you overflow the receive buffer, then either you increase the speed of receiving or slow down the speed of sending.

Now, you might get it right by optimizing the receiver DataArrival procedure and trial and error, but what happens if some other process puts a load on the CPU of the receiver for a period... Then suddenly the problem returns, or occurs intermittently.  Those are the worst kinds of problems to try and resolve.

To optimize your receiving procedure, you want to do as little processing as possible on the incoming data in the DataArrival event.  If you show me the code I'll see what I can offer.
0
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 
oren_amatAuthor Commented:
Here is the code:

Calling procedure:
                    Call Establish_Connection_TCP_IP(SelectedCageNum, SelectedSlotNum, SelectedNodeNum)
                       
                            MaxTimeOutCnt = TotFileSize / 100
                            TimeOutCnt = 0   'assuming WS_Data connected, 'ofer 13/5/07
                           
                            Prev = -1 'reset previous progress bar value,ofer 31/7/07
                           
                            Do Until FileDataLen >= TotFileSize Or TimeOutCnt = MaxTimeOutCnt 'ofer 12/7/07
                               
                                DoEvents  'let winsock buffer receive data
                               
                                If DataStr <> "" Then
                                     
                                       'activate progress bar
                                     
                                     ProgValue = CLng(100 * (FileDataLen / TotFileSize))   'TotFileSize - global variable
                                     
                                     If ProgValue <> Prev Then
                                     
                                        If ProgValue < 100 Then
                                            Fil_Trans_ProgBar = ProgValue
                                            Prog_Lbl = Fil_Trans_ProgBar & "%"
                                        Else
                                            Fil_Trans_ProgBar.Value = 100
                                            Prog_Lbl = Fil_Trans_ProgBar & "%"
                                        End If
                                   
                                        Prev = ProgValue
                                       
                                     End If
                                     
                                     DataStr = "" 'clear data string                                    
                                                                 
                                Else
                               
                                    TimeOutCnt = TimeOutCnt + 1
                                                                       
                                End If
                               
                            Loop
                           
                                'disconnect connection
                            CloseTCPConnection
                                                            'test finished reason
                            If FileDataLen >= TotFileSize + HeaderLen + 1 Then
                               
                                     'get approval for end of operation from sender
                                Data = Wait_Node_Ack_And_Data(Response, PacketIndex, CmdIndex, NodeIndex)
         
                                If Data = AddZeros(Hex(TotFileSize), 8) & AddZeros(Hex(YSize), 8) & _
                                                                  AddZeros(Hex(XSize), 8) & Right(HexOffAddr, 8) Then
                   
                                    If Fil_Trans_ProgBar < 100 Then 'set it to maximum
                                   
                                        Fil_Trans_ProgBar = 100 'reset progress bar value
                                        Prog_Lbl = Fil_Trans_ProgBar & "%" 'reset progress label value
                                       
                                    End If
                                   
                                    'paint node with Green (checkbox on GUI)
                                    Node(NodeNum).DownPicture = .Green_Square_Pic.Picture  
                                    Node(NodeNum).Picture = .Green_Pic.Picture
                                    Node(NodeNum).Refresh
                                                                       
                                 Else                                
                                    Call Display_Node(NodeIndex, _
                                                      "Cage " & SelectedCageNum _
                                                      & "   Slot " & SelectedSlotNum _
                                                      & "   Node " & SelectedNodeNum _
                                                      & " - " & ERR_FILE_SAVE_FAILED, _
                                                      vbRed)  'paint node with Red + error message                                  
                                 End If
                               
                            Else    'File not completed
                               
                               Call Display_Node(NodeIndex, _
                                                                         "Cage " & SelectedCageNum _
                                                                      & "   Slot " & SelectedSlotNum _
                                                                      & "   Node " & SelectedNodeNum _
                                                                      & " - " & ERR_FILE_SAVE_FAILED, _
                                                                      vbRed)                                
                                                         
                            End If
   
Receive event:
Private Sub WS_Data_DataArrival(ByVal bytesTotal As Long)
 
    On Error GoTo WS_Data_DataArrival_Err
     
    WS_Data.GetData DataStr, vbArray + vbByte
   
    If bytesTotal > 0 Then
   
        Print #1, DataStr 'print to file
        FileDataLen = FileDataLen + bytesTotal
        Seek #1, FileDataLen
                               
    End If
   
    Exit Sub
   
WS_Data_DataArrival_Err:

    If Not ScriptRun Then
        Show_Error_Message (Err.Number)
    End If

    If Err.Number = ERRNUM_RESET_CONNECTION Then    'connection is reset by remote side
        ConnectionResetError = True
    End If

 End Sub

Thank  you.
0
 
PaulHewsCommented:
Ideally, you want your receiving program to be idle except for the DataArrival events.  So really, polling the file length in a loop, even with DoEvents, is not such a great idea and can cause problems of the type you mention.  Calling a routine to update the progress bar from the DataArrival event is a much better way to handle this:

Calling procedure:
   
    Call Establish_Connection_TCP_IP(SelectedCageNum, SelectedSlotNum, SelectedNodeNum)
End Sub  'Last thing in subroutine so that we can let the DataArrival events drive the application.


Private Sub WS_Data_DataArrival(ByVal bytesTotal As Long)
 
    On Error GoTo WS_Data_DataArrival_Err
     
    WS_Data.GetData DataStr, vbArray + vbByte
   
    If bytesTotal > 0 Then
   
        Print #1, DataStr 'print to file
        FileDataLen = FileDataLen + bytesTotal
        Seek #1, FileDataLen

        Call UpdateProgressBar
                               
    End If
   
    Exit Sub
   
WS_Data_DataArrival_Err:

    If Not ScriptRun Then
        Show_Error_Message (Err.Number)
    End If

    If Err.Number = ERRNUM_RESET_CONNECTION Then    'connection is reset by remote side
        ConnectionResetError = True
    End If

 End Sub

Private Sub UpdateProgressBar()
   'Logic to update progress bar, check for completion and do whatever needs to be done if the file is finished.

End Sub
0
 
oren_amatAuthor Commented:
Hi Paul,
I tried what you suggested and the program looks more right, but now the save fails a little bit often, maybe because the event is a little bit loaded.
0
 
PaulHewsCommented:
Try commenting out the UpdateProgress routine.  Just use a test to see if the file is complete.  The idea is to see if a minimal amount of processing can make it work.  If it can't then you will not be able to avoid losing data by speeding up the receiver.  You can't make a winsock out of a sow's ear.
0
 
oren_amatAuthor Commented:
I tried it and i'm still not receiving data properly.
Thank you anyway.
0
 
justchat_1Commented:
What type of file handle are you using? (I assume binary output but can you post the code anyway)
0
 
oren_amatAuthor Commented:
Hi,
I use of course binary output.
The code:
Open Filename For Output As 1 'output file
'save data to file
Seek #1, 1
FileDataLen = 0 (global variable)

       'see the code above (every DataArrivalEvent new DataStr(global  variable) is printed to file)

Close #1

Thanks.
0
 
justchat_1Commented:
No thats not binary thats a standard file output...
http://vb-helper.com/howto_read_write_binary_file.html
0
 
oren_amatAuthor Commented:
Hi justchat 1,
The file contains a string header which is inserted before printing binary data by DATAArrival (the code was omitted becaues this irrelevant to receiving problem - the transfer failure exists no matter if the file binary or sequential).
0
 
justchat_1Commented:
True but writing using a regular write method can sometimes cause a byte offset problem which can be quite nasty (ASCII vs Unicode)... Also, i probably just missed it but what size buffer are you using when sending the files?
0
 
oren_amatAuthor Commented:
Hi everybody,
Sorry for the long time to reply.
I accept the solution of Paul Hews (Sage) as good but partial one and ask to reward him 450 points.
Therefore i don't no exactly how to close this question. Please do it for me.
Thanks.

The solution:
When transferring data, break the data into small chunks.  Send the chunks one at a time, and when you receive them, send back an acknowledgment.  Wait until you receive an acknowledgment for one chunk before sending the next.

Simple example follows

Sample client:
Option Explicit
Private mstrMyStrings(3) As String
Private intPos As Integer

Private Sub Command1_Click()
    Winsock1.RemoteHost = "127.0.0.1"
    Winsock1.RemotePort = 5000
    Winsock1.Connect
   
   
End Sub

Private Sub Form_Load()
    mstrMyStrings(0) = "This is a test" & vbCrLf
    mstrMyStrings(1) = "of the emergency broadcast system" & vbCrLf
    mstrMyStrings(2) = "I will understand" & vbCrLf
    mstrMyStrings(3) = "if you choose to ignore it." & vbCrLf
End Sub

Private Sub Winsock1_Connect()
    Winsock1.SendData mstrMyStrings(0)

End Sub

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
    Debug.Print "DataArrival"
    Static strReceive As String
    Dim strTemp As String
    Winsock1.GetData strTemp
    strReceive = strReceive & strTemp
    Debug.Print strReceive
    If CInt(strReceive) = Len(mstrMyStrings(intPos)) Then
        strReceive = ""
        intPos = intPos + 1
        If intPos = UBound(mstrMyStrings) + 1 Then
            Winsock1.Close
            Exit Sub
        End If
        Debug.Print mstrMyStrings(intPos)
        Winsock1.SendData mstrMyStrings(intPos)
    End If
End Sub

Server example:
Option Explicit
Private strArrive As String
Private Sub Form_Load()
   
    Winsock1.LocalPort = 5000
    Winsock1.Listen
End Sub

Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long)
    If Winsock1.State <> sckClosed Then
        Winsock1.Close
    End If
    Winsock1.Accept requestID

End Sub

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
    Debug.Print "DataArrival"
    Dim strTemp As String
    Dim strEnd As String
    strEnd = vbCrLf
    Winsock1.GetData strTemp
    strArrive = strArrive & strTemp
    If InStr(1, strArrive, strEnd) Then
        Winsock1.SendData CStr(Len(strArrive))  'Send length of string as acknowledgment
        Debug.Print strArrive
        strArrive = ""
    End If
End Sub
0
 
oren_amatAuthor Commented:
Paul, congratulations for getting Genius degree.
0
 
PaulHewsCommented:
Thanks oren_amat.  Since we didn't find you a real solution I think a PAQ/refund would be fine.
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

  • 8
  • 5
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now