libnet - libnet 在socket库基础上的同步阻塞api,socket库本身是异步非阻塞api
作者:王城钧
一、概述
libnet 是基于异步非阻塞 socket 库的同步阻塞 API 封装,专为 sys.taskInitEx 创建的任务设计,提供阻塞式网络操作,简化异步编程。
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;
3、更加完整和详细的 demo,请参考 LuatOS 仓库 中各个产品目录下的 demo/socket/client/long_connection
-- 关于本库的一些说明:本文中的描述内容,使用了网卡和网络适配器这两种概念,这两种概念所表达的意思完全一样;
-- 本库用于网络通信, 支持TCP, UDP, 也支持TLS加密传输;
-- 支持加密传输版本有 TLS 1.0/1.1/1.2/1.3, DTLS 1.0/1.2, 当前不支持TLS 1.3;
-- 不支持 SSL 3.0, 该协议已经被废弃, 也不安全;
-- 支持的加密算法有 RSA, ECC, AES, 3DES, SHA1, SHA256, MD5 等等;
-- 完整的加密套件列表, 可通过 crypto.cipher_suites() 获取;
-- 本库的函数, 除非特别说明, 都是立即返回的非阻塞函数;
-- 这意味着, 函数调用成功, 并不代表网络操作成功, 只代表网络操作已经开始;
PROJECT = "SOCKET_CONNECTION"
VERSION = "001.000.000"
local libnet = require "libnet"
local SERVER_ADDR = "112.125.89.8" -- 根据实际修改
local SERVER_PORT = 47826 -- 根据实际修改
local TASK_NAME = "socket_connect_task"
local send_queue = {}
local TCP_SENDER_TASK_NAME = "tcp_send_task"
local recv_buff = nil
local function send_data_req_proc_func(tag, data, cb)
table.insert(send_queue, {data = "send from "..tag..": "..data, cb = cb})
sys.sendMsg(TCP_SENDER_TASK_NAME, socket.EVENT, 0)
end
function tcp_clientc_sender_proc(task_name, socket_client)
while #send_queue > 0 do
local send_item = table.remove(send_queue, 1)
-- 发送这条数据,超时时间15秒钟
local result = libnet.tx(task_name, 15000, socket_client, send_item.data)
-- 发送失败
if not result then
log.error("tcp_client_sender.proc", "libnet.tx error")
-- 如果当前发送的数据有用户回调函数,则执行用户回调函数
if send_item.cb and send_item.cb.func then
send_item.cb.func(false, send_item.cb.para)
end
return false
end
log.info("tcp_client_sender.proc", "send success")
-- 发送成功,如果当前发送的数据有用户回调函数,则执行用户回调函数
if send_item.cb and send_item.cb.func then
send_item.cb.func(true, send_item.cb.para)
end
end
return true
end
function tcp_client_sender_exception_proc()
while #send_queue > 0 do
local send_item = table.remove(send_queue, 1)
if send_item.cb and send_item.cb.func then
send_item.cb.func(false, send_item.cb.para)
end
end
end
sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)
function tcp_client_receiver_proc(socket_client)
-- 如果socket数据接收缓冲区还没有申请过空间,则先申请内存空间
if recv_buff == nil then
recv_buff = zbuff.create(1024)
end
while true do
local result = socket.rx(socket_client, recv_buff)
if not result then
log.error("tcp_client_receiver_proc", "socket.rx error")
return false
end
-- 如果读取到了数据, used()就必然大于0, 进行处理
if recv_buff:used() > 0 then
log.info("tcp_client_receiver_proc", "recv data len", recv_buff:used())
-- 读取socket数据接收缓冲区中的数据,赋值给data
local data = recv_buff:query()
-- 将数据data通过"RECV_DATA_FROM_SERVER"消息publish出去,给其他应用模块处理
sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data)
recv_buff:del()
else
break
end
end
return true
end
local function tcp_client_main_task_func()
local socket_client
local result, para1, para2
while true do
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
while not socket.adapter(socket.dft()) do
log.warn("tcp_client_main_task_func", "wait IP_READY", socket.dft())
sys.waitUntil("IP_READY", 1000)
end
-- 检测到了IP_READY消息
log.info("tcp_client_main_task_func", "recv IP_READY", socket.dft())
-- 创建socket client对象
socket_client = socket.create(nil, TASK_NAME)
-- 如果创建socket client对象失败
if not socket_client then
log.error("tcp_client_main_task_func", "socket.create error")
goto EXCEPTION_PROC
end
-- 配置socket client对象为tcp client
result = socket.config(socket_client)
-- 如果配置失败
if not result then
log.error("tcp_client_main_task_func", "socket.config error")
goto EXCEPTION_PROC
end
-- 连接server
result = libnet.connect(TASK_NAME, 15000, socket_client, SERVER_ADDR, SERVER_PORT)
-- 如果连接server失败
if not result then
log.error("tcp_client_main_task_func", "libnet.connect error")
goto EXCEPTION_PROC
end
log.info("tcp_client_main_task_func", "libnet.connect success")
while true do
if not tcp_client_receiver_proc(socket_client) then
log.error("tcp_client_main_task_func", "tcp_client_receiver_proc error")
break
end
if not tcp_clientc_sender_proc(TASK_NAME, socket_client) then
log.error("tcp_client_main_task_func", "tcp_client_sender_proc error")
break
end
result, para1, para2 = libnet.wait(TASK_NAME, 15000, socket_client)
log.info("tcp_client_main_task_func", "libnet.wait", result, para1, para2)
if not result then
log.warn("tcp_client_main_task_func", "connection exception")
break
end
end
-- 出现异常
::EXCEPTION_PROC::
-- 数据发送应用模块对来不及发送的数据做清空和通知失败处理
tcp_client_sender_exception_proc()
-- 如果存在socket client对象
if socket_client then
-- 关闭socket client连接
libnet.close(TASK_NAME, 5000, socket_client)
-- 释放socket client对象
socket.release(socket_client)
socket_client = nil
end
sys.wait(5000)
end
end
sys.taskInitEx(tcp_client_main_task_func, TASK_NAME)
sys.run()
三、常量详解
核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
libnet 扩展库没有常量。
四、函数详解
libnet.waitLink(task_name,timeout,socket_ctrl)
功能
阻塞等待 socket_ctrl 所表示的 socket 对象绑定的网卡网络连接正常,只能用于 sys.taskInitEx 创建的任务函数中
参数
task_name
参数含义:为消息通知的task_name;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"socket_client_task";
timeout
参数含义:超时时间,如果=0或者空,则没有超时,一直等待;
数据类型:number;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:10;
socket_ctrl
参数含义:socket.create得到的socket_ctrl;
数据类型:userdata;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:暂无;
返回值
local result = libnet.waitLink(socket_client_task, 0, socket_ctrl)
result
含义说明:失败或者超时返回false,成功返回true;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:暂无;
示例
local result = libnet.waitLink(socket_client_task, 0, socket_ctrl)
libnet.connect(task_name,timeout,socket_ctrl, ip, remote_port, need_ipv6_dns)
功能
阻塞等待 IP 或者域名连接上,如果加密连接还要等握手完成,只能用于 sys.taskInitEx 创建的任务函数中
参数
task_name
参数含义:为消息通知的task_name;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"socket_client_task";
timeout
参数含义:超时时间,如果=0或者空,则没有超时一致等待;
数据类型:number;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:10;
socket_ctrl
参数含义:socket.create得到的socket_ctrl;
数据类型:userdata;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:暂无;
ip
参数含义:ip地址或者ip或者域名,如果是IPV4,可以是大端格式的int值;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:暂无;
remote_port
参数含义:服务器端口号,小端格式;
数据类型:number;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:暂无;
need_ipv6_dns
参数含义:域名解析是否要IPV6,true要,false不要,默认false不要,只有支持IPV6的协议栈才有效果;
数据类型:boolean;
取值范围:true或false;
是否必选:可选传入此参数;
注意事项:暂无;
参数示例:暂无;
返回值
local result = libnet.connect(TASK_NAME, 15000, socket_client, SERVER_ADDR, SERVER_PORT)
result
含义说明:失败或者超时返回false 成功返回true;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:暂无;
示例
local result = libnet.connect(socket_client_task, 5000, socket_ctrl, ip, port)
libnet.listen(task_name,timeout,socket_ctrl)
功能
阻塞等待客户端连接上,只能用于 sys.taskInitEx 创建的任务函数中。
参数
task_name
参数含义:为消息通知的task_name;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"socket_client_task";
timeout
参数含义:超时时间,如果=0或者空,则没有超时一致等待;
数据类型:number;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:10;
socket_ctrl
参数含义:socket.create得到的socket_ctrl;
数据类型:userdata;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:暂无;
返回值
local result = libnet.listen(task_name,timeout,socket_ctrl)
result
含义说明:失败或者超时返回false 成功返回true;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:暂无;
示例
local result = libnet.listen(socket_client_task, 0, socket_ctrl)
libnet.tx(task_name,timeout,socket_ctrl,data, ip, port, flag)
功能
阻塞等待数据发送完成,只能用于 sys.taskInitEx 创建的任务函数中
参数
task_name
参数含义:为消息通知的task_name;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"socket_client_task";
timeout
参数含义:超时时间,如果=0或者空,则没有超时一致等待;
数据类型:number;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:10;
socket_ctrl
参数含义:socket.create得到的socket_ctrl;
数据类型:userdata;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:暂无;
data
参数含义:待发送的数据 或者 userdata zbuff 要发送的数据;
数据类型:string/zbuff;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:暂无;
ip
参数含义:对端IP,如果是TCP应用则忽略,
如果是UDP,如果留空则用connect时候的参数,
如果是IPV4,可以是大端格式的number值;
数据类型:string或者number;
取值范围:暂无;
是否必选:可选传入此参数;
注意事项:暂无;
参数示例:暂无;
port
参数含义:对端端口号,小端格式,如果是TCP应用则忽略,如果是UDP,如果留空则用connect时候的参数;
数据类型:number;
取值范围:暂无;
是否必选:可选传入此参数;
注意事项:暂无;
参数示例:暂无;
flag
参数含义:发送参数,目前预留,不起作用;
数据类型:int;
取值范围:暂无;
是否必选:可选传入此参数;
注意事项:暂无;
参数示例:暂无;
返回值
local result, buff_full = libnet.tx(task_name,timeout,socket_ctrl, data, ip, port, flag)
result
含义说明:失败或者超时返回false 成功返回true;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:暂无;
buff_full
含义说明:缓存区是否满了;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:暂无;
示例
function tcp_client_sender.proc(task_name, socket_client)
local send_item
local result, buff_full
-- 遍历数据发送队列send_queue
while #send_queue>0 do
-- 取出来第一条数据赋值给send_item
-- 同时从队列send_queue中删除这一条数据
send_item = table.remove(send_queue,1)
-- 发送这条数据,超时时间15秒钟
result, buff_full = libnet.tx(task_name, 15000, socket_client, send_item.data)
-- 发送失败
if not result then
log.error("tcp_client_sender.proc", "libnet.tx error")
-- 如果当前发送的数据有用户回调函数,则执行用户回调函数
if send_item.cb and send_item.cb.func then
send_item.cb.func(false, send_item.cb.para)
end
return false
end
-- 如果内核固件中缓冲区满了,则将send_item再次插入到send_queue的队首位置,等待下次尝试发送
if buff_full then
log.error("tcp_client_sender.proc", "buffer is full, wait for the next time")
table.insert(send_queue, 1, send_item)
return true
end
log.info("tcp_client_sender.proc", "send success")
-- 发送成功,如果当前发送的数据有用户回调函数,则执行用户回调函数
if send_item.cb and send_item.cb.func then
send_item.cb.func(true, send_item.cb.para)
end
-- 发送成功,通知网络环境检测看门狗功能模块进行喂狗
sys.publish("FEED_NETWORK_WATCHDOG")
end
return true
end
libnet.wait(task_name,timeout, socket_ctrl)
功能
阻塞等待新的网络事件,只能用于 sys.taskInitEx 创建的任务函数中,可以通过 sys.sendMsg(task_name,socket.EVENT,0)强制退出
参数
task_name
参数含义:为消息通知的task_name;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"socket_client_task";
timeout
参数含义:超时时间,如果=0或者空,则没有超时一致等待;
数据类型:number;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:10;
返回值
local result, param = libnet.wait(task_name,timeout, socket_ctrl)
result
含义说明:网络异常返回false,其他返回true;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:暂无;
param
含义说明:超时返回false,有新的网络事件到返回true;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:暂无;
示例
local result, param, param2 = libnet.wait(socket_client_task, 15000, socket_ctrl)
log.info("libnet", "wait", result, param, param2)
if not result then
-- 网络异常了, 那就断开了, 执行清理工作
log.info("socket", "服务器断开了", result, param)
break
libnet.close(task_name,timeout, socket_ctrl)
功能
阻塞等待网络断开连接,只能用于 sys.taskInitEx 创建的任务函数中
task_name
参数含义:为消息通知的task_name;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"socket_client_task";
timeout
参数含义:超时时间,如果=0或者空,则没有超时一致等待;
数据类型:number;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:socket_client_task;
socket_ctrl
参数含义:socket.create得到的socket_ctrl;
数据类型:userdata;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:暂无;
返回值
nil
示例
libnet.close(socket_client_task, 5000, socket_ctrl)
五、产品支持说明
支持 LuatOS 开发的所有产品都支持 libnet 扩展库。