Skip to content

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 就是成功(错误码表见下)。

用 net 手写 TCP 客户端、TCP 服务器、UDP 客户端、UDP 服务器四条调用链,以及各自必须给的参数

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,返回句柄(不是数字)。

参数

#参数类型必填默认值说明
1typestring是—"TCP" 或 "UDP"(大小写都行)。没有 net.TCP 常量,直接写字符串

返回值

类型说明
userdatasocket 句柄;失败是 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
end

net.bind ​

lua
net.bind(sock, [port])
lua
err, realport = sock:bind([ip, port])

把 socket 绑定到本机的 ip:port。

参数

#参数类型必填默认值说明
1sock句柄是—net.socket 返回的句柄(写成 net.bind(sock, ...) 也一样)
2ipinteger | string否本机所有网卡32 位本地 IP(net.packip 得到)或点分字符串如 "192.168.1.100";填 0 / "0.0.0.0" / 不填都一样
3portinteger否0本地端口。服务器必须填;填 0 由系统分配

返回值

类型说明
integer错误码,0 为成功
integer实际绑定的端口。传 0 时用它知道系统分了哪个端口

net.listen ​

lua
net.listen(sock, [backlog])
lua
err = sock:listen([backlog])

让 TCP socket 开始接受连接。服务器的第二步(第一步 bind,第三步 accept)。

参数

#参数类型必填默认值说明
1sock句柄是—已 bind 的 TCP 句柄
2backloginteger否5等待队列长度

返回值

类型说明
integer错误码

说明

  • UDP socket 调它会返回错误码(不抛异常)。

net.connect ​

lua
net.connect(sock, port, [timeout_ms])
lua
err = sock:connect(ip, port, [timeout_ms])

连接远端地址。

参数

#参数类型必填默认值说明
1sock句柄是—net.socket 返回的句柄
2ipinteger | string是—32 位目标 IP 或点分字符串
3portinteger是—目标端口
4timeout_msinteger否—TCP 连接最多等多少毫秒。不填就是一直等到系统超时(可能几十秒)

返回值

类型说明
integer错误码

说明

  • UDP 上这一步的语义是「记下默认目标地址」,不会真的发包:之后 send / recv 不用再写地址。
  • 写客户端建议带 timeout_ms,否则对端不通时这一步会卡住主循环很久。

net.accept ​

lua
net.accept(sock, [timeout, ...])
lua
client, err = sock:accept([timeout])

接受一个客户端连接。可以反复调用,一个服务器接多个客户端。

参数

#参数类型必填默认值说明
1sock句柄是—已 listen 的 TCP 句柄
2timeoutinteger否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:临时指定目标

发送数据。

参数

#参数类型必填默认值说明
1sock句柄是—net.socket 返回的句柄
2datastring是—数据。非字符串会返回错误码(不抛异常)
3ipinteger | string否—仅 UDP:目标 IP,不填就用 connect 设的默认目标
4portinteger否—仅 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)。

参数

#参数类型必填默认值说明
1sock句柄是—net.socket 返回的句柄
2datastring是—数据
3ipinteger | string是—目标 IP
4portinteger是—目标端口

返回值

类型说明
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)。

参数

#参数类型必填默认值说明
1sock句柄是—net.socket 返回的句柄
2maxsizeinteger | 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 上也能用,返回的是已连接的对端。

参数

#参数类型必填默认值说明
1sock句柄是—已 bind 的句柄
2maxsizeinteger否1024最大接收字节数,单次最多 65536

返回值

类型说明
string收到的数据
integer发件人 32 位 IP
integer发件人端口
integer错误码

UDP 一问一答时序:客户端发到设备已 bind 的端口,设备 recvfrom 同时拿到数据与发件人 ip、port,再用 sendto 原样回包


net.close ​

lua
net.close(sock)
lua
err = sock:close()

关闭 socket,腾出设备里那 16 个名额之一。

参数

#参数类型必填默认值说明
1sock句柄是—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,不会被某个不说话的连接堵死。

参数

#参数类型必填默认值说明
1sockstable | 句柄是—要等的句柄数组(也可以只传一个),最多 32 个
2timeout_msinteger否0最多等多少毫秒。0 = 立刻返回(轮询);负数 = 一直等

返回值

类型说明
table就绪的句柄数组(按传入顺序),超时就是空表 #ready == 0
integer错误码。超时是 1,不是故障

TCP 服务器一轮循环的流程图:定时器里先 accept 收新连接,再用 select 挑出就绪的客户端,只对这些 fd recv 与回包,断开的及时关闭剔除


net.peer ​

lua
net.peer(sock)
lua
ip, port, err = sock:peer()

