跳转至

WebSocket

一、WebSocket 介绍

WebSocket 是 HTML5 下一种新的协议(本质上是一个基于 TCP 的协议),它实现了浏览器与服务器之间的全双工通信,能够节省服务器资源和带宽,达到实时通讯的目的。WebSocket 协议通过握手机制,允许客户端和服务器之间建立一个类似 TCP 的连接,从而方便它们之间的通信。

  • 在线聊天应用:允许用户实时发送和接收消息,而无需页面刷新或轮询服务器。
  • 实时协作应用:支持多用户实时编辑文档或共享白板等场景。
  • 实时游戏:允许多个玩家之间进行实时的游戏交互。
  • 实时数据展示:用于显示实时数据,如股票市场变化、天气预报更新等。
  • 实时通知和提醒:用于向用户发送实时的通知消息,如新邮件提醒、社交媒体通知等。
  • 在线会议和视频通话:支持实时的音视频通信。

二、演示功能概述

本 demo 教你怎么使用 LuatOS 脚本语言,就可以让 Air8101 开发板连接上一个 WebSocket 服务器,并且模组和服务器之间实现数据的交互。

三、准备硬件环境

“古人云:‘工欲善其事,必先利其器。’在深入介绍本功能示例之前,我们首先需要确保以下硬件环境的准备工作已经完成。”

参考:硬件环境清单,准备以及组装好硬件环境。

四、软件环境

“凡事预则立,不预则废。”在详细阐述本功能示例之前,我们需先精心筹备好以下软件环境。

  1. Luatools 工具
  2. 内核固件文件(底层 core 固件文件):LuatOS-SoC_V10001_Air8101.soc;参考项目使用的内核固件
  3. luatos 需要的脚本和资源文件

脚本和资源文件:https://gitee.com/openLuat/LuatOS-Air8101/tree/master/demo/wlan/softAP

lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;

准备好软件环境之后,接下来查看如何烧录项目文件到 Air8101 开发板,将本篇文章中演示使用的项目文件烧录到 Air8101 开发板中。

五、 API 介绍

5.1 配置是否打开 debug 信息

wsc:debug(onoff)

参数

**传入值类型**
**解释**
boolean
是否打开debug开关

返回值

**返回值类型**
**解释**
nil
无返回值

例子

5.2 websocket 客户端创建

websocket.create(adapter, url, keepalive, use_ipv6)

参数

**传入值类型**
**解释**
int
适配器序号, 参考socket库的常量,默认为nil,会选择平台自带的方式
string
连接字符串,参考usage
int
心跳间隔,默认60秒. 2024.4.28新增
boolean
是否使用ipv6,默认false. 2024.6.17新增

返回值

**返回值类型**
**解释**
userdata
若成功会返回websocket客户端实例,否则返回nil

例子

-- 普通TCP链接wsc = websocket.create(nil,"ws://air32.cn/abc")-- 加密TCP链接wsc = websocket.create(nil,"wss://air32.cn/abc")

5.3 注册 websocket 回调

wsc:on(cb)

参数

**传入值类型**
**解释**
function
cb websocket回调,参数包括websocket_client, event, data, payload

返回值

**返回值类型**
**解释**
nil
无返回值

例子

wsc:on(function(websocket_client, event, data, payload)-- 打印各种事件log.info("websocket", "event", event, data, payload)end)--[[event的值有:    conack 连接服务器成功,已经收到websocket协议头部信息,通信已建立    recv   收到服务器下发的信息, data, payload 不为nil    sent   send函数发送的消息,服务器在TCP协议层已确认收到    disconnect 服务器连接已断开其中 sent/disconnect 事件在 2023.04.01 新增]]

5.4 连接服务器

wsc:connect()

参数

返回值

**返回值类型**
**解释**
boolean
发起成功返回true, 否则返回false

例子

-- 开始建立连接wsc:connect()-- 本函数仅代表发起成功, 后续仍需根据ready函数判断websocket是否连接正常

5.5 自动重连

wsc:autoreconn(reconnect, reconnect_time)

参数

**传入值类型**
**解释**
bool
是否自动重连
int
自动重连周期 单位ms 默认3000ms

返回值

例子

wsc:autoreconn(true)

5.6 发布消息

wsc:send(data, fin, opt)

参数

**传入值类型**
**解释**
string
待发送的数据,必填
int
是否为最后一帧,默认1,即马上设置为最后一帧, 也就是单帧发送
int
操作码, 默认为字符串帧0, 可选1

返回值

**返回值类型**
**解释**
bool
成功返回true,否则为false或者nil

例子

-- 简单发送数据wsc:send("123")-- 分段发送数据, 最后要用1(即FIN帧结束)wsc:send("123", 0)wsc:send("456", 0)wsc:send("789", 1)

