LuatOS作为专为物联网设备设计的轻量级操作系统,提供了高效稳定的网络通信能力,其中Socket编程是实现设备与服务器间数据交互的核心技术。掌握LuatOS下的Socket基础原理与开发流程,是构建可靠联网应用的关键一步。本文将从底层机制讲起,结合实际案例,系统讲解Socket在LuatOS环境中的应用开发方法。
一、主要内容
LuatOS socket是LuatOS开发中最常用到的网络应用之一;
LuatOS socket课程主要包含以下几个部分:
1、TCP/IP总体介绍;
2、LuatOS上的 4G/WiFi/以太网 三种网络环境下的TCP/IP协议栈总体介绍;
3、TCP/UDP/SSL/证书以及socket概念;
4、LuatOS socket核心库和libnet扩展库功能介绍;
5、LuatOS socket client应用开发框架(TCP,UDP,SSL,网络环境检测看门狗);
6、如何分析LuatOS socket日志;
二、TCP/IP总体介绍
今天我们讲的LuatOS socket是LuatOS开发中最常用到的网络应用之一,用户使用LuatOS socket开发网络应用,会直接接触到TCP/UDP/SSL/认证等一些网络核心概念;
而说到网络应用,就绕不开TCP/IP;
2.1 TCP/IP网络模型
有网络应用开发经验的人,应该都听说过OSI七层模型、TCP/IP协议四层模型和TCP/IP协议五层模型;
这三种网络协议模型的说明,参考下表:

看了这张表之后,我们应该有以下几点认识:
都采用了分层的思想,将复杂的通信过程分解为更小、更易于管理的部分;
每一层都为其上层提供服务,并使用其下层提供的服务;
这三种网络模型,只是分层的颗粒度不一样,实际上,这三种网络模型的本质内容都是一样的;
既然这三种网络模型的本质内容是一样的,为什么还要存在三种网络模型呢?每种网络模型又有什么作用呢?
我们简单地看一下这三种网络模型的历史:

最终TCP/IP模型在实践中得到广泛应用;
至于TCP/IP四层模型和TCP/IP五层模型,二者的差别不大,主要体现在对最底层的划分不同:
1、四层模型隐藏了底层细节,将网络接入视为一个黑盒,更关注对软件层面的设计,不关心具体的硬件;
2、五层模型明确包含了物理硬件层;
四层模型和五层模型本质上是同一个东西的两种不同表述方式,所以我们接下来不纠结四层还是五层模型,而是统称为TCP/IP模型,下文中会根据不同的应用场景来决定选择四层模型还是五层模型来讲解。
2.2 TCP/IP协议
在了解了TCP/IP网络模型之后,接下来我们看一下TCP/IP协议这个概念;
TCP/IP网络模型是理论上的框架和蓝图,而TCP/IP协议是这个框架的具体实现;
我们来看下面这张表格

可以看到,每一层都有多个协议去实现,在所有的这些协议中,TCP和IP两种协议是其中的核心协议,所以用TCP/IP协议代指网络模型中的所有协议;
所以说,TCP/IP协议 并不是两个单一的协议,而是一个协议家族,包含TCP/IP网络模型中的所有协议;
所有协议协同工作,从软件上完成了互联网上的数据传输任务;
2.3 TCP/IP协议的核心工作原理
TCP/IP协议通过功能分层以及数据封装/解封装,来协同工作完成网络数据传输任务;
2.3.1 功能分层
下层为上层提供服务,上层使用下层提供的服务;
每一层都只专注于自己的任务;
TCP/IP五层模型,从上到下划分为应用层,传输层,网络层,数据链路层,物理层;
每一层的主要功能如下:
- 应用层 :应用层的主要工作是定义数据格式并按照对应的格式解读数据,应用层定义了各种各样的协议来规范数据格式,常见的有 HTTP、MQTT、FTP、WebSocket等;
例如常见的HTTP协议,服务端收到客户端的请求以后,就能按照HTTP报文的格式,正确地解析客户端发来的数据,当请求处理完以后,再按照HTTP报文的格式返回结果给客户端,客户端收到结果后,按照HTTP报文的格式进行解析;
- 传输层 :传输层的主要工作是定义端口,标识应用程序身份,实现端到端的通信,为应用层的应用程序提供可靠的或不可靠的数据传输服务;
数据包是从发送端设备的某个应用程序发出,然后由接收端设备的某个应用程序接收。而每台设备都有可能同时运行着很多个应用程序,所以要通过传输层定义的端口来确认使用的是设备上的哪一个应用程序;
例如HTTP应用程序的默认端口就是80,也可以自定义端口;
- 网络层 :网络层的主要工作是定义网络IP地址,区分网段,逻辑寻址和在不同的公共网络之间进行路由选择;
网络层的通信视野是全局的,它从源IP地址出发,最终要到达目的IP地址,中间可能经过无数个不同的网络(例如以太网、Wi-Fi、4G LTE等),它管的是"全局"的事情;
例如下图中红框所标注的网络单元中的网络路由选择,就是靠网络层实现的:

- 数据链路层 :数据链路层的主要工作是在本地局域网内将数据发送给接收方;
可以是带交换机功能的WiFi路由器和这个WiFi下的设备相互发送数据,或者这个WiFi路由器下的两个设备相互发送数据;
也可以是以太网交换机和这个交换机下的设备相互发送数据,或者这个交换机下的两个设备相互发送数据;
也可以是4G网络中,某个运营商基站和这个基站下的设备(例如手机)相互发送数据;
本地局域网的数据链路层也需要寻址,以太网和WiFi网络环境中,需要用到设备的MAC地址进行寻址;4G网络中,需要用到RNTI(无线网络临时标识符)进行寻址;
数据链路层的通信视野是局部的,只负责局域网内将数据发送给接收方,它管的是"局部"的事情;
例如下图中红框所标注的网络单元中的局域网内的寻址,就是靠数据链路层实现的:

- 物理层 :物理层的主要工作是传输0和1的电信号,这些电信号通过物理传输介质进行传输;
常见的物理层传输介质有:
(1) 光纤(我们日常生活中可以接触到的就是,运营商将光纤直接从运营商机房铺设到用户家中,提供百兆、千兆的高速互联网接入。你家办理的中国电信、联通或移动的"千兆光网"。你家的"光猫"就是接收光信号并将其转换为电信号的设备。)
(2) 双绞线(例如在以太网网络环境中常用的网线就是双绞线的一种)
(3) 无线电波(例如Wi-Fi、4G、5G网络都必须依赖无线电波作为载体才能进行通信。它们本质上都是通过调制解调技术,将数字信号(0和1)加载到无线电波上发射出去,接收端再解调回来)
不同的物理层传输介质,决定了电信号(0和1)的传输方式,物理介质的不同决定了电信号的传输带宽、速率、传输距离以及抗干扰性等等。
例如下图中红框所标注的网络单元间的数据传输,就是靠物理层实现的:

TCP/IP协议的上面四层(应用层,传输层,网络层,数据链路层),可以理解为是软件层面的实现,最终封装成数据包;
而TCP/IP协议的最下面一层(物理层)将数据包转化为 0 和 1 的电信号,通过物理介质进行传输才能到达下一个网络单元,因此物理介质可以认为是网络通信的基石。
大家现在对每一层的功能先有一个总体认识即可,不用关注细节;
后续会有各种网络环境下的实例来进一步说明如何工作;
2.3.2 数据封装/解封装
理解了功能分层的概念之后,我们再来看下面这张图片,了解一下数据的封装和解封装;


1、在数据发送端,应用层的网络协议生成应用数据;
2、应用数据向下传,交给传输层,增加了传输层的头部信息,在头部信息中,有很多字段,其中有两个字段分别是源端口号和目标端口号,这两个端口号的作用是:定义了发送端和接收端的应用程序,例如http应用的默认端口号是80;可以提供两个主机之上端到端的通信;
3、传输层头部+应用数据向下传,交给网络层,增加了网络层的头部信息,在头部信息中,有很多字段,其中有两个字段分别是源IP地址和目标IP地址,这两个IP地址的作用是:定义了发送端和接收端的网络设备地址;可以提供主机到主机的通信;
4、网络层头部+传输层头部+应用数据向下传,交给数据链路层,增加了数据链路层的头部信息;
5、最终,数据链路层头部+网络层头部+传输层头部+应用数据的数据包,通过物理层转换为0和1的电信号,然后通过物理传输介质发给了接收端;
6、接收端收到数据后,从下到上,依次解析并且剥离出数据链路层头部、网络层头部、传输层头部,最终将应用数据交给应用程序去处理;
7、接收端处理完应用数据后,如果需要返回应用数据给发送端;此时发送端和接收端的角色互换,再走一遍数据分层的封装和解封装过程;
了解了功能分层和数据封装/解封装的核心工作原理之后,大家可能对TCP/IP网络数据传输的理解还是比较模糊,因为刚才说的这些都是偏理论的东西,理解起来不是那么直观;
接下来我使用电脑的网络浏览器访问http://airtest.openluat.com/,抓取一个完整的数据包封装实例,再来加深一下对功能分层和数据封装/解封装的理解:

接下来我们打开下面这张图片,实际分析一下http请求数据包

通过上面这个http请求的网络数据包分析,大家对 TCP/IP的功能分层以及数据封装/解封装 应该有了进一步的认识;
2.4 不同网络环境下的TCP/IP工作实例
讲到这里,不知道大家有没有发现,之前讲述的网络模型,网络数据包分析,我使用的都是极度简化后的网络拓扑结构,只有一个网络硬件单元或者两个网络硬件单元;
但是在现实世界中,网络拓扑结构不可能如此简单,从源设备到目标设备之间,可能要经过几十个网络硬件单元;
接下来,我分别以4G网络环境、WiFi网络环境和以太网网络环境为例,来说明一个实例:
为了方便理解,接下来的每个实例中都会给出一个总体的网络拓扑结构图,此处给出的网络拓扑结构图和之前的网络拓扑结构图相比,会更加详细,但是所给出的仍然是是简化后的网络拓扑结构图;
虽然也是简化后的拓扑图,但是这里所描述的网络拓扑结构图,对于我们理解整个网络系统的工作流程,也会提供很大的帮助;可以让我们从一个全局的视野,去理解TCP/IP是如何在整个网络系统中配合工作的;
2.4.1 4G网络环境下TCP/IP协议的工作实例
第一种实例:使用中国移动4G手机卡网络的手机访问托管在中国电信的服务器
我们打开下面这张图总体了解一下这个过程

