Posted: 20th Nov 2023 16:40
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.

+ Code Snippet
// 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