5.7 websocket 客户端关闭(关闭后资源释放无法再使用)

wsc:close()

参数

返回值

例子

wsc:close()

5.8 websocket 客户端是否就绪

wsc:ready()

参数

返回值

**返回值类型**
**解释**
bool
客户端是否就绪

例子

local stat = wsc:ready()

5.9 设置额外的 headers

wsc:headers(headers)

参数

**传入值类型**
**解释**
table/string
可以是table,也可以是字符串

返回值

**返回值类型**
**解释**
bool
客户端是否就绪

例子

-- table形式wsc:headers({Auth="Basic ABCDEFGG"})-- 字符串形式wsc:headers("Auth: Basic ABCDERG\r\n")

六、代码示例介绍

6.1 核心脚本代码详解

6.1.1 websocket 客户端创建

-- ws 普通链接不加密
    wsc = websocket.create(nil, "ws://echo.airtun.air32.cn/ws/echo")
--加密链接 wss 表示加密,
    wsc = websocket.create(nil, "wss://echo.airtun.air32.cn/ws/echo")

6.1.2 设置额外的 headers

-- table形式
wsc:headers({Auth="Basic ABCDEFGG"})
-- 字符串形式
wsc:headers("Auth: Basic ABCDERG\r\n")

6.1.3 设置自动重连机制

wsc:autoreconn(true, 3000) -- 自动重连机制

6.1.4 注册 websocket 回调

wsc:on(function(wsc, event, data, fin, optcode)
    --[[
        event的值有:
        conack 连接服务器成功,已经收到websocket协议头部信息,通信已建立
        recv   收到服务器下发的信息, data, payload 不为nil
        sent   send函数发送的消息,服务器在TCP协议层已确认收到
        disconnect 服务器连接已断开
        其中 sent/disconnect 事件在 2023.04.01 新增
    ]]
    -- data 当事件为recv是有接收到的数据
    -- fin 是否为最后一个数据包, 0代表还有数据, 1代表是最后一个数据包
    -- optcode, 0 - 中间数据包, 1 - 文本数据包, 2 - 二进制数据包
    -- 因为lua并不区分文本和二进制数据, 所以optcode通常可以无视
    -- 若数据不多, 小于1400字节, 那么fid通常也是1, 同样可以忽略
    log.info("wsc", event, data, fin, optcode)
    -- 显示二进制数据
    -- log.info("wsc", event, data and data:toHex() or "", fin, optcode)
    if event == "conack" then -- 连接websocket服务后, 会有这个事件
        log.info("WebSocket connect succeed!")
        sys.publish("wsc_conack")----连接成功发布"wsc_conack"
    end
end)

6.1.5 连接服务器

wsc:connect()
    -- 等待conack是可选的
    sys.waitUntil("wsc_conack")------等待连接成功 并订阅到 "wsc_conack"

6.1.6 发布消息

-- 定期发业务ping也是可选的, 但为了保存连接, 也为了继续持有wsc对象, 这里周期性发数据
    while true do
        wsc:send((json.encode({action="echo", msg=os.date()})))
     sys.wait(15000)
        -- 发送二进制帧, 2023.06.21 之后编译的固件支持
        -- wsc:send(string.char(0xA5, 0x5A, 0xAA, 0xF2), 1, 1)
    end

6.1.7 websocket 客户端关闭(关闭后资源释放无法再使用)

wsc:close()

6.2 完整程序清单

注:完整复制后保存为 main.lua,可直接使用

-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "WebSocketdemo"
VERSION = "1.0.0"

--[[
本demo需要mqtt库,
mqtt也是内置库, 无需require
]]

-- sys库是标配
_G.sys = require("sys")

sys.taskInit(function()
    -----------------------------
    -- 统一联网函数, 可自行删减
    ----------------------------
    if wlan and wlan.connect then
        -- wifi 联网, Air8101系列均支持
        local ssid = "Xiaomi_1100"
        local password = "1234567890"
        log.info("wifi", ssid, password)

        wlan.init()
        wlan.setMode(wlan.STATION)
        wlan.connect(ssid, password, 1)
        --等待WIFI联网结果,WIFI联网成功后,内核固件会产生一个"IP_READY"消息
        local result, data = sys.waitUntil("IP_READY")
        log.info("wlan", "IP_READY", result, data)
        device_id = wlan.getMac()

    else
        -- 其他不认识的bsp, 循环提示一下吧
        while 1 do
            sys.wait(1000)
            log.info("bsp", "本bsp可能未适配网络层, 请查证")
        end
    end
    log.info("已联网")

    sys.publish("net_ready")
end)

local wsc = nil