第二种实例:使用中国移动4G手机卡网络的手机访问托管在中国移动的服务器
和 中国移动4G手机卡的手机访问托管在中国电信的服务器 相比,中国移动4G手机卡的手机访问托管在中国移动的服务器,不需要经过中国移动和边界路由器和中国电信边界路由器,在中国移动的地市、省干、国干核心路由器之间进行路由转发,如下图所示(差异部分已通过浅绿色背景进行区分):

2.4.2 WiFi网络环境下TCP/IP协议的工作实例
第一种实例:使用中国移动WIFI网络的手机访问托管在中国电信的服务器

第二种实例:使用中国移动WiFi网络的手机访问托管在中国移动的服务器
和 中国移动WiFi网络的手机访问托管在中国电信的服务器 相比,中国移动4G手机卡的手机访问托管在中国移动的服务器,不需要经过中国移动和边界路由器和中国电信边界路由器,在中国移动的地市、省干、国干核心路由器之间进行路由转发,如下图所示(差异部分已通过浅绿色背景进行区分):

2.4.3 以太网网络环境下TCP/IP协议的工作实例
第一种实例:使用中国移动以太网网络的电脑访问托管在中国电信的服务器

第二种实例:使用中国移动以太网网络的电脑访问托管在中国移动的服务器
和 中国移动WiFi网络的电脑访问托管在中国电信的服务器 相比,中国移动以太网的电脑访问托管在中国移动的服务器,不需要经过中国移动和边界路由器和中国电信边界路由器,在中国移动的地市、省干、国干核心路由器之间进行路由转发,如下图所示(差异部分已通过浅绿色背景进行区分):

三、LuatOS上的TCP/IP协议栈总体介绍
在总体了解了TCP/IP的核心理论知识,以及不同网络环境下的TCP/IP工作实例之后,大家对TCP/IP应该具备了一个总体的认识;
接下来我们一起看下LuatOS上对TCP/IP协议栈的支持情况,以及提供了哪些编程接口给LuatOS项目应用脚本来使用,参考下表:

四、TCP/UDP/SSL/证书以及socket概念
经过上一节的学习,大家对LuatOS上TCP/IP网络应用的总体支持情况,应该有了一个总体的认识;
因为我们本讲课程是要讲解LuatOS socket,所以从本节开始,我们重点学习一下和socket有关的理论知识和实践方法;
先简单地看一下socket的概念:
socket是TCP/IP提供的网络应用开发的编程接口,包含地址、端口、传输层/安全层协议 三部分;
地址比较好理解,可以是域名地址,也可以是ip地址,用来标识网络上的一台设备;
端口也比较好理解,在socket应用中,服务器端的端口一般都是服务器自定义的,用来标识自定义的应用;
传输层/安全层协议包括TCP/UDP/SSL/证书的知识,这一部分知识有点儿复杂,接下来我们重点看下TCP/UDP/SSL/证书的一些理论知识;
4.1 TCP/UDP
先来看一张TCP/UDP的对比表格:

TCP 提供了面向连接的、可靠的、支持流量控制的传输层服务;
UDP 提供了无连接的、不可靠的、不支持流量控制的传输层服务;
4.1.1 是否面向连接
TCP的"面向连接"不仅仅是一个形式,而是实现其所有可靠性保障的基础。它的作用可以概括为:为后续可靠、有序的数据传输做好必要的准备工作和管理工作。
具体来说,它的作用体现在以下两个关键阶段:
- 建立连接(三次握手)
在发送任何实际应用数据之前,客户端和服务器必须通过三次握手建立一个双向的通信通道;
过程:
SYN (Synchronize Sequence Numbers): 客户端发送一个SYN包(一个特殊的TCP报文)到服务器,并带上自己的初始序列号(Seq = x)。意思是:"你好,我想和你建立连接。我这边数据的起始编号是x。"
SYN-ACK: 服务器收到后,如果同意连接,会发送一个SYN-ACK包。其中包含:
ACK = x + 1: "你刚才发的序列号x我收到了,我期待你下一个数据包的序列号是x+1。"(这是对客户端SYN的确认)
同时,服务器也发送自己的初始序列号(Seq = y)。意思是:"我也同意建立连接。我这边数据的起始编号是y。"
ACK: 客户端收到服务器的SYN-ACK后,再发送一个ACK包(ACK = y + 1)给服务器。意思是:"你发的序列号y我也收到了,我期待你下一个数据包的序列号是y+1。"
抓包分析:
TCP/UDP web测试工具:创建TCP Server;
SSCOM:使用SSCOM提供的TCP Client工具; 或者 LLCOM:使用LLCOM提供的socket客户端工具(支持TLS)
网络抓包工具:wireshark,这个工具的用法,在这里就不讲了,大家如果有不懂的,上网自行学习;
Client连接Server之后,在wireshark中过滤搜索 ip.addr == XXX && tcp.port == YYY,XXX为服务器ip地址,YYY为服务器端口,例如ip.addr == 112.125.89.8 && tcp.port == 47082
下面我们对一个TCP连接成功的网络包进行分析:


下面我们对一个TCP连接失败的网络包进行分析,客户端去连接一个不存在的服务器:

作用:
交换初始序列号 (ISN): 这是最核心的作用。序列号是TCP实现可靠性(确认、重传、排序)的根基。双方通过握手告知对方自己将从哪个号码开始编号字节数据。
确认双方通信能力: 证明客户端和服务器之间的网络路径在两个方向上(Client->Server 和 Server->Client)都是通的。如果握手失败,说明根本没法通信,应用层会立刻得到错误信息,而不用等待数据传输超时。
分配资源: 在握手成功后,双方的操作系统都会为这个连接分配资源,如创建socket、分配发送和接收缓冲区等。如果连接无法建立,就避免了资源的浪费。
- 终止连接(四次挥手)
过程:
一方发送FIN包,表示"我数据发完了,要关闭我到你方向的连接"。
另一方ACK这个FIN包,表示"我知道你要关了"。
可能另一方向也还有数据要发送,等发完后,它再发送一个FIN包。
最初的一方再ACK这个FIN包。
抓包分析:

作用:
可靠地释放资源: 确保双方都知道通信即将结束,可以安全地释放为这个连接分配的所有内核资源(TCB、缓冲区等)。
保证数据完整性: 确保在连接关闭前,所有在途的数据包都已经被正确处理完毕。防止一方以为连接已断而另一方还在发送数据的情况。
而UDP呢?提供的是一个无连接的传输层服务,意思就是在正式发送应用数据之前,客户端和服务端没有三次握手连接,客户端和服务器端之间没有网络数据交互,如下图所示:

同理,因为就没有连接动作,所以UDP断开时,也没有四次挥手的断开连接动作;
4.1.2 是否可靠
TCP 提供的是一种可靠的传输层服务;TCP 通过多种机制确保数据准确无误地送达:
1、确认应答机制 (ACK): 接收方收到数据后,会发送一个确认报文(ACK)给发送方。
2、超时重传机制: 发送方在发送一个数据段后启动一个定时器。如果在规定时间内没有收到对应的ACK,它会重新发送该数据段。
3、序列号和确认号机制: 每个字节的数据都被分配一个序列号(Seq),ACK号则告诉发送方"我期望收到的下一个字节的序列号是多少"。这解决了数据包乱序和重复的问题。
4、校验和机制: 每个数据包都包含一个校验和。接收方会验证它,如果校验失败则丢弃该包,从而触发发送方的重传机制。


而UDP没有这些可靠的机制,我们看一下UDP发送数据的网络包:


和TCP相比,UDP数据发送出去之后,没有机制确认数据是否发送成功;在网络不稳定的情况下,会出现丢失数据的问题;
4.1.3 是否支持流量控制
TCP 提供的是一种支持流量控制的传输层服务;TCP 通过滑动窗口协议确保数据准确无误地送达:
使用滑动窗口协议来防止发送方发送数据过快,导致接收方缓冲区溢出。
接收方在ACK中会告知发送方自己当前剩余的缓冲区大小(窗口大小),发送方根据这个窗口大小调整发送速率。


下面的例子是一个出现0窗口的网络包,因为我这边没有环境复现这个问题,所以从网上找了一张图片,给大家看一下:

而UDP是没有ACK机制的,所以也没有窗口控制协议;
4.2 数字证书
4.2.1 数字证书是什么?
数字证书,是一种数字文件,其核心作用类似于现实世界中的身份证或营业执照。
它的根本目的是:将一个数字证书中的公钥与一个实体(个人、设备、组织、域名)的身份信息进行强绑定,并由一个受信任的第三方进行担保。
简单来说,数字证书就是证明"这个公钥确实属于某某某" 的电子凭证。
4.2.2 为什么需要数字证书?
要理解证书的价值,必须先理解它要解决的问题:中间人攻击。
当客户端(如浏览器)访问一个服务器时,如果一个攻击者拦截了通信,他可以将自己的数字证书(包含公钥)发送给客户端,并冒充服务器。
客户端用攻击者的公钥加密数据。
攻击者截获数据,用自己的私钥解密,窃取信息。
攻击者再用服务器的真实公钥加密数据,转发给服务器。整个过程神不知鬼不觉。
核心问题:如何确保你拿到的服务器的证书公钥,确实来自你真正想通信的一方,而不是攻击者?
数字证书就是答案。它由一个受信任的第三方机构(Certificate Authority, CA)对公钥和身份信息进行签名,任何信任该CA的人都可以验证这张证书的真伪,从而信任其中的公钥。
4.2.3 数字证书里包含什么?(X.509标准)
数字证书遵循X.509标准格式。它包含以下核心信息:
基本身份信息(证书主体)
版本号:使用的X.509版本(如v3)。
序列号:由CA分配的唯一标识符,用于吊销查询。
签名算法:CA签发此证书时使用的算法(如SHA256withRSA)。
颁发者:签发此证书的证书颁发机构(CA) 的名称。
有效期:证书有效的起止日期和时间。证书在此时间窗口外无效。
主题:证书持有者的身份信息。最重要的是通用名称(Common Name, CN),通常是域名。
核心密码学材料
公钥本身(一串很长的数字)。
该公钥所使用的算法(如RSA 2048位、ECC)。
信任的证明(CA的签名)
数字签名:这是最关键的部分。首先对上述所有信息(除了签名本身)计算一个哈希摘要,然后对这个摘要使用CA自己的私钥进行加密,得到的结果就是数字签名。
我们现在看下docs网站的数字证书:


