本文最后更新于:June 30, 2023 pm
本文作者:[wangwenhai] # 概要:本文主要讲如何从协议涉及到代码,再到实现一个简单的基于TCP的物联网服务器的过程。
Trap Protocol协议设计文档
1.背景阐述
近期在家里看着墙发呆的时候,闹钟引起了我的注意。这个闹钟是大学的时候用过的,一直到现在都还在正常显示,突然想起来上一次换时间貌似是2017年12月–一个冬天,现在都快两年了,拿过来摆弄了一番,发现里面装了3节南孚电池。令我惊讶的是,闹钟还能很大声的响铃。
我不由得来了兴趣,仔细分析了一下这个闹钟的基本构造。闹钟用了液晶数码管作为显示屏,而液晶数码管的功耗就非常低了,这节省了很大的电量。同时有3节7号电池供电,电压是1.5*3=4.5V,差不多工作电流是0.001MA(百度了一下基本的参数做了对比),在如此低功耗条件下工作了快2年,确实值得研究。
于是我想了一下,能不能发明一个专门针对低功耗的芯片的通信协议,来实现以非常低的功率长时间工作。最好是 纽扣电池,或者是马铃薯、番茄电池都能维持通信。我的闹钟上面可以显示温湿度,如果加以改造,就可以把这些数据上传到云服务器,岂不是可以时刻监控温湿度而实现动态调节?同时我还想远程换个铃声,这又涉及到了远程及时通信了,在这个比较有趣的情景下,我决定发明一个简单的基于TCP的,但是靠谱的,针对低端尤其是低功耗的设备进行少量数据传输的协议。
为何叫Trap?可能我觉得我的能力不够,做出来的东西就是个坑(Trap)吧,为了避免过分解读和吐槽,简单定义为:一个简单的玩具协议。
2.参照标准
在设计之前,我参考了好几种常见的协议,站在巨人的肩上工作能无限接近天花板不是吗。
近期参考了EMQ的一些设计思路(尤其是EMQ的产品介绍PPT),还有之前的一个TCP服务器Demo也给了我一定程度的启发,EMQ给我的思路是:通信需要并发处理,TCP服务器Demo给我的思路是:越简单越好,这句话也是Python之禅的名言。
我协议设计的主要思路来源于Erlang的TCP并发处理模式和低功耗的芯片通信。
3.同类协议
在这个场景下,前辈们做了不少研究,业界比较知名的就是MQTT和CoAP两个协议。我在这里简单的讲一下。
1.CoAP协议
CoAP(Constrained Application Protocol)是一种在物联网世界的类HTTP协议,它的详细规范定义在 RFC 7252。COAP名字翻译来就是“受限应用协议”,顾名思义,使用在资源受限的物联网设备上。物联网设备的资源很受限,运行HTTP是不可以接受的。
COAP是一个非常简单的协议,非常轻量级,甚至可以看作是HTTP的精简版(当然和HTTP区别很大)。
2.MQTT 协议
MQTT 全称为 Message Queuing Telemetry Transport(消息队列遥测传输)是一种基于发布/订阅范式的“轻量级”消息协议,由 IBM 发布。MQTT 可以被解释为一种低开销,低带宽占用的即时通讯协议,可以用极少的代码和带宽的为连接远程设备提供实时可靠的消息服务,它适用于硬件性能低下的远程设备以及网络状况糟糕的环境下,因此 MQTT 协议在 IoT(Internet of things,物联网),小型设备应用,移动应用等方面有较广泛的应用。
MQTT 可以看作是TCP之上的一个高级封装协议,功能强大,很适合作为物联网设备采集数据使用的协议。
关于其他的就不赘述了,上面介绍这两种协议最终目的就是让大家看到一个共同之处:物联网世界的通信大部分是:低功率、条件受限,资源不足。我们就按照这个设计思路出发设计新的协议,能同时支持TCP和类CoAP协议的协议,貌似是简单合体又比单独两个协议更好玩(不是取代,也不是挑战,而是另一种思路,给学习者带来一种心情的愉悦)的简单协议。
4.详细设计
1. 报文固定包头
数据固定报头使用4个字节(byte)来表示,如下表所示:
字节0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|---|
字节1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
字节2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
字节3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
包头详解:
字节0的高四位(higher 4bit)表示连接模式Mode,该Mode目前有2个值:1和2。其中1表示TCP长连接,2表示类似于COAP POST的短连接;·字节0低四位(lower 4bit)表示消息报文类型,目前留16个值,实际用不到这么多。具体如下表所示。·字节1、字节2表示消息的长度,最长支持2^16=65536(byte)个字符,也就是64KB。
字节3为扩展字节,预留给其他数据域使用。如果客户端需要认证,在这里填充Client ID的长度,最长支持2^8=256(byte)个字符,可以用来标识唯一识别号,此时ClientID的内容被放在数据域的第一段。
Mode=1(TCP模式)的情况下数据报文类型列表:
成功返回:
\binary** | \十进制** | \客户端** | \服务端** | \含义** |
---|---|---|---|---|
0000 | 0 | PING | PONG | 心跳包\心跳回复 |
0001 | 1 | AUTH | OK | 认证成功\认证失败 |
0010 | 2 | SEND | OK | 发送成功\认证失败 |
0011 | 3 | PUBLISH | OK | 发布成功\发布失败 |
错误返回:
\binary** | \十进制** | \客户端** | \服务端** | \含义** |
---|---|---|---|---|
0100 | 4 | AUTH | AUTH_FAILURE | 认证失败 |
0101 | 5 | SEND | SEND_FAILURE | 发送失败 |
0110 | 6 | PUBLISH | PUB_FAILURE | 发布失败 |
Mode=2(Trap模式)的情况下数据报文类型列表:
成功返回:
\binary** | \十进制** | \客户端** | \服务端** | \含义** |
---|---|---|---|---|
0000 | 1 | SEND | OK | 发送成功 |
0001 | 2 | PUBLISH | OK | 发布成功 |
错误返回:
\binary** | \十进制** | \客户端** | \服务端** | \含义** |
---|---|---|---|---|
0010 | 3 | SEND | SEND_FAILURE | 发送失败 |
0110 | 4 | PUBLISH | PUB_FAILURE | 发布失败 |
2.两种模式
TCP模式
当包头的字节0的高4位为0001(十进制为1)的时候,表示此时协议是TCP模式。在此模式下,客户端和服务端保持长连接。客户端可以向服务端SEND数据,也可以向另一个客户端PUBLISH数据,此模式是一个双工模式,服务端,客户端之间可以双向通信。
TCP模式下客户端的连接过程
请求连接
客户端向服务端发送一个请求连接的CONNECT报文,此时仅仅是为了保证Socket连接成功,所以没有任何身份信息。
回复确认
如果网络没有问题,服务端正常,客户端可信,此时回复一个OK报文,表示服务端准备就绪。
如果配置了ACL或者是因为别的原因,服务端识别到非法客户端,此时直接回复REFUSE_CONNECT报文,告诉客户端不允许连接。
发送Client Id进行认证
服务端回复确认OK包以后,客户端发送AUTH包,同时带上自己的ClientID请求连接,假如说ClientID此时为:CLIENT001,此时是9Byte,则字节3的值为CLIENT_ID_LENGTH=9(1001),同时数据区的第一段CLIENT_ID_LENGTH长度的数据即为真正的CLIENT_ID。如下图所示:
回复认证结果
如果认证成功,服务端返回OK包,如果认证失败,服务端返回AUTH_FAILURE包。
SEND:发送数据
客户端把数据提交到服务端,服务端可进行存储或者丢弃处理,不做转发。假如客户端CLIENT001送”helloworld”到服务端,其中字节3低四位表示客户端的CLIENTID长度;字节1、2连起来表示helloworld的长度即为10,二进制表示为:0000000000001010:
发送成功返回OK包,发送失败返回SEND_FAILURE包。
PUBLISH:发布数据
表示客户端发送数据到另一个客户端,其实就是客户端之间的相互通信过程。当发送PUBLISH包的时候,需要注意下面的问题:
- FROM:数据从何而来,指的是数据源;
- TO:数据从何而去,指的是数据的目的地,指的是另一个客户端的ID。
发布成功返回OK包,发布失败返回PUB_FAILURE包。
Trap模式
当包头的字节0的高4位为0010(十进制为2)的时候,表示此时协议是Trap模式。在此模式下,只支持SEND形式发送数据,客户端只能提交数据到服务器上,不能进行转发,而是否持久化或者丢弃是服务端来决定的。Trap在发送数据的时候,必须带上ClientId。
Trap模式和TCP模式的区别在于:TCP模式SEND结束以后保持连接,而Trap模式则断开连接,SEND完成后,如果成功则返回OK包,如果失败则返回SEND_FAILURE包,最后都是断开客户端的连接。
其中Trap模式下SEND数据和TCP没有区别:
CLIENT001发送(SEND)“helloworld”到服务端,其中字节3低四位表示客户端的CLIENTID长度;字节1、2连起来表示helloworld的长度即为10,二进制表示为:0000000000001010:
5.报文
- CONNECT
- CONNECT_OK
- CONNECT_FAILURE
- AUTH
- AUTH_OK
- AUTH_FAILURE
- SEND
- SEND_OK
- SEND_FAILURE
- PUBLISH
- PUB_OK
- PUB_FAILURE
- CMD
- CMD_OK
- CMD_FAILURE
6.状态码
操作 | 成功 | 备注 | 失败 | 备注 |
---|---|---|---|---|
请求连接 | 0 | 0000 | 1 | 0001 |
发送数据 | 0 | 0000 | 2 | 0010 |
发布数据 | 0 | 0000 | 3 | 0011 |
执行命令 | 0 | 0000 | 4 | 0100 |
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!