Winsock Data Handling - VB6

Introduction

This tutorial will teach you the "proper" way to send and receive data using the Winsock control. Using this method will save you from a lot of unexpected/unexplained bugs in the future which can cause a lot of grief. Being mostly a Winsock/client-server programmer myself, I hope to prevent others from experiencing the same depressing problems I have faced in the past with my programs.

A lot of people (myself included) jump right into using the Winsock control before ever fully grasping what exactly is going on "behind the scenes". I won't go into great detail about TCP or the other low-level stuff because (fortunately) most of it is irrelevant and already taken care for us by the kind developers of the Winsock (windows sockets) class, and even more so, with the Winsock control.

This tutorial is going to be very quick, and straight forward. No, long, boring technical crap that you have to memorize. I do assume some basic experience and knowledge of the Winsock control, mainly its properties, methods, and events. If you don't know these, there's plenty of tutorials on that so go learn. But even with minimal knowledge you should still be able to follow along.

Proper sending

Ok, first mistake people make. They assume one SendData call from Winsock will equal one DataArrival on the other end. This is not the case. TCP will usually split up large packets (average of about 4096 bytes or 4 KB in size) or take smaller ones and send them together. Not programming accordingly will make your client/server programs extremely fragile and bugs will pop up with no clear indication of where. So how do we avoid this problem? Most will use a pause method, the Sleep API function, a timer, or something of that sort. There is no need for that. Let's look at a common example.

Winsock1.SendData "Message1"
Winsock1.SendData "Message2"

Ok, so logically thinking, the data will arrive on the other end like:

Message1
Message2

Right? Wrong. Sometimes, maybe, if you're lucky. In most cases, it will arrive like:

Message1Message2

This can create all kinds of problems in your programs. So, how do we avoid this? How can we still call consecutive .SendData calls without ever having to worry about this? The solution is simple. Add something to the end of each data "packet" (and I use that term loosely because TCP is actually a streaming protocol and not treated as separate packets).

Let's add in something random like a Chr$(2) to the end of each packet. So, if they arrive together, the receiving end will know where one starts, and the other ends. Here's an example:

Winsock1.SendData "Message1" & Chr$(2)
Winsock1.SendData "Message2" & Chr$(2)

That's all there is to it. If they get sent at the same time, there will be something (called a delimiter) to separate each packet.

Most packets in client/server programs aren't this simple. They contain many parameters in each packet and not just a simple "Message". The same rule applies. Delimit (separate) them with another character, like Chr$(4). For example, a chat message packet:

Winsock1.SendData "MSG" & Chr$(4) & "Username" & Chr$(4) & "Message" & Chr$(2)
Winsock1.SendData "MSG" & Chr$(4) & "Username" & Chr$(4) & "Message2" & Chr$(2)

Now you have a packet with a delimiter for each piece of information as well as a delimiter for each actual packet.

Proper receiving

Now that we have the sending part all done, we still need to receive it properly. In case you forgot already, I stated earlier that TCP is a streaming protocol. All these packets will arrive at your receiving end as a stream, one after the other, sometimes only half of one, sometimes all of them arrive perfectly. Our program needs to handle this smoothly.

Luckily, I have developed this code so you can basically copy & paste it into your own program with only having to change a few things. Please at least try to understand how it works. If you're just not familiar enough with this stuff, or Winsock in general, maybe come back and try again when you are. But really, it's pretty simple.

Before showing the code, let's look at the 2 possibilities that we should expect to happen in a client/server program.

  1. 2 packets get joined together (already solved with delimiter).
  2. 1 packet gets split in the middle.

Ok, so we're not home free yet. What if one of these packets get cut up right in the middle? VB (nor Winsock/TCP) have any idea how our program works and how we're sending data. All it knows is there is a set limit on how much data it can trasmit at once at any given time. So it will cut up our data however it sees fit. Let's look at an example of one of our packets arriving completely, but the 2nd one getting split mid-way.

To keep things simple, a forward slash (/) will be used as a parameter delimiter, and a * as a packet delimiter (instead of Chr$(2) and Chr$(4)).

Winsock1.SendData "MSG/Username/Message 1*"
Winsock1.SendData "MSG/Username/Message 2*"

Ok, so we sent 2 messages. What if the 2nd one got split up in the middle?

MSG/Username/Message1*MSG/Usern

Boy that sucks. What are we supposed to do with this? Our 2nd packet isn't even complete! So what do we do? Nothing. We ignore it. We store the truncated packet into a temporary variable until the next DataArrival event where we add on to it and don't process it until it's complete. Pretty simple.