证书文件的每个字段,我们来对照说明一下;
4.2.4 数字证书的工作原理:信任链(Chain of Trust)
数字证书的信任并非凭空产生,它依赖于一个层级化的信任模型,称为公钥基础设施(Public Key Infrastructure, PKI)。
4.2.4.1 信任链的组成:
1、根证书(Root CA Certificate)
2、位于信任链顶端的、自签名的证书。
3、它们的公钥被预先内置在操作系统(如Windows、macOS)和浏览器(如Chrome、Firefox)的信任根证书存储区中。这是所有信任的起点。
4、根CA的私钥被极其严格地离线保管,几乎不直接用于签发服务器证书。
5、中间证书
6、由根CA签发,用于代表根CA去实际签发最终的用户证书。
7、这种层级结构增加了安全性:即使中间CA的私钥被泄露,可以迅速吊销该中间证书,而根CA依然安全,无需重建整个信任体系。
8、一个服务器证书通常伴随着一个或多个中间证书。
9、终端实体证书
10、也就是我们通常为网站、服务申请和使用的证书。
如下图所示,服务器使用就是一个三层证书结构:

4.2.4.2 证书验证过程:
当浏览器收到网站的证书时,它会执行以下验证步骤:
- 验证基本信息:检查证书的有效期是否当前时间在内,检查证书的"主题"或"SAN"字段是否包含正

2. 验证签名(构建信任链):
浏览器使用中间证书A里的公钥,去解密网站证书的数字签名,得到一个哈希值H1。
浏览器对网站证书的内容(除签名外)用相同的哈希算法计算,得到另一个哈希值H2。
如果H1 == H2,则说明:
网站证书的内容完整,未被篡改。
该证书确实是由中间证书A对应的CA签发的(因为只有用中间CA的私钥才能生成能用其公钥解开的签名)。

3. 递归验证:
浏览器接着发现,它可能不直接信任中间证书A。于是它继续向上验证:使用根证书的公钥,去验证中间证书A的签名。
因为根证书的公钥已经内置在浏览器的信任存储中,且是自签名的,验证到此结束。

**4. 吊销检查:**浏览器还会通过证书吊销列表(CRL) 或在线证书状态协议(OCSP) 查询该证书是否已被CA提前吊销(例如因为私钥泄露)。
**5. 最终信任:**所有步骤都通过后,浏览器才最终信任该网站证书,并使用证书中的公钥与服务器进行加密通信;
数字证书的知识,理解起来可能有点儿复杂,即使大家不了解关系也不大,最终在LuatOS socket编程中,如果用到证书,使用起来会非常简单,在下文中我们会重点说明如何使用;
4.3 SSL/TLS
4.3.1 什么是 SSL/TLS?
SSL(Secure Sockets Layer,安全套接层) 和 TLS(Transport Layer Security,传输层安全) 是加密协议,旨在为计算机网络通信提供安全性和数据完整性。它们的核心目标是建立一个安全的通信通道,防止窃听、篡改和消息伪造。
关系: TLS 是 SSL 的继任者。由于历史原因,两者名称经常混用,但现在所有现代应用都使用 TLS。SSL 3.0 及其早期版本已被发现存在严重漏洞(如 POODLE 攻击)并被完全废弃。现在当我们说"SSL"时,通常指的是 TLS。
主要目标:
1、加密(Encryption): 混淆传输的数据,防止第三方窃听。
2、认证(Authentication): 验证通信方的身份(通常是验证服务器的身份,也可选择验证客户端身份),防止中间人攻击。
3、完整性(Integrity): 检测数据在传输过程中是否被篡改或损坏。
4.3.2 为什么需要 SSL/TLS?
在没有 SSL/TLS 的情况下,网络通信(如 HTTP、FTP、SMTP)以纯文本形式传输。这意味着在你和服务器之间的任何节点(如你的路由器、ISP、公共Wi-Fi热点)都可以看到你发送的所有内容,包括密码、信用卡号、私人消息等。SSL/TLS 通过在传输层之上建立一个加密层来解决这个问题,从而保护上层应用协议(如 HTTP→HTTPS)。
例如,如果一个TCP Client连接上一个TCP Server之后,如果TCP Client给Server发送数据LuatOS,在整个网络传输过程中的任何节点(局域网的交换机和路由器,基站,服务网关,数据网关,核心路由器,边界路由器)都能看到明文数据LuatOS,如下图所示:

如果在TCP之上支持了TLS,在传输数据之前,Client和Server会动态协商一个密钥,密钥协商成功后,后续发送方传输的应用数据都会通过密钥加密,然后再传输到接收方,接收方使用密钥进行解密;只有发送端和接收端的应用程序才能加密数据以及解密为原始明文数据,在网络传输过程中,网络中间节点因为不知道密钥,即使截获了数据,因为没有密钥,也无法解密数据,如下图所示:


4.3.3 SSL/TLS 如何工作?------ 握手过程详解
SSL/TLS 的核心是其握手过程。这个过程在双方开始传输实际应用数据之前进行,目的是协商连接参数、验证身份并生成会话密钥。以下是TLS 1.2 握手流程:
整个过程的目标是在客户端和服务器之间安全地协商出两样东西:
1、会话密钥(Session Keys):用于后续通信使用的对称加密密钥。
2、加密参数:使用哪种对称加密算法(如 AES)、哪种哈希算法(如 SHA256)等。
整个握手过程在 TCP 连接建立之后进行,可以概括为以下四个主要阶段和 10 个步骤:

第一阶段:Hello 消息交换(协商安全参数)
步骤 1: Client Hello
客户端向服务器发起握手,发送一个 Client Hello 消息,包含以下关键信息:
客户端随机数(Client Random):一个由客户端生成的 32 字节随机数,用于后续生成主密钥。
支持的协议版本:例如 TLS 1.2。
支持的密码套件列表(Cipher Suites):客户端支持的所有加密算法组合的列表,按优先级排序。例如 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。

步骤 2: Server Hello
服务器响应客户端的 Hello 消息,发送一个 Server Hello 消息,包含以下关键信息:
服务器随机数(Server Random):一个由服务器生成的 32 字节随机数,同样用于生成主密钥。
选择的协议版本:通常是双方都支持的最高版本,例如 TLS 1.2。
选择的密码套件:从客户端提供的列表中选出一个服务器也支持的套件,例如 TLS_RSA_WITH_AES_128_CBC_SHA。
会话ID(Session ID):服务器为本次会话生成的一个唯一ID,用于后续会话恢复。
选择的扩展。
至此,双方就使用哪种加密算法进行通信达成了一致。

第二阶段:服务器认证与密钥交换(Server Key Exchange)
步骤 3: Server Certificate
服务器将自己的数字证书发送给客户端。这个证书包含了服务器的公钥、域名、颁发机构(CA)等信息,并由CA的私钥签名。客户端会验证该证书的有效性(是否过期、域名是否匹配、签发者是否可信等),以确认正在通信的确实是目标服务器,而不是中间人。
步骤 4: Server Key Exchange Message(可选)
注意:此步骤仅在选择的密码套件不是 RSA 密钥交换时才需要(例如使用 DHE 或 ECDHE 时)。如果使用 RSA 套件(如本例),服务器会跳过这一步。 对于 DHE/ECDHE 套件,服务器会在此消息中发送其临时密钥交换参数(如迪菲-赫尔曼参数),并用证书对应的私钥签名,以供客户端验证。
步骤 5: Server Hello Done
服务器发送一个 Server Hello Done 消息,表示它已经将所有初始协商信息发送完毕。

第三阶段:客户端认证与密钥交换(Client Key Exchange)
步骤 6: Client Certificate(可选)
注意:此步骤仅在服务器要求对客户端进行认证(例如银行系统、API网关等场景)时才发生。绝大多数 HTTPS 网站访问不需要这一步。 如果需要,服务器会在之前发送 Certificate Request 消息(本例未列出),然后客户端在此步骤发送自己的证书。
步骤 7: Client Key Exchange Message
这是客户端密钥交换的核心步骤。客户端会生成一个 Pre-Master Secret(预主密钥),这是一个 48 字节的随机数。
对于 RSA 套件:客户端会用步骤 3 中收到的服务器证书里的公钥加密这个 Pre-Master Secret,然后将加密后的结果发送给服务器。
对于 DHE/ECDHE 套件:客户端会基于服务器的参数,计算并发送自己的临时密钥交换参数。
至此,只有拥有对应私钥的服务器才能解密出 Pre-Master Secret。

