主题
net — 网络工具
模块名:net
底层 Socket、IP 地址换算、DNS、Ping、NTP 等网络工具的集合。日常收发数据使用封装好的 tcp_client / tcp_server / udp 更简便;net 面向需要手写 socket 流程或做地址、连通性、校时处理的场景。
TCP / UDP 的客户端和服务器都能用 net 手写出来:客户端 socket → connect → send / recv;服务器 socket → bind → listen → accept → send / recv,多客户端配合 net.select;UDP 服务器用 bind + recvfrom + sendto。
socket 是一个对象,不是一个数字。 net.socket() 返回 socket 句柄,两种写法完全等价,挑一种用到底:
lua
local s = net.socket("TCP")
s:bind("0.0.0.0", 8000) -- 对象式(推荐)
net.bind(s, "0.0.0.0", 8000) -- 库式,效果一样用完要 s:close(),忘了也不会漏:句柄没人引用时被 Lua 回收会自动关闭;已经 close 的句柄再拿去用会返回错误码(不抛异常),不会静默失败。
套接字数量是硬额度,且各平台不同:ESP32 机型 16 个、4G 机型(LP8910 系列)只有 8 个。 这份额度是和云服务、MQTT、HTTP 共用同一份池子的 —— 脚本里一次开太多,先被挤掉的是云连接。 超限时 net.socket 返回 nil + 错误码「资源不足」(不报错崩溃)。
多数操作返回一个错误码,0 就是成功(错误码表见下)。
4G 机型(LP8910 / LP8913 / LP8916 / LP8922)上的 net
net 在 4G 机型上可用,socket / connect / send / recv / select / lookup / packip / unpackip / peer / sockname / setoption / getoption / shutdown / errstr / ping / get_ntp_time_async 的写法与 ESP32 机型完全一致,同一份脚本不用改。四处不同:
| 项 | 4G 上是什么样 | 该怎么做 |
|---|---|---|
net.localip | 恒返回 nil —— 4G 的出口地址是运营商现场分配的,机器上没有"网口"可查 | 要拿本机地址:net.socket("UDP") 连一个外网地址,再 sock:sockname() |
net.ping | 能不能通取决于这张 SIM 卡的 APN 放不放行 ICMP。不放行就是「请求超时」,不是代码坏了 | 链路自检请把「域名解析失败」和「超时」分开处理;确认远端活着更稳的办法是用 tcp_client 连业务端口 |
| 组播 | 4G 上 setoption 的可用选项只有 rcvtimeo / sendtimeo / keepalive / nodelay / reuseaddr / broadcast | 组播是局域网内的事,4G 上用不了;发现设备请改走平台下发 |
服务器(bind + listen + accept) | 函数都能调、也能等,但模组拿到的是运营商私网地址,公网侧连不进来 | 只在同一内网或专线 APN 下当服务器;要让外部访问就走平台/云端中转 |
net 在 4G 上是 23 个库级函数(少 net.ping_stats,那条只在调试构建里注册)。
函数总览
Socket 操作
| 函数 | 作用 |
|---|---|
net.socket(string) | 创建 socket,"TCP" / "UDP",返回句柄(失败返回 nil) |
sock:bind([ip], [port]) | 绑定本地 ip:port(服务器必填 port) |
sock:listen([backlog]) | 开始监听(TCP 服务器) |
sock:accept([timeout]) | 接受一个客户端连接(可反复调用) |
sock:connect(ip, port, [timeout]) | 连接远端 / 设定 UDP 默认目标 |
sock:send(data, [ip, port]) | 发送数据 |
sock:sendto(data, ip, port) | UDP 发到指定地址 |
sock:recv(maxsize) | 接收数据(TCP / 已设定目标的 UDP) |
sock:recvfrom([maxsize]) | 接收并带回发件人地址(UDP 服务器) |
net.select(...)({sock, ...}, [timeout]) | 等一批 socket 就绪,一台服务器带多个客户端 |
sock:peer() / sock:sockname() | 对端 / 本机 ip:port |
sock:close() | 关闭(不关也会在回收时自动关) |
sock:shutdown([how]) | 半关:只关发送或只关接收方向 |
Socket 选项
设置类只有一个入口 setoption,不再一个选项一个函数(省下来的名字和常量都要占 Lua 栈);回读用 getoption:
| 函数 | 作用 |
|---|---|
sock:setoption(选项名, ...) | 超时 / keepalive / nodelay / 地址复用 / UDP 广播,见该节表格 |
sock:getoption(选项名) | 把设过的选项读回来,确认是否生效 |
地址与解析
| 函数 | 作用 |
|---|---|
net.packip(string) | 点分字符串 / 四段 → 32 位 IP |
net.unpackip(integer) | 32 位 IP → 字符串或四段 |
net.lookup(string) | DNS 解析域名 |
net.localip(interface, format) | 取本机网卡 IP |
net.errstr(integer) | 错误码转中文说明 |
诊断与时间
| 函数 | 作用 |
|---|---|
net.ping(target, callback) | Ping 一个目标,结果走回调(调用后立即返回) |
net.ping_stats() | socket 计数 {created, closed},只在调试版固件里有 |
net.get_ntp_time_async(string) | 异步获取 NTP 时间 |
错误码
库里不再有 net.ERR_* 常量(连 net.TCP/net.UDP 也拿掉了——少一个名字就少占一份 Lua 栈)。 口径只有一条:err == 0 就是成功,非 0 失败,打日志用 net.errstr。
所有错误都走返回值,绝不抛异常。 参数写错、句柄已关闭、平台不支持某个选项……这些一律返回错误码(或 nil),不会中断您的脚本——PLC 脚本要一直循环跑,一句写错不能把后面的逻辑全拖垮。linpan_debug 构建(DEBUG)还会额外用 linpan_printf 打印一条诊断方便排查;release 构建静默返回错误码,程序照常运行。 要按错误类型分支时用下面这张表,直接写数字:
| 值 | 含义 | 什么时候会出现 | 老脚本里的同名常量 |
|---|---|---|---|
0 | 成功 | — | net.ERR_OK |
1 | 超时 | accept / recv / select 等到了时限(正常结果,不是故障) | net.ERR_TIMEDOUT |
2 | 连接已关闭 | TCP 对端断开;或对方 shutdown 了写方向 | net.ERR_CLOSED |
3 | 连接被中止 | 连接中途被打断 | net.ERR_ABORTED |
4 | 资源不足 | 建 socket 时 16 个已开满 | net.ERR_OVERFLOW |
5 | 其它错误 | 兜底,配合 net.errstr 打日志 | net.ERR_OTHER |
最后一列只是给老脚本对号入座用的名字,在新固件上取到的是 nil —— 写进代码就会变成 net.socket(nil) 这类当场报错。右侧那批常量只在尚未升级的机型固件上真实存在(见文末「老脚本迁移」一节)。
网卡编号(用于 net.localip):STA = 0,AP = 1,ETH = 2。
net.socket
lua
net.socket(type)lua
sock, err = net.socket("TCP" | "UDP")创建一个 socket,返回句柄(不是数字)。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | type | string | 是 | — | "TCP" 或 "UDP"(大小写都行)。没有 net.TCP 常量,直接写字符串 |
返回值
| 类型 | 说明 |
|---|---|
| userdata | socket 句柄;失败是 nil(建不出来 / 已经开满 16 个) |
| integer | 错误码,0 为成功 |
说明
- 拿到的是句柄,
print(sock)会显示net.socket(TCP): fd=7 已绑定这类信息。 - 类型只传字符串:
net.socket("TCP")/net.socket("UDP"),大小写都行。传数字或漏传都建不出来,返回nil+5。 - 用完
sock:close()。忘了也不会漏:句柄没人引用时被回收会自动关闭。 设备一共只有 16 个 socket,开满之后net.socket返回nil+4(资源不足)。 - 已经 close 的句柄再拿去收发会返回错误码(不抛异常),判
err处理即可,不会中断脚本。
示例
lua
local s, err = net.socket("TCP")
if not s then
print("建不出来:", net.errstr(err))
return
endnet.bind
lua
net.bind(sock, [port])lua
err, realport = sock:bind([ip, port])把 socket 绑定到本机的 ip:port。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | net.socket 返回的句柄(写成 net.bind(sock, ...) 也一样) |
| 2 | ip | integer | string | 否 | 本机所有网卡 | 32 位本地 IP(net.packip 得到)或点分字符串如 "192.168.1.100";填 0 / "0.0.0.0" / 不填都一样 |
| 3 | port | integer | 否 | 0 | 本地端口。服务器必须填;填 0 由系统分配 |
返回值
| 类型 | 说明 |
|---|---|
| integer | 错误码,0 为成功 |
| integer | 实际绑定的端口。传 0 时用它知道系统分了哪个端口 |
net.listen
lua
net.listen(sock, [backlog])lua
err = sock:listen([backlog])让 TCP socket 开始接受连接。服务器的第二步(第一步 bind,第三步 accept)。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | 已 bind 的 TCP 句柄 |
| 2 | backlog | integer | 否 | 5 | 等待队列长度 |
返回值
| 类型 | 说明 |
|---|---|
| integer | 错误码 |
说明
- UDP socket 调它会返回错误码(不抛异常)。
net.connect
lua
net.connect(sock, port, [timeout_ms])lua
err = sock:connect(ip, port, [timeout_ms])连接远端地址。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | net.socket 返回的句柄 |
| 2 | ip | integer | string | 是 | — | 32 位目标 IP 或点分字符串 |
| 3 | port | integer | 是 | — | 目标端口 |
| 4 | timeout_ms | integer | 否 | — | TCP 连接最多等多少毫秒。不填就是一直等到系统超时(可能几十秒) |
返回值
| 类型 | 说明 |
|---|---|
| integer | 错误码 |
说明
- UDP 上这一步的语义是「记下默认目标地址」,不会真的发包:之后
send/recv不用再写地址。 - 写客户端建议带
timeout_ms,否则对端不通时这一步会卡住主循环很久。
net.accept
lua
net.accept(sock, [timeout, ...])lua
client, err = sock:accept([timeout])接受一个客户端连接。可以反复调用,一个服务器接多个客户端。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | 已 listen 的 TCP 句柄 |
| 2 | timeout | integer | 否 | 0 | 等待连接的超时毫秒。0 = 一直等 |
返回值
| 类型 | 说明 |
|---|---|
| userdata | 新客户端的句柄;超时/失败是 nil |
| integer | 错误码 |
说明
- 超时是最常见的结果,不是故障:没等到连接就返回
nil+1(超时),配合timer周期性调用即可。 - 新连接会继承这里的超时,后面的
recv不会永久卡死。 - 必须先
bind+listen,没 listen 过会返回错误码(不抛异常)。 - 要客户端地址就用
client:peer(),accept 本身不返回地址。
net.send
lua
net.send(sock, data, [port])lua
sended, err = sock:send(data) -- TCP / 已设默认目标的 UDP
sended, err = sock:send(data, ip, port) -- UDP:临时指定目标发送数据。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | net.socket 返回的句柄 |
| 2 | data | string | 是 | — | 数据。非字符串会返回错误码(不抛异常) |
| 3 | ip | integer | string | 否 | — | 仅 UDP:目标 IP,不填就用 connect 设的默认目标 |
| 4 | port | integer | 否 | — | 仅 UDP:目标端口 |
返回值
| 类型 | 说明 |
|---|---|
| integer | 实际发送字节数 |
| integer | 错误码 |
说明
- TCP 会一直发到全部发完才返回,所以成功时
sended == #data。 - UDP 没设过默认目标又没传地址,会返回
0+ 错误码(不会发到错误地址)。
net.sendto
lua
net.sendto(sock, data, port)lua
sended, err = sock:sendto(data, ip, port)UDP 往指定地址发一包,不需要先 connect。UDP 服务器回包就用它(地址来自 recvfrom)。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | net.socket 返回的句柄 |
| 2 | data | string | 是 | — | 数据 |
| 3 | ip | integer | string | 是 | — | 目标 IP |
| 4 | port | integer | 是 | — | 目标端口 |
返回值
| 类型 | 说明 |
|---|---|
| integer | 实际发送字节数 |
| integer | 错误码 |
net.recv
lua
net.recv(sock)lua
data, err = sock:recv(maxsize)
data, err = sock:recv("*l") -- 读一行(读到 \n 为止,不含换行符,兼容 \r\n)接收数据。TCP 直接收;UDP 要先 connect 设过默认目标(要收任意来源的包用 recvfrom)。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | net.socket 返回的句柄 |
| 2 | maxsize | integer | string | 是 | — | 最大接收字节数;或 "*l" 读一行(读到 \n 为止,不含换行符,兼容 \r\n)。单次最多 65536 字节 |
返回值
| 类型 | 说明 |
|---|---|
| string | 收到的数据(没收到就是空串) |
| integer | 错误码。1 = 超时还没数据;2 = TCP 对端已断开 |
net.recvfrom
lua
net.recvfrom(sock, [maxsize])lua
data, from_ip, from_port, err = sock:recvfrom([maxsize])收一个 UDP 包并带回发件人地址。UDP 服务器的当家函数——只有拿到对方地址才能回包。TCP 上也能用,返回的是已连接的对端。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | 已 bind 的句柄 |
| 2 | maxsize | integer | 否 | 1024 | 最大接收字节数,单次最多 65536 |
返回值
| 类型 | 说明 |
|---|---|
| string | 收到的数据 |
| integer | 发件人 32 位 IP |
| integer | 发件人端口 |
| integer | 错误码 |
net.close
lua
net.close(sock)lua
err = sock:close()关闭 socket,腾出设备里那 16 个名额之一。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | net.socket 返回的句柄 |
返回值
| 类型 | 说明 |
|---|---|
| integer | 错误码,总是 0(重复关一次也没事) |
说明
- 主动关是好习惯:设备一共只有 16 个 socket,服务器每接一个客户端就占一个。
- 忘了也不会泄漏:句柄没人引用后被回收时会自动关掉(实测:连开 20 个不关,第 17 个开始建不出来,一次 GC 之后又能建了)。
- 关掉之后再拿这个句柄收发会返回错误码(不抛异常),不会默默返回错误码。
net.select
lua
net.select([timeout_ms])lua
ready, err = net.select({sock, sock, ...}, [timeout_ms])
ready, err = net.select(sock, [timeout_ms])等一批 socket 里谁「有数据可读」(TCP 监听口就是「有新连接」)。一台服务器同时带多个客户端就靠它:先问谁就绪,再只对就绪的那个 recv,不会被某个不说话的连接堵死。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | socks | table | 句柄 | 是 | — | 要等的句柄数组(也可以只传一个),最多 32 个 |
| 2 | timeout_ms | integer | 否 | 0 | 最多等多少毫秒。0 = 立刻返回(轮询);负数 = 一直等 |
返回值
| 类型 | 说明 |
|---|---|
| table | 就绪的句柄数组(按传入顺序),超时就是空表 #ready == 0 |
| integer | 错误码。超时是 1,不是故障 |
net.peer
lua
net.peer(sock)lua
ip, port, err = sock:peer()取对端地址(连上的是谁)。accept 拿到客户端后想知道对方是谁就用它。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | net.socket 返回的句柄 |
返回值
| 类型 | 说明 |
|---|---|
| integer | 对端 32 位 IP |
| integer | 对端端口 |
| integer | 错误码。没连过的 socket 返回非 0 |
net.sockname
lua
net.sockname(sock)lua
ip, port, err = sock:sockname()取本机这一端的地址。bind 时端口传 0 的,用它知道系统实际分了哪个端口。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | net.socket 返回的句柄 |
返回值
| 类型 | 说明 |
|---|---|
| integer | 本机 32 位 IP |
| integer | 本机端口 |
| integer | 错误码 |
net.errstr
lua
net.errstr(err) --> string把错误码翻成中文,方便打印日志。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | err | integer | 是 | — | net.recv / net.send 等返回的错误码 |
返回值
| 类型 | 说明 |
|---|---|
| string | 如 "OK" / "超时" / "连接已关闭" / "连接被中止" / "资源不足" / "其它错误" |
net.setoption
lua
net.setoption(sock, option, ...)lua
err = sock:setoption(选项名, ...)设置 socket 选项的唯一入口。以前 rcvtimeo / sendtimeo / keepalive / nodelay 各占一个 Lua 函数,现在全部合并到这一个(省下来的函数名和常量都要占 Lua 栈)。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | net.socket 返回的句柄 |
| 2 | option | string | 是 | — | 见下表,传其它字符串返回错误码(不抛异常)(大小写不敏感) |
| 3+ | 依 option 而定 | integer | — | — | 见下表 |
option | 额外参数 | 作用 |
|---|---|---|
"rcvtimeo" | 超时毫秒数(必填) | 收超时,0 表示一直等 |
"sendtimeo" | 超时毫秒数(必填) | 发超时 |
"keepalive" | 空闲秒 / 探测间隔秒 / 次数,可省,默认 30 / 3 / 3 | TCP 保活 |
"nodelay" | 无 | 关 Nagle,小包立即发 |
"reuseaddr" | 无 | 允许端口立即重用。服务器重启必备,否则上次监听的端口要等一会儿才能再绑 |
"broadcast" | 无 | 允许 UDP 广播(发 255.255.255.255 前必须开) |
返回值
| 类型 | 说明 |
|---|---|
| integer | 错误码:0 成功,非 0 失败。设置失败不报错,只返回非 0,要确认就检查返回值 |
示例
lua
local s = net.socket("TCP")
s:connect("192.168.1.100", 502, 3000)
if s:setoption("rcvtimeo", 5000) ~= 0 then
print("接收超时设置失败")
end老脚本怎么改
| 以前 | 现在 |
|---|---|
net.rcvtimeo(sock, 1000) | sock:setoption("rcvtimeo", 1000) |
net.sendtimeo(sock, 1000) | sock:setoption("sendtimeo", 1000) |
net.keepalive(sock, 30, 3, 3) | sock:setoption("keepalive", 30, 3, 3) |
net.nodelay(sock) | sock:setoption("nodelay") |
net.getoption
lua
net.getoption(sock, option)lua
value, err = sock:getoption(选项名)把已经设过的选项读回来,用来确认 setoption 真的生效了(配了 keepalive 却连不上,多半是被系统挡了)。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | net.socket 返回的句柄 |
| 2 | option | string | 是 | — | "rcvtimeo" / "sendtimeo" / "nodelay" / "broadcast" / "keepalive"(大小写都行),其它名字返回 nil + 5 |
返回值
| 类型 | 说明 |
|---|---|
| integer | 读到的值:rcvtimeo / sendtimeo 是毫秒数,其余三个是 0(没开)/ 1(开了);读不到是 nil |
| integer | 错误码,0 为成功 |
说明
- 只有
setoption表里那五个名字能回读;"reuseaddr"读不回来。 keepalive只回开没开,空闲秒 / 探测间隔 / 次数这三个值读不到(协议栈不提供)。nodelay只有 TCP 句柄能问,拿去问 UDP 返回nil+5。
示例
lua
s:setoption("rcvtimeo", 5000)
local ms, err = s:getoption("rcvtimeo")
print("当前收超时:", ms, net.errstr(err)) -- 期望 5000 / OKnet.shutdown
lua
net.shutdown(sock, [how])lua
err = sock:shutdown([how])半关连接:只关发送方向或只关接收方向,句柄本身还留着。close 是两半一起关。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | sock | 句柄 | 是 | — | net.socket 返回的句柄 |
| 2 | how | string | 否 | "both" | "write" 只关发送(告诉对端「我说完了」,自己还能收);"read" 只关接收;"both" 两边都关。其它写法返回 5 |
返回值
| 类型 | 说明 |
|---|---|
| integer | 错误码,0 为成功 |
说明
- 典型用法是发完最后一个包就
shutdown("write"):对端recv会拿到2(连接已关闭)从而知道数据发完了,而您这头还能把对方剩下的收尾消息读完再close。 - 关掉的这一半之后再用会报错:
shutdown("write")之后send失败。 shutdown不释放那 16 个 socket 名额之一,最后仍要close。
示例
lua
s:send("BYE\r\n")
s:shutdown("write") -- 告诉对端发完了
local data, err = s:recv(128) -- 仍可收对方的收尾数据
s:close()net.packip
lua
net.packip(a, b, c, d) --> ip
net.packip("a.b.c.d") --> ip把点分字符串或四段整数转成 32 位 IP。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| — | 四段 | integer × 4 | — | — | 每段 0-255 |
| — | 字符串 | string | — | — | 形如 "192.168.1.100" |
返回值
| 类型 | 说明 |
|---|---|
| integer | 32 位 IP |
说明
- 段越界或字符串格式错误会返回错误码(不抛异常)。
net.unpackip
lua
net.unpackip(ip, ["*n"]) --> a, b, c, d
net.unpackip(ip, "*s") --> "a.b.c.d"把 32 位 IP 转回四段或字符串。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | ip | integer | 是 | — | 32 位 IP |
| 2 | fmt | string | 否 | "*n" | "*n" 返回 4 个整数;"*s" 返回点分字符串;其它值返回 nil(不抛异常) |
返回值
| 类型 | 说明 |
|---|---|
| integer × 4 / string | 视 fmt 而定 |
net.lookup
lua
net.lookup(host) --> ip | nilDNS 解析域名。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | host | string | 是 | — | 域名 |
返回值
| 类型 | 说明 |
|---|---|
| integer | 成功返回 32 位 IP |
| nil | 解析失败返回 nil(无返回值) |
net.localip
lua
net.localip(iface, [fmt]) --> ip获取本机某个网卡的 IP。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | iface | integer | 是 | — | 网卡编号:0=STA、1=AP、2=ETH。其它值返回 nil(不抛异常) |
| 2 | fmt | string | 否 | "*n" | 同 net.unpackip:"*n" 四段,"*s" 字符串 |
返回值
| 类型 | 说明 |
|---|---|
| integer × 4 / string | 视 fmt 而定 |
说明
- 以太网编号是
2,不是1(1是 WiFi AP)。 - 4G 机型(LP8910 / LP8913 / LP8916 / LP8922)上本函数恒返回
nil—— 4G 的出口地址是运营商拨号时 现场分配的,机器上没有"本机网卡"可问。要拿本机实际用的出口地址与端口:net.socket("UDP")→connect一个外网地址 →sock:sockname()。
net.ping
lua
net.ping(target, callback)Ping 一个目标。函数立即返回,阻塞的 ping 在固件后台任务里做,结果以一条消息字符串回调。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | target | string | 是 | — | 目标 IP 或域名 |
| 2 | callback | function | 是 | — | 结果回调,入参是一条中文结果字符串 |
返回值
无返回值。必须正好传 2 个参数。
示例
lua
net.ping("8.8.8.8", function(msg)
print(msg) -- 例如 "来自 8.8.8.8 的回复: 时间=1.23ms TTL=55"
end)说明
- 回调收到的是已格式化好的中文字符串,不是结构化数据。域名解析失败也走这个回调。
- 4G 机型(LP8910 / LP8913 / LP8916 / LP8922)上能不能 ping 通,取决于这张 SIM 卡的 APN 放不放行 ICMP。 不放行时回调里是「ping X 失败: 请求超时」,这是链路的限制、不是脚本或固件坏了; 同一份 ping 代码在 ESP32 机型与模拟器上是正常出回包的。要确认远端服务活着, 更可靠的写法是用
tcp_client连业务端口(见 D. API 全集与机型可用性 里那条警告)。
实用例程
lua
--启动一个5秒的定时器
timeobj=timer.start(5000,function()
--测试与 192.168.1.100 的连通状态
net.ping("192.168.1.100", function(message)
print(message)
end)
end)
--将打印下面的数据
--来自 192.168.1.100 的回复: 时间=11.44ms TTL=64
--启动一个5秒的定时器
timeobj2=timer.start(5000,function()
--测试与 www.baidu.com 的连通状态
net.ping("www.baidu.com", function(message)
print(message)
end)
end)把上面的地址、主题、引脚替换成你项目里的即可。
net.ping_stats
lua
net.ping_stats() --> {created, closed}返回 ICMP 用过的 socket 计数器:created 是累计创建次数,closed 是累计关闭次数,两者相等说明没有泄漏。
参数
无。
返回值
一个表:{ created = <整数>, closed = <整数> }。
示例
lua
local s = net.ping_stats()
print(s.created, s.closed) -- 两者相等说明每次 ping 的 socket 都关掉了说明
- 这个函数只在调试版固件里注册,正式固件里
net.ping_stats是nil,调用会报attempt to call a nil value。客户程序不要依赖它做运行期判断,它用来核对固件自身的资源回收。 - 返回的是表,
print(net.ping_stats())只会打出table: 0x…,要像示例那样取字段。
net.get_ntp_time_async
lua
net.get_ntp_time_async(host, callback)在后台向 NTP 服务器请求时间,结果通过回调返回。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | host | string | 是 | — | NTP 服务器地址,如 "ntp.aliyun.com" |
| 2 | callback | function | 是 | — | 回调 (timestamp, err) |
返回值
无返回值。
回调签名
lua
function(timestamp, err)| 参数 | 类型 | 说明 |
|---|---|---|
timestamp | integer | nil | Unix 时间戳(秒);失败时为 nil |
err | nil | string | 成功时为 nil;失败时为中文错误串 "从NTP服务器获取时间失败" |
即标准 Lua 的 (结果, 错误) 约定:判成功要写 if ts then 或 if not err then。 不要写 if err == 0 —— 成功时 err 是 nil 而不是 0,nil == 0 恒为假, 校时语句一次都不会执行。
示例
lua
net.get_ntp_time_async("ntp.aliyun.com", function(ts, err)
if ts then
plc.rtc_set(ts) -- 用网络时间校准 RTC
print("已校时:", os.date("%Y-%m-%d %H:%M:%S", ts))
end
end)常见用法
开机校时
lua
net.get_ntp_time_async("ntp.aliyun.com", function(ts, err)
if ts then plc.rtc_set(ts) end
end)判断外网连通性
lua
net.ping("223.5.5.5", function(msg) print("外网:", msg) end)注意事项
- socket 是句柄不是数字:
net.socket()返回的只能拿去调net.xxx(sock, ...)或sock:xxx(...),不能当数字比较、不能自己存一个整数当 fd 用。 - 一共只有 16 个 socket:服务器的监听口 + 每个客户端各占一个。用完
close,忘了也会在句柄被回收时自动关,但主动关更可控。 recv收 TCP 和「已connect设过默认目标」的 UDP 都行;UDP 服务器要收任意来源的包必须用recvfrom。- 手写服务器必须按
socket→bind→listen→accept的顺序;少了listen直接accept会报错。 accept/recv超时返回1是正常结果,不是故障,要按错误码区分而不是一律当断线处理。- 服务器重启时给监听 socket 加
sock:setoption("reuseaddr"),否则上次占用的端口要等一段时间才能再绑。 - UDP 广播:发
255.255.255.255之前要先sock:setoption("broadcast")。 net.localip的以太网编号是2,不要与 AP 的1混淆。net.ping必须传 2 个参数,结果是字符串不是数值。- 手写 socket 流程容易出错且可能阻塞主循环:能用
tcp_client/tcp_server/udp就优先用它们;自己写时务必设超时、并用net.select避免被单个连接堵死。
老脚本迁移(2026-09-26 起)
socket 从「整数 fd」改成了「句柄」,选项函数合并成了一个,net.TCP / net.ERR_* 这批常量一并删掉。老脚本照下面的表改:
| 老写法 | 新写法 |
|---|---|
local fd = net.socket(net.TCP)(fd 是数字) | local s = net.socket("TCP")(句柄,失败是 nil) |
net.bind(fd, ip) | s:bind(ip, port) |
local c, ip, err = net.accept(fd, port, timeout)(三值,没有端口) | 先 s:bind(ip, port) + s:listen(),再 local c, err = s:accept(timeout),地址用 c:peer() |
net.close(fd) | s:close() |
net.rcvtimeo(fd, ms) 等四个 | s:setoption("rcvtimeo", ms) 等 |
net.ERR_OK / net.ERR_TIMEDOUT 等 | 直接写数字 0 / 1,含义见错误码 |
忘了 net.close 就永久泄漏 | 句柄没人引用时自动关闭 |
这套新写法对应的是 ESP32 系列机型(LP3201 / LP3202 / LP3203 / LP3222 / LP3232 / LP3288)、软 PLC 模拟器和上位机。 STM32 系列机型(LP2xxx,如 LP2300 / LP2323)的固件还是上一代实现:net.socket 只认 net.TCP 这种整数常量、 返回的是数字 fd,也没有 listen / select / setoption。在那些机型上照本页改写会让脚本连不上—— 先确认手上的机型属于哪一类,或把它升级到与本页面口径一致的固件版本。
例程
每个例程都是可直接下载运行的完整脚本。
net是底层工具,日常收发优先用tcp_client/tcp_server/udp;net适合校时、连通性、手写 socket。 API 只用本页已文档化的函数。统一模板「场景 / 完整代码 / 要点 / 验证方法 / 注意事项」。
例程 1:开机 NTP 校时
适用:LP3201 / LP3202 / LP3203 / LP3222 / LP3232 / LP3288 / 软PLC模拟器 | 依赖模块:net plc | 难度:★☆☆
场景:设备上电后先用网络时间校准 RTC,否则 os.time() 一直为 0,所有时间戳无意义。
完整代码(可直接下载运行):
lua
net.get_ntp_time_async("ntp.aliyun.com", function(ts, err)
if ts then
plc.rtc_set(ts) -- 用网络时间写 RTC
print("已校时:", os.date("%Y-%m-%d %H:%M:%S", ts))
else
print("校时失败:", err) -- err 是中文错误串,成功时这里是 nil
end
end)要点:
get_ntp_time_async异步请求,不阻塞主循环,结果在回调里按(时间戳, 错误)返回。- 判成功要写
if ts then:成功时err是nil,写if err == 0会让校时永远不执行。 plc.rtc_set(ts)把秒级时间戳写入 RTC。
验证方法:
- 重启设备,日志打印校时后的本地时间。
- 上位机/脚本读
os.time()不再是 0。
注意事项:
- 没校时前
os.time()为 0,上报时间戳无意义——校时要在程序早期跑。 - 网络不通或 NTP 被墙会
err ~= 0,要有失败兜底。