某些 Android 设备上的严重 UDP 数据包丢失

Severe UDP packet loss on some Android devices

我搜索了 interwebz 没有结果。我们正面临一些 Android 设备出现严重丢包的问题。为了提供一些背景信息,应用程序连接到特定的 Wifi 并查找在端口 17216 上广播的 UDP 数据包。这些数据包大小为 832 字节,不包括包装的 headers,并以每秒四个的常规速率发送.

我们只在两台设备上遇到过这个问题,一台 low-end Turbox Rubik II 平板电脑和一台 ASUS Memo Pad HD 7。我们测试过的其他设备(手机和平板电脑)都在以下位置收集数据包规定的固定间隔。

接收数据包的函数是这样的:

public void run()
{
    while (isUDPServerRunning)
    {
        try
        {
            socket.receive(packet);

            ProcessRawPacketData();

            DisplayLoggingInfo();

        }
        catch (IOException e)
        {
            Log.e("receive", e.getMessage());
            e.printStackTrace();
        }
    }
}

这是 Runnable 的一部分。套接字是这样创建的:

byte[] buffer = new byte[1024];

DatagramSocket socket;
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

在我们的 Service 扩展的 onCreate() 方法中初始化套接字:

socket = new DatagramSocket(SERVERPORT);

Wifi 模块正在接收数据包。我们已经通过对其中一台设备进行 root 并安装数据包嗅探器来确认这一点,因此问题一定与代码有关。

在受影响的设备上,数据包被正确接收了几秒钟,然后完全丢失持续了几秒钟,所以我估计损失超过 50%。

如有任何帮助,我们将不胜感激。我们拔头发了。

更新 我搞错了数据包嗅探器。数据包嗅探器似乎也在根设备上丢失了几个相关数据包。 有时,不过,只需启动数据包嗅探器即可解决问题!像下面建议的那样打开蓝牙 on/off 似乎没有什么不同。这可能是另一个硬件问题吗?

更新 2 这是我在 socket.receive() 行之后立即打印的日志示例。请注意它如何跳过半分钟的数据包,然后在几秒钟内正常工作。

05-25 15:44:38.670: D/LOG(4393): Packet Received
05-25 15:44:38.941: D/LOG(4393): Packet Received
05-25 15:45:09.482: D/LOG(4393): Packet Received
05-25 15:45:09.716: D/LOG(4393): Packet Received
05-25 15:45:09.928: D/LOG(4393): Packet Received
05-25 15:45:10.184: D/LOG(4393): Packet Received
05-25 15:45:10.451: D/LOG(4393): Packet Received
05-25 15:45:10.661: D/LOG(4393): Packet Received

这听起来与在 Android(和 iOS - 实际上任何同时具有 WiFi 和蓝牙的设备)上看到的蓝牙干扰症状非常相似。

2.4Ghz WiFi 和蓝牙共享相同的带宽并且会相互干扰 - 在某些设备上,这非常明显,可能是由于内部布局。

也有可能您可以在某些设备上看到它,而在其他设备上看不到它,因为它们支持的 WiFi 版本 - 较新的基于 5GHz 的 wifi 不会以相同的方式干扰蓝牙,但一些较旧或更基本的Android 设备可能不支持此功能。

您可以通过在测试时关闭设备上的蓝牙来很容易地测试这是否是原因(如果您的应用程序可以在没有蓝牙的情况下运行)。

只是一个大胆的猜测,但是您对数据包的计算需要多长时间?是否有可能为套接字分配的缓冲区已满并开始丢弃包?

我知道,对于大约 4 KB/s 的传输速率,这听起来不太可能...但是如果您的计算时间超过 250 毫秒,那么这迟早会发生。这也可以解释为什么有些设备工作起来很有魅力,而另一些则不然。

您是否尝试过删除计算并仅打印 "package received" 消息进行调试?

有趣的是,这两款出现 UDP 数据包丢失的设备恰好都有联发科技的 SoC。您的其他测试设备是否具有相同的芯片组?

这可能是这些 SoC 的 Wi-Fi 驱动程序中的错误。因为它只出现在UDP上,并不总是100%,所以大家可能一直没注意到。

数据包丢失(如您所知,当然)可能发生在传输的多个阶段:

  1. 从服务器发送
  2. 网络传输
  3. 客户端物理接收和硬件处理
  4. Processing/buffering的数据包在kernel/OS
  5. Handling/buffering 您应用中的数据包。

您可以让其他设备在连接到同一个 Wifi 路由器时收听同一个广播,从而快速检查第 1 点还是第 2 点是否有问题。听起来您已经这样做了,而且没有问题。 (请注意,如果您 运行 服务器上的 WireShark 转储中丢失了在第 2 步(有时甚至是第 1 步)丢弃的数据包。)

因此,第 3 点到第 5 点可能是问题所在,它们可能更难分离出来。

以下几点可能会有帮助:

  • 就像@Mick 建议的那样,不要只打印出 何时 你收到了数据包,而是给每个数据包一个递增的 ID 号,以确定你是否真的丢失了一个数据包或是否只是延迟了。
  • 将您的数据包接收代码移到它自己的线程中(如果它还没有的话)并 set the priority of that thread to MAX_PRIORITY 以尽量减少您的代码阻碍午餐队伍的机会。鉴于 Memo Pad 是四核 1.2GHz 机器,MAX_PRIORITY 甚至不是必需的,但如果您当前没有 运行 在其自己的专用线程中设置接收循环,您无论如何都可能会看到问题。如果这解决了问题,只需使用最小的接收循环将数据包粘贴到您自己的缓冲区队列中,并让一个独立的线程处理它们。
  • Check/increase 用于通过 setReceiveBufferSize(...) (more verbose Java reference here 接收数据包的数据包缓冲区的大小。确保您指定的大小可以容纳许多数据包。鉴于 运行 包嗅探器有时似乎有所帮助,听起来确实可能有一些套接字设置可以改善事情,嗅探器恰好设置了。
  • 在服务器上您还可以向数据包添加一个标签,告诉所有相关设备如何处理该数据包。如果您调用 setTrafficClass(IPTOS_RELIABILITY),您是在要求所有相关人员优化他们的数据包处理以获得最大的可靠性。并非所有设备都会关心,但它可能会有所不同。
  • 您可以尝试使用DatagramChannels instead of DatagramSockets and then use select()等待下一个数据包读取。虽然这在技术上应该没有什么区别,但有时使用不同的 API 调用可以解决问题。
  • 不幸的是Android是一个非常异构的环境,许多制造商会提供他们自己的内核模块等。这也到处引入了各种不兼容或非标准行为。您也许能够为您的一个或两个问题设备找到自定义 ROM(Cyanogen 等?)。如果安装它而不是出厂 ROM 解决了您的问题,那么它是制造商提供的(内核)网络驱动程序中的错误,在这种情况下,您可能很幸运找到解决方法,或者您可以提交错误报告与他们一起使用,但一般来说,您可能只需要 select Play 商店不支持这些设备以避免差评...

最后,这里有一个解决方法可以肯定地解决问题:

向您的客户端添加一些代码来检测丢弃的数据包,如果丢弃率过高,则改为打开到服务器的 TCP 连接,这将保证数据包的传送。鉴于您的数据包很小且不频繁,并且只有少数设备需要使用此机制,我认为这不会对您的服务器负载造成问题。如果您没有办法更改服务器代码以提供 TCP 流,您可以编写一个独立的代理服务器来收集 UDP 数据包并通过 TCP 提供它们。如果你可以 运行 它与原始服务器在同一台机器上,你甚至可以知道它的 IP 地址(与到达的 UDP 数据包的源地址相同)。