步骤 8: Certificate Verify(可选)
如果客户端发送了证书(步骤6),它还需要用其私钥对之前所有握手消息的哈希值进行签名,并发送给服务器。服务器用客户端证书中的公钥验证这个签名,以证明客户端确实拥有该证书对应的私钥,完成了客户端认证。
第四阶段:完成握手(Switch to Encryption)
此时,客户端和服务器已经拥有了生成会话密钥的所有材料:
Client Random
Server Random
Pre-Master Secret
双方使用相同的伪随机函数(PRF),根据这三个参数独立计算出相同的:
Master Secret(主密钥)
最终用于加密和完整性验证的会话密钥块(Key Block),其中包含:
客户端到服务器的对称加密密钥
服务器到客户端的对称加密密钥
客户端到服务器的MAC验证密钥
服务器到客户端的MAC验证密钥
步骤 9: Change Cipher Spec
客户端发送一个 Change Cipher Spec 消息。这是一个简单的信号,通知服务器:"从现在开始,我发送的所有消息都将使用我们刚刚协商好的加密算法和密钥进行加密。"
紧接着,客户端发送 Finished 消息。这条消息本身已经是加密状态,它包含之前所有握手消息的哈希值的MAC(消息认证码)。服务器收到后,会解密并验证这个MAC。如果验证成功,说明:
1、客户端计算出的密钥与服务器的一致。
2、握手过程没有被篡改。

步骤 10: Change Cipher Spec & Finished
服务器同样发送 Change Cipher Spec 消息,通知客户端:"我也准备好了,后续消息将加密。"
紧接着,服务器发送加密后的 Finished 消息。客户端同样进行解密和验证。

握手完成
至此,TLS 1.2 握手全部完成。一个安全的加密通道已经建立。之后所有的应用数据(HTTP请求/响应等)都将使用协商出的对称会话密钥进行加密和完整性保护。
4.3.4 TLS和TCP/UDP是什么关系?
TLS 运行在 TCP 之上,为基于 TCP 的应用提供安全;
而 TLS 本身不能直接运行在 UDP 之上,但有其对应的安全版本 DTLS;DTLS应用没有TLS广泛,所以本讲课程不再讲述DTLS,大家有兴趣可以自行上网学习了解;
我们再来看一下这张OSI七层模型 和 TCP/IP五层模型表格:

从这张表可以清晰地看到:
TCP/UDP 属于传输层。它们的主要职责是建立主机到主机的连接,解决数据如何可靠(TCP)或高效(UDP)地送达的问题。
TLS 大致对应于表示层。它不关心数据如何送达,它的职责是解决数据在送达后内容是否安全的问题,即加密、身份认证和完整性验证。
一个非常恰当的比喻:
TCP 像是一个可靠的邮政服务。它确保你的信件(数据包)不丢失、不损坏、按顺序投递。
TLS 则像是给这封信件加上了一个防弹、防窥视、防篡改的保险箱。邮政服务(TCP)只负责运送保险箱,并不关心也不知晓保险箱里装的是什么。只有拥有密钥的收件人才能打开保险箱读取里面的原始信件。
因此,TLS 强烈依赖于 TCP 的可靠性。TLS 握手本身包含多条消息交换,需要严格按顺序到达,任何丢包或乱序都会导致握手失败,而这正是 TCP 所擅长的。
4.4 socket概念
在了解了TCP/UDP/TLS/证书知识之后,大家可能觉得这些内容太复杂了,如果在编程过程中,直接操作这么复杂的接口,简直是一个灾难!
这时socket就出现了,socket的中文名称是套接字;
socket是TCP/IP提供的网络应用开发的编程接口,包含地址、端口、传输层/安全层协议 三部分;
socket 是网络应用程序与网络协议栈(TCP/IP栈)之间的桥梁,用户可以直接使用socket api,而socket内部设计会根据用户的api配置选择对应的传输层和安全层协议,可以大大简化网络编程工作量。
LuatOS上也支持socket编程接口,提供了socket核心库和libnet扩展库两个库文件;
接下来我们重点看下这两个库的使用方法;
五、LuatOS上的socket核心库
5.1 常量详解
核心库常量,顾名思义是由 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
5.1.1 网络适配器编号:
注意:
-
网络适配器,它的一个更广为人知的名字是------网卡,后续我们会使用网卡和网络适配器这两种名称,大家只要知道这两种名称表示同一个概念就行了;
-
LuatOS上的网络适配器和电脑上的网络适配器的作用是完全一样的,我们先来看一张电脑上的网络适配器图片

在上面这张图片中,有WiFi网卡、标准的物理以太网卡、虚拟的USB RNDIS以太网卡三种网卡;
- 从下面这张TCP/IP五层模型图中,我们再进一步理解下网卡这个概念

网卡主要实现的是 物理层 和 数据链路层 的功能;
物理层的主要功能包括:
信号转换:将数字比特流(0和1)和 能够在物理传输介质(网线、光纤、无线电波)上传输的物理信号(如电信号、光信号、无线电波) 之间转换;
信号发送与接收:通过接口(RJ-45水晶头、天线)向传输介质上发送和接收这些物理信号;
数据链路层的主要功能包括
MAC地址控制:
使用唯一的MAC地址或者RNTI标识(4G网络中使用)来标识网卡,并处理帧的寻址问题。网卡会检查所有流过网络的帧,只接收目标MAC地址与自己地址匹配或为广播地址的帧,丢弃其他所有帧;
数据帧的封装与解封装:
发送时,将从上层(网络层)接收到的数据包,加上帧头(包含目标MAC地址、源MAC地址等信息)和帧尾封装成帧;
接收时,检查接收到的帧,去掉帧头和帧尾,将里面的数据包提取出来交给上层的网络层处理;
- 可以这样理解,从本质上说,网卡建立了一条支持发送和接收的双向通信的数据链路网络承载,网络承载类型可以是以太网,WiFi网络,4G网络等;
对于应用层,传输层,网络层来说,不需要关心下面的网络承载类型,一旦网络承载建立之后,可以使用DHCP应用层协议动态获取ip地址或者直接配置静态ip地址,ip地址成功获取之后,整个网络环境就算准备就绪,就可以使用应用层协议的接口或者使用socket接口开发具体的网络应用了;
-
LuatOS上支持4G,WiFi,以太网,自定义虚拟等多种类型的网卡;目前Air6101/Air8101系列默认使用的是WiFi网卡,默认编号为 socket.LWIP_STA;其他Air8000/Air780系列等默认使用的是4G网卡,默认编号为socket.LWIP_GP;
-
LuatOS上只需要直接使用默认的网卡,或者根据自己的需求调用API配置使用的某一种或者多种网卡即可,至于数据链路网络承载的建立,ip地址的分配,完全由核心库或者扩展库自动实现,使用起来非常简单;
-
下面所列举的网卡编号常量,仅仅是一个编号,关于这个编号的理解,分为以下两种情况:
第一种是,AirXXXX设备(Air8000/Air780/Air6101系列)内自带的网卡,这种网卡的编号是固定的,不允许配置修改;例如socket.LWIP_GP的编号为1,从LWIP_GP的字面意思来看,这个是蜂窝数据网络网卡,所以使用蜂窝数据网络(例如4G网络)上网时,LuatOS内核固件中默认就使用了这个编号,而且LuatOS内核固件没有开放接口允许把蜂窝数据网络的网卡配置为其他编号;这种类型的网卡编号有socket.LWIP_GP,socket.LWIP_STA,socket.LWIP_AP;
第二种是,AirXXXX设备(Air8000/Air780/Air6101系列)需要外挂的网卡,这种网卡的编号,可以使用我们推荐的编号值,也可以使用自定义类型的编号值;例如通过SPI外挂CH390以太网卡时,这种网卡的编号可以使用推荐的socket.LWIP_ETH,也可以使用自定义的socket.LWIP_USER0/1/2/3/4/5/6/7中的任何一个;如果通过SPI外挂了5块CH390以太网卡,这5块以太网卡的编号可以从socket.LWIP_ETH、socket.LWIP_USER0/1/2/3/4/5/6/7,这9个网卡编号中选择任意5个;
socket.LWIP_GP

socket.LWIP_STA

socket.LWIP_AP

socket.LWIP_ETH

socket.ETH0

socket.USB

socket.LWIP_USER0

socket.LWIP_USER1

socket.LWIP_USER2

socket.LWIP_USER3

socket.LWIP_USER4

socket.LWIP_USER5

socket.LWIP_USER6

socket.LWIP_USER7

5.1.2 网络事件:
"IP_RAEDY"
常量含义
表示某一个网络适配器编号对应的网卡准备就绪,string类型; 此处的"准备就绪"是指:AirXXX硬件(例如Air780EPM)的某一个网卡在狭义或者广义的内网内,和路由设备之间双向通信的数据链路已经建立,并且已经获取到了本地的ip地址; 4G网络环境下,是指AirXXX硬件和4G运营商的分组数据网关之间的数据链路已经建立,并且分组数据网关已经给AirXXX硬件分配了运营商内部的私有ip地址;
如下图红框所示(手机相当于AirXXX硬件,例如Air780EPM):

一般来说,只要使用的sim卡没有欠费,AirXXX硬件开机之后,3秒左右就可以产生4G网卡的"IP_READY"消息;
以太网网络环境下,是指AirXXX硬件(例如Air8000)通过网线和内网内的路由器之间的数据链路已经建立,并且路由器已经给AirXXX硬件分配了内网内部的内网ip地址或者AirXXX硬件主动设置了内网静态ip地址;
如下图红框所示(电脑相当于AirXXX硬件,例如Air8000):

一般来说,只要通过网线和路由器之间的连接正常,2秒左右就可以产生以太网网卡的"IP_READY"消息; WiFi网络环境下,是指AirXXX硬件(例如Air6101)通过WiFi连接上了内网的无线路由器,并且路由器已经给AirXXX硬件分配了内网的ip地址或者AirXXX硬件主动设置了内网静态ip地址;
如下图红框所示(手机相当于AirXXX硬件,例如Air6101);

一般来说,只要输入正确的密码去连接WiFi路由器,2秒左右就可以产生WiFi网卡的"IP_READY"消息;

