- 概述
- UDP 基础概念
- 方案一:UDP 扩展组件(推荐)
- UDP 发送数据
- UDP 接收数据
- UDP 广播和多播
- 实战案例一:局域网设备发现
- 实战案例二:UDP 传输图片思路
- 方案二:中间服务转发
- 常见问题
- 性能优化建议
- 安全注意事项
- 相关资源
概述
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,常用于局域网设备发现、实时数据传输、物联网设备通信等场景。与 TCP 不同,UDP 不需要建立连接,发送速度快,但不保证数据到达。
重要提示:App Inventor 2 没有内置的 UDP 组件。内置的 Web客户端(Web)组件仅支持 HTTP 协议(基于 TCP),无法直接发送 UDP 数据包。要实现 UDP 通信,需要借助以下方案:
| 方案 | 难度 | 适用场景 | 说明 |
|---|---|---|---|
| UDP 扩展组件 | ⭐⭐ | 需要直接收发 UDP | 使用社区开发的 UDP 扩展(.aix 文件) |
| 中间服务转发 | ⭐⭐⭐ | UDP 场景不复杂时 | 在局域网部署一个 TCP/HTTP 转发服务,App 通过 Web 组件调用 |
| Activity Starter 调用原生 App | ⭐⭐⭐⭐ | 有现成 UDP 工具时 | 调用第三方 UDP 工具 App |
推荐:优先使用 UDP 扩展组件方案,功能完整,积木块操作直观。
UDP 基础概念
UDP vs TCP
| 特性 | UDP | TCP |
|---|---|---|
| 连接方式 | 无连接,直接发送 | 需要先建立连接 |
| 可靠性 | 不保证送达,可能丢包 | 保证送达,自动重传 |
| 速度 | 快,延迟低 | 相对较慢 |
| 数据顺序 | 不保证顺序 | 保证顺序 |
| 适用场景 | 局域网发现、实时数据、广播 | 文件传输、网页浏览、API 调用 |
UDP 广播地址
在局域网中使用 UDP 广播时,需要了解常见广播地址:
| 类型 | 地址 | 说明 |
|---|---|---|
| 受限广播 | 255.255.255.255 |
发送到同网段所有设备 |
| 子网广播 | 如 192.168.1.255 |
发送到指定子网所有设备(需根据实际网段计算) |
| 组播地址 | 224.0.0.0 ~ 239.255.255.255 |
发送到加入同一组播组的设备 |
UDP 端口
端口号范围为 0 ~ 65535:
- 0 ~ 1023:系统保留端口(如 80 = HTTP,443 = HTTPS)
- 1024 ~ 49151:注册端口
- 49152 ~ 65535:动态/私有端口(推荐使用此范围)
⚠️ 注意:避免使用系统保留端口(如 80、443、53 等),推荐使用 49152 以上的端口号。
方案一:UDP 扩展组件(推荐)
获取 UDP 扩展
由于 App Inventor 2 没有内置 UDP 组件,需要使用社区开发的 UDP 扩展(.aix 文件)。
常见的获取途径:
- MIT App Inventor 社区:在 community.appinventor.mit.edu 搜索 “UDP extension”
- Kodular 社区:在 community.kodular.io 搜索 “UDP”
- 自行开发扩展:如果你有 Java/Android 开发基础,可以基于
java.net.DatagramSocket开发自定义 UDP 扩展
导入扩展
- 在 App Inventor 2 设计视图左下角,找到 “扩展”(Extensions) 区域
- 点击 “导入扩展” 按钮
- 选择下载的 UDP 扩展
.aix文件 - 导入成功后,扩展组件会出现在组件面板中
💡 导入扩展后,如果使用 AI 伴侣测试,务必重启 AI 伴侣,否则可能出现 “no such class” 运行时错误。
UDP 扩展常见接口
以下是一个典型的 UDP 扩展组件提供的积木块接口(不同扩展可能略有差异):
属性(Properties):
| 属性 | 类型 | 说明 |
|---|---|---|
LocalPort |
数值 | 本地监听端口 |
RemoteAddress |
文本 | 远程目标 IP 地址 |
RemotePort |
数值 | 远程目标端口 |
IsListening |
布尔值 | 是否正在监听 |
事件(Events):
| 事件 | 参数 | 说明 |
|---|---|---|
DataReceived |
message(文本)、senderAddress(文本)、senderPort(数值) |
收到 UDP 数据时触发 |
ErrorOccurred |
errorMessage(文本) |
发生错误时触发 |
方法(Methods):
| 方法 | 参数 | 说明 |
|---|---|---|
SendText(text) |
要发送的文本 | 发送文本数据 |
SendBytes(bytes) |
要发送的字节列表 | 发送二进制数据 |
StartListening() |
无 | 开始监听 UDP 数据 |
StopListening() |
无 | 停止监听 |
UDP 发送数据
发送文本数据
使用 UDP 扩展发送文本到指定 IP 和端口:
组件设置:
| 组件 | 名称 | 用途 |
|---|---|---|
| UDP 扩展 | UDP1 |
UDP 通信 |
| TextBox | TextBox_IP |
输入目标 IP |
| TextBox | TextBox_Port |
输入目标端口 |
| TextBox | TextBox_Message |
输入要发送的消息 |
| Button | Button_Send |
发送按钮 |
| Label | Label_Status |
显示状态 |
积木块伪代码:
当 Button_Send.被点击 执行:
设置 UDP1.RemoteAddress = TextBox_IP.文本
设置 UDP1.RemotePort = 转换为数字(TextBox_Port.文本)
调用 UDP1.SendText(TextBox_Message.文本)
设置 Label_Status.文本 = "已发送: " + TextBox_Message.文本
发送广播消息
发送 UDP 广播用于局域网设备发现,目标地址设为 255.255.255.255:
当 Button_Broadcast.被点击 执行:
设置 UDP1.RemoteAddress = "255.255.255.255"
设置 UDP1.RemotePort = 50000
调用 UDP1.SendText("DISCOVER_REQUEST")
设置 Label_Status.文本 = "广播已发送"
⚠️ 注意:Android 系统发送广播需要 Wi-Fi 网络处于连接状态。移动数据网络(4G/5G)通常不支持 UDP 广播。
UDP 接收数据
监听 UDP 数据
在 Screen 初始化时启动监听,收到数据时处理:
当 Screen1.初始化 执行:
设置 UDP1.LocalPort = 50000
调用 UDP1.StartListening()
设置 Label_Status.文本 = "正在监听端口 50000..."
当 UDP1.DataReceived(message, senderAddress, senderPort) 执行:
设置 Label_Received.文本 = "来自 " + senderAddress + ":" + senderPort + " 的消息: " + message
停止监听
在 Screen 离开或不需要接收时,停止监听释放资源:
当 Screen1.离开屏幕 执行:
调用 UDP1.StopListening()
当 Button_Stop.被点击 执行:
调用 UDP1.StopListening()
设置 Label_Status.文本 = "已停止监听"
UDP 广播和多播
局域网广播
广播是 UDP 的重要特性,可以让同网段所有设备收到消息。
发送端(广播发现):
当 Button_Discover.被点击 执行:
设置 UDP1.RemoteAddress = "255.255.255.255"
设置 UDP1.RemotePort = 50001
调用 UDP1.SendText("DEVICE_DISCOVERY:WHO_IS_THERE")
接收端(响应发现):
当 UDP1.DataReceived(message, senderAddress, senderPort) 执行:
如果 message = "DEVICE_DISCOVERY:WHO_IS_THERE" 则:
设置 UDP1.RemoteAddress = senderAddress
设置 UDP1.RemotePort = senderPort
调用 UDP1.SendText("DEVICE_RESPONSE:MY_NAME_IS_ANDROID_001")
子网定向广播
如果需要跨网段广播或更精确控制,使用子网广播地址:
当 Button_SubnetBroadcast.被点击 执行:
// 假设设备在 192.168.1.x 网段
设置 UDP1.RemoteAddress = "192.168.1.255"
设置 UDP1.RemotePort = 50002
调用 UDP1.SendText("HELLO_SUBNET")
多播(Multicast)
多播比广播更精确,只有加入特定多播组的设备才能收到数据。
⚠️ 注意:并非所有 UDP 扩展都支持多播。如果你的扩展不支持,可以考虑使用广播替代。
多播地址范围:224.0.0.0 ~ 239.255.255.255
224.0.0.1:本子网所有主机224.0.0.251:mDNS(组播 DNS)239.x.x.x:管理范围多播地址(推荐使用)
实战案例一:局域网设备发现
场景描述
手机 App 需要在局域网中自动发现运行了服务的设备(如电脑上的 TCP 服务、IoT 设备等),获取其 IP 地址。
设计思路
- App 发送 UDP 广播消息
- 局域网中的服务端收到广播后,回复自己的 IP 和服务信息
- App 收到回复后,显示发现的设备列表
组件设计
| 组件 | 名称 | 用途 |
|---|---|---|
| UDP 扩展 | UDP1 |
UDP 通信 |
| Button | Button_Scan |
“扫描设备”按钮 |
| ListView | ListView_Devices |
显示发现的设备列表 |
| Label | Label_Status |
显示状态信息 |
| Clock | Clock1 |
定时扫描超时 |
积木块实现
定义变量:
全局变量 foundDevices = 创建空列表
全局变量 scanTimeout = 3000 // 扫描超时 3 秒
扫描按钮逻辑:
当 Button_Scan.被点击 执行:
设置 foundDevices = 创建空列表
设置 ListView_Devices.元素列表 = foundDevices
设置 Label_Status.文本 = "正在扫描..."
设置 UDP1.LocalPort = 50003
调用 UDP1.StartListening()
设置 UDP1.RemoteAddress = "255.255.255.255"
设置 UDP1.RemotePort = 50003
调用 UDP1.SendText("SCAN:WHO_IS_HERE")
启用 Clock1
接收响应逻辑:
当 UDP1.DataReceived(message, senderAddress, senderPort) 执行:
如果 包含文本(message, "DEVICE:") 则:
设备名称 = 替换文本(message, "DEVICE:", "")
设备信息 = 设备名称 + " (" + senderAddress + ")"
如果 不(列表中包含(foundDevices, 设备信息)) 则:
添加到列表(foundDevices, 设备信息)
设置 ListView_Devices.元素列表 = foundDevices
超时处理:
当 Clock1.计时 执行:
禁用 Clock1
调用 UDP1.StopListening()
设置 Label_Status.文本 = "扫描完成,发现 " + 列表长度(foundDevices) + " 个设备"
电脑端服务(Python 示例)
电脑端需要一个 UDP 服务响应 App 的广播请求。以下是 Python 示例:
import socket
import json
UDP_PORT = 50003
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.bind(('', UDP_PORT))
print(f"设备发现服务启动,监听端口 {UDP_PORT}...")
while True:
data, addr = sock.recvfrom(1024)
message = data.decode('utf-8')
print(f"收到来自 {addr} 的消息: {message}")
if message == "SCAN:WHO_IS_HERE":
response = "DEVICE:MY_COMPUTER"
sock.sendto(response.encode('utf-8'), addr)
print(f"已回复 {addr}")
实战案例二:UDP 传输图片思路
场景描述
通过 UDP 在局域网内传输图片数据(如手机截图发送到电脑)。
技术挑战
UDP 传输图片面临以下挑战:
| 挑战 | 说明 |
|---|---|
| 数据大小限制 | 单个 UDP 数据包最大约 64KB(实际建议不超过 1472 字节避免分片) |
| 无序到达 | UDP 不保证包的顺序 |
| 可能丢包 | UDP 不保证送达 |
| 需要分包重组 | 大文件需要拆分为多个小包发送 |
解决方案:分包传输 + 序号重组
核心思路:
- 将图片转为 Base64 编码字符串
- 将长字符串按固定大小分包(如每包 1000 字节)
- 每个包添加 包序号 和 总包数 头部信息
- 接收端按序号重组所有包,最后 Base64 解码还原图片
数据包格式设计
包格式: "PKT:序号:总包数:数据"
示例: "PKT:1:10:aGVsbG8gd29ybGQ..."(第1包,共10包)
发送端积木块(伪代码)
当 Button_SendImage.被点击 执行:
// 获取图片路径(如拍照或从相册选择)
图片路径 = Camera1.图片路径
// 使用文件组件读取图片(如扩展支持)或使用 Web 组件的 Base64 编码
图片Base64 = 调用 Web1.UriEncode(图片路径)
// 分包参数
包大小 = 1000
总长度 = 文本长度(图片Base64)
总包数 = 向上取整(总长度 / 包大小)
设置 Label_Status.文本 = "共 " + 总包数 + " 个包,开始发送..."
循环 i 从 1 到 总包数:
起始位置 = (i - 1) * 包大小 + 1
结束位置 = min(i * 包大小, 总长度)
数据片段 = 文本子串(图片Base64, 起始位置, 结束位置 - 起始位置 + 1)
包内容 = "PKT:" + i + ":" + 总包数 + ":" + 数据片段
设置 UDP1.RemoteAddress = TextBox_IP.文本
设置 UDP1.RemotePort = 50004
调用 UDP1.SendText(包内容)
// 包间延迟,避免发送过快丢包
调用 Clock2.等待(50)
设置 Label_Status.文本 = "图片发送完成!"
接收端积木块(伪代码)
全局变量 receivedPackets = 创建空列表
全局变量 totalPackets = 0
全局变量 packetData = 创建字典
当 UDP1.DataReceived(message, senderAddress, senderPort) 执行:
如果 包含文本(message, "PKT:") 则:
// 解析包内容
分割结果 = 分割文本(message, ":")
序号 = 转为数字(列表第2项(分割结果)) // "PKT:序号:总包数:数据"
总包数 = 转为数字(列表第3项(分割结果))
数据 = 合并列表项(列表子列表(分割结果, 3), ":") // 重新合并(数据中可能含冒号)
设置 totalPackets = 总包数
// 存储到字典
设置字典值(packetData, 序号, 数据)
已收包数 = 字典键数(packetData)
设置 Label_Status.文本 = "接收中: " + 已收包数 + "/" + totalPackets
如果 已收包数 = totalPackets 则:
// 所有包到齐,重组
完整Base64 = ""
循环 i 从 1 到 totalPackets:
完整Base64 = 完整Base64 + 获取字典值(packetData, i)
// 使用 Base64 还原图片(需要文件操作扩展支持)
// 或将 Base64 字符串保存到文件
调用 File1.保存文件(完整Base64, "/sdcard/received_image.txt")
设置 Label_Status.文本 = "图片接收完成!"
// 重置
设置 packetData = 创建字典
设置 totalPackets = 0
替代方案:使用 TCP/Web 组件传输图片
由于 UDP 传输图片的可靠性问题,如果需要稳定传输,建议使用 TCP 方案:
// 使用 Web 组件 PostFile 直接上传(基于 TCP/HTTP,更可靠)
当 Button_Upload.被点击 执行:
设置 Web1.Url = "http://电脑IP:8080/upload"
调用 Web1.PostFile(Camera1.图片路径)
💡 建议:如果只是点对点传输图片,使用
Web客户端的PostFile方法(TCP/HTTP)更简单可靠。UDP 方案更适合一对多广播或实时流媒体场景。
方案二:中间服务转发
如果无法获取 UDP 扩展,可以在局域网中部署一个转发服务:
架构
手机 App (Web组件/TCP) --> 转发服务 (Python/Node.js) --> 目标设备 (UDP)
手机 App (Web组件/TCP) <-- 转发服务 (Python/Node.js) <-- 目标设备 (UDP)
App 端实现
App 端使用内置的 Web客户端 组件,向转发服务发送 HTTP 请求:
当 Button_SendUDP.被点击 执行:
设置 Web1.Url = "http://转发服务IP:8080/send?ip=" + TextBox_TargetIP.文本 +
"&port=" + TextBox_TargetPort.文本 +
"&msg=" + TextBox_Message.文本
调用 Web1.Get()
当 Web1.收到文本(responseCode, responseType, responseContent) 执行:
设置 Label_Status.文本 = "转发结果: " + responseContent
转发服务示例(Python)
from http.server import HTTPServer, BaseHTTPRequestHandler
import socket
from urllib.parse import urlparse, parse_qs
UDP_PORT = 50005
def send_udp(ip, port, message):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(message.encode('utf-8'), (ip, int(port)))
sock.close()
return True
class UDPProxyHandler(BaseHTTPRequestHandler):
def do_GET(self):
params = parse_qs(urlparse(self.path).query)
ip = params.get('ip', [''])[0]
port = params.get('port', [''])[0]
msg = params.get('msg', [''])[0]
if ip and port and msg:
send_udp(ip, port, msg)
self.send_response(200)
self.end_headers()
self.wfile.write(b'UDP sent successfully')
else:
self.send_response(400)
self.end_headers()
self.wfile.write(b'Missing parameters')
server = HTTPServer(('0.0.0.0', 8080), UDPProxyHandler)
print("UDP Proxy running on port 8080...")
server.serve_forever()
⚠️ 注意:此方案仅解决了发送 UDP 的问题。如果还需要接收 UDP,转发服务还需要实现 UDP 监听 + 轮询接口(App 定时用 Web.Get 获取收到的 UDP 数据)。
常见问题
Q:App Inventor 2 有内置的 UDP 组件吗?
A:没有。 App Inventor 2 内置的网络通信组件只有:
- Web 客户端(HTTP/HTTPS,基于 TCP)
- BluetoothClient / BluetoothServer(蓝牙 SPP)
- Serial(串口通信)
要使用 UDP,必须借助扩展或中间服务。
Q:在哪里可以下载 UDP 扩展?
A: 可以通过以下途径获取:
- 在 MIT App Inventor 社区 搜索 “UDP”
- 在 Kodular 社区 搜索 “UDP extension”
- 在 GitHub 搜索 “appinventor udp extension”
- 如果有 Java 开发能力,可以基于
java.net.DatagramSocket自行开发扩展
Q:发送 UDP 广播时提示失败?
A: 请检查:
- 手机是否连接了 Wi-Fi(移动数据网络通常不支持广播)
- 目标端口是否被其他应用占用
- Android 权限是否正确(需要
android.permission.INTERNET和android.permission.ACCESS_NETWORK_STATE,App Inventor 编译时会自动添加) - 部分 Android 版本可能需要
android.permission.ACCESS_WIFI_STATE权限
Q:UDP 数据接收不到?
A: 请排查:
- 确认已调用
StartListening()并设置了正确的LocalPort - 确认发送端的目标 IP 和端口与接收端一致
- 检查防火墙是否阻止了 UDP 端口
- 确认两台设备在同一局域网内
- 部分路由器的 AP 隔离功能会阻止设备间通信,需要关闭
Q:UDP 传输数据大小有限制吗?
A: 有。单个 UDP 数据包的理论最大值为 65507 字节(扣除 IP 和 UDP 头部),但建议:
- 局域网内:每包不超过 1472 字节(避免 IP 分片)
- 互联网传输:每包不超过 512 字节更安全
- 超过限制的数据需要分包传输(参见”UDP 传输图片”案例)
Q:UDP 扩展在 iOS 上能用吗?
A: 不能。 目前 App Inventor 的扩展(.aix 文件)都是基于 Java/Android 开发的,iOS 版 AI 伴侣不支持扩展。如果你的 App 需要同时在 iOS 上运行,建议使用中间服务转发方案(Web 组件 + 服务端代理)。
Q:如何实现 UDP 和 TCP 混合使用?
A: 推荐架构:
- UDP 用于设备发现、广播通知(快速、一对多)
- TCP(Web 组件)用于数据传输、文件上传(可靠、保证送达)
例如:先用 UDP 广播发现设备 IP,再用 Web 组件的 HTTP 请求与设备建立可靠的 TCP 通信。
性能优化建议
- 控制发送频率:UDP 发送过快可能导致丢包,建议包间间隔 ≥ 20ms
- 使用合理包大小:局域网推荐 1000 ~ 1400 字节/包
- 添加校验机制:在数据中包含校验和,接收端验证数据完整性
- 实现超时重发:对于关键数据,实现简单的超时重传机制
- 及时停止监听:不使用时调用
StopListening()释放系统资源 - 避免主线程阻塞:UDP 扩展通常在后台线程处理网络 I/O,但大量数据处理仍需注意 UI 响应性
安全注意事项
- UDP 是明文传输,不要通过 UDP 发送敏感信息(密码、密钥等)
- 接收 UDP 数据时,验证数据来源和内容格式,防止恶意数据注入
- 局域网内的 UDP 广播可能被同网段所有设备截获
- 如需安全通信,应在应用层实现加密(如 AES)
相关资源
© 2025 App Inventor 2 中文网 · 本文内容由社区整理,仅供参考。
如有疑问或建议,欢迎访问 App Inventor 2 中文网 讨论。
扫码添加客服咨询