Miro Kropacek
Miro Kropacek

Reputation: 2886

How reliable socket stream's flush() is?

Consider this (simplified) piece of code:

public class Test {
    // assigned elsewhere
    InetSocketAddress socketAddress;
    String socketHost;
    int socketPort;
    Socket socket;

    int COMMAND = 10;
    int CONNECTION_TIMEOUT = 10 * 1000;
    int SOCKET_TIMEOUT = 30 * 1000;
    DataOutputStream dos;
    DataInputStream  dis;

    protected void connect() throws IOException, InterruptedException {
        socket.connect(socketAddress != null ? socketAddress : new InetSocketAddress(socketHost, socketPort), CONNECTION_TIMEOUT);

        socket.setSoTimeout(SOCKET_TIMEOUT);
        socket.setTcpNoDelay(true);
    }

    void initializeDataStreams() throws IOException {
        dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream(), socket.getSendBufferSize()));
        dis = new DataInputStream( new BufferedInputStream( socket.getInputStream(),  socket.getReceiveBufferSize()));
    }

    void run() {
        try {
            connect();
            initializeDataStreams();

            sendCommand(COMMAND, true);

            sendIdAndUsername(true);

            sendSyncPreference(true);

            sendBlockedIds(true);

            sendHeaders();

            // reading from 'dis' here
            // ...

        } catch (InterruptedException | IOException e){
            /* ... */
        }
    }

    void sendCommand(int command, boolean buffered) throws IOException {
        dos.write(command);
        if (!buffered) {
            dos.flush();
        }
    }

    void sendIdAndUsername(boolean buffered) throws IOException {
        sendId(true);  // always buffered
        String username = "user name";
        dos.writeBoolean(username != null);
        if (username != null) {
            dos.writeUTF(username);
        }
        if (!buffered) {
            dos.flush();
        }
    }

    void sendId(boolean buffered) throws IOException {
        dos.writeUTF("user id");
        if (!buffered) {
            dos.flush();
        }
    }

    void sendSyncPreference(boolean buffered) throws IOException {
        boolean fullSync = true;
        dos.writeBoolean(fullSync);
        if (!buffered) {
            dos.flush();
        }
    }

    void sendBlockedIds(boolean buffered) throws IOException {
        Set<String> blockedCrocoIds = new HashSet<>();

        ObjectOutputStream oos = new ObjectOutputStream(dos);
        oos.writeObject(blockedCrocoIds);
        if (!buffered) {
            oos.flush();
        }
    }

    private void sendHeaders() throws IOException {
        dos.writeUTF("some string");
        dos.writeInt(123);
        // some other writes...

        // this should flush everything, right?
        dos.flush();
    }
}

I left it intentionally with all the methods, just in case I've made some terribly obvious mistake there. When I execute Test.run(), sometimes (really hard to predict when exactly) it seems like the flush() in sendHeaders() doesn't work at all.

Server side doesn't receive anything on its ServerSocket.accept() for next 22 seconds (don't ask me where this number comes from, part of the mystery).

The idea was that I wont call flush() on every transmission but call it only once, to save the bandwidth.

So what's wrong with this code? How to ensure writes to my stream are reliable / immediate so the server can read it ASAP?

I also accept answer "there's nothing wrong", in that case it must be something which is being done in parallel and affecting the network stack on Android.

EDIT: Server code is really nothing special:

ListeningThread listeningThread = new ListeningThread();
listeningThread.start();
listeningThread.join();

and then:

public class ListeningThread extends Thread {
    private ServerSocket serverSocket;

    public ListeningThread() {
        try {
            // unbound server socket
            serverSocket = new ServerSocket();
            serverSocket.setReuseAddress(true);
            serverSocket.bind(new InetSocketAddress(NetworkUtil.APP_SERVER_PORT));
        } catch (IOException e) {
            log(e);
        }
    }

    @Override
    public void run() {
        log("run");

        while (serverSocket.isBound() && !isInterrupted()) {
            try {
                Socket socket = serverSocket.accept();
                new CommandThread(socket).start();
            } catch (IOException e) {
                log(e);
            }
        }

        try {
            serverSocket.close();
        } catch (IOException e) {
            log(e);
        }
    }
}

and finally:

public class CommandThread extends Thread {
    private final Socket socket;

    public CommandThread(Socket socket) {
        log("CommandThread");

        this.socket = socket;
    }

    @Override
    public void run() {
        log("run");

        try {
            socket.setSoTimeout(NetworkUtil.SOCKET_TIMEOUT);
            socket.setTcpNoDelay(true);

            InputStream is = socket.getInputStream();
            int cmd = is.read(); // <========= so actually this is failing
            switch (cmd) {
                // handling of the command
                case COMMAND:
                    new DownloadMessagesThread(socket).start();
                break;
            }
        } catch (IOException | SQLException e) {
            log(e);
        }
    }
}

As mentioned in the comments, I'd open to agree on anything wrong with the object streams & co but the trouble is that I'm unable to reach (again, it's just sometimes, it's very random...) CommandThread's run(). So unless I'm missing something else, there's no way Object Streams could cause this kind of failure.

EDIT 2: Correction: it's not accept() I cannot reach, it's the first read operation:

03-07 11:22:42.965 00010 CommandThread: CommandThread

03-07 11:22:42.966 00108 CommandThread: run

[... nothing happening ...]

03-07 11:23:04.549 00111 DownloadMessagesThread: run

Could this be caused by mixing the object stream and data stream after all?

Upvotes: 3

Views: 315

Answers (2)

user207421
user207421

Reputation: 310869

To answer the question in your title, it is 100% reliable, as it doesn't do anything. Only the flush() methods of streams that are buffered actually do anything, and that only includes ObjectOutputStream and BufferedOutputStream, and PrintStream depending on how you construct it. Not DataOutputStream, and not the output stream of the socket itself.

So in this case the only flush method that does anything is the buffered output stream's, and you can certainly rely on that, as it is just code, and has been working for twenty years.

If this is affecting the speed of accept(), there must be something odd about your accept loop that you haven't shown us: typically, doing I/O in the accept loop instead of in the started thread.

And you should certainly not create an ObjectOutputStream in the middle of the connection. Create it at the start and use it for everything, and an ObjectInputStream at the other end.

NB setting the buffer sizes to the socket buffer sizes respectively is really fairly pointless. The defaults are adequate.

Upvotes: 2

Xvolks
Xvolks

Reputation: 2155

You should verify that the ObjectOutputStream creation in sendBlockedIds is not the culprit. I've already had some protocol "deadlocks" while mixing DataStreams and ObjectStreams, since the creation of the Writer/Reader pair of ObjectStreams implies a kind of handshake that may fail while mixing those streams.

EDIT: While reading again your question, I realized that I had not answered it. So yes, it is reliable. And +1 for EJP answer.

Upvotes: 3

Related Questions