Please just show me the code

Ok, so assuming we are sending data correctly, here is the code I have come up with in its simplest form for handling streaming data like the chat messages above. If optimization is an issue I have also posted a faster method (which might be used in a time-critical server) that does all of this using byte arrays instead of strings.

I can't be bothered color-coding all of this VB code using CSS, so just paste it into VB and read through the comments there.

Also, it may look a bit long. Don't be discouraged, it's 90% comments. Just try your best to follow along. Also, this enables you to add in your own commands without having to change this code. All you have to do is add your own Case "CMD" to the Select/Case statements and send them accordingly from your sender program.

Option Explicit

'Form-scope variable to store all received data
'in a buffer.
Private strBuffer As String

'Ok, so our received data is this (from the tutorial):
'MSG/Username/Message1*MSG/Usern

'On the next DataArrival, we will receive the rest of it.
'ame/Message2*
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
    Dim strData As String, bolTruncated As Boolean
    Dim strTruncated As String
    
    'First, get the received data and put it into a variable.
    Winsock1.GetData strData, vbString, bytesTotal
    
    'strData now contains:
    'MSG/Username/Message1*MSG/Usern
    
    'Add this temp data to the buffer.
    strBuffer = strBuffer & strData
    strData = vbNullString
    
    'First thing we do, is check for the packet delimiter (*).
    'If it's not found, just exit. We haven't even received one
    'full packet yet (very rare but possible I guess).
    If InStr(1, strBuffer, "*") = 0 Then Exit Sub
    
    'Code has gotten this far so there is a *
    'Before processing the packets, let's check if we have
    'any truncated ones.
    
    'If all packets are complete, the last character will be a *
    If Right$(strBuffer, 1) <> "*" Then
        'Crap.
        bolTruncated = True
        'Let's take everything from the last * found and to the right.
        'This is the truncated part that we'll process later.
        Dim lonPos As Long
        lonPos = InStrRev(strBuffer, "*")
        strTruncated = Mid$(strBuffer, lonPos + 1)
        'Done.
    End If
    
    'If there was any truncated data, we stored it away for now.
    'Let's deal with the packets that are complete.
    
    'First, split up all data into separate packets.
    Dim strPackets() As String, lonCount As Long
    Dim lonLoop As Long
    
    strPackets = Split(strBuffer, "*")
    'Now, we are left with:
    'strPackets(0) - MSG/Username/Message1
    'strPackets(1) - MSG/Usern
    
    'The 2nd one (#1) is truncated, so we'll ignore it.
    lonCount = UBound(strPackets) 'Number of packets total.
    If bolTruncated Then lonCount = lonCount - 1 'minus the last one if it's truncated.
    
    'Loop through all the packets (except truncated one if found).
    For lonLoop = 0 To lonCount
        'strPackets(lonLoop) is the current packet.
        
        'Must be at least 3 bytes long in this example
        'since the command itself (MSG - signifying a chat message)
        'is 3 bytes long.
        If Len(strPackets(lonLoop)) > 3 Then
            'Let's determine what to do judging by the first 3 bytes
            'or "command".
            Select Case Left$(strPackets(lonLoop), 3)
                
                'Chat message.
                Case "MSG"
                    'Call your ParseMessagePacket() sub here.
                
                'Add your own. :)
                'Case "XXX"
                    'ParseXXXPacket()
                
                'Case "YYY"
                    'ParseYYYPacket()
            End Select
        End If
        'DoEvents
    Next lonLoop
    Erase strPackets
    
    'We just processed all packets that were complete.
    'Now what?
    
    'First, erase the buffer. We're done with it.
    strBuffer = vbNullString
    
    'But what about the truncated part? Next-time around, we need to
    'process that one (if it's complete).
    'So if the data was truncated, add it back into the buffer.
    If bolTruncated Then strBuffer = strTruncated
    
    'Done.
    'Buffer will automatically keep adding to itself due to this line at the top:
    'strBuffer = strBuffer & strData
End Sub

Summary

I hope this makes sense, if not, just copy & paste the receiving end code into your programs and send the data according to the examples above.

I included the "optimized" version of this code (strings are actually plenty fast since buffer length stays pretty small) but who knows. I like rewriting string code to work with byte arrays because the speed difference can sometimes be amazing (or sometimes not so amazing). Anyway, it is included with this tutorial in the Sample folder.

Best of luck with all your Winsock programs! (And try and have fun ;))