取对端地址(连上的是谁)。accept 拿到客户端后想知道对方是谁就用它。

参数

#参数类型必填默认值说明
1sock句柄是—net.socket 返回的句柄

返回值

类型说明
integer对端 32 位 IP
integer对端端口
integer错误码。没连过的 socket 返回非 0

net.sockname ​

lua
net.sockname(sock)
lua
ip, port, err = sock:sockname()

取本机这一端的地址。bind 时端口传 0 的,用它知道系统实际分了哪个端口。

参数

#参数类型必填默认值说明
1sock句柄是—net.socket 返回的句柄

返回值

类型说明
integer本机 32 位 IP
integer本机端口
integer错误码

net.errstr ​

lua
net.errstr(err) --> string

把错误码翻成中文,方便打印日志。

参数

#参数类型必填默认值说明
1errinteger是—net.recv / net.send 等返回的错误码

返回值

类型说明
string如 "OK" / "超时" / "连接已关闭" / "连接被中止" / "资源不足" / "其它错误"

net.setoption ​

lua
net.setoption(sock, option, ...)
lua
err = sock:setoption(选项名, ...)

设置 socket 选项的唯一入口。以前 rcvtimeo / sendtimeo / keepalive / nodelay 各占一个 Lua 函数,现在全部合并到这一个(省下来的函数名和常量都要占 Lua 栈)。

参数

#参数类型必填默认值说明
1sock句柄是—net.socket 返回的句柄
2optionstring是—见下表,传其它字符串返回错误码(不抛异常)(大小写不敏感)
3+依 option 而定integer——见下表
option额外参数作用
"rcvtimeo"超时毫秒数(必填)收超时,0 表示一直等
"sendtimeo"超时毫秒数(必填)发超时
"keepalive"空闲秒 / 探测间隔秒 / 次数,可省,默认 30 / 3 / 3TCP 保活
"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 却连不上,多半是被系统挡了)。

参数

#参数类型必填默认值说明
1sock句柄是—net.socket 返回的句柄
2optionstring是—"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 / OK

net.shutdown ​

lua
net.shutdown(sock, [how])
lua
err = sock:shutdown([how])

半关连接:只关发送方向或只关接收方向,句柄本身还留着。close 是两半一起关。

参数

#参数类型必填默认值说明
1sock句柄是—net.socket 返回的句柄
2howstring否"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"

返回值

类型说明
integer32 位 IP

说明

  • 段越界或字符串格式错误会返回错误码(不抛异常)。

net.unpackip ​

lua
net.unpackip(ip, ["*n"]) --> a, b, c, d
net.unpackip(ip, "*s")     --> "a.b.c.d"

把 32 位 IP 转回四段或字符串。

参数

#参数类型必填默认值说明
1ipinteger是—32 位 IP
2fmtstring否"*n""*n" 返回 4 个整数;"*s" 返回点分字符串;其它值返回 nil(不抛异常)

返回值

类型说明
integer × 4 / string视 fmt 而定

net.lookup ​

lua
net.lookup(host) --> ip | nil

DNS 解析域名。

参数

#参数类型必填默认值说明
1hoststring是—域名

返回值

类型说明
integer成功返回 32 位 IP
nil解析失败返回 nil(无返回值)

net.localip ​

lua
net.localip(iface, [fmt]) --> ip

获取本机某个网卡的 IP。

参数

#参数类型必填默认值说明
1ifaceinteger是—网卡编号:0=STA、1=AP、2=ETH。其它值返回 nil(不抛异常)
2fmtstring否"*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 在固件后台任务里做,结果以一条消息字符串回调。

参数

#参数类型必填默认值说明
1targetstring是—目标 IP 或域名
2callbackfunction是—结果回调,入参是一条中文结果字符串

返回值

无返回值。必须正好传 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 服务器请求时间,结果通过回调返回。

参数

#参数类型必填默认值说明
1hoststring是—NTP 服务器地址,如 "ntp.aliyun.com"
2callbackfunction是—回调 (timestamp, err)

返回值

无返回值。

回调签名

lua
function(timestamp, err)
参数类型说明
timestampinteger | nilUnix 时间戳(秒);失败时为 nil
errnil | 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。

验证方法:

  1. 重启设备,日志打印校时后的本地时间。
  2. 上位机/脚本读 os.time() 不再是 0。

注意事项:

  • 没校时前 os.time() 为 0,上报时间戳无意义——校时要在程序早期跑。
  • 网络不通或 NTP 被墙会 err ~= 0,要有失败兜底。

其余例程(各自一页) ​

LPPLC 工业物联网云平台 · 软硬件与云平台全栈自研