10

Background

My application gathers data from the phone and sends the to a remote server.
The data is first stored in memory (or on file when it's big enough) and every X seconds or so the application flushes that data and sends it to the server.

It's mission critical that every single piece of data is sent successfully, I'd rather send the data twice than not at all.

Problem

As a test I set up the app to send data with a timestamp every 5 seconds, this means that every 5 seconds a new line appear on the server.
If I kill the server I expect the lines to stop, they should now be written to memory instead.

When I enable the server again I should be able to confirm that no events are missing.

The problem however is that when I kill the server it takes about 20 seconds for IO operations to start failing meaning that during those 20 seconds the app happily sends the events and removes them from memory but they never reach the server and are lost forever.

I need a way to make certain that the data actually reaches the server.

This is possibly one of the more basic TCP questions but non the less, I haven't found any solution to it.

Stuff I've tried

Additional info

I cannot change how the server responds meaning I can't tell the server to acknowledge the data (more than mechanics of TCP that is), the server will just silently accept the data without sending anything back.

Snippet of code

Initialization of the class:

socket = new Socket(host, port);
socket.setTcpNoDelay(true);

Where data is sent:

while(!dataList.isEmpty()) {
    String data = dataList.removeFirst();
    inMemoryCount -= data.length();
    try {
        OutputStream os = socket.getOutputStream();
        os.write(data.getBytes());
        os.flush();
    }
    catch(IOException e) {
        inMemoryCount += data.length();
        dataList.addFirst(data);
        socket = null;
        return false;
    }
}

return true;

Update 1

I'll say this again, I cannot change the way the server behaves.
It receive data over TCP and UPD and does not send any data back to confirm the receive. This is a fact and sure in a perfect world the server would acknowledge the data but that will simply not happen.


Update 2

The solution posted by Fraggle works perfect (closing the socket and waiting for the input stream to be closed).

This however comes with a new set of problems.
Since I'm on a phone I have to assume that the user cannot send an infinite amount of bytes and I would like to keep all data traffic to a minimum if possible.

I'm not worried by the overhead of opening a new socket, those few bytes will not make a difference. What I am worried about however is that every time I connect to the server I have to send a short string identifying who I am.

The string itself is not that long (around 30 characters) but that adds up if I close and open the socket too often.

One solution is only to "flush" the data every X bytes, the problem is I have to choose X wisely; if too big there will be too much duplicate data sent if the socket goes down and if it's too small the overhead is too big.


Final update

My final solution is to "flush" the socket by closing it every X bytes and if all didn't got well those X bytes will be sent again.

This will possibly create some duplicate events on the server but that can be filtered there.

Nicklas A.
  • 6,501
  • 7
  • 40
  • 65
  • hint: before sending try read w/ setSoTimeout(1), it's a bit of overhead but you get exception on the read it the socket is closed. On a side note: data reaching the server TCP doesn't mean the server has managed to read/parse/commit the data, w/o a notification from the server, there is no guarantee. – bestsss Jun 12 '11 at 01:04
  • I can assume that once the packet has been received by the server that all is well. I basically want a way verify that some data has arrived using only the build in TCP mechanics – Nicklas A. Jun 12 '11 at 01:12

6 Answers6

4

Dan's solution is the one I'd suggest right after reading your question, he's got my up-vote.

Now can I suggest working around the problem? I don't know if this is possible with your setup, but one way of dealing with badly designed software (this is your server, sorry) is to wrap it, or in fancy-design-pattern-talk provide a facade, or in plain-talk put a proxy in front of your pain-in-the-behind server. Design meaningful ack-based protocol, have the proxy keep enough data samples in memory to be able to detect and tolerate broken connections, etc. etc. In short, have the phone app connect to a proxy residing somewhere on a "server-grade" machine using "good" protocol, then have the proxy connect to the server process using the "bad" protocol. The client is responsible for generating data. The proxy is responsible for dealing with the server.

Just another idea.

Edit 0:

You might find this one entertaining: The ultimate SO_LINGER page, or: why is my tcp not reliable.

Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171
  • Well, the server isn't poorly designed. It's mostly that it wasn't designed for this type of application. This is most likely not a viable solution, this app should work with any server straight of of the box. A good solution however, +1 – Nicklas A. Jun 12 '11 at 05:22
  • Maybe not the server, but the protocol :) – Nikolai Fetissov Jun 12 '11 at 12:55
  • Well, it's good 'ol TCP for you :) I just wish there was a method to say "wait until all sent packages has arrived", it would definitely be possible. – Nicklas A. Jun 12 '11 at 15:53
  • Hmm, I mean the *application* protocol. TCP is the *transport*. HTTP would be the application-layer protocol on top of that. See, for example, http://en.wikipedia.org/wiki/TCP/IP_model – Nikolai Fetissov Jun 12 '11 at 16:06
  • Well there is no protocol, you send data over raw TCP. This was designed for minimum overhead. – Nicklas A. Jun 12 '11 at 16:10
  • Thanks for the ultimate SO_LINGER page :) – Matthieu May 09 '15 at 14:55
  • Link broken, but it was never a quality resource in the first place. – user207421 Apr 26 '17 at 21:15
