App Inventor 2 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 文件)。

常见的获取途径:

  1. MIT App Inventor 社区:在 community.appinventor.mit.edu 搜索 “UDP extension”
  2. Kodular 社区:在 community.kodular.io 搜索 “UDP”
  3. 自行开发扩展:如果你有 Java/Android 开发基础,可以基于 java.net.DatagramSocket 开发自定义 UDP 扩展

导入扩展

  1. 在 App Inventor 2 设计视图左下角,找到 “扩展”(Extensions) 区域
  2. 点击 “导入扩展” 按钮
  3. 选择下载的 UDP 扩展 .aix 文件
  4. 导入成功后,扩展组件会出现在组件面板中

💡 导入扩展后,如果使用 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 地址。

设计思路

  1. App 发送 UDP 广播消息
  2. 局域网中的服务端收到广播后,回复自己的 IP 和服务信息
  3. 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 不保证送达
需要分包重组 大文件需要拆分为多个小包发送

解决方案:分包传输 + 序号重组

核心思路:

  1. 将图片转为 Base64 编码字符串
  2. 将长字符串按固定大小分包(如每包 1000 字节)
  3. 每个包添加 包序号总包数 头部信息
  4. 接收端按序号重组所有包,最后 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: 可以通过以下途径获取:

  1. MIT App Inventor 社区 搜索 “UDP”
  2. Kodular 社区 搜索 “UDP extension”
  3. 在 GitHub 搜索 “appinventor udp extension”
  4. 如果有 Java 开发能力,可以基于 java.net.DatagramSocket 自行开发扩展

Q:发送 UDP 广播时提示失败?

A: 请检查:

  1. 手机是否连接了 Wi-Fi(移动数据网络通常不支持广播)
  2. 目标端口是否被其他应用占用
  3. Android 权限是否正确(需要 android.permission.INTERNETandroid.permission.ACCESS_NETWORK_STATE,App Inventor 编译时会自动添加)
  4. 部分 Android 版本可能需要 android.permission.ACCESS_WIFI_STATE 权限

Q:UDP 数据接收不到?

A: 请排查:

  1. 确认已调用 StartListening() 并设置了正确的 LocalPort
  2. 确认发送端的目标 IP 和端口与接收端一致
  3. 检查防火墙是否阻止了 UDP 端口
  4. 确认两台设备在同一局域网
  5. 部分路由器的 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 通信。


性能优化建议

  1. 控制发送频率:UDP 发送过快可能导致丢包,建议包间间隔 ≥ 20ms
  2. 使用合理包大小:局域网推荐 1000 ~ 1400 字节/包
  3. 添加校验机制:在数据中包含校验和,接收端验证数据完整性
  4. 实现超时重发:对于关键数据,实现简单的超时重传机制
  5. 及时停止监听:不使用时调用 StopListening() 释放系统资源
  6. 避免主线程阻塞:UDP 扩展通常在后台线程处理网络 I/O,但大量数据处理仍需注意 UI 响应性

安全注意事项

  • UDP 是明文传输,不要通过 UDP 发送敏感信息(密码、密钥等)
  • 接收 UDP 数据时,验证数据来源和内容格式,防止恶意数据注入
  • 局域网内的 UDP 广播可能被同网段所有设备截获
  • 如需安全通信,应在应用层实现加密(如 AES)

相关资源


© 2025 App Inventor 2 中文网 · 本文内容由社区整理,仅供参考。
如有疑问或建议,欢迎访问 App Inventor 2 中文网 讨论。

文档反馈