主题
udp — UDP 通信
模块名:udp
无连接的数据收发。适合设备发现、局域网广播、与传感器做快速轻量交换。UDP 不保证送达也不保证顺序,需要可靠传输请用 tcp_client。
对象方法用冒号写法(u:send(...)),库表里也注册了同名函数,udp.send(u, ...) 等价。
函数总览
库级函数
| 函数 | 作用 |
|---|---|
udp.creat(port, ip) | 创建 UDP 对象,绑定本地端口和网卡 |
对象方法
| 方法 | 作用 |
|---|---|
u:reg | 注册收包回调 |
u:start | 启动收包线程 |
u:send | 向指定地址发送数据 |
常量
| 常量 | 回调入参 | 说明 |
|---|---|---|
udp.RECVDATA | (ip, port, data) | 收到数据包。ip 已转成点分字符串 |
这是本模块唯一可注册的事件类型。
udp.creat
lua
udp.creat(port, ip) --> udp创建 UDP 对象并绑定本地端口与网卡。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | port | integer | 是 | — | 本机监听端口。传 0 由系统分配(只发不收时可用) |
| 2 | ip | string | 是 | — | 本机网卡 IP。传 "0.0.0.0" 表示所有网卡 |
返回值
| 类型 | 说明 |
|---|---|
| userdata | UDP 对象。必须用变量长期持有,被 GC 回收会关闭 socket |
示例
lua
udps = udp.creat(6000, "0.0.0.0")说明
- 两个参数都是必填的,省略任何一个都会报错。只发不收也要写全,例如
udp.creat(0, "0.0.0.0")。
实用例程
lua
0, 0, 0, 0, 0, 0, 0, 0, -- Reference Timestamp
0, 0, 0, 0, 0, 0, 0, 0, -- Origin Timestamp
0, 0, 0, 0, 0, 0, 0, 0, -- Receive Timestamp
0, 0, 0, 0, 0, 0, 0, 0 -- Transmit Timestamp
)
udp.send(sock, self.server, self.port, packet)
end
function isYearGreaterOrEqualTo2024()
local currentYear = tonumber(os.date("%Y", os.time())) -- 提取当前年份
return currentYear >= 2024
end
-- NTP校时标志位,防止重复校时
isNtpSyncing = false
-- 记录上次NTP校时的小时,防止重复校时
lastNtpSyncHour = -1
-- Create UDP socket and register callback
sock = udp.creat(2000, "192.168.1.100") -- Create UDP object on port 2000, bind to all interfaces for WiFi
-- Register callback for received data
udp.reg(sock, udp.RECVDATA, function(ip, port, buffer)
-- Extract timestamp from response (last 8 bytes)
local seconds = network_bytes_to_int(buffer, 41)
local fraction = network_bytes_to_int(buffer, 45)
-- Convert NTP time to Unix time
local unix_time = seconds - NTP_TIMESTAMP_DELTA
-- Calculate milliseconds part
local milliseconds = math.floor(fraction / 4294967295.0 * 1000.0 + 0.5)
local ntp_timestamp = unix_time * 1000.0 + milliseconds
local ts = math.floor((ntp_timestamp) / 1000.0)
-- 转换为北京时间并设置RTC
local beijing_timestamp = ts + 8 * 3600
plc.rtc_set(beijing_timestamp)
print("NTP time <设备ID或凭据> <设备ID或凭据>: " .. os.date("%Y-%m-%d %H:%M:%S", beijing_timestamp))把上面的地址、主题、引脚替换成你项目里的即可。
u:reg
lua
u:reg(event, callback)注册收包回调。要在 start 之前注册好。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | event | integer | 是 | — | 只支持 udp.RECVDATA |
| 2 | callback | function | 是 | — | 回调函数。类型不是 function 会直接报错 |
返回值
无返回值。
示例
lua
udps:reg(udp.RECVDATA, function(ip, port, data)
print(string.format("来自 %s:%d 的 %d 字节: %s", ip, port, #data, data))
end)说明
- 回调参数顺序是
(ip, port, data),数据在最后。
u:start
lua
u:start()启动收包线程。本函数立即返回。
参数
无参数。
返回值
无返回值。
u:send
lua
u:send(ip, port, data) --> integer向指定地址发送一个 UDP 包。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | ip | string | 是 | — | 目标 IP。广播地址如 "192.168.1.255" |
| 2 | port | integer | 是 | — | 目标端口 |
| 3 | data | string | 是 | — | 数据,可为二进制 |
返回值
| 类型 | 说明 |
|---|---|
| integer | 实际发送的字节数。data 为空串时直接返回 0 |
示例
lua
udps:send("192.168.1.255", 6000, "DISCOVER")u:close / u:is_linked / u:set_timeout / u:set_transparent —— udp 不提供这四个方法
close / is_linked / set_timeout / set_transparent 是 tcp_client 对象上的方法(tcp_client、tcp_server、udp 三者名字相近但实现各自独立)。udp 模块没有注册这四个名字,对 udp 对象调用会直接报错:
lua
udps = udp.creat(6000, "0.0.0.0")
udps:start()
udps:close() --> 报错:attempt to call method 'close' (a nil value)参数
close—— 不存在。UDP 无连接,没有"断开某个对端"可言;要停止收包,把持有 udp 对象的变量置nil并等 GC,对象回收时 socket 随之关闭is_linked—— 不存在。UDP 是无连接协议,根本没有"已连接/未连接"状态可查;对端是否活着只能用"发出去、等回包、自己判超时"实现set_timeout—— 不存在。udp 的收包线程由start内部驱动,回调式收数据,模块没有对外暴露任何超时参数set_transparent—— 不存在。串口透传是tcp_client专属能力,udp 侧没有对应实现
返回值
以 u:xxx() 形式调用这四个名字一律报错,没有返回值。
示例
lua
-- 想知道对端在不在:发一个查询包,自己计时等回包
udps = udp.creat(6000, "0.0.0.0")
got_reply = false
udps:reg(udp.RECVDATA, function(ip, port, data) got_reply = true end)
udps:start()
udps:send("192.168.1.100", 6000, "PING")
timer.start(3000, function()
print(got_reply and "对端在线" or "3 秒没回包") -- 单次定时器
end, true)常见用法
局域网设备发现:收到 DISCOVER 就回应自己的身份
lua
udps = udp.creat(6000, "0.0.0.0")
udps:reg(udp.RECVDATA, function(ip, port, data)
if data == "DISCOVER" then
udps:send(ip, port, "LPPLC " .. plc.plc_name() .. " " .. plc.eth_ip())
end
end)
udps:start()周期广播状态
lua
timer.start(5000, function()
udps:send("192.168.1.255", 6000,
json.encode({ name = plc.plc_name(), temp = plc.df(100) }))
end)注意事项
udp.creat的两个参数都必填,没有默认值。- 回调参数顺序是
(ip, port, data)。 - UDP 单包不宜过大,局域网建议控制在 1400 字节以内,避免 IP 分片。
- 不保证送达、不保证顺序、不去重。需要可靠性得自己加序号和重传,或直接换 TCP。
- 和 TCP 服务器一样,本模块没有任何认证。如果回调里会写 PLC 寄存器,请自行校验来源。
例程
每个例程都是可直接下载运行的完整脚本:
creat建对象、reg注册收包回调、start启动收包线程,脚本注册完即结束。 API 只用本页已文档化的函数。统一模板「场景 / 完整代码 / 要点 / 验证方法 / 注意事项」。
例程 1:局域网设备发现(收到 DISCOVER 回身份)
适用:LP3201 / LP3202 / LP3203 / LP3222 / LP3232 / LP3288 / 软PLC模拟器 | 依赖模块:udp plc | 难度:★☆☆
场景:上位机在网内广播 DISCOVER,设备收到后回应自己的设备名和 IP,方便自动发现。
完整代码(可直接下载运行):
lua
udps = udp.creat(6000, "0.0.0.0") -- 两参必填:本地端口 + 绑定网卡
udps:reg(udp.RECVDATA, function(ip, port, data)
if data == "DISCOVER" then
udps:send(ip, port, "LPPLC " .. plc.plc_name() .. " " .. plc.eth_ip())
end
end)
udps:start()要点:
udp.creat(port, ip)两个参数都必填,省略任何一个报错;"0.0.0.0"表示所有网卡。RECVDATA回调参数顺序是(ip, port, data)——数据在最后。
验证方法:
- 同网段用工具向
192.168.1.255:6000广播DISCOVER。 - 设备回
LPPLC <设备名> <IP>。
注意事项:
- 只发不收也要写全两参,例如
udp.creat(0, "0.0.0.0")。 udp没有close/is_linked/set_timeout/set_transparent,调用会报错。