3

The bad news: You can't detect a failed connection except by trying to send or receive data on that connection.

The good news: As you say, it's OK if you send duplicate data. So your solution is not to worry about detecting failure in less than the 20 seconds it now takes. Instead, simply keep a circular buffer containing the last 30 or 60 seconds' worth of data. Each time you detect a failure and then reconnect, you can start the session by resending that saved data.

(This could get to be problematic if the server repeatedly cycles up and down in less than a minute; but if it's doing that, you have other problems to deal with.)

Community
  • 1
  • 1
Dan Breslau
  • 11,472
  • 2
  • 35
  • 44
  • This is a possibility but due to the nature of networking on phones this will probably cause a lot of data to be sent twice. It'd be a solution but I would prefer a better one if possible :) – Nicklas A. Jun 12 '11 at 00:32
  • I can also add it is when sending that the error occurs, it's just that there is a delay, likely due to a TCP timeout. – Nicklas A. Jun 12 '11 at 00:33
2

See the accepted answer here: Java Sockets and Dropped Connections

  1. socket.shutdownOutput();
  2. wait for inputStream.read() to return -1, indicating the peer has also shutdown its socket
Community
  • 1
  • 1
Fraggle
  • 8,607
  • 7
  • 54
  • 86
  • Hmm, this sounds like a possible solution. What I could do is to open/close the socket after X bytes of data. If something went wrong those X bytes will be resent. – Nicklas A. Jun 12 '11 at 03:09
0

Won't work: server cannot be modified

Can't your server acknowledge every message it receives with another packet? The client won't remove the messages that the server did not acknowledge yet.

This will have performance implications. To avoid slowing down you can keep on sending messages before an acknowledgement is received, and acknowledge several messages in one return message.

If you send a message every 5 seconds, and disconnection is not detected by the network stack for 30 seconds, you'll have to store just 6 messages. If 6 sent messages are not acknowledged, you can consider the connection to be down. (I suppose that logic of reconnection and backlog sending is already implemented in your app.)

9000
  • 39,899
  • 9
  • 66
  • 104
0

What about sending UDP datagrams on a separate UDP socket while making the remote host respond to each, and then when the remote host doesn't respond, you kill the TCP connection? It detects a link breakage quickly enough :)

Chris Dennett
  • 22,412
  • 8
  • 58
  • 84
0

Use http POST instead of socket connection, then you can send a response to each post. On the client side you only remove the data from memory if the response indicates success.

Sure its more overhead, but gives you what you want 100% of the time.

Fraggle
  • 8,607
  • 7
  • 54
  • 86