跳转至

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

参数含义:域名解析是否要IPV6true要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 扩展库。