java - 使用 UDP 传输协议减少 Android 的 WiFi 直接通信中的抖动
问题描述
我正在开发一个实时应用程序,该应用程序需要通过 WiFi 直接连接每 5ms 传输大约 512 个字节的数据,从从属电话/s 到主(组所有者)。平均传输延迟约为 15 毫秒,这不是很好,但最大的问题是我注意到一个非常一致的周期模式,其中延迟大约每 2~3 秒高达 100 毫秒,加上每分钟约 9 秒的周期,其中吞吐量大大降低。请看一下延迟随时间变化的散点图. 我还附上了我的套接字编程代码。主从内部时钟在 1ms 误差内同步(不用担心时钟漂移)。请让我知道您是否也经历过这些结果,以及是否可以采取一些措施来改善这个问题。有趣的是,通常有 0 数据丢失,当一个数据包被延迟时,所有数据包也都在它之前(从图中很容易看出),即使这是 UDP。我在某处读到,即使使用 UDP,数据包也需要在 MAC 层得到确认,这可能会导致大约 100 毫秒的延迟。
以下是从站(发送者)处的代码:
public class DataTransmissionServiceV3 {
public static final String TAG = "DataTransmissionService";
private long mClockOffset;
private DatagramChannel mDatagramChannel;
private boolean running;
public void start(InetAddress remoteAddress, long clockOffset) {
if (!running) {
running = true;
mClockOffset = clockOffset;
Thread t = new Thread(new TransmissionTask(remoteAddress));
t.setPriority(Thread.MAX_PRIORITY);
t.start();
}
}
public void close() {
running = false;
if (mDatagramChannel != null) {
mDatagramChannel.socket().close();
}
}
private class TransmissionTask implements Runnable {
private int mCount;
private final ByteBuffer mByteBuffer = ByteBuffer.allocateDirect(
Constants.DATA_PACKET_SIZE);
private final byte[] mBytes = new byte[Constants.DATA_PACKET_SIZE];
private final InetAddress mRemoteAddress;
public TransmissionTask(final InetAddress remoteAddress) {
mRemoteAddress = remoteAddress;
}
@Override
public void run() {
try {
mDatagramChannel = DatagramChannel.open();
mDatagramChannel.socket().connect(new InetSocketAddress(mRemoteAddress,
Constants.TRANS_MASTER_SERVER_PORT));
mDatagramChannel.configureBlocking(false);
} catch (IOException ioe) {
Log.e(TAG, "Exception while connecting", ioe);
}
while (running) {
TimeUtils.busyWait(5000000); // busy wait for 5ms
send();
}
}
private void send() {
mCount++;
DataPacket.setSequenceNumber(mBytes, mCount);
DataPacket.setTimestamp(mBytes, TimeUtils.getTimestamp() + mClockOffset); // synchronized timestamp to master device to measure delay
mByteBuffer.clear();
mByteBuffer.put(mBytes);
mByteBuffer.flip();
try {
mDatagramChannel.write(mByteBuffer);
} catch (Exception e) {
Log.e(TAG, "Exception while sending data packet", e);
running = false;
}
}
}
}
以下是主机(接收器)的代码:
public class DataTransmissionServerV3 {
public static final String TAG = "DataTransmissionServer";
private DatagramChannel mDatagramChannel;
private boolean mRunning;
private Handler mHandler;
public DataTransmissionServerV3(Handler handler) {
mHandler = handler;
}
public void start(InetAddress localAddress) {
if (!mRunning) {
mRunning = true;
Thread t = new Thread(new ReceiveDataTask(localAddress));
t.setPriority(Thread.MAX_PRIORITY);
t.start();
}
}
public void close() {
mRunning = false;
if (mDatagramChannel != null) {
mDatagramChannel.socket().close();
}
}
private class ReceiveDataTask implements Runnable {
private final InetAddress mLocalAddress;
private final ByteBuffer mByteBuffer = ByteBuffer.allocateDirect(
Constants.DATA_PACKET_SIZE);
private final byte[] mBytes = new byte[Constants.DATA_PACKET_SIZE];
private int mCount;
public ReceiveDataTask(final InetAddress localAddress) {
mLocalAddress = localAddress;
}
@Override
public void run() {
try {
bind();
while (mRunning) {
receive();
}
} catch (IOException ioe) {
Log.e(TAG, "Exception while binding datagram socket", ioe);
}
}
private void bind() throws IOException {
mDatagramChannel = DatagramChannel.open();
mDatagramChannel.socket().bind(new InetSocketAddress(mLocalAddress,
Constants.TRANS_MASTER_SERVER_PORT));
mDatagramChannel.configureBlocking(true);
}
private boolean receive() {
mByteBuffer.clear();
try {
SocketAddress isa = mDatagramChannel.receive(mByteBuffer);
long t2 = TimeUtils.getTimestamp();
if (isa != null) {
mByteBuffer.flip();
mByteBuffer.get(mBytes);
mCount++;
TransmissionStat stat = TransmissionStat.get(mBytes, mCount, t2);
handlePacket(stat); // a statistic that is saved to file for later analysis (ignore this)
return true;
}
} catch (IOException ioe) {
Log.e(TAG, "Exception while receiving data", ioe);
mRunning = false;
}
return false;
}
private void handlePacket(TransmissionStat stat) {
Message msg = mHandler.obtainMessage();
msg.what = Constants.TRANSMISSION_PACKET_RECEIVED_CODE;
msg.obj = stat;
msg.sendToTarget();
}
}
解决方案
推荐阅读
- jquery - 使用 laravel 和 jquery 取消选中单选按钮
- tcl - 映射/取消映射或提高/降低堆叠顺序以显示/隐藏选项卡式显示?
- c++ - C++ 比较器覆盖
- gspread - copy() 仍然使用参数 folder_id 抛出错误
- amazon-web-services - 为什么 AWS ELB EC2 实例在被调度程序停止后会重新启动?
- html - 如何使用 CSS 和 HTML 在圆圈上添加文本?
- docker - Perl 脚本不在 OpenShift/Cirrus/Hybrid Cloud 上运行,但在本地运行良好
- javascript - 无法读取未定义错误的属性“forEach”
- python - 在 python 3.x 中表示浮动无穷大的最佳实践
- flutter - 如何使用 FirebaseAuth 在 IOS 上保持登录状态