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
参数含义:使用sys.taskInitEx创建的task名称;
libnet.tx接口需要在此task的任务处理函数中被调用;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"tcp_client_send_task";
timeout
参数含义:超时时间,如果=0或者空,则没有超时,一直等待;
数据类型:number;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:10;
socket_ctrl
参数含义:使用socket.create接口创建的socket对象;
数据类型:userdata;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:必须先创建socket对象,才能使用此接口发送数据到对端;
参数示例:暂无;
返回值
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,remote_addr, remote_port, need_ipv6_dns)
功能
连接对端,阻塞等待连接结果的返回;
只能在sys.taskInitEx创建的task的任务处理函数中使用,因为要阻塞等待并且接收定向消息;
参数
task_name
参数含义:使用sys.taskInitEx创建的task名称;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"tcp_client_task";
timeout
参数含义:连接超时时长,单位为毫秒,如果为0或者空,则表示没有超时限制,一直等待连接结果的返回;
数据类型:number或者nil;
取值范围:暂无;
是否必选:可选传入此参数;
注意事项:如果要连接的对端是域名,此处需要考虑dns域名解析的耗时;
不建议此处的timeout设置的时长过短,针对不同的连接要预留足够的时间;
参数示例:30000;
socket_ctrl
参数含义:使用socket.create接口创建的socket对象;
数据类型:userdata;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:必须先创建socket对象,才能使用此接口连接对端;
参数示例:暂无;
remote_addr
参数含义:服务器地址,支持域名或者ip地址;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"docs.openluat.com"或者"112.125.89.8";
remote_port
参数含义:服务器端口号;
数据类型:number;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:34574;
need_ipv6_dns
参数含义:域名解析时,是否优先解析并且返回域名remote_addr对应的IPV6地址;
true表示需要,false表示不需要,默认false;
数据类型:boolean;
取值范围:true或false或nil;
是否必选:可选传入此参数,默认魏nil;
注意事项:暂无;
参数示例:nil或者为空;
返回值
local result = libnet.connect(TASK_NAME, 15000, socket_client, SERVER_ADDR, SERVER_PORT)
result
含义说明:连接结果;失败或者超时返回false 成功返回true;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:true;
示例
-- 创建socket client对象
socket_client = socket.create(nil, "tcp_client_task")
-- 如果创建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("tcp_client_task", 15000, socket_client, "112.125.89.8", 34036)
-- 如果连接server失败
if not result then
log.error("tcp_client_main_task_func", "libnet.connect error")
goto EXCEPTION_PROC
end
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的任务处理函数中使用,因为要阻塞等待并且接收定向消息;
参数
task_name
参数含义:使用sys.taskInitEx创建的task名称;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"tcp_client_task";
timeout
参数含义:连接超时时长,单位为毫秒,如果为0或者空,则表示没有超时限制,一直等待连接结果的返回;
数据类型:number或者nil;
取值范围:暂无;
是否必选:可选传入此参数;
注意事项:
一般来说,10秒,15秒,20秒都比较合适;再短的话,不推荐;
再长的话,只要项目业务允许,也可以使用;
参数示例:15000;
socket_ctrl
参数含义:使用socket.create接口创建的socket对象;
数据类型:userdata;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:必须先创建socket对象,才能使用此接口连接对端;
参数示例:暂无;
data
参数含义:待发送的数据;
数据类型:string或者zbuff;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:TCP模式下,只要ram够用,发送的一包数据长度没有特别限制;
UDP模式下,发送的一包数据长度不要超过1460字节;
参数示例:"123456";
ip
参数含义:UDP模式下,对端IP地址;
UDP模式下,如果为空,发送数据时会使用libnet.connect接口中的remote_address参数;
UDP模式下,如果不为空,数据会直接发给这个ip参数所表示的对端IP地址;
TCP模式下,此参数没有任何意义;
数据类型:string或者nil;
取值范围:暂无;
是否必选:可选传入此参数;
注意事项:此参数和libnet.connect接口中的remote_addr都表示对端地址;
参数示例:"112.125.89.8";
port
参数含义:UDP模式下,对端端口号;
UDP模式下,如果为空,发送数据时会使用libnet.connect接口中的remote_port参数;
UDP模式下,如果不为空,数据会直接发给这个port参数所表示的对端端口号;
TCP模式下,此参数没有任何意义;
数据类型:number或者nil;
取值范围:暂无;
是否必选:可选传入此参数;
注意事项:此参数和libnet.connect接口中的remote_port都表示对端端口号;
参数示例:8888;
flag
参数含义:发送参数,目前预留,不起作用;
数据类型:number或者nil;
取值范围:暂无;
是否必选:可选传入此参数;
注意事项:暂无;
参数示例:暂无;
返回值
local result, buff_full = libnet.tx(task_name,timeout,socket_ctrl, data, ip, port, flag)
result
含义说明:发送结果;失败或者超时返回false,成功返回true;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:true;
buff_full
含义说明:缓存区是否满了;
数据类型:boolean;
取值范围:true或false;
注意事项:缓冲区满只有一种情况,就是此次发送的数据动态申请内存失败了;
返回示例:false;
示例
-- 发送这条数据,超时时间15秒钟
local result, buff_full = libnet.tx("tcp_client_send_task", 15000, socket_client, "123456")
-- 发送失败
if not result then
log.error("tcp_client_sender.proc", "libnet.tx error")
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")
-- todo:此处存储这次待发送的数据,等待下次在socket主业务逻辑中检测到socket.EVENT消息再发送
return true
end
libnet.wait(task_name,timeout, socket_ctrl)
功能
socket连接已经成功后,阻塞等待socket对象上新的网络事件消息socket.EVENT;
只能在sys.taskInitEx创建的task的任务处理函数中使用,因为要阻塞等待并且接收定向消息;
参数
task_name
参数含义:使用sys.taskInitEx创建的task名称;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"tcp_client_task";
timeout
参数含义:等待超时时长,单位为毫秒,如果为0或者空,则表示没有超时限制,一直等待socket.EVENT消息;
数据类型:number或者nil;
取值范围:暂无;
是否必选:可选传入此参数;
注意事项:这里的超时时长到达后,并不会认定出现异常;
参数示例:15000;
socket_ctrl
参数含义:使用socket.create接口创建的socket对象;
数据类型:userdata;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:必须先创建socket对象,才能使用此接口连接对端;
参数示例:暂无;
返回值
local result, param = libnet.wait(task_name,timeout, socket_ctrl)
result
含义说明:网络异常返回false,其他返回true;
数据类型:boolean;
取值范围:true或false;
注意事项:返回false时,要主动的调用libnet.close接口关闭socket,再调用socket.release释放资源;
返回示例:true;
param
含义说明:超时返回false,有新的网络事件到返回true;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:暂无;
示例
local result, param = libnet.wait("tcp_client_task", 15000, socket_ctrl)
log.info("libnet", "wait", result, param)
-- 网络异常
if not result then
log.info("socket", "connection exception")
-- 如果存在socket client对象
if socket_ctrl then
-- 关闭socket client连接
libnet.close("tcp_client_task", 5000, socket_ctrl)
-- 释放socket client对象
socket.release(socket_client)
socket_client = nil
end
end
libnet.close(task_name,timeout, socket_ctrl)
功能
主动断开socket连接;
只能在sys.taskInitEx创建的task的任务处理函数中使用,因为要阻塞等待并且接收定向消息;
参数
task_name
参数含义:使用sys.taskInitEx创建的task名称;
数据类型:string;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:暂无;
参数示例:"tcp_client_task";
timeout
参数含义:TCP模式下四次挥手断开连接的等待超时时长,单位为毫秒;
如果为0或者空,则表示没有超时限制,一直等待四次挥手断开;
数据类型:number或者nil;
取值范围:暂无;
是否必选:可选传入此参数;
注意事项:这里的超时时长到达后,如果四次挥手还没有断开连接,则会强制断开连接;
一般来说,此处的timeout值设置为5秒就行;
参数示例:30000;
socket_ctrl
参数含义:使用socket.create接口创建的socket对象;
数据类型:userdata;
取值范围:暂无;
是否必选:必须传入此参数;
注意事项:必须先创建socket对象,才能使用此接口发送数据到对端;
参数示例:暂无;
返回值
nil
示例
libnet.close(socket_client_task, 5000, socket_ctrl)
五、产品支持说明
支持 LuatOS 开发的所有产品都支持 libnet 扩展库。