3
votes

I'm developing a client and a server file transfer application with C++ sockets over UDP (SOCK_DGRAM). For a PUT method, I have the following execution (after handshaking) :

client

Send a packet;
if time_out
    send the packet again;
else  //ack received
    send a new packet;

server

wait for the first packet;
send acknowledge;
while(!EOF)
    Get a packet;
    Send an ack;

Where EOF is basically checking if I've received the whole file by comparing to a file size that I send during handshake.

Packets can be lost at any point during this exchange. The client can send a data packet that gets lost. It'll time out waiting for an ACK and will resend the packet. The server receives a data packet and sends an ACK. That ACK can be lost, at which point the client times out and re-sends his data packet. Since these packets have a numbered sequence, we can just disregard packet we've received multiple times.

My confusion occurs at the end of the file transfer. The client just sent the packet containing the last bits of the file. The server successfully receives it and sends his ACK. This packet gets lost. The server application is now outside of the while loop because he's written the last bits to the file and, from his point of view the file transfer was successful. However, the client never received the ACK and so times out and re-sends the data packet. What should happen here? The server is not listening or is listening for a new request, not data. At which point should the client or server consider the transfer complete and stop trying to communicate?

2

2 Answers

3
votes

It sounds like you're reinventing TFTP, but with client and server roles backwards. Are you sure you need to do this? It's a very inefficient protocol due to lack of a sliding window or data pipeline.

I see 3 solutions to your conundrum:

  • A three-way handshake at the end of the file transfer. After receiving the last data block, the server not only sends an ACK but it waits for the client to ACK the ACK. When that happens, the server knows that the client knows the file transfer is complete. If the server never receives the ACK of the ACK, it periodically resends its ACK of the last data block.
  • The server remembers completed file transfers for several minutes after they're finished (or several RTTs, whichever is greater). If, during that time, it receives any data block from a client for a file transfer that was supposed to be already complete, the server just ACKs it without doing anything and resets the end-of-transfer timer for that file transfer. This is more complex but has the advantage over the previous solution that the server was able to move on and do other work after the file transfer completed.
  • The server responds with an error (a NAK) if it ever receives any data block that is not part of an active file transfer. That goes for whether it's part of a nonexistent, invalid, or already-completed transfer. This is the simplest to implement, but also the ugliest because the client has to terminate with an error when it receives that NAK. But maybe that's OK, because the file transfer was still successful from the server's point of view.
1
votes

The obvious solution would be to not prevent the server from listening immediately afterwards. I would have the server continually send a special packet whose value represents end of file, until an acknowledgement is received for it from the client.

Have you considered using a library that already implements this kind of UDP file transfer, for instance Raknet? It might make your life a little easier, since you're kind of reinventing the wheel here.