发布并且处理"IP_RAEDY"消息的完整周期如下:
1、初始化配置某一种网卡(4G网卡不需要主动配置,WiFi和以太网需要调用exnetif的接口主动配置);
2、配置成功之后,LuatOS内核固件会自动执行数据链路的建立以及ip地址的获取动作;
3、准备就绪后,LuatOS内核固件中会执行sys.publish("IP_RAEDY", ip, adapter)发布全局消息; "IP_RAEDY"为消息名称,string类型; ip为本地ip地址,string类型; adapter为网卡编号,number类型,和本文5.1.1章节所描述的网络适配器编号常量对应;
4、LuatOS应用脚本中通过sys.subscribe或者sys.waitUntil订阅处理"IP_RAEDY"这个消息; 如果使用sys.subscribe,要在第1步之前就要订阅,否则会有遗漏"IP_RAEDY"消息的风险; 如果使用sys.waitUntil,在sys.waitUntil之前使用socket.adapter接口主动查询下网卡状态, 如果查询结果是已经准备就绪,则不再需要sys.waitUntil,否则再使用sys.waitUntil;
对某一种网卡来说,以下两种情况会产生"IP_READY"消息:
1、初始化配置网卡之后,网卡准备就绪后,会产生"IP_READY"消息;
2、如果网卡发生掉线产生了"IP_LOSE"消息,再次准备就绪后,会产生"IP_READY"消息;
再来看另外一个问题,如果某个网卡产生了"IP_READY"消息,是不是意味着使用这个网卡,肯定可以正常访问外网了?这个是不一定的,以下图中的WiFi网络为例来说明一下:

如果无线路由器已经正常上电,处于运行状态,但是路由器并没有连接外网(例如和ISP接入网的CGANT设备之间的光纤没有连接);
这种状态下,红框内的数据链路可以正常建立,并且无线路由器也可以给手机(类似于Air8000)分配内网ip地址,Air8000就能产生"IP_READY"消息,但是Air8000并不能访问外网,因为无线路由器无法访问外网;
同理,4G网络环境下,以太网环境下的"IP_READY"消息都是表示一个广义或者狭义的内网网络环境准备就绪,能否访问外网,取决于内网到外网以及整个外网的网络环境是否正常;
也就是说:
没有"IP_READY"是不能访问外网的;
有"IP_READY",大概率是可以访问外网的,但是也存在一些极低极低的概率不能访问外网;
即使如此,用户在编程时,还是要以"IP_READY"来作为判断可以连接外网的第一个条件,然后在应用层,使用socket,http,mqtt等网络应用接口去实现自己的业务逻辑,如果socket,http,mqtt连接不成功,就有可能是外网不通,遇到这种情况,在socket,http,mqtt执行重连就行了,用户不用关系链路层的异常的原因,只关于自己的应用层异常应该怎么处理就行了;
应用层的网络业务的核心代码逻辑如下所示:

使用示例
使用sys.subscribe处理的方式:

使用sys.waitUntil处理的方式:

"IP_LOSE"
常量含义
表示某一个网络适配器编号对应的网卡从正常状态下发生掉线,string类型;
发生掉线的原因包含但不仅仅包含以下几种:
1、4G网络环境下,sim卡不识别、sim卡欠费、没有4G网络信号等;
2、WiFi网络环境下,WiFi路由器关闭、WiFi路由器做了限制、离开WiFi路由器有效信号范围等;
3、以太网网络环境下,以太网路由器关闭、以太网路由器做了限制、网线断开等;
发生掉线后,LuatOS内核固件中会执行sys.publish("IP_LOSE", adapter)发布全局消息; "IP_LOSE"为消息名称,string类型; adapter为网卡编号,number类型,和本文5.1.1章节所描述的网络适配器编号常量对应;
使用示例
一般情况下,推荐使用sys.subscribe处理的方式来监控"IP_LOSE"消息,监控到这个消息之后,可以根据自己的项目需求走一些具体项目业务逻辑的显示,例如通知已断网,但是不需要手动写代码再去控制网卡重试连网,因为在内核固件中,会自动重试连网动作;

socket.LINK(了解即可)
关于这个常量,了解即可,LuatOS项目应用脚本程序中不推荐使用


socket.ON_LINE(了解即可)
关于这个常量,了解即可,LuatOS项目应用脚本程序中不推荐使用

socket.EVENT

socket.TX_OK(了解即可)
关于这个常量,了解即可,LuatOS项目应用脚本程序中不推荐使用

socket.CLOSED(了解即可)
关于这个常量,了解即可,LuatOS项目应用脚本程序中不推荐使用

5.2 函数详解
socket核心库提供的api比较多,有一部分api是异步操作,使用起来比较复杂,已经不推荐使用,在此处就不再讲解;如果大家还需要了解这些已经不推荐的异步api的使用方法
这些不推荐的异步api,在libnet扩展库中已经被封装成了同步api,使用起来更加方便,在下一章节,我们会详细讲解这些libnet扩展库中的同步api接口;
除此之外,socket核心库还有一些同步api接口,这些api主要涉及到网卡操作,socket的创建、配置、读取、销毁等功能,本章节,我们先详细学习一下这些同步api接口;
socket.adapter(adapter_id)
功能
查看网卡的联网状态
参数
adapter_id

返回值
local is_ready, index = socket.adapter(adapter_id)
is_ready

index

示例

socket.dft(adapter_id)
功能
读取或者设置当前使用的默认网卡;
默认网卡的作用如下:
创建socket,mqtt,http等网络应用对象时,可以通过参数主动指定要使用的网卡;
如果没有制定,就会使用系统中的默认网卡;
例如:
socket.create(socket.LWIP_GP, "tcp_task") 表示使用socket.LWIP_GP网卡创建一个socket对象,后续这个socket对象的所有数据通信都会基于socket.LWIP_GP网卡进行;
socket.create(nil, "tcp_task") 表示使用当前时刻的默认网卡创建一个socket对象,后续这个socket对象的所有数据通信都会基于当前时刻的默认网卡进行;
socket.create(nil, "tcp_task")等价于socket.create(socket.dft(), "tcp_task");
默认网卡的设置有以下三种方式:
1、LuatOS内核固件运行起来之后,已经初始化设置了默认网卡;目前Air8000/Air780系列等默认使用的是4G网卡,编号为socket.LWIP_GP;Air6101/Air8101系列默认使用的是WiFi设备模式网卡,编号为 socket.LWIP_STA;
2、使用扩展库exnetif中的接口exnetif.set_priority_order配置单网卡或者多网卡时,set_priority_order接口内部会根据配置自动设置默认网卡;
3、LuatOS项目的用户应用脚本运行起来之后,在脚本中可以调用接口socket.dft(adapter_id)主动设置新的默认网卡adapter_id;
在这三种设置默认网卡的方式中:
第1种设置方式,用户无法参与设置;
第2种设置方式,用户可以参与设置,如果需要更改设置默认网卡,推荐使用这一种方式;这样设置有两个好处:
1、exnetif扩展库已经高度集成,使用这个扩展库提供的接口,无论是单网卡设置还是多网卡设置,使用起来非常方便;
2、默认网卡设置和具体的socket,mqtt,http网络应用解耦,用户只需要在初始化时配置一次,所有的网络应用中不要再指定具体的网卡,就可以使用初始化配置的优先级使用默认网卡;
我们结合实际代码看一下exnetif扩展库接口的使用方式;
第3种设置方式,用户可以参与设置,如果需要更改设置默认网卡,不推荐使用这一种方式;因为和第2种方式相比,使用起来会复杂;
参数
adapter_id

返回值
查询默认网卡时:
local adapter_id, last_reg_adapter_id = socket.dft()
adapter_id

last_reg_adapter_id

设置默认网卡时:
local new_adapter_id, last_reg_adapter_id = socket.dft(adapter_id)
new_adapter_id

last_reg_adapter_id

示例
不推荐直接使用socket.dft来设置默认网卡,所有此处不再列举设置功能的示例,仅列举查询功能的示例

socket.localIP(adapter_id)
功能
获取某个网卡上的本地 ip地址、网络掩码和网关地址;
只有网卡准备就绪后,才能读取到这三个值;
参数
adapter_id

返回值
local ip, netmask, gateway = socket.localIP(adapter_id)
有三个返回值 ip, netmask, gateway
192.168.31.156 255.255.255.0 192.168.31.1 nil
ip

netmask

gateway

示例

socket.setDNS(adapter_id, dns_index, ip)
功能
设置某个网卡使用的DNS服务器IP地址;
内核固件中有4个位置可以存储DNS服务器IP地址;
对应的位置索引为本接口的dns_index参数,取值范围为1到4;
所以最多同时存在4个DNS服务器IP地址;
DNS服务器IP地址有两大类:
1、第一类是:网卡初始化过程中,自动从网络运营商获取到默认的DNS服务器IP地址;
这一类DNS服务器IP地址通常由2个或者1个,这一类DNS服务器IP地址存储在dns_index为3和4的位置;
2、第二类是:在应用脚本程序中调用socket.setDNS接口手动设置的自定义的DNS服务器IP地址;例如:
socket.setDNS(socket.LWIP_GP, 1, "223.5.5.5")表示为4G网卡在位置1设置自定义的DNS服务器IP地址"223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
socket.setDNS(socket.LWIP_GP, 2, "114.114.114.114")表示为4G网卡在位置2设置自定义的DNS服务器IP地址"114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
DNS解析服务的逻辑是:
遍历位置1到4的DNS服务器IP地址;
如果当前位置存在DNS服务器IP地址,则解析域名,每个DNS服务器IP地址尝试3次,每次超时分别是1秒钟、2秒钟、3秒钟;
如果当前位置不存在DNS服务器IP地址,则直接跳过;
也就是说,如果4个位置上都有DNS服务器IP地址,则最多尝试12次,最长超时24秒可以返回解析结果;
为了防止出现默认的DNS服务器IP地址 解析服务器不稳定的问题,把位置3和4用来存储默认的DNS服务器IP地址,同时,可以在位置1和2设置为自定义的DNS服务器IP地址,如下所示,为了方便用户参考使用,我们在每个网络应用demo中,已经为所有使用到的网卡增加了如下配置:

参数
adapter_id

dns_index

ip

返回值
local result = socket.setDNS(adapter_id, dns_index, ip)
result

示例

