为啥 Android 套接字定期读取这么慢?

Posted

技术标签:

【中文标题】为啥 Android 套接字定期读取这么慢?【英文标题】:Why is Android socket reading so slow periodically?为什么 Android 套接字定期读取这么慢? 【发布时间】:2015-08-15 06:27:42 【问题描述】:

套接字创建如下:

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 (Asus MeMO) 的 android 平板电脑完成的。当使用具有四核 CPU (AGPTek TP714AQ) 的低端通用 Android 平板电脑完成测试时,isSocketInput.read() 使用的时间显着改善。在前 200 次迭代后,长时间经过的次数降至零。即使在最初的 200 次左右的迭代中,也只有少数长时间的经过(> 100 毫秒)。

目前,我认为单核 CPU 设备的长时间运行主要是由答案中提到的 “进程或线程被重新安排” 引起的,这种情况发生得更多经常在单核 CPU 设备上。

【问题讨论】:

【参考方案1】:

它的读取速度不会比数据到达的速度快,而且它的读取速度也不会比数据发送的速度快。不要责怪接收代码、责怪发送者或网络。

当您获得大量字节时,可能是在您阻塞更长时间之后,并且在您的进程或线程再次重新调度之前到达了更多数据。您也无法通过代码来控制它。

注意事项:

您应该在连接之前调用setReceiveBufferSize(),而不是之后。这样窗口缩放才能生效。 如果您没有指定本地端口,则调用 setReuseAddress() 毫无意义。

【讨论】:

非常感谢您的及时答复。我没有讲完整个故事,因为我认为这个问题足够长并且想要保持内容高度相关。我正在研究这个,因为我的延误时间很长。我发现瓶颈是获取数据,而不是处理。 Windows 应用使用完全相同的 URI 和协议从完全相同的服务器流式传输,但没有延迟。我一直怀疑原因是您提到的“您的进程或线程被重新安排”。这就是我暂停其他线程进行测试的原因。有没有办法确认这一点? 如果这是一个调度问题,我会感到惊讶,除非您的机器经常因就绪线程而超载。你有什么L1链接?无线上网? 3G? 我建议您尝试在正确的位置设置接收缓冲区大小。 如果不是发件人,你会发现很大的不同。 奇怪的是我没有收到新 cmets 的通知。 @MartinJames 连接是 802.11g。 我一看到你的回答就改变了接收缓冲区语句的位置,但不幸的是它似乎对这个问题没有任何影响。产生巨大差异的是四核通用平板电脑。报告的测试是使用华硕 MeMO 单核平板电脑完成的。当我使用通用的四核低端平板电脑进行测试时,长时间运行的次数急剧下降。在循环的前 100 次左右迭代后实际上下降到零。

以上是关于为啥 Android 套接字定期读取这么慢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥写入此套接字会丢弃读取缓冲区(这真的是发生了啥)?

在 TCP 套接字程序中,客户端发送一些数据,但服务器需要多次读取。为啥?

套接字:为啥阻塞 read() 会因 ENOTCONN 而失败?

Android(本机):为啥我的套接字对其他进程不可用?

android AsyncTask执行为啥这么慢

当我从套接字接收到一些数据时,为啥会附加很多空格?