sys.taskInit(function()

    sys.waitUntil("IP_READY")                -- 等待联网成功

    -- 这是个测试服务, 当发送的是json,且action=echo,就会回显所发送的内容
    -- 加密TCP链接 wss 表示加密
    wsc = websocket.create(nil, "wss://echo.airtun.air32.cn/ws/echo")
    -- 这是另外一个测试服务, 能响应websocket的二进制帧
    -- wsc = websocket.create(nil, "ws://echo.airtun.air32.cn/ws/echo2")
    -- 以上两个测试服务是Java写的, 源码在 https://gitee.com/openLuat/luatos-airtun/tree/master/server/src/main/java/com/luatos/airtun/ws

    if wsc.headers then
        wsc:headers({Auth="Basic ABCDEGG"})
    end
    wsc:autoreconn(true, 3000) -- 自动重连机制
    wsc:on(function(wsc, event, data, fin, optcode)
        --[[
            event的值有:
            conack 连接服务器成功,已经收到websocket协议头部信息,通信已建立
            recv   收到服务器下发的信息, data, payload 不为nil
            sent   send函数发送的消息,服务器在TCP协议层已确认收到
            disconnect 服务器连接已断开

            其中 sent/disconnect 事件在 2023.04.01 新增
        ]]
        -- data 当事件为recv是有接收到的数据
        -- fin 是否为最后一个数据包, 0代表还有数据, 1代表是最后一个数据包
        -- optcode, 0 - 中间数据包, 1 - 文本数据包, 2 - 二进制数据包
        -- 因为lua并不区分文本和二进制数据, 所以optcode通常可以无视
        -- 若数据不多, 小于1400字节, 那么fid通常也是1, 同样可以忽略
        log.info("wsc", event, data, fin, optcode)
        -- 显示二进制数据
        -- log.info("wsc", event, data and data:toHex() or "", fin, optcode)
        if event == "conack" then -- 连接websocket服务后, 会有这个事件
            log.info("WebSocket connect succeed!")
            sys.publish("wsc_conack")
        end
    end)
    wsc:connect()
    -- 等待conack是可选的
    sys.waitUntil("wsc_conack")
    --local stat = wsc:ready()
    -- 定期发业务ping也是可选的, 但为了保存连接, 也为了继续持有wsc对象, 这里周期性发数据
    while true do
        --发送的是 json,且 action=echo,就会回显所发送的内容
        wsc:send((json.encode({action="echo", msg=os.date()})))
        sys.wait(15000)
        -- 发送二进制帧, 2023.06.21 之后编译的固件支持
        -- wsc:send(string.char(0xA5, 0x5A, 0xAA, 0xF2), 1, 1)
    end
    wsc:close()
    wsc = nil
end)

-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!

七、功能验证

7.1 下载固件的基本设置

7.2 打印 wifi 信息连接热点

7.3 通过 WebSocket 创建连接,连接到 wss://``echo.airtun.air32.cn/ws/echo,并设置了自动重连机制。当连接成功时,会发布一个 wsc_conack 事件。

7.4 周期发送数据

7.5 热点关闭一段时间,然后再打开热点,wifi 可以自动重新连接

热点断开:

热点自动连接获取 ip:

7.6 热点关闭一段时间,然后再打开热点,自动重新连接

首先开启自动重连机制 wsc:autoreconn(true, 3000) --

然后热点关闭一段时间,WebSocket 连接断开,再打开热点,wifi 可以自动重新连接,并且能够自动重新连接 WebSocket 。

WebSocket 连接断开的日志:

WebSocket 自动重新连接日志:

七、常见问题

  • WebSocket 服务器的连接地址,格式为 ws(或 wss)://xxx 开头
  • WebSocket 需要在任务中启动,带自动重连,支持心跳协议
  • WebSocket 心跳包,建议 180 秒

八、扩展

在 WebSocket 中,WS 和 WSS 代表两种不同的连接类型,它们分别具有以下特点:

8.1 WS(WebSocket)

含义:表示非安全的 WebSocket 连接,即没有加密的 WebSocket 通信。

端口:默认情况下,WebSocket 的 WS 协议使用 80 端口。

使用场景:适用于不需要数据加密的场景,或者在安全性要求不高的环境中使用。然而,对于敏感数据或需要保护通信内容的情况,WS 可能不是最佳选择。

8.2 WSS(WebSocket Secure)

含义:表示安全的 WebSocket 连接,即在 TLS(传输层安全协议)之上的 WebSocket 通信。WSS 相当于 HTTPS 在 WebSocket 中的应用,提供了数据加密和完整性验证等安全功能。

端口:默认情况下,WSS 协议使用 443 端口,这是大多数网站用于 HTTPS 通信的标准端口。

使用场景:适用于需要保护通信内容、防止数据窃取或篡改的场景。WSS 是处理敏感数据或进行安全通信时的推荐选择。

总结

至此,我们已使用 Air8101 开发板完成了 WebSocket 通信的基本功能。