Results 1 to 10 of 10

Thread: VB6 - IOCP problem

  1. #1

    Thread Starter
    Frenzied Member
    Join Date
    Dec 2012
    Posts
    1,473

    VB6 - IOCP problem

    In the process of developing an I/O Completion Port server and client, each program was tested with a program built with SimpleSock. Then I attempted to run the IOCP client with the IOCP server, and the client program had some serious issues transferring multi record files. The problem was isolated to a speed issue. The SimpleSock programs were relatively slow, which left ample time for the IOCP programs to complete their record transfers. When the two faster programs were matched together, testing revealed partial records were being received.

    The server was not at fault. It was sending complete records. The buffer was loaded with a header (5 bytes) and data (8192 bytes) and sent to the client. This is where the problem originated. The outgoing TCP/IP buffer (64K bytes) was more than large enough to handle the records, but the TCP/IP stack breaks down those records and sends them as packets (about 1500 bytes). On the receiving end, the client reassembles the packets in the receiving buffer for as long as they keep coming, up to the maximum (64K). Unlike the IOCP send function, which loads each record and its length before sending, the IOCP receive function loads the maximum length (8197 bytes) in advance of the actual data and waits for the Completion Port to signal data arrival. The Completion Port then delivers whatever data is in the buffer along with its length (maximum 8197 bytes), and because of the speed of IOCP, partial records can be received. Tests run with both programs running in the IDE indicate that the second and third blocks are pretty well always partial records, which makes it difficult to control what gets sent to file. I assume the first record is delivered correctly because the operating system is busy getting the file ready to be added to disk. I temporarily solved the problem by slowing down the client by introducing a Sleep function. That is not a permanent solution however, as slow incoming packets would cause the same problem.

    Spinoza alludes to this problem in section 3.7.1 here:
    https://www.codeproject.com/Articles...r-Client-Class
    His solution seems overly complex, and I see the solution as providing for an independent buffer that would not process until a complete record is available. The excess would be moved to the front of the buffer to wait for the rest of the next record. That would mean the independent buffer would need to be fixed at twice the record size. Using a variable length buffer would only slow down the process as it would have to be reallocated each time.

    Does this sound like a reasonable approach?

    J.A. Coutts
    Last edited by couttsj; Feb 24th, 2024 at 01:42 PM.

  2. #2
    Fanatic Member
    Join Date
    Apr 2021
    Posts
    516

    Re: VB6 - IOCP problem

    Forgive me for responding despite not fully understanding what you're trying to do...but I've messed around with winsock and various other server-client communication systems so many times and found synchronisation to be a massive problem between the two machines in almost all cases (even when both server and client are the SAME machine on localhost). You're sending in large buffers, larger than TCP/IP allows, so it chops them up into bits for the other machine to rebuild...we know this much. Could you learn from BitTorrent here? Assign each 1kb block a numerical value referencing which block it is and, assumedly, a checksum byte...send the 1k block with its block number (the numerical value) and checksum, and don't bother waiting for the next block to arrive, just send the next block, keep on sending all the blocks until you are done. Receiver should receive the 1k block and checksum, perform confirmation that the 1k block generates that checksum and return a confirmation to say that Block X arrived fine. Sender waits until it receives confirmation for all blocks to arrive, and re-sends any blocks that haven't been confirmed. In this way you keep each block under TCP/IP limitations for a packet and you keep track of what data has successfully been received and can "repair" any missing (partial records) data by re-sending the missing parts.

    It's overly complicated, probably, but I have found that sometimes things need to be...as a bittorrent client, it is an extremely simple implementation :-)

  3. #3

    Thread Starter
    Frenzied Member
    Join Date
    Dec 2012
    Posts
    1,473

    Re: VB6 - IOCP problem

    SmUX2k;

    Thank you for responding. What you have to appreciate in this attempt is that both the Winsock Control and my own SimpleSock are too slow. IOCP is much faster because much of the work is done by the Kernel. The downside is that it is also less flexible, and there is not a lot of information available, especially with VB6. As a matter of fact, many people will tell you it is not possible.

    Because the intent is to eventually add encryption, the individual records have to be processed as a lump sum, and this requires a header. The size of the record is dependent on the purpose of the application. Short bursts of data from many sources will use shorter record lengths. Longer streams of data will use longer records, but not so long that they slow down the encryption/decryption routines. The record size is a trade-off, but necessary. If the application supports many different record sizes, there are even more support issues. VB6 is not known for its speed, but when compiled to Native Code, it is much better than script based code. In the IDE, it is relatively easy to troubleshoot, but it was never designed to support concurrent processing as I am trying to do.

    J.A. Coutts

  4. #4
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,144

    Re: VB6 - IOCP problem

    Quote Originally Posted by couttsj View Post
    . . . many people will tell you it is not possible.
    It's your experience with getting "partial" records (that is currently stumbling your implementation) which explains the general sentiment about IOCP but my hunch here is that you are having threading issue i.e. the problem has nothing to do with IOCP nature, implementation or being too fast (which is ridiculous) to have complete records received.

    It's the basics which are hard to get once you skip too much base material and advance too fast -- e.g. hashes, algebraic rings/groups, key exchnages, prime numbers, Fermat little theorem, unicode, byte-arrays, COM, threading, synchronization primitives where your problem in understanding lies. Btw, there is no one else to debug your codes but you and in this case the pain is self-inflicted.

    cheers,
    </wqw>

  5. #5
    Frenzied Member VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    1,382

    Wink Re: VB6 - IOCP problem

    Quote Originally Posted by wqweto View Post
    e.g. hashes, algebraic rings/groups, key exchanges, prime numbers, Fermat little theorem, unicode, byte-arrays, COM, threading, synchronization primitives where your problem in understanding lies.
    Your forgot to add "solving the Riemann hypothesis" to your list but other than that, yeah, multi-threading issues are caused mainly by synchronization problems.

  6. #6
    Fanatic Member
    Join Date
    Apr 2021
    Posts
    516

    Re: VB6 - IOCP problem

    Going on what you said and others also, there's a few things I have to say that might help a little.

    Firstly, the suggestion of creating a "bittorrent-style" packet system whereby you have 1k packet size is NOT a replacement for your 64k packet system, it's merely the transport layer of the process. If you divide the packet number by 64 you will know what "local packet" it goes into and if you multiply the local packet number by 64 and deduct that from the transport packet number you'll have which part of the local packet it is. Once all 64 parts of a local packet is received, you can process it locally as if it was sent as one big packet. Thus, I am not saying you switch to a 1k packet system, just create a layer inbetween that performs more reliably and can be micro-managed as a go-between for the system you need that might not be working as well. I'm also not saying that this is the best option, it is just something that I would consider if I had a problem with reliability (as the protocol was designed specifically to ensure efficient transfer of data with robust error correction when the source of the data transfer is unreliable). The extra overheads of a bittorrent-style transport layer (the amount of wasted bytes used when transporting just 1024 bytes of data) make it a wasteful option, but if it is currently plagued by unreliability then having this layer may make a difference.

    Also, as others have suggested you might not fully understand the process, another suggestion is for you to start again with the app in a brand new programming environment without all the bloat and code you already have. Try to implement the transfer of data again in this new app, almost totally ignoring the code in your current program (anything that you KNOW works can be copied over, and anything that supports it also...don't have to 100% write everything from scratch), though perhaps being mindful of what will be needed in that program if you manage to get it working in the new app so that you can transfer the code over if you're happy with it. I have found that starting from scratch with code that I don't fully understand will allow me to better understand the process that needs to be followed, and I will gain experience and knowledge in that specific method in the process. Even if you think you understand it enough to write code to implement the process, your results suggest otherwise and starting from scratch will allow you to try again and maybe even learn something about the process. What's the worst that can happen? Perhaps you'll encounter the same problem again, but this time you will have a project you can package up into a ZIP and post here for other people to look at and offer suggestions for. I won't be among those people (are you shocked? :-) ) but I am sure there are a few who would be able to help debug the code. I've done this so many times with different projects in the past, and gained so much extra knowledge by repeating the process. Repetition aids in learning, often EVEN when you are good in a specific task...there's a reason why people who want to get better choose to train in their skill by repeatedly doing it :-)

    I am not suggesting you start again with the app...I am suggesting you start the process again in a new app, with the aim being to gain further experience in how everything works with everything, and possibly come up with an implementation that doesn't have a bug you accidentally put in it before (which is often the case with me :-) )

    Lastly, those who do not learn from their mistakes are doomed to repeat them.

    https://www.codeproject.com/Articles...ver-COM-Compon may also be handy, but it is written in VS rather than VB6. I can find numerous examples written in VS or C++, but very little in VB6. Perhaps if you broke down either a C or VS version to its component parts and worked out how to implement them in VB6 you might be able to get somewhere? What is it about VB6 that makes it harder to achieve there in comparison to VS, for instance? "VB6 doesn't do [this]"...why the hell not? People in this forum have proven time and time again that VB6 can do almost ANYTHING as long as people are willing to spend time on it :-)

  7. #7
    Hyperactive Member
    Join Date
    Mar 2019
    Posts
    425

    Re: VB6 - IOCP problem

    I don't understand the mechanism you are using but TCP will always break the traffic down to the agreed MTU between end points. Have you thought about including a header of a fixed length that contains the size of the data you intend to transmit and once you get that wait until you have received that many bytes before reading the received data?

  8. #8

    Thread Starter
    Frenzied Member
    Join Date
    Dec 2012
    Posts
    1,473

    Re: VB6 - IOCP problem

    Quote Originally Posted by vbwins View Post
    I don't understand the mechanism you are using but TCP will always break the traffic down to the agreed MTU between end points. Have you thought about including a header of a fixed length that contains the size of the data you intend to transmit and once you get that wait until you have received that many bytes before reading the received data?
    That is basically what I intend to do, only I don't have control over what gets delivered. The completion port does. The other option is a circular buffer, which I have never implemented before. What do you think?

    J.A. Coutts

  9. #9

    Thread Starter
    Frenzied Member
    Join Date
    Dec 2012
    Posts
    1,473

    Re: VB6 - IOCP problem

    To solve the problem, I came up with this simulation. The bulk of the code is for the generation of the samples, and the only part relative to the issue is the "Process" routine. My intention is to make another routine using a circular buffer, and to test each routine with real world data to find out which is the fastest.

    The clearing of the Record buffer is not really necessary, and was provided for clarity only.

    J.A. Coutts
    Code:
    Option Explicit
    
    Private Const bMax As Long = 15 'Max Length of InBuf
    Private Const rMax As Long = 10 'Max Length of RecBuf
    
    Private InBuf(bMax - 1) As Byte 'Simulates completion port buffer
    Private RecBuf(rMax - 1) As Byte 'simulates record buffer
    Private RecLen As Long 'Length of current record
    Private RecType As Long 'Current record type
    Private Buf1(bMax - 1) As Byte 'Sample 1
    Private Buf2(bMax - 1) As Byte 'Sample 2
    Private Buf3(bMax - 1) As Byte 'Sample 3
    Private Buf4(bMax - 6) As Byte 'Sample 4
    Private nBytes As Long 'Byte count recoverd from header
    Private pFlg As Long 'Partial record flag
    
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    
    Private Sub PrintBuf()
        Dim N%
        Debug.Print "InBuf:";
        For N% = 0 To 14
            Debug.Print InBuf(N%);
        Next N%
        Debug.Print
    End Sub
    
    Private Sub PrintRec()
        Dim N%
        Debug.Print "RecBuf:";
        For N% = 0 To 9
            Debug.Print RecBuf(N%);
        Next N%
        Debug.Print
    End Sub
    
    Private Sub Process()
        If pFlg > 0 Then 'pFlg indicates partial record fill
            RecType = 0
            'Retain current RecLen
        Else 'Full record received
            RecType = InBuf(0)
            RecLen = InBuf(3) * 256 + InBuf(4)
        End If
        If nBytes < bMax And RecType <> 9 Then
            pFlg = RecLen + 5 - nBytes
            Erase RecBuf
            CopyMemory RecBuf(0), InBuf(pFlg), nBytes - 5
            PrintRec
            Debug.Print " Incomplete Record - Do Not Process"
        Else
            Erase RecBuf
            CopyMemory RecBuf(0), InBuf(5), RecLen
            PrintRec
        End If
    End Sub
    
    Private Sub cmd1_Click()
        nBytes = 11
        CopyMemory InBuf(0), Buf1(0), nBytes
        PrintBuf
        Process
    End Sub
    
    Private Sub cmd2_Click()
        CopyMemory InBuf(0), Buf1(nBytes), pFlg
        CopyMemory InBuf(pFlg), Buf2(0), bMax - pFlg
        PrintBuf
        Process
    End Sub
    
    Private Sub cmd3_Click()
        CopyMemory InBuf(0), Buf2(nBytes), pFlg
        CopyMemory InBuf(pFlg), Buf3(0), bMax - pFlg
        PrintBuf
        Process
        pFlg = 0
    End Sub
    
    Private Sub cmd4_Click()
        nBytes = UBound(Buf4) + 1
        CopyMemory InBuf(0), Buf4(0), nBytes
        PrintBuf
        Process
    End Sub
    
    Private Sub Form_Load()
        Dim N%
        Buf1(0) = 9: Buf1(1) = 3: Buf1(2) = 3: Buf1(3) = 0: Buf1(4) = 10
        Buf2(0) = 9: Buf2(1) = 3: Buf2(2) = 3: Buf2(3) = 0: Buf2(4) = 10
        Buf3(0) = 9: Buf3(1) = 3: Buf3(2) = 3: Buf3(3) = 0: Buf3(4) = 10
        For N% = 5 To 14
            Buf1(N%) = N%
            Buf2(N%) = N% + 30
            Buf3(N%) = N% + 50
        Next N%
        Buf4(0) = 9: Buf4(1) = 3: Buf4(2) = 3: Buf4(3) = 0: Buf4(4) = 4
        Buf4(5) = 71: Buf4(6) = 72: Buf4(7) = 73: Buf4(8) = 74
        nBytes = 15
    End Sub

  10. #10

    Thread Starter
    Frenzied Member
    Join Date
    Dec 2012
    Posts
    1,473

    Re: VB6 - IOCP problem

    The only circular buffer examples I could find dealt with single byte buffers subject to overflow. But that information pointed me in the direction of double buffer without the possibility of overflow. Although IOCP cannot predict when a full record is ready for release, it doesn't release a new record until you instruct the completion port that you are ready to accept it. This allows us to establish a buffer twice the size of the IOCP buffer and access a complete data record using pointers.

    The simulation below uses two different scenarios. Command buttons 1 to 4 produce 4 I/O records (5 header + 10 data), the first one of which is short by 4 bytes. The next 2 records are of full I/O record length, but the first 4 bytes before the header simply fills in the preceding short record. The last record consists of the header and 4 bytes, for a total data length of 34 bytes.
    Code:
    Option Explicit
    
    Private Const bMax As Long = 15 'Max Length of InBuf
    Private Const rMax As Long = 10 'Max Length of Record
    
    Private InBuf(bMax - 1) As Byte 'Simulates completion port buffer
    Private RecBuf(2 * bMax - 1) As Byte 'simulates record buffer
    Private RecLen As Long 'Length of current record
    Private RecType As Long 'Current record type
    Private FileLen As Long
    Private Buf1(bMax - 1) As Byte 'Sample 1
    Private Buf2(bMax - 1) As Byte 'Sample 2
    Private Buf3(bMax - 1) As Byte 'Sample 3
    Private Buf4(bMax - 6) As Byte 'Sample 4
    Private nBytes As Long 'Byte count
    Private pFlg As Long 'Partial record flag
    Private Ptr1 As Long 'Start pointer
    Private Ptr2 As Long 'End pointer
    
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    
    Private Sub PrintBuffer()
        Dim N%
        Debug.Print "Buffer:";
        For N% = 0 To 2 * bMax - 1
            Debug.Print RecBuf(N%);
        Next N%
        Debug.Print
    End Sub
    
    Private Sub PrintInBuf()
        Dim N%
        Debug.Print "InpBuf:";
        For N% = 0 To nBytes - 1
            Debug.Print InBuf(N%);
        Next N%
        Debug.Print
    End Sub
    
    Private Sub PrintRec(pStart As Long, pEnd As Long)
        Dim N%
        Debug.Print "Record:";
        For N% = pStart + 5 To pEnd - 1
            Debug.Print RecBuf(N%);
        Next N%
        Debug.Print
    End Sub
    
    Private Sub Process()
        'Ptr2 - Ptr1 defines current RecBuf including header
        If Ptr2 > bMax Then
            If pFlg Then
                CopyMemory RecBuf(Ptr2), InBuf(0), pFlg
                Ptr2 = 0
                CopyMemory RecBuf(Ptr2), InBuf(pFlg), nBytes - pFlg
            End If
        Else
            CopyMemory RecBuf(Ptr2), InBuf(0), nBytes
        End If
        Ptr2 = Ptr1 + nBytes - pFlg
        RecType = RecBuf(Ptr1)
        RecLen = RecBuf(Ptr1 + 3) * 256 + RecBuf(Ptr1 + 4)
        PrintBuffer
        FileLen = FileLen - RecLen
        If pFlg Then 'Print previous record
            If Ptr1 = bMax Then
                PrintRec 0, bMax
            Else
                PrintRec bMax, bMax * 2
            End If
        End If
        If Ptr2 - Ptr1 < RecLen + 5 Then 'Incomplete record
            PrintRec Ptr1, Ptr2
            Debug.Print " Incomplete Record - Do Not Process"
            pFlg = RecLen + 5 - (Ptr2 - Ptr1)
        Else 'Complete record
            If FileLen < rMax Then Ptr2 = Ptr1 + pFlg + 5 'Last record
            PrintRec Ptr1, Ptr2
            pFlg = 0: Ptr1 = 0: Ptr2 = 0
            Exit Sub
        End If
        If Ptr1 = 0 Then Ptr1 = bMax Else Ptr1 = 0
    End Sub
    
    Private Sub cmd1_Click()
        nBytes = 11
        FileLen = 34
        CopyMemory InBuf(0), Buf1(0), nBytes
        PrintInBuf
        Process
    End Sub
    
    Private Sub cmd2_Click()
        CopyMemory InBuf(0), Buf1(nBytes), pFlg
        CopyMemory InBuf(pFlg), Buf2(0), bMax - pFlg
        nBytes = bMax
        PrintInBuf
        Process
    End Sub
    
    
    Private Sub cmd3_Click()
        CopyMemory InBuf(0), Buf2(nBytes - pFlg), pFlg
        CopyMemory InBuf(pFlg), Buf3(0), bMax - pFlg
        nBytes = bMax
        PrintInBuf
        Process
    End Sub
    
    Private Sub cmd4_Click()
        CopyMemory InBuf(0), Buf3(nBytes - pFlg), pFlg 'Last 4 bytes of Buf3
        CopyMemory InBuf(pFlg), Buf4(0), nBytes - pFlg 'First 9 bytes of Buf4
        nBytes = 13
        PrintInBuf
        Process
    End Sub
    
    Private Sub cmd5_Click()
        nBytes = bMax
        FileLen = 34
        CopyMemory InBuf(0), Buf1(0), nBytes
        PrintInBuf
        Process
    End Sub
    
    Private Sub cmd6_Click()
        nBytes = 11
        CopyMemory InBuf(0), Buf1(0), nBytes
        PrintInBuf
        Process
    End Sub
    
    
    Private Sub cmd7_Click()
        CopyMemory InBuf(0), Buf2(nBytes), pFlg
        CopyMemory InBuf(pFlg), Buf3(0), bMax - pFlg
        nBytes = bMax
        PrintInBuf
        Process
    End Sub
    
    Private Sub cmd8_Click()
        CopyMemory InBuf(0), Buf3(nBytes - pFlg), pFlg 'Last 4 bytes of Buf3
        CopyMemory InBuf(pFlg), Buf4(0), nBytes - pFlg 'First 9 bytes of Buf4
        nBytes = 13
        PrintInBuf
        Process
    End Sub
    
    Private Sub Form_Load()
        Dim N%
        Buf1(0) = 9: Buf1(1) = 3: Buf1(2) = 3: Buf1(3) = 0: Buf1(4) = 10
        Buf2(0) = 9: Buf2(1) = 3: Buf2(2) = 3: Buf2(3) = 0: Buf2(4) = 10
        Buf3(0) = 9: Buf3(1) = 3: Buf3(2) = 3: Buf3(3) = 0: Buf3(4) = 10
        For N% = 5 To 14
            Buf1(N%) = N% + 5
            Buf2(N%) = N% + 15
            Buf3(N%) = N% + 25
        Next N%
        Buf4(0) = 9: Buf4(1) = 3: Buf4(2) = 3: Buf4(3) = 0: Buf4(4) = 4
        Buf4(5) = 40: Buf4(6) = 41: Buf4(7) = 42: Buf4(8) = 43
        nBytes = 15
    End Sub
    Command buttons 5 to 9 produce a similar set of records, except that the first record is complete and the second record is short.

    I realize that these are not the only possible scenarios, but at this point in time I am not sure what to expect. I never really expected the short records that I have already experienced, which was confirmed using a packet analyzer. The failure occurs at the partial record but surfaces further in the process because the records are out of sync.

    120 <---(list rec'd)
    17 <----(file name sent)
    8197 <--(record 1 rec'd)
    5840 <--(Partial record)
    2920 <--(Rest of record & part of next)
    8197 <--(Random Record)
    8197 <--(Random Record)
    Failure

    J.A. Coutts

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width