socket.sslLog(log_level)
功能
设置内核固件中ssl功能的log等级;
一般来说,当需要抓取更多的日志给我们技术人员分析时,可以使用此接口,设置输出更多日志信息的等级;
如果打开debug开关,使用Luatools抓日志时,在主界面窗口会出现类似于下面的日志信息(出现很多mbedtls开头的日志):

参数
log_level

返回值
nil
示例

socket.create(adapter_id, task_name)
功能
在指定网卡上创建一个socket对象;
在ram资源足够的情况下:
1、Air780系列/Air8000系列的模组,允许创建的同时存在的socket对象数量为64个;
2、Air6101系列/Air8101系列的模组,允许创建的同时存在的socket对象数量为32个;
参数
adapter_id

task_name

返回值
local socket_ctrl = socket.create(nil, "socket_client_task")
socket_ctrl



socket.debug(socket_ctrl, onoff)
功能
配置是否打开内核固件中network的debug日志信息;
一般来说,当需要抓取更多的日志给我们技术人员分析时,可以打开此开关;
如果打开debug开关,使用Luatools抓日志时,在主界面窗口会出现类似于下面的日志信息(出现很多network开头的日志):

参数
socket_ctrl

onoff

返回值
nil
示例

socket.config(socket_ctrl, local_port, is_udp, is_tls, keep_idle, keep_interval, keep_cnt, server_cert, client_cert, client_key, client_password)
功能
配置socket对象的参数(本地端口号,TCP/UDP/TLS协议,TCP keep alive心跳参数,证书认证信息)
本地端口号,TCP/UDP/TLS协议,证书认证信息,这三部分的知识,我们在理论知识章节,已经介绍过,此处就不再重复介绍了
TCP keep alive心跳参数,之前还没有介绍过,我们先看一下TCP keep alive心跳;
TCP Keepalive 是一种用于检测 TCP 连接另一端是否仍然存活和可达的机制。它通过在一条空闲的连接上定期发送特殊的探测报文(Keepalive 探针)来实现;
例如客户端和服务器建立了 TCP 连接之后,双方再也没有应用数据要发送。此时,连接处于空闲状态;
但是客户端又想一直维持这个连接,否则有可能空闲超时被网络服务商给断开(例如4G网络服务商发现某个连接长时间没有数据通信,可能是几分钟,也能更长时间,时间不确定,就会主动的断开这个连接);
TCP Keepalive机制可以定时发送Keepalive 探针报文,防止连接空闲时间太长,导致连接被异常断开;
在TCP协议的规范中,TCP Keepalive机制的数据交互过程受三个核心参数控制:
keep_idle(默认值7200秒,也就是2小时):连接空闲多长时间后,开始发送第一个 Keepalive 探针报文;
keep_interval(默认值75秒):发送第一个探针后,如果没收到ACK回复,间隔多久再发送下一个探针;
keep_cnt(默认值9次):总共发送多少次探针后,如果依然没有回复,则判定连接已断开;
假设这三个参数都使用默认值,TCP Keepalive机制的工作流程如下:
连接建立后,空闲了 7200 秒(2小时);
客户端 发送第一个 Keepalive 探针报文到服务器;
可能出现三种结果:
成功:服务器回复 ACK,客户端知道和服务器之间的连接还存在;定时器重置,再等待2小时的空闲时间;
超时:客户端等待 75 秒,没收到ACK,则重试发送第二个探针;
错误:服务器回复异常报文,并且断开连接;
如果客户端连续发送9个探针(每两个探针之间间隔75秒)后都超时无任何响应,客户端就判断连接已断开;
连接已经断开的情况下,从开始探测到最终检测到已经断开的结果,最长需要: 7200 + 75 * 9 ≈ 7875 秒,约 2 小时 11 分钟;
而4G网络环境下,最长空闲十几分钟,大概率就被网络服务商给异常中断了,所以说,这三个核心参数如果使用默认值,TCP keepalive心跳没有任何作用,根本起不到保持长连接的作用;
所以我们在LuatOS的socket核心库中,提供了一个接口socket.config,可以配置这三个参数的值,让TCP keepalive真正能够起到保持长连接的作用; 例如keep_idle可以设置为300秒,keep_interval可以设置为10秒,keep_cnt可以设置为3次;
如果使用了TCP keepalive心跳机制,并且三个核心参数配置合适,就可以不再使用应用心跳机制来保持长连接;
如果项目中定义了应用心跳机制,并且应用心跳间隔合适,就可以不启用TCP keepalive机制,keep_idle参数为空或者传入nil即可;
参数
socket_ctrl

local_port

is_udp

is_tls

keep_idle

keep_interval

keep_cnt

server_cert

client_cert

client_key

client_password

返回值
local result = socket.config(socket_ctrl, local_port, is_udp, is_tls, keep_idle, keep_interval, keep_cnt, server_cert, client_cert, client_key, client_password)
result

示例
配置为普通的tcp socket client

配置为不做证书校验的tcp ssl socket client

配置为单向证书校验的tcp ssl socket client

配置为普通的udp socket client

socket.rx(socket_ctrl, buff, flag, limit)
功能
接收对端发送过来的数据,注意数据已经缓存在内核固件底层,使用本函数只是读取出来;
TCP模式下,对端发送过来的一包应用数据长度,没有特别限制;
UDP模式下,对端发送过来的一包应用数据长度,不要超过1460字节数据;
对于tcp来说,数据已经缓存到内核固件底层的数据接收缓冲区,这个数据接收缓冲区就是滑动窗口,滑动窗口的理论知识
LuatOS tcp socket本地的窗口初始大小为8040字节,在接收数据过程中,会将窗口的动态大小通知给发送方,发送方必须遵守这个窗口的限制;
例如 tcp client和server建立连接后,server应用层输入了34560字节的数据要发送过来,点击发送按钮,在tcp层传输数据时,server会根据client这边的动态窗口大小,动态调整发送数据的速度;先看下Luatools抓取到的Air8000的应用日志:

一共收到34560字节的数据,没有丢失;
我们再看看这个数据接收过程中网络数据交互,滑动窗口机制是如何工作的;
下面这张截图是从Air8000的运行日志中导出的网络数据包:

所以说,tcp socket,不用关心内核固件底层的接收缓冲区有多大,也不用关心对端发送过来的数据有多少,tcp的滑动窗口机制、序列号和确认号机制、超时重传机制,保证了数据接收的完整性;
在编程时,唯一需要注意的就是,当内核固件接收到数据时,在应用脚本中及时读出来数据即可,这样内核固件的窗口大小就会动态变化;
对于udp来说,虽然udp协议规范应用数据一包的最大长度为65535字节,但是由于udp数据传输不可靠,另外收到大数据时,LuatOS内核分配连续的内存空间也存在失败的概率,双重失败概率叠加,会导致udp大数据接收存在失败的概率增高;
从传输可靠性说,整个udp应用数据包含在单个mtu内最佳,最常见的mtu是1500字节,减去网络层和传输层的头部,剩余1460字节的长度给应用数据,所以所以LuatOS上的udp socket,限制可以接收的一包udp应用数据长度为1460字节;这是一个推荐值,目前实际使用时,可能可以接收到更长的数据,但是为了可靠性,尽量将一包应用数据的长度控制在1460字节;
参数
socket_ctrl

buff

flag

limit

返回值
local succ, data_len, remote_ip, remote_port = socket.rx(socket_ctrl, buff, flag, limit)
succ

data_len

remote_ip

remote_port

示例

socket.state(socket_ctrl)
功能
获取 socket 当前状态;
参数
socket_ctrl

返回值
local state, str = socket.state(socket_ctrl)
state

str

示例

socket.release(socket_ctrl)
功能
主动释放掉 socket对象,此接口和socket.create接口是一对逆操作;
使用socket.release之后,如果要发起重连,需要重新从socket.create开始;
参数
socket_ctrl

返回值
nil
示例

六、LuatOS上的libnet扩展库
刚才我们已经在上一章节学习了socket核心库中的同步api接口,这些api主要涉及到网卡操作,socket的创建、配置、读取、销毁等功能;
在本章节,我们先详细学习一下libnet扩展库的api接口;
socket核心库和libnet扩展库的主要区别是:
1、socket核心库提供了完整的api,可以实现socket的完整业务逻辑,但是部分api是异步接口,使用起来复杂;
2、所以我们开发一套libnet扩展库api接口,将socket核心库中的异步接口,封装成同步接口,放到libnet扩展库中,使用起来会更加方便;
最终,我们推荐使用 socket核心库中的同步api接口+libnet扩展库中的同步api接口 的方式,来开发完整的socket业务逻辑;这两部分如何组合使用,在本文有一个详细的socket client应用开发框架来演示;
libnet.connect(task_name, timeout, socket_ctrl, remote_addr, remote_port, need_ipv6_dns)
功能
连接对端,阻塞等待连接结果的返回;
只能在sys.taskInitEx创建的task的任务处理函数中使用,因为要阻塞等待并且接收定向消息;
以下三种情况会返回:
1、连接成功;
2、连接失败;
3、连接超时(超时时长受timeout参数控制);
参数
task_name

timeout

socket_ctrl

remote_address

remote_port

need_ipv6_dns

返回值
local result = libnet.connect(task_name, timeout, socket_ctrl, remote_addr, remote_port, need_ipv6_dns)
result

示例

libnet.tx(task_name, timeout, socket_ctrl, data, ip, port, flag)
功能
发送数据到对端,阻塞等待发送结果的返回;
只能在sys.taskInitEx创建的task的任务处理函数中使用,因为要阻塞等待并且接收定向消息;
以下三种情况会返回:
1、发送成功;
2、发送失败;
3、发送超时(超时时长受timeout参数控制);
参数
task_name

timeout

socket_ctrl

data

ip

port

flag

返回值
local result, buff_full = libnet.tx(task_name, timeout, socket_ctrl, data, ip, port, flag)
result

buff_full

示例

