为什么安卓套接字读取数据有时非常缓慢?

4
套接字的创建如下所示:
Socket socket = new Socket();
socket.setReuseAddress(true);
socket.setSoTimeout(iTimeout);
socket.connect(new InetSocketAddress(InetAddress.getByName(uri.getHost()), iPort), 6000);
socket.setReceiveBufferSize(iReceiveBufferSize); //iReceiveBufferSize = 1024*256
final InputStream is = socket.getInputStream();

一个调试方法被创建并测试,只是为了提出这个问题:
public void SocketDebug(InputStream isSocketInput)
{
    try {
        byte[] abBuffer = new byte[1024*256];
        for(int i = 0; i < 1000; i++)
        {
            long lStart = System.currentTimeMillis();
            int iRead = isSocketInput.read(abBuffer, 1024 * 10, 1024 * 128);
            int iElapse = (int)(System.currentTimeMillis() - lStart);
            if(iElapse > 100)
            {
                utility.logd("Debug", "i:" + i + " iElapse:" + iElapse + " iRead:" + iRead);
            }
        }
    }
    catch(Exception ex)
    {

    }
}

以下是logcat记录的部分内容:

01-Jun  37:26.8 i:9     iElapse:234 iRead:1448
01-Jun  37:29.5 i:114   iElapse:299 iRead:1448
01-Jun  37:29.8 i:126   iElapse:298 iRead:1448
01-Jun  37:30.1 i:132   iElapse:300 iRead:1448
01-Jun  37:30.4 i:139   iElapse:283 iRead:1448
01-Jun  37:30.7 i:146   iElapse:287 iRead:1448
01-Jun  37:31.0 i:160   iElapse:269 iRead:1448
01-Jun  37:31.3 i:169   iElapse:251 iRead:44888
01-Jun  37:31.5 i:170   iElapse:192 iRead:1448
01-Jun  37:31.7 i:185   iElapse:170 iRead:1448
01-Jun  37:32.0 i:198   iElapse:171 iRead:1448
01-Jun  37:32.2 i:217   iElapse:158 iRead:1448
01-Jun  37:32.5 i:240   iElapse:162 iRead:1448
01-Jun  37:32.7 i:259   iElapse:135 iRead:1448
01-Jun  37:33.0 i:281   iElapse:103 iRead:1448
01-Jun  37:34.2 i:324   iElapse:826 iRead:1448
01-Jun  37:34.4 i:330   iElapse:233 iRead:1448
01-Jun  37:34.7 i:336   iElapse:264 iRead:1448
01-Jun  37:35.0 i:341   iElapse:299 iRead:1448
01-Jun  37:35.3 i:346   iElapse:300 iRead:1448
01-Jun  37:35.6 i:352   iElapse:297 iRead:1448
01-Jun  37:36.0 i:354   iElapse:317 iRead:21720
01-Jun  37:36.3 i:355   iElapse:304 iRead:13032

数据源是视频流服务器。通常情况下,isSocketInput.read()仅需1至3毫秒(未在上面的日志中显示)。但是,周期性地需要100至1000毫秒。从红色字节计数可以看出,显然1448是TCP负载。所有红字节的数字都是1448的倍数。有人可能会认为服务器发送TCP数据包需要这么长时间。难以理解的是,isSocketInput.read()有时会读取如此多的数据包(例如31个数据包=44888个字节),并且需要很长时间才能返回。当有数据可用时,它是否应尽快返回?
当运行SocketDebug()时,应用程序的所有其他线程基本上都进入休眠状态(即在包含Thread.sleep()的循环中)。
有人可以提供关于长时间套接字读取时间可能原因的提示吗?
更新(2015-06-03):
以上测试是使用具有单核CPU的Android平板电脑(Asus MeMO)进行的。当使用具有四核CPU的低端通用Android平板电脑(AGPTek TP714AQ)进行测试时,isSocketInput.read()使用的时间显着改善。在第一次200次迭代之后,长时间间隔的数量降至零。即使在最初的200次左右迭代期间,也只有少数长时间间隔(> 100 ms)。
在这个时候,我认为单核CPU设备上的长时间间隔主要是由答案中所称的“进程或线程被重新安排”的原因造成的,这在单核CPU设备上发生得更加频繁。
1个回答

2
它不能比数据到达的速度更快地读取数据,而这个速度不能比数据发送的速度更快。不要责怪接收代码,应该责怪发送方或网络。
当你获得更多的字节时,可能是因为你被阻塞了更长时间,而在你的进程或线程再次被重新调度之前,更多的数据到达了。你无法从你的代码中控制它。
注意:
- 在连接之前应该调用 setReceiveBufferSize(),而不是之后。这样窗口缩放就可以生效。 - 如果没有指定本地端口,则调用 setReuseAddress() 是无意义的。

非常感谢您的及时回答。我没有讲述整个故事,因为我认为问题已经足够长了,而且想保持内容高度相关。我正在研究这个问题,因为我遇到了很长的延迟。我发现瓶颈在于获取数据,而不是处理数据。一个Windows应用程序从完全相同的服务器使用完全相同的URI和协议进行流传输,但它没有延迟。我一直怀疑原因是您提到的“您的进程或线程被重新调度”,这就是为什么我暂停了其他线程进行测试的原因。有没有办法确认这一点? - Hong
我会感到惊讶,如果这是一个调度问题,除非您的计算机经常超载准备线程。你使用什么L1链接?WiFi?3G? - Martin James
我在看到你的回答后立即更改了接收缓冲区语句的位置,但不幸的是,这似乎对这个问题没有任何影响。真正产生显著差异的是一款四核通用平板电脑。报告中的测试是使用一款Asus MeMO单核平板电脑进行的。当我在一款通用的四核低端平板电脑上运行测试时,长时间间隔的数量大大减少。实际上,在循环的前100次左右之后就降为零了。 - Hong
谢谢大家的建议。现在已经很晚了,我明天会创建一些跟踪报告,看看它们是否能提供任何线索。 - Hong
我在Android设备监视器的线程选项卡和分析跟踪中没有找到任何线索。另一个非常有趣的观察结果是:在将Thread.sleep(10)添加到循环后,长时间间隔的频率显著增加(几乎每次迭代都会发生)。我最初认为这可能有所帮助。同样,四核低端平板电脑(AGPTek TP714AQ)几乎没有长时间间隔。实际上,在此时刻,它经常在1000次循环中没有长时间间隔。请让我知道您的想法。我已经准备好猜测整个事情并接受您的答案。 - Hong
显示剩余3条评论

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