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

Kri*_*ato 9 java sockets networking android udp

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

我们只在两台设备上遇到了问题,一台低端的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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个部分Runnable.这样创建套接字:

byte[] buffer = new byte[1024];

DatagramSocket socket;
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
Run Code Online (Sandbox Code Playgroud)

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

socket = new DatagramSocket(SERVERPORT);
Run Code Online (Sandbox Code Playgroud)

Wifi模块正在接收数据包.我们已经确认通过生根其中一个设备并安装数据包嗅探器,因此问题必须以某种方式与代码相关.

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

任何帮助将非常感激.我们正在拔头发.

更新我错误的数据包嗅探器.似乎数据包嗅探器也在root设备上丢失了几个相关的数据包.但有时候,只需启动数据包嗅探器即可解决问题!如下所示打开/关闭蓝牙似乎没有什么区别.这可能是另一个硬件问题吗?

更新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
Run Code Online (Sandbox Code Playgroud)

Mar*_* A. 5

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

  1. 从服务器发送
  2. 通过网络传输
  3. 客户端物理接收和硬件处理
  4. 在内核/操作系统中处理/缓冲数据包
  5. 在您的应用程序中处理/缓冲数据包。

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

因此,第 3 点到第 5 点可能是问题所在,并且可能更难将它们分开。

以下几点可能会有所帮助:

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

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

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