libnet.wait(task_name,timeout, socket_ctrl)
功能
socket连接已经成功后,阻塞等待socket对象上新的网络事件消息socket.EVENT;
只能在sys.taskInitEx创建的task的任务处理函数中使用,因为要阻塞等待并且接收定向消息;
以下三种情况会退出阻塞等待状态:
1、socket对象和对端之间的连接出现异常(例如对端主动断开,网络环境出现异常等),此时在内核固件中会发送消息socket.EVENT;
2、socket对象接收到对端发送过来的数据,此时在内核固件中会发送消息socket.EVENT;
3、在应用脚本中需要的位置,主动调用sys.sendMsg(task_name, socket.EVENT, 0)发送消息socket.EVENT给task_name对应的task;
参数
task_name

timeout

socket_ctrl

返回值
local result, param = libnet.wait(task_name, timeout, socket_ctrl)
result

param

示例

libnet.close(task_name, timeout, socket_ctrl)
功能
主动断开socket连接;
TCP模式下,先四次挥手断开连接(超时时长为timeout),再直接强制断开;
UDP模式下,直接强制断开;
只能在sys.taskInitEx创建的task的任务处理函数中使用,因为要阻塞等待并且接收定向消息;
参数
task_name

timeout

socket_ctrl

返回值
nil
示例

七、LuatOS上的socke client 应用开发框架
现在,LuatOS socket和libnet的两个重要的库文件,基本上讲完了,接下来,我们来实际看一个完整的socket client长连接的demo项目代码,重点分析下如何在项目中使用LuatOS socket核心库和libnet扩展库;
7.1 总体设计框图
demo项目的总体设计框图如下:

7.2 模拟器上运行这个项目(使用模拟器单网卡演示项目完整的业务逻辑)
首先我们在LuatOS模拟器上运行一下这个demo项目,让大家对实现的功能有一个直观的认识
如果要在LuatOS模拟器上运行这个项目,代码需要做以下几点修改:
1、netdrv_device.lua中打开pc模拟器的网卡驱动文件require "netdrv_pc",注释掉其他其他网卡文件;
2、 因为这个demo项目需要用到uart功能,要在pc上模拟uart收发功能,需要在pc上安装一个虚拟串口工具,生成一组串口,例如串口1和串口2,如果这两个串口id在你的pc上已经被占用,可以自定义生成任意两个id的串口就行;
生成的这一对串口可以互相给对方发数据,也能互相接收对方发送过来的数据;
如果生成的是串口1和串口2,这个demo项目中的uart_app.lua中使用的uart1,不用修改uart_app.lua的代码,此时在pc上使用sscom或者llcom串口工具打开串口2即可,这样的话,模拟器中的uart1就可以和sscom或者llcom打开的uart2进行收发数据;
如果生成的一对串口没有串口1,假设是串口11和串口12,则需要修改uart_app.lua中的代码,串口id修改为11,pc上使用sscom或者llcom串口工具打开串口12即可,这样的话,模拟器中的uart11就可以和sscom或者llcom打开的uart12进行收发数据;
3、创建一个TCP server、一个UDP server,一个TCP SSL server,根据创建的服务器地址和端口,修改代码中对应的地址和端口;
软件环境准备好之后,接下来我们在模拟器上实际运行一下这个项目看看效果;
双击 cmd 命令行窗口,然后输入下面一行命令,运行 luatos 批处理文件,同时输入要运行的 luatos 项目配置文件
luatos --llt=H:\Luatools\project\Air8000_socket_client_long_connection.ini
然后按回车键,就可以运行 socket_client_long_connection 项目软件;
7.3 分析项目代码
这个socket demo中的readme文件,以及代码中的注释都比较详细,接下来我用vscode直接打开这份demo项目代码,和大家一起分析下项目代码;
7.4 Air8000开发板上运行演示这个项目(重点演示单网卡和多网卡的使用)
准备硬件环境:
1、Air8000开发板一块+可上网的sim卡一张+4g天线一根+wifi天线一根+网线一根:
sim卡插入开发板的sim卡槽
天线装到开发板上
网线一端插入开发板网口,另外一端连接可以上外网的路由器网口或者交换机网口;
2、TYPE-C USB数据线一根 + USB转串口数据线一根,Air8000开发板和数据线的硬件接线方式为:
Air8000开发板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
准备软件环境:
1、手机打开WiFi热点zhutianhua 123qweasd,修改netdrv_wifi.lua和netdrv_multiple.lua中的WiFi信息;
2、打开TCP/UDP web工具,创建一个TCP server、一个UDP server,一个TCP SSL server,根据创建的服务器地址和端口,修改代码中对应的地址和端口;
3、netdrv_device.lua分别打开单网卡和多网卡驱动单独演示;
4、Luatools烧录内核固件以及修改后的demo脚本;
多网卡演示:
开机后,在Luatools运行日志中搜索 new adapter,如下图所示,按照红色文字操作演示

单网卡演示:
直接运行日志即可;
八、如何分析LuatOS socket日志
在socket开发使用过程中,或多或少,我们都会遇到一些问题,遇到问题时,如果我们自己可以根据日志进行初步分析,可能会大大提高解决问题的效率;在本章节我们提供三种日志分析方法;
8.1 三种日志分析方法
8.1.1 Luatools抓取的应用日志分析
这部分是Luatools抓取的应用日志,在Luatools的主窗口可以实时显示,如下图所示:

应用日志分为两种类型:
1、一部分是Lua脚本中输出的日志,有D/、I/、W/、E/几种前缀,这部分日志,大家根据自己编写的Lua脚本逻辑自行分析即可;
2、另一部分是内核固件中输出的一些关键日志,没有D/、I/、W/、E/几种前缀,这部分日志,大家不知道所表示的意义,我们会在本章节的以下内容中针对一些常见的日志进行逐一分析;
8.1.2 抓取LuatOS模拟器上的网络交互包进行分析
socket应用完全可以在LuatOS模拟器上运行,所以我们可以使用LuatOS模拟器来运行自己的程序;
在运行过程中使用wireshark抓取网络交互包,进行详细分析,例如下图是在LuatOS模拟器运行过程中,tls+单向认证连接服务器失败的网络交互包,从交互包中可以准确的得知,失败原因是Unknown CA,未识别的CA证书

这种分析方法对于解决socket,http,mqtt,ftp等网络应用的问题,帮助很大!
8.1.3 Luatools抓取的底层日志网络交互包分析
这里所说的抓取底层日志网络交互包分析,和 8.1.2 抓取LuatOS模拟器上的网络交互包进行分析 相比,一个是在真实的硬件板上(例如Air8000)运行抓日志,一个是在LuatOS模拟器上运行抓网络交互包;
在真实的硬件板上抓到日志后,然后再使用底层日志分析工具提取出来网络交互包,然后再使用wireshark对提取出来的网络交互包进行分析;
和模拟器运行直接抓网络交互包相比,这种方式有以下几种明显的缺点:
1、抓取日志以及分析操作繁琐;
2、硬件板内存资源有限,抓到的网络日志会丢包,特别是网络数据交互频繁的时候,丢包问题严重;
虽然有以上两项缺点,但是因为是真实的运行环境,对于LuatOS模拟器运行无法分析解决的问题,最终还是要通过这种方式解决;
如果需要用到这种方式来分析解决问题,当前阶段,只需要提交Luatools抓到的日志给我们技术人员即可,由技术人员进行分析;
后续我们也会在docs.openluat.com写一篇文章,单独讲解如何使用这种方法分析问题;大家如果有兴趣,可自行查看文档学习。
所以,在本章节接下来的内容中,我会针对一些常见的问题,分别使用Luatools应用日志分析 和LuatOS模拟器上的网络交互包分析 这两种方法来讲述;
8.2 socket创建
8.2.1 创建失败
Luatools应用日志分析
使用socket.create(adapter_id, task_name)接口创建socket时,常见的错误日志,如下图所示:

这种异常日志的意思是:无法为socket对象分配资源;
出现这种异常,通常是因为同时存在的socket数量超过了内核固件限制的最大数量:
Air780系列/Air8000系列的模组,允许同时存在的socket对象数量为64个;
Air6101系列/Air8101系列的模组,允许同时存在的socket对象数量为32个;
通常是以下两种原因造成的:
项目中开发业务代码时,socket.create和socket.release是一对逆操作,如果不断的create,但是没有release,就会出现这种问题,这种错误的使用方式比较常见;
例如在一个实际项目中,创建socket,socket连接,socket收发业务逻辑处理,如果业务逻辑处理过程中出现异常,就会断开socket,然后再销毁socket;这是一套完整的操作,出现异常后,会从socket创建开始重试;在实际代码开发过程中,有可能会忘记销毁socket;这样的话,每次重试都会创建一个新的socket;随着重试的次数增多,最终同时存在的socket对象就会超过上限而出现错误;例如下图中,如果漏写了红框内的代码,随着时间的推移,最终就会出现问题

项目中正常业务逻辑,同时使用的socket数量超过了限制,这种情况下,只能简化业务逻辑,减少对socket的使用;不过这种情况几乎不会出现,因为一个项目的正常业务逻辑,几乎不会出现同时使用几十个socket的情况;
LuatOS模拟器上的网络交互包分析
socket创建失败,不涉及网络交互包,所以不适用于此方法进行分析;
8.3 socket连接
8.3.1 dns解析失败
Luatools应用日志分析
如下图所示
一共四个DNS服务器,每个服务器尝试解析3次,最终没有解析成功,提示:dns_run 649:no ipv6, no ipv4

出现此种错误,可以通过以下几步尝试分析解决:
1、确认下域名输入是否正确;
2、参考socket.setDNS(adapter_id, dns_index, ip)的说明配置自定义的DNS服务器;
LuatOS模拟器上的网络交互包分析

8.3.2 tcp握手连接失败
Luatools应用日志分析
当对端ip地址存在,端口不存在时,例如:连接netlab tcp服务器,ip地址为:112.125.89.8,端口42145(不存在)
会有以下异常日志: net_lwip_tcp_err_cb 662:adapter 1 socket 24 not closing, but error -14

