QB64 Phoenix Edition
Steve's Data Transfer Protocol - Printable Version

+- QB64 Phoenix Edition (https://qb64phoenix.com/forum)
+-- Forum: QB64 Rising (https://qb64phoenix.com/forum/forumdisplay.php?fid=1)
+--- Forum: Prolific Programmers (https://qb64phoenix.com/forum/forumdisplay.php?fid=26)
+---- Forum: SMcNeill (https://qb64phoenix.com/forum/forumdisplay.php?fid=29)
+---- Thread: Steve's Data Transfer Protocol (/showthread.php?tid=176)



Steve's Data Transfer Protocol - SMcNeill - 04-25-2022

As requested:

I've recently been playing around with various programs which communicate with each other via TCP/IP, I've decided that I needed some sort of simple protocol to make certain that the data I send and receive from computer to computer is correct and not corrupted.  Here's the routine I've basically set up so far:


Code: (Select All)
DIM SHARED Host AS LONG

SCREEN _NEWIMAGE(800, 600, 32)
COLOR &HFFFFFFFF, &HFF000000
Host = _OPENHOST("TCP/IP:7990") ' this will be the host code

DO
    Player = GetClient 'Not
    IF Player THEN
        PRINT "New Player connected"
        'Do stuff
        UserData$ = In$(Player)
        'do stuff with the data the user sent

        'and close the connection
        CLOSE Player
        Player = 0
    END IF
    _LIMIT 30
LOOP


FUNCTION GetClient
    GetClient = _OPENCONNECTION(Host) ' receive any new connection
END FUNCTION

FUNCTION In$ (who)
    DIM b AS _UNSIGNED _BYTE
    'CHR$(2) = Start of Text
    'CHR$(3) = End of Text
    'CHR$(4) = End of Transmission (It's what we use to tell the client, "We give up!  Closing connection!"
    'CHR$(6) = Acknowledge
    'CHR$(15) = Not Acknowledge

    GET #who, , b 'just check for a single byte from each connection

    IF b <> 2 THEN
        'If we get something which isn't a CHR$(2) to start communication, send back a failure notice.
        SendError who
        EXIT FUNCTION 'Exit so we can move on to the next connection to check for that leading chr$(2)
    END IF

    'Only if that initial byte is CHR$(2), do we acknowledge receipt and await further messages.

    SendConfirmation who 'we send ACKnowledgement back to tell the client we're ready for them to talk to us.

    DO
        count = count + 1
        timeout## = ExtendedTimer + 5
        DO
            _LIMIT 100 'no need to check for incoming information more than 100 times a second!
            GET #who, , a$
            IF a$ <> "" THEN tempIn$ = tempIn$ + a$ 'tempIn$ should never be more than 105 bytes
            ETX = INSTR(a$, CHR$(3)) '       chr$(3) is our ETX character (End of TeXt)
            IF ETX THEN EXIT DO
            IF ExtendedTimer > timeout## THEN 'If it takes over 5 seconds to send 100 bytes (or less) of info
                SendError who '              something is wrong.  Terminate the attempt, but be nice, and let
                EXIT FUNCTION '              the other client know something went wrong, so they can try again,
            END IF '                         if they want to.
        LOOP UNTIL LEN(tempIn$) > 105 'If we have over 105 bytes with our string, we didn't send the data properly.
        IF LEN(tempIn$) > 105 THEN
            SendError who 'send the client an error message
            EXIT FUNCTION
        END IF
        tempIn$ = _TRIM$(LEFT$(tempIn$, ETX - 1)) 'strip off the ETX character and check to make certain data is valid.

        c$ = RIGHT$(tempIn$, 4) 'these 4 bytes are the checksum

        CheckSum = CVL(c$) 'Check to make certain the data apprears valid.
        FOR i = 1 TO LEN(l$): Check = Check + ASC(l$, i): NEXT
        IF CheckSum <> Check THEN '            Our data is not what we expected.  Part may be lost, or corrupted.
            SendError who
            EXIT FUNCTION
        ELSE
            SendConfirmation who
            EXIT DO
        END IF
    LOOP UNTIL count = 5
    'If we get bad data 5 times in a row, something is wrong.  We're just going to close the connection.
    IF count = 5 THEN
        SendError who
        EXIT FUNCTION
    END IF

    'and if we're down this far, our data has been recieved, verified, and is now good to use.
    In$ = LEFT$(tempIn$, 4) 'left part of the string is the data the user is sending us
END FUNCTION

SUB SendError (who)
    DIM b AS _UNSIGNED _BYTE
    b = 4
    PUT #who, , b
END SUB

SUB SendConfirmation (who)
    DIM b AS _UNSIGNED _BYTE
    b = 6
    PUT #who, , b
END SUB

FUNCTION ExtendedTimer##
    DIM m AS INTEGER, d AS INTEGER, y AS INTEGER
    DIM s AS _FLOAT, day AS STRING
    day = DATE$
    m = VAL(LEFT$(day, 2))
    d = VAL(MID$(day, 4, 2))
    y = VAL(RIGHT$(day, 4)) - 1970
    SELECT CASE m 'Add the number of days for each previous month passed
        CASE 2: d = d + 31
        CASE 3: d = d + 59
        CASE 4: d = d + 90
        CASE 5: d = d + 120
        CASE 6: d = d + 151
        CASE 7: d = d + 181
        CASE 8: d = d + 212
        CASE 9: d = d + 243
        CASE 10: d = d + 273
        CASE 11: d = d + 304
        CASE 12: d = d + 334
    END SELECT
    IF (y MOD 4) = 2 AND m > 2 THEN d = d + 1 'add a day if this is leap year and we're past february
    d = (d - 1) + 365 * y 'current month days passed + 365 days per each standard year
    d = d + (y + 2) \ 4 'add in days for leap years passed
    s = d * 24 * 60 * 60 'Seconds are days * 24 hours * 60 minutes * 60 seconds
    ExtendedTimer## = (s + TIMER)
END FUNCTION


Now this isn't going to work to send binary files, as I'm using some of the ASCII characters as reserved command codes, but since this isn't meant to be a file transfer protocol, I don't think it should be a problem.  My command codes are as follows:
    'CHR$(2) = Start of Text

    'CHR$(3) = End of Text

    'CHR$(4) = End of Transmission (It's what we use to tell the client, "We give up!  Closing connection!"

    'CHR$(6) = Acknowledge

    'CHR$(15) = Not Acknowledge



The idea the behind the process is this one:



First, we simply wait for a CHR$(2) character to come in, as a request from a client saying they want to send us data.  If we get anything else before that, we send them an error message.  All messages start with chr$(2), and when we get it, we send a confirmation back to the client so they know we're all set to receive their data (CHR$(6)).



At this point, they send us the data, which is limited to being 105 bytes or less.  This 105 byte structure consists of up to 100 bytes of data, 4 bytes for a checksum of the data sent, and then the termination code.  (CHR$(3))



Once we verify that everything is correct, we either send back a success, or failure signal, to the client.  If we fail, they can try to resend the data, otherwise all is golden.



I tried to comment the process here so that it'd be easily understood by anyone who looks it over, but if anyone has any questions, just ask them.  If there appears to be something wrong with my logic, feel free to tell me about that as well.  I haven't actually tested this in a working program yet (my test game is still in development and hasn't gotten to the point where it's trying to talk back and forth to other games yet), but I don't see anything that looks wrong with it.  Unless I just made a common typo, or other silly mistake, it should work as intended here...


Take a look at it.  See if it looks like a process that will hold up to general usage to send plain text back and forth between computers.  And, if you see something that I goofed on, or overlooked, kindly point it out to me.  If all works as intended, this will end up going into a transfer library later for me, so I can just plug it into any project and use it to send and receive data between devices.  Smile