1. 方案概述
在工业控制、物联网网关、安防监控等场景中,Android 开发板与局域网服务器(或 PC)之间的数据交互对实时性要求极高。相较于 TCP,UDP 协议具有无连接、无握手开销、首部开销小等天然优势,是局域网内高速数据传输、设备发现(OSD/ONVIF 探测)的绝佳选择。
本方案提供了一个完整的 UDP 客户端 Demo,配合标准 PC 端测试软件,帮助您快速评估我司开发板的网络吞吐与低延迟特性。
2. Android 客户端核心实现 (生产级 Demo)
Android 的网络操作必须在子线程中执行,且由于 Android 6.0+ 的权限管理,需要在 AndroidManifest.xml 中声明权限。
Step 1: 权限配置 (AndroidManifest.xml)
<!-- 允许访问网络状态和进行 network 连接 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 如果需要支持 UDP 组播/广播,可选添加 -->
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
Step 2: 核心通信类 UdpClientManager.java
这是一个完整的、开箱即用的 UDP 管理类,采用了线程池设计,避免频繁创建线程,且支持单例模式。
package com.example.udpdemo;
import android.util.Log;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class UdpClientManager {
private static final String TAG = "UdpClientManager";
private DatagramSocket mSocket;
private ExecutorService mThreadPool;
private boolean isListening = false;
private OnUdpMessageListener mListener;
public interface OnUdpMessageListener {
void onMessageReceived(String message, String hostAddress, int port);
void onError(Exception e);
}
public UdpClientManager(OnUdpMessageListener listener) {
this.mListener = listener;
this.mThreadPool = Executors.newFixedThreadPool(2);
initSocket();
}
private void initSocket() {
try {
if (mSocket == null || mSocket.isClosed()) {
mSocket = new DatagramSocket();
Log.d(TAG, "UDP Socket 初始化成功, 本地端口: " + mSocket.getLocalPort());
}
} catch (SocketException e) {
Log.e(TAG, "Socket 初始化失败", e);
if (mListener != null) mListener.onError(e);
}
}
public void sendMessage(final String msg, final String destIp, final int destPort) {
mThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
initSocket();
byte[] bytes = msg.getBytes("UTF-8");
InetAddress address = InetAddress.getByName(destIp);
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, destPort);
mSocket.send(packet);
Log.d(TAG, "已发送数据到 " + destIp + ":" + destPort + " -> " + msg);
} catch (UnknownHostException e) {
Log.e(TAG, "未知主机 IP", e);
} catch (IOException e) {
Log.e(TAG, "发送 IO 异常", e);
}
}
});
}
public void startReceiveThread() {
if (isListening) return;
isListening = true;
mThreadPool.execute(new Runnable() {
@Override
public void run() {
byte[] buffer = new byte[1024 * 2];
while (isListening) {
try {
initSocket();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
Log.d(TAG, "开始阻塞监听来自 PC 的 UDP 回应...");
mSocket.receive(packet);
String recvStr = new String(packet.getData(), 0, packet.getLength(), "UTF-8");
String hostIP = packet.getAddress().getHostAddress();
int port = packet.getPort();
Log.d(TAG, "收到数据来自 [" + hostIP + ":" + port + "]: " + recvStr);
if (mListener != null) {
mListener.onMessageReceived(recvStr, hostIP, port);
}
} catch (IOException e) {
Log.e(TAG, "接收异常(可能 Socket 已关闭)", e);
}
}
}
});
}
public void close() {
isListening = false;
if (mSocket != null && !mSocket.isClosed()) {
mSocket.close();
mSocket = null;
}
if (mThreadPool != null && !mThreadPool.isShutdown()) {
mThreadPool.shutdownNow();
}
Log.d(TAG, "UDP 连接已关闭, 资源已释放");
}
}
Step 3: 在 MainActivity.java 中调用
在界面上放置两个输入框(IP 和内容)、一个“发送”按钮、一个文本框(显示接收内容)。
package com.example.udpdemo;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity implements UdpClientManager.OnUdpMessageListener {
private EditText etTargetIp, etMessage;
private TextView tvLog;
private Button btnSend;
private UdpClientManager udpManager;
private final int TARGET_PORT = 8888;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etTargetIp = findViewById(R.id.et_target_ip);
etMessage = findViewById(R.id.et_message);
tvLog = findViewById(R.id.tv_log);
btnSend = findViewById(R.id.btn_send);
udpManager = new UdpClientManager(this);
udpManager.startReceiveThread();
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String ip = etTargetIp.getText().toString().trim();
String msg = etMessage.getText().toString();
udpManager.sendMessage(msg, ip, TARGET_PORT);
}
});
}
@Override
public void onMessageReceived(final String message, final String hostAddress, final int port) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tvLog.append("\n收到 " + hostAddress + ":" + port + " -> " + message);
}
});
}
@Override
public void onError(Exception e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tvLog.append("\n发生错误: " + e.getMessage());
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (udpManager != null) {
udpManager.close();
}
}
}
3. PC 服务端配置(如何配合测试)
为了让板子发送的 UDP 数据有去有回,PC 端需要运行一个 UDP 调试助手。无需客户自己写代码,推荐使用以下主流免安工具:
推荐软件:NetAssist (网络调试助手) 或 SSCOM
软件配置步骤:
1. 协议类型:选择 UDP。
2. 本地 IP 地址:选择您当前 PC 的局域网 IP(例如 192.168.1.100)。
3. 本地端口号:填入 8888(需与 Android 客户端代码中的目标端口一致)。
4. 点击 “连接” 或 “打开” 开始监听。
注意:测试前请确保 Android 开发板与 PC 连接在同一个路由器/交换机下(同网段),并检查 PC 的 Windows 防火墙,确保其允许该调试软件访问网络。
4. 联合调试闭环验证(5分钟测试指南)
1. 硬件准备:用网线将 Android 开发板 和 PC 接入同一交换机(或路由器),确保两端获取到同网段 IP。
2. PC端准备:打开 NetAssist,类型选 UDP,本地端口填 8888,点击“打开”。
3. 板子端运行:打开 Demo App,在目标 IP 输入框中填入 PC 的 IP,点击 “发送”。
4. 效果看点:板子发送后,PC 端 NetAssist 窗口立刻会收到板子传过来的字符串。在 PC 端 NetAssist 的“远程主机”栏填入板子的 IP 和随机生成的本地端口,点击发送,板子界面也将实时刷新显示接收到的内容。
5. 为什么选择我们的板子?
千兆级以太网原生支持:本板搭载原生网卡芯片,PHY 层物理层硬件优化,UDP 吞吐量接近理论物理极限,杜绝因硬件导致的丢包。
极低传输时延:精简的 Linux 内核/Android 系统网络栈定制,减少数据包从内核态到用户态的拷贝时间,非常适合工业高频通信。
全天候稳定性:支持 7×24 小时长时间大流量 UDP 压测不掉线,守护客户关键业务。