这里有一个错误值:-14,表示:
ERR_RST = -14
含义:连接被重置;
场景:TCP 连接收到对端发送的 RST(重置)报文,导致连接强制关闭;
意味着,tcp连接已经发到了对端ip地址,但是被对端直接给rst了;
还有一种常见的错误是对端IP不存在,此时在异常日志中的错误值很可能是:-13;
如下图所示,连接一个不存在IP地址:113.126.89.86
net_lwip_tcp_err_cb 662:adapter 1 socket 5 not closing, but error -13
这里有一个错误值:-13,表示:
ERR_ABRT = -13
含义:连接被中止。
场景:TCP 连接被本地或对端异常中止(如收到 RST 包,或本地主动调用tcp_abort)。
具体到本日志,因为对端ip不存在,所以应该是tcp三次握手过程中,超时,内核固件主动断开了连接;
在LuatOS内核固件中,使用的是LwIP协议栈,此处的错误值的完整的取值范围如下所述(大家在平时开发过程中,如果遇到异常,根据日志中的错误值,可以参考这部分说明自行简单分析下是什么原因):
1、**ERR_OK = 0:**无错误,操作成功;
2、**ERR_MEM = -1:**内存分配失败;
3、ERR_BUF = -2:
含义:缓冲区错误(不足或大小不匹配)。
场景:发送数据时缓冲区空间不足(如pbuf大小不够存放待发送数据),或接收时缓冲区溢出。
4、ERR_TIMEOUT = -3
含义:操作超时。
场景:TCP 连接超时(未收到 SYN-ACK)、ARP 请求超时(未收到目标 MAC 地址应答)、netconn_recv等待数据超时等。
5、ERR_RTE = -4
含义:路由错误(无可用路由)。
场景:发送 IP 数据包时,lwip 路由表中找不到目标 IP 地址的有效路由(如未配置默认网关且无直连路由)。
6、ERR_INPROGRESS = -5
含义:操作正在进行中。
场景:非阻塞模式下的操作(如tcp_connect)尚未完成,需等待后续回调通知结果。
7、ERR_VAL = -6
含义:无效值(参数值非法)。
场景:传入 API 的参数值超出合法范围(如端口号为 0 或大于 65535,IP 地址格式错误等)。
8、ERR_WOULDBLOCK = -7
含义:操作会阻塞(非阻塞模式下)。
场景:非阻塞模式下调用netconn_recv时暂无数据可接收,或tcp_send时发送窗口未满导致无法立即发送。
9、ERR_USE = -8
含义:地址已被使用。
场景:绑定端口时(udp_bind、tcp_bind),指定的端口已被其他连接占用。
10、ERR_ALREADY = -9
含义:已在连接中。
场景:对已处于连接过程中的 TCP 控制块再次调用tcp_connect。
11、ERR_ISCONN = -10
含义:连接已建立。
场景:对已连接的 TCP 连接再次调用tcp_connect,或对已连接的netconn执行不需要连接的操作(如bind)。
12、ERR_CONN = -11
含义:未连接状态。
场景:对未建立连接的netconn调用send,或关闭未连接的连接。
13、ERR_IF = -12
含义:底层网络接口(netif)错误。
场景:网络接口未初始化、链路断开(如以太网物理层未连接),导致数据包无法发送。
14、ERR_ABRT = -13
含义:连接被中止。
场景:TCP 连接被本地或对端异常中止(如收到 RST 包,或本地主动调用tcp_abort)。
15、ERR_RST = -14
含义:连接被重置。
场景:TCP 连接收到对端发送的 RST(重置)报文,导致连接强制关闭。
16、ERR_CLSD = -15
含义:连接已关闭。
场景:对已正常关闭的连接执行读写操作(如tcp_send在连接关闭后调用)。
17、ERR_ARG = -16
含义:非法参数(参数类型或指针无效)。
场景:传入 API 的参数为NULL(如netconn_new传入无效的协议类型,tcp_send传入NULL缓冲区)。
18、ERR_IF_HIGH_WATER = -17
含义:底层网络接口达到高水位线(缓冲区满)。
场景:网络接口发送缓冲区已满,暂时无法接收新的发送请求(通常用于流量控制)。
19、ERR_IF_SUSPEND = -18
含义:底层网络接口被挂起。
场景:网络接口因某种原因(如手动暂停、错误恢复中)暂时不可用。
20、ERR_IF_OOS = -19
含义:底层网络接口处于 "OutOfService"(服务中断)状态。
场景:网络接口硬件故障、驱动错误等导致完全无法提供服务。
LuatOS模拟器上的网络交互包分析

8.3.3 tls握手连接失败
Luatools应用日志分析
在tls连接+仅支持单向认证的场景中,如果我们在客户端配置了错误的CA证书,本来应该是baidu_parent_ca.crt文件中的内容,现在配置为了错误的内容"123",如下图所示:

在连接过程中,会有以下异常日志:network_state_shakehand 807:0x2700, 3

这里有一个错误值:0x2700,MBEDTLS_ERR_X509_CERT_VERIFY_FAILED,表示证书验证失败;
在LuatOS内核固件中,使用的是MbedTLS开源库,TLS握手连接过程中,MbedTLS中有以下常见的错误值(大家在平时开发过程中,如果遇到异常,根据日志中的错误值,可以参考这部分说明自行简单分析下是什么原因,如果这里没有覆盖出现的错误值,可以使用AI工具提问,例如错误值为0x2700,可以提问:tls握手连接过程中,0x2700表示什么错误):
1、基础加密 / 解密错误
MBEDTLS_ERR_SSL_DECRYPTION_FAILED(0x7080):解密失败(如对称加密密钥错误、密文损坏)。
MBEDTLS_ERR_SSL_BAD_HMAC(0x7082):HMAC 验证失败(消息完整性校验不通过,可能被篡改)。
MBEDTLS_ERR_SSL_BAD_RECORD_MAC(0x7084):记录层 MAC 校验失败(与 HMAC 类似,针对 TLS 记录的完整性)。
2、协议版本与协商错误
MBEDTLS_ERR_SSL_UNSUPPORTED_VERSION(0x7000):不支持对方的 TLS 版本(如客户端要求 TLS 1.0,服务器仅支持 TLS 1.2+)。
MBEDTLS_ERR_SSL_VERSION_MISMATCH(0x7002):版本协商不匹配(如客户端和服务器协商的版本不一致)。
3、密码套件与算法错误
MBEDTLS_ERR_SSL_NO_SHARED_CIPHER(0x7004):无共同支持的密码套件(客户端与服务器的密码套件列表无交集)。
MBEDTLS_ERR_SSL_UNSUPPORTED_CIPHERSUITE(0x7006):对方选择的密码套件本地不支持。
MBEDTLS_ERR_SSL_UNSUPPORTED_EXTENSION(0x7008):不支持对方发送的 TLS 扩展(如 ALPN、SNI 扩展不被认可)。
4、证书验证错误
MBEDTLS_ERR_X509_CERT_VERIFY_FAILED(0x2700):证书验证失败(如签名无效、过期、吊销)。
MBEDTLS_ERR_X509_UNKNOWN_CA(0x2702):证书链中存在未知 CA(根证书不被信任)。
MBEDTLS_ERR_SSL_CERTIFICATE_REQUIRED(0x7040):服务器要求客户端证书,但客户端未提供。
MBEDTLS_ERR_SSL_BAD_CERTIFICATE(0x7042):证书格式错误或内容无效(如解析失败、字段不合法)。
5、密钥交换与认证错误
MBEDTLS_ERR_SSL_KEY_EXCHANGE_FAILED(0x7020):密钥交换过程失败(如 RSA 密钥解密失败、ECDH 密钥协商错误)。
MBEDTLS_ERR_SSL_BAD_CLIENT_KEY_EXCHANGE(0x7022):客户端密钥交换消息格式错误或内容无效。
MBEDTLS_ERR_SSL_BAD_CERTIFICATE_VERIFY(0x7044):客户端证书验证消息(CertificateVerify)无效(如签名不匹配)。
6、握手流程与消息错误
MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE(0x7060):收到意外的握手消息(如流程顺序错误,如先收到 Finished 再收到 Certificate)。
MBEDTLS_ERR_SSL_INVALID_HANDSHAKE_MESSAGE(0x7062):握手消息格式无效(如长度错误、字段缺失)。
MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE(0x7064):握手失败(通用错误,可能因上述多种原因导致,如服务器拒绝客户端配置)。
MBEDTLS_ERR_SSL_HANDSHAKE_TIMEOUT(0x7066):握手超时(未在规定时间内收到对方响应)。
7、连接与状态错误
MBEDTLS_ERR_SSL_CONN_EOF(0x70a0):握手过程中连接被关闭(对方发送了关闭通知)。
MBEDTLS_ERR_SSL_CONNECTION_RESET(0x70a2):连接被重置(如底层 TCP 连接断开)。
MBEDTLS_ERR_SSL_WANT_READ / MBEDTLS_ERR_SSL_WANT_WRITE(0x70c0 / 0x70c2):非阻塞模式下需要继续读写数据(非错误,需重试)。
LuatOS模拟器上的网络交互包分析

8.4 socket收发数据和断开
这些业务逻辑的异常情况,就不再一一分析了,大家遇到问题后,可以参考上面几种异常的分析方法,自行分析;
九、课后作业
实现一个TCP短连接项目(LuatOS模拟器上或者AirXXXX硬件板上开发调试,二选一);
项目需求:
1、启动一个tcp server;
2、开发LuatOS项目,实现一个tcp client,每隔5分钟循环执行一次以下业务逻辑:
连接tcp server;如果连接失败,退出本次循环;如果连接成功,继续向下执行;
发送任意一包数据到server,如果发送失败,立即断开连接,退出本次循环;
server收到数据后,回复任意数据到client;
client等待接收数据,超时时间20秒;
在20秒内,如果收到数据,立即断开连接,退出本次循环;
20秒超时没有收到数据,立即断开连接,退出本次循环;
在以上业务逻辑过程中,如果出现异常,立即断开连接,退出本次循环;
作业提交内容:
1、2个Lua文件:main.lua,tcp_client_main.lua;
2、1个运行日志文件
今天的内容就分享到这里了~