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.
|