套接字流的flush()可靠性如何?

时间:2016-07-06 11:39:07

标签: java android networking tcp

考虑这段(简化的)代码:

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();
    }
}

我故意用所有方法离开它,以防我在那里犯了一些非常明显的错误。当我执行Test.run(),有时(真的很难预测到什么时候)似乎sendHeaders()中的flush()根本不起作用。

服务器端在接下来的22秒内没有在其ServerSocket.accept()上收到任何东西(不要问我这个号码来自何处,这是神秘的一部分)。

我的想法是,我不会在每次传输时调用flush(),但只调用一次,以节省带宽。

这个代码有什么问题?如何确保对我的流的写入是可靠/立即的,以便服务器可以尽快读取它?

我也接受回答“没有错”,在这种情况下,它必须是并行完成并影响Android上的网络堆栈的东西。

编辑:服务器代码真的没什么特别的:

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

然后:

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);
        }
    }
}

最后:

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);
        }
    }
}

正如评论中所提到的,我愿意同意对象流的任何错误。但问题是我无法触及(再次,它只是有时,它是非常随机的......)CommandThread的run()。因此,除非我遗漏了其他内容,否则Object Streams无法导致这种失败。

编辑2:更正:它不接受()我无法触及,这是第一次读取操作:

  

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

     

03-07 11:22:42.966 00108 CommandThread:运行

     

[......什么也没发生......]

     

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

这可能是由于混合了对象流和数据流吗?

2 个答案:

答案 0 :(得分:3)

您应该验证ObjectOutputStream中的sendBlockedIds创建不是罪魁祸首。 我已经有了一些协议&#34;死锁&#34;在混合DataStreams和ObjectStreams的同时,由于ObjectStreams的Writer / Reader对的创建意味着在混合这些流时可能会失败的一种握手。

编辑:在再次阅读你的问题时,我意识到我没有回答。所以,是的,它是可靠的。和EJP回答+1。

答案 1 :(得分:2)

要回答标题中的问题,它是100%可靠的,因为它没有做任何事情。只有缓存的流的flush()方法实际上做了任何事情,并且只包括ObjectOutputStreamBufferedOutputStream以及PrintStream,具体取决于您构建它的方式。不是DataOutputStream,而不是套接字本身的输出流。

因此,在这种情况下,唯一可以执行任何操作的flush方法是缓冲输出流,您当然可以依赖它,因为它只是代码,并且已经工作了20年。

如果这会影响accept()的速度,那么您的接受循环肯定有些奇怪但您没有向我们展示过:通常,在接受循环中而不是在启动循环中执行I / O线程。

你当然应该在连接中间创建ObjectOutputStream。在开始时创建它并将其用于所有内容,并在另一端使用ObjectInputStream

NB分别将缓冲区大小设置为套接字缓冲区大小实际上是毫无意义的。默认值足够。