安卓TCP在套接字关闭前不会刷新。

3

我一直在尝试各种实现方式使其正常工作,并在StackOverflow和Android Developers上搜索解决方案,但由于我不太熟练编程,无法让这个代码块正常工作。

我的意图:

  1. 这是一个线程,将循环检查是否有outMessage,如果有,它将发送该消息。
  2. 接下来它将检查in-stream中是否有任何内容,如果有,它将将其发送到我的主要活动处理程序中。
  3. 最后,它将睡眠1秒,然后再次检查。
  4. 这应该允许我多次读写而无需关闭和打开套接字。

问题:

  • outstream直到我关闭套接字才会刷新。 flush()似乎没有效果。

我的请求:

  • 请发布所需更改以使此代码按照上述描述正常工作(任何解释为什么需要更改的注释都将不胜感激。链接到其他类似的问题/答案将有助于我学习,但我已经看了几周了,就是不能让它正常工作,所以请确保还包括此代码所需的更改。提前致谢。

其他:

  • 我想知道我的instream和/或outstream是否需要查找行结束字符?
  • 是否会在这里使用TCP_NODELAY之类的东西?
  • 任何额外的信息都将非常感激。我想学好这些东西,但目前无法让任何事情正常工作。

代码:

 public void run() {                        
        while (connectionStatus == TCP_SOCKET_STATUS_CONNECTED) {
            try {   
                if (outMessage != null){
                   OutStream.writeBytes(outMessage);
                   OutStream.flush();           
                   outMessage = ("OUT TO SERVER: " + outMessage);           
                // socketClient.close();     
                   sendMessageToAllUI(0, MAINACTIVITY_SET_TEXT_STATE, "appendText" , outMessage);
                   outMessage = null;               
                } 
                if (InStream != null) {                     
                    String modifiedSentence = InStream.readLine();      
                    sendMessageToAllUI(0, MAINACTIVITY_SET_TEXT_STATE, "appendText" , "\n" + "IN FROM SERVER: " + modifiedSentence);
            }
            Thread.sleep(1000);
        } catch (IOException e) {               
            connectionLost();
            break;
        } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }                           
}

制作套接字的线程:
public void run() {
        if(I) Log.i(LOGTAG, "Attempt Connection with IP: " + serverIP + " ...");
        setName("AttemptConnectionThread");
        connectionStatus = TCP_SOCKET_STATUS_CONNECTING;
        try {
            SocketAddress sockaddr = new InetSocketAddress(serverIP, port);
            tempSocketClient = new Socket(); // Create an unbound socket

            // This method will block no more than timeoutMs. If the timeout occurs, SocketTimeoutException is thrown.
            tempSocketClient.connect(sockaddr, timeoutMs);
            OutStream = new DataOutputStream(tempSocketClient.getOutputStream());
            InStream = new BufferedReader(new InputStreamReader(tempSocketClient.getInputStream()));
            socketClient = tempSocketClient;
            socketClient.setTcpNoDelay(true);
            connected(); 
        } catch (UnknownHostException e) {
            if(I) Log.i(LOGTAG,"     ...UnknownException e: e.getMessage() shows: " + e.getMessage());
            connectionFailed();
        } catch (SocketTimeoutException e) {
            if(I) Log.i(LOGTAG,"     ...SocketTimoutException e: e.getMessage() shows: " + e.getMessage());
            connectionFailed();
        } catch (IOException e) {
            if(I) Log.i(LOGTAG,"     ...caught on run()");
            // Close the socket
            try {
                tempSocketClient.close();
            } catch (IOException e2) {
                Log.e(LOGTAG, "unable to close() socket during connection failure", e2);
            }
            if(I) Log.i(LOGTAG,"     ...IOException e: e.getMessage() shows: " + e.getMessage());
            connectionFailed();
            return;
        }
    } 

我发现在线上使用的 Java 服务器,在将其移植到真实服务器之前会一直使用它。
public class Server {

private static String SERVERIP;

/**
 * @param args
 * @throws IOException
 */

public static void main(String[] args) throws IOException {
    String clientSentence;
    String capitalizedSentence;

    try {
        ServerSocket welcomeSocket = new ServerSocket(8888);
        getIp();
        System.out.println("Connected and waiting for client input!\n");

        while (true) {
            Socket connectionSocket = welcomeSocket.accept();
            BufferedReader inFromClient = new BufferedReader(
                    new InputStreamReader(connectionSocket.getInputStream()));
            DataOutputStream outToClient = new DataOutputStream(
                    connectionSocket.getOutputStream());

            clientSentence = inFromClient.readLine();
            String ip = connectionSocket.getInetAddress().toString()
                    .substring(1);
            System.out.println("In from client (" + ip + "): "
                    + clientSentence);
            if (clientSentence != null) {
                capitalizedSentence = clientSentence.toUpperCase() + '\n';
                System.out.println("Out to client (" + ip + "): "
                        + capitalizedSentence);
                outToClient.writeBytes(capitalizedSentence + "\n");
            }

        }
    } catch (IOException e) {
        // if server is already running, it will not open new port but
        // instead re-print the open ports information
        getIp();
        System.out
                .println("Server connected and waiting for client input!\n");

    }
}

private static void getIp() {
    InetAddress ipAddr;
    try {
        ipAddr = InetAddress.getLocalHost();
        System.out.println("Current IP address : "
                + ipAddr.getHostAddress());

    } catch (UnknownHostException e) {
        e.printStackTrace();
    }
}
}

这些消息有多大?它们是在不同的线程中生成的吗?没有锁定吗? - Nikolai Fetissov
我的意思是“锁定”,以避免线程之间的竞争条件。我的问题是outMessage在哪里创建的? - Nikolai Fetissov
outMessage是我的服务中的静态成员。它目前是一个字符串,但一旦套接字正常工作,我将最终将其更改为队列。此方法位于服务内的嵌套类中。因此,我可以在多个活动之间保持套接字打开,这些活动可以绑定到该服务。 - Nissi
禁用 Nagle 可能是此处的首要任务。我的意思是,如果您在不同线程上创建消息,则需要将 outMessages 标记为“易失性”,以便内存更新从一个线程传播到另一个线程。 - Nikolai Fetissov
为了使其成为volatile,我研究了java.util.concurrent.atomic,并对此有了一定的了解。我应该将变量初始化为原子变量吗?这个网站提到了一些缺失/丢失标签或引用的问题。是否有特殊的方法与volatile变量交互以更新或引用它?此外,到目前为止,我对此变量的更新进展顺利。我只是幸运吗? - Nissi
显示剩余10条评论
2个回答

3

我猜你正在消费文本内容,但没有进行文本写入,因此消费者会一直阻塞等待获取 EOS 并将所有内容一次性传递。在发送文本时,请根据需要添加行结束符号。


这解决了我所发布的特定问题,但又让我陷入了另一个问题。我的线程在inputStream.readLine()处挂起,我在这里发布了有关该问题的新问题链接,以防您或任何其他人想要查看。感谢您的帮助。 - Nissi
我知道这个答案已经有3-4年的历史了,但如果你碰巧看到我的评论,能否详细解释一下你说“但你没有写行”是什么意思?看起来我遇到了类似的问题,这可能是原因。 - Sergey Maslov

1

由于这些是短消息(< MSS),我猜测堆栈可能正在实现Nagle算法。服务器是否在执行某种延迟ACK?如果可能的话,您应该捕获跟踪并查看其他方面的待处理确认。

在任何一种情况下,TCP_NODELAY都应该有所帮助。


  1. ACK代表什么?我在谷歌上搜索了但找不到定义。
  2. 我会尝试使用你和Nikolai建议的TCP_NODELAY,并标记答案,如果它确实回答了这个问题。感谢迄今为止的帮助!
- Nissi
1
ACK是TCP确认。服务器可能会等待将它们捆绑在一起而不是为每个段发送一个ACK。如果客户端实现了Nagle算法,则在发送小于MSS的段之前,它将等待ACK。在TCP跟踪中,您需要查找TCP头中的ACK号码并将其与SEQ号码相关联,以查看是否发生了这种情况。或者您可以在此处发布捕获片段。 - jman
好的,我更新了问题,展示了在我的线程中创建套接字以及Java服务器的运行方法。你会发现我已经在那里使用了socketClient.setTcpNoDelay(true);,但它并没有解决问题。文档中指出应该是socketClient.setTcpNoDelay(on);,但是我需要初始化on。我有点困惑,不知道我应该将“on”初始化为什么才能使socketClient.setTcpNoDelay(on);起作用? - Nissi
1
你可以在服务器端使用tcpdump来进行跟踪。 - jman
@Nissi setTcpNoDelay(true) 是正确的,但我怀疑这不是真正的问题。 - user207421
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接