That will complicate your protocol, but it can work if your protocol design allows for it. Such as by breaking up the file data into small chunks and send each chunk as an individual packet, and each packet carries a header specifying its type and size so the client can differentiate one packet from another. Make sure to NEVER overlap packets - don't send a new packet until a previous packet is done. That requires putting a lock on the socket if you send packets from multiple threads.
Assuming your protocol doesn't require the client to explicitly ack everything it receives (most protocols don't), then the only way to detect that condition is when SendBuf() fails with a WSAEWOULDBLOCK error because the socket's outgoing buffer filled up due to the client not removing data from that buffer. Until that buffer fills up, any data "sent" is simply buffered locally by the socket without error and is picked up by the OS behind the scenes, so you won't know if the data was actually transmitted and received until an error occurs.
You have to check the socket error code to find out why SendBuf() failed. Any error code other than WSAEWOULDBLOCK should be treated as a fatal error so close the connection. Otherwise, cache all current unsent data and future data somewhere until the OnClientWrite event tells you that the socket can accept data again, then you can try re-sending your cached data, removing any bytes that are accepted by SendBuf() without error, until either the cache is empty or a new error occurs.
Yes, exactly.
The logic is actually really simple:
When sending a new packet, if the cache is not empty then append the entire packet to the end of the cache and don't attempt to send it yet.
Otherwise, send as many bytes as possible for the packet. If SendBuf() fails, stop sending the packet. If the error is WSAEWOULDBLOCK then save the remaining unsent bytes to the end of the cache.
In the OnClientWrite event, if the cache is not empty then send as many bytes as possible from the cache, removing any bytes that SendBuf() accepts. If SendBuf() fails for any reason (you don't need to look at the error code here), stop sending the cache and leave the remaining unsent bytes in it for a future event to re-try.
I've posted examples of this many times in the past in various forums. I use the TCustomWinSocket.Data property to hold the cache (usually using a TMemoryStream), that way the cache follows the socket as its passed around.
But yes, the cache could grow very large if you don't put a max cap or timeout on it. If you try to send a lot of data to an unresponsive client, or if data stays in the cache for a long period of time, then assume the client is dead (even if the OS hasn't reported it yet) and close the connection.
On a similar note, the OnClientRead event can do similar caching. It should read all available bytes from the socket and put them into a separate cache, then you can remove and process only complete packets from the cache, waiting for future events to finish incomplete packets. Packets can span across multiple OnClientRead events per socket.