This builds upon my other
socket example. As you know, the tcp socket has a 1.4kb limitation so how do you send data larger than that? You break it into chunks, or multiple packets. Unlike UDP, TCP guarantees delivery AND order of the packets so long as the socket connection hasn't changed. The first step is in create a standard packet header your app will use. In my example, I simply write a code to the first byte. For normal game data which comes in at less than the 1.4kb limit, you could simply write 0 so the client knows it's a single packet with nothing following it. For an image, I send a 2 followed by total size to expect. The client will see the code, know it's an image and the next integer in that packet will contain the size. We then begin flushing the image data to the client. The client will receive the raw data and use it to rebuild the image until that size is met. Once all the bytes are received by the client, the client will then go back to reading packets the normal way; looking at the code in the first byte.
An option I didn't include but could be added is to send a checksum with the initial coded packet. Then when the image has been fully reconstructed you can use the checksum to verify the integrity of the image.
edit: corrected a typo that somehow still worked correctly.
// Project: socket_image
// Created: 2023-11-20
// show all errors
SetErrorMode(2)
// set window properties
SetWindowTitle( "socket_image" )
SetWindowSize( 1024, 768, 0 )
SetWindowAllowResize( 1 ) // allow the user to resize the window
// set display properties
SetVirtualResolution( 1024, 768 ) // doesn't have to match the window
SetScissor( 0,0,0,0 ) // use the maximum available screen space, no black borders
UseNewDefaultFonts( 1 ) // since version 2.0.22 we can use nicer default fonts
// The listener is our server. In this example, we will listen for incoming
// connections on the localhost at port 8080
listener = createSocketListener("127.0.0.1", 8080)
// This will be the server's connection to connected client. In a true server
// this would be an array as you'd potentially have multiple clients connecting.
// For this example, we'll only deal with a single client, ourself.
conn = 0
// The client's connection to the server.
client = 0
fromServer$ = ""
fromClient1$ = ""
fromClient2$ = ""
msg$ = "Client disconnected."
#CONSTANT DATA_MEMBLOCK_IMG = 2
// Data coming from server
img = loadImage("bttf2_3478174a.jpg")
m = createMemblockFromImage(img)
mSize = getMemblockSize(m)
sendingMemblock = 0
mPointer = 0
// Used on client
doingLargeThing = 0
clientMem = 0
cPointer = 0
clientImg = 0
do
// Free the sockets on hitting ESC and exit the app
if getRawKeyPressed(27) = 1
deleteSocketListener(listener)
deleteSocket(conn)
deleteSocket(client)
end
endif
// Press 'c' to connect the client to the server
if getRawKeyPressed(67) = 1
client = connectSocket('127.0.0.1', 8080, 3000)
endif
// We could loop through all the bytes at once, letting AGK flush
// everything automatically. But by doing only 1k iterations at a time,
// it allows our program to continue running without being blocked.
// On small data files you'd hardly notice, but sending larger files
// would hault the program until its all sent. We don't want that!
if sendingMemblock = 1
packSize = mPointer + 1000
if packSize > mSize-4 then packSize = mSize-4
// Loop through the next 1000 bytes, writing integers (hence the step 4)
for i = mPointer to packSize step 4
SendSocketInteger(conn, getMemblockint(m, i))
next i
mPointer = packSize+4
// Send the data
flushSocket(conn)
// If we've sent all the memblock data
if packSize+4 >= mSize
sendingMemblock = 0
endif
endif
// Press 'spacebar' to send timestamp from server to the client
if getRawKeyPressed(32) = 1
// Make sure the client is still connected
if getSocketConnected(conn) = 1
// Send our data to the buffer
//sendSocketString(conn, str(GetMilliseconds()))
// The first packet sent needs to let the client know what it's about to receive.
// The first byte will contain a code 'DATA_MEMBLOCK_IMG' letting the client
// know how to handle the other incoming packets as well as any other initialization data
// this packet might contain. In this case, the total size of the object we're about to send.
if sendingMemblock = 0
sendingMemblock = 1
SendSocketByte(conn, DATA_MEMBLOCK_IMG)
SendSocketInteger(conn, mSize)
// Flush the data. Until you flush the socket, anything
// you write to the buffer will sit there.
// Caution: Once the buffer has reach 1.4KB, it will automatically flush the buffer.
flushSocket(conn)
endif
endif
endif
// Until we have a client connected, the server will
// continue to listen for incoming connections. You
// would modify this to always listen if your server
// is to handle multiple clients at once.
if conn = 0
lc = getSocketListenerConnection(listener)
if lc > 0 then conn = lc
endif
// The client listens for incoming data from the server
if client > 0
// Make sure client is still connected to the server
if getSocketConnected(client) = 1
msg$ = "Connected to server at " + getSocketRemoteIP(client)
// Check if any data is awaiting to be read
if getSocketBytesAvailable(client) > 0
// Not pending any large/chunked transfers
if doingLargeThing = 0
// All initial packets will have the first byte telling us what's to come
code = getSocketByte(client)
// Code indicates we're about to receive an image memblock.
if code = DATA_MEMBLOCK_IMG
// We know the next data sent in this packet should contain the memblock size needed.
clientMemSize = getSocketInteger(client)
// Allocate the memblock space
clientMem = createMemblock(clientMemSize)
// Pointer of this memblock used for tracking where we write the incoming data
cPointer = 0
// Once this packet is received, we know the next packets will be intended for
// building this memblock
doingLargeThing = 1
endif
else
// If bytes are available AND we haven't reached the total size of our memblock yet
// The latter is important because a new fresh packet for something else could be immediately sent
// after the server has completed sending us the image. We want that to handled above.
while getSocketBytesAvailable(client) and cPointer < clientMemSize
// Get each integer and write it to the memblock
b = getSocketInteger(client)
setMemblockInt(clientMem, cPointer, b)
// increase out memblock pointer
inc cPointer,4
// If we've reached the end of the intended size for this memblock
if cPointer >= clientMemSize
// Stop checking for memblock chunks
doingLargeThing = 0
// create an image from the memblock
clientImg = createImageFromMemblock(clientMem)
// delete memblock, it's no longer needed
deleteMemblock(clientMem)
// create a sprite to display our image
cs = createSprite(clientImg)
// end the while loop so new packets can be handled above appropriately.
exit
endif
endwhile
endif
endif
else
msg$ = "Client disconnected."
endif
endif
if cs > 0 then setSpritePosition(cs, getRawMousex(), getRawMouseY())
print("Press 'c' to connect to server. Then 'spacebar' to send image to client.")
print("")
print(msg$)
print("")
p = floor(((cPointer+0.0) / clientMemSize) * 100)
if clientMem > 0
print(str(p)+"% completed.")
else
print("0% completed.")
endif
print("")
print("FPS: "+str(floor(screenfps())))
Sync()
loop