一、UDP协议介绍
UDP(User Datagram Protocol, 用户数据报协议),无连接的传输层协议,提供面向事务的简单不可靠信息传输服务。强调传输性能而不是传输完整性,多用于视频和多媒体应用。
2.1 报文格式
2.2 协议特点
- 无连接:
- 不可靠传输:消息尽力传播,但不关注是否传输过去
- 面向数据报:以数据报为传输的基本单位,不拆分、不合并、原样发送
- 全双工:一条路径双向通信,既能读,也能写
- 大小受限:UDP协议首部有2个字节,16位的长度内容,限制了UDP传输数据最大长度为64K(含UDP首部)
- 缓冲区:UDP只有接收缓冲区,没有发送缓冲区
二、物联网中实战
不同物联网平台对协议中所承载的业务内容均有自己的规范,但原理基本一致,本文基于中国电信AIoT物联网平台进行实战。
2.1 在物联网平台创建产品设备
具体操作流程可见平台操作手册
重点关注:
- 创建产品时,通信协议选择UDP
- 创建产品时,“是否透传”,选择透传或者物模型,将对应不同的上下行报文格式。透传:平台对报文不进行解析,将报文透传给应用或终端;物模型:按照在平台定义的服务与属性,对上下行报文进行解析。(https://img-blog.csdnimg.cn/direct/d73abe3f1bf04acbada68b35c964bd3f.jpeg)
2.2 交互流程
设备登录
-
用户在通用组件服务添加设备,填写设备编号。通用组件服务生成device ID和特征串。device ID由产品ID和设备编号拼接生成。设备初始状态为:已注册。
-
设备携带deviceId、password(平台生成的特征串)登录到平台
-
平台对设备进行认证,并将认证结果返回给设备。认证成功后,设备状态为:已激活、在线。
心跳保活
- 平台默认心跳周期为5分钟,心跳超时之前,设备需要发送心跳报文到平台用于会话的延长。超时而没有发送心跳报文的,平台将删除会话。
数据上报
- 设备以0x04消息类型上报数据,平台解析后会以0x84回复响应结果。
下行指令
设备登出
3. 设备登出需要发送登出请求,携带deviceId,平台收到请求后删除会话,并向设备发送登出响应。设备状态更新为:下线。
2.3 报文格式(下文转自官方手册)
消息格式
应用层数据报文以1个字节的类型字段作为分割,平台支持登录、心跳、上下行业务数据、登出等消息类型。消息携带的参数必须包含两个字节的参数长度。
UDP报文Payload包含三部分内容:
消息类型(1 BYTE)、消息ID(2 BYTE)、消息内容(n BYTE)。
消息类型 | 消息ID | 消息内容 |
定义8种消息类型:
消息类型 | 类型描述 |
0x01 | 登录请求 |
0x02 | 心跳 |
0x03 | 登出请求 |
0x04 | 上行数据 |
0x05 | 下行数据 |
0x81 | 登录响应 |
0x82 | 心跳响应 |
0x83 | 登出响应 |
0x84 | 上行数据响应 |
0x85 | 下行数据响应 |
登录消息
类型描述 | 消息类型 | 消息ID | 消息内容格式 | 备注 |
登录请求 | 0x01 | 设备生成,2个字节 | [deviceId_length (2字节)]+[deviceId] +[password_length(2字节)]+[password] +[version_length(2字节)]+[version] | deviceId由产品Id和设备编号拼接而成的字符串;version默认填写为1.0 |
登录响应 | 0x81 | 与请求报文一致。 | 结果码(1字节) | 登录结果: 0 成功 1 未知错误 2 设备不存在 3 设备认证失败 |
心跳消息
类型描述 | 消息类型 | 消息ID | 消息内容格式 | 备注 |
心跳请求 | 0x02 | 设备生成,2个字节 | [deviceId_length (2字节)]+[deviceId] | 终端与平台心跳周期暂定5分钟; |
心跳响应 | 0x82 | 与请求报文一致。 | 结果码(1字节) | 心跳结果: 0 成功 1 设备id错误或设备不在线 |
业务数据
透传
类型描述 | 消息类型 | 消息ID | 消息内容格式 | 备注 |
上行数据 | 0x04 | 设备生成,2个字节 | [deviceId_length (2字节)] +[deviceId] +[data_length (2字节)] +[data] | data_length字段不可为0 |
上行数据响应 | 0x84 | 与请求报文一致。 | 结果码(1字节) | 上报结果: 0 成功 1 设备id错误或设备不在线 |
下行数据 | 0x05 | 平台生成,2个字节(taskId) | [data_length (2字节)] +[data] | |
下行数据响应 | 0x85 | 与请求报文一致。(taskId) | 结果码(1字节)+[data_length (2字节)] +[data] | 指令执行结果码: 0 成功 1 失败 data_length字段可设置为0 |
非透传
类型描述 | 消息类型 | 消息ID | 消息内容格式 | 备注 |
上行数据 | 0x04 | 设备生成,2个字节 | [deviceId_length (2字节)] +[deviceId] +[data_length (2字节)] +[serviceId (2字节)] +[params] | data_length为serviceId字段与 params字段的总长度 |
上行数据响应 | 0x84 | 与请求报文一致。 | 结果码(1字节) | 上报结果: 0 成功 1 设备id错误或设备不在线 |
下行数据 | 0x05 | 平台生成,2个字节(taskId) | [data_length (2字节)] +[serviceId (2字节)] +[params] | data_length为serviceId字段与 params字段的总长度 |
下行数据响应 | 0x85 | 与请求报文一致。(taskId) | 结果码(1字节)+[data_length (2字节)] +[serviceId (2字节)] +[params] | 指令执行结果码: 0 成功 1 失败 data_length字段可设置为0 data_length为serviceId字段与 params字段的总长度 |
1. 上行数据
物模型如下:
服务类型 | 服务标识 | 服务ID | 属性顺序 | 属性标识符 | 属性类型 | 属性长度 |
数据上报 | up | 1 | 1 | seq | 无符号整型 | 2 |
2 | data1 | 定长字符串 | 5 | |||
3 | data2 | 变长字符串 | / |
2. 下行数据
物模型如下:
服务类型 | 服务标识 | 服务ID | 参数顺序 | 参数标识符 | 参数类型 | 参数长度 |
指令下发 | dn | 8001 | 1 | cmd | 定长字符串 | 5 |
指令下发响应 | rsp | 9001 | 1 | time | 定长字符串 | 5 |
2.4 代码
2.4.1 代码总体框架
每一次进行业务交互,主要是对client.sendto(payload, server_address)
中payload
部分和client.recvfrom(1024)
的内容进行编辑与解析。
import socket
# 创建UDP socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 服务器地址
# 获取服务端主机与端口,可在设备管理->产品->产品概况中查看,如果是域名,可在本地ping一下获得IP
host = "180.109.255.252"
# 设置端口与服务器端一致
port = 15683
server_address = (host, port)
# 发送消息
# client.sendto(payload, server_address)
# 接收回应
# response, server_address = client.recvfrom(1024)
# print(f"Received response: {response}")
# 关闭socket
client_socket.close()
2.4.1 UDP透传设备
2.4.1.1. 登录
1.登录报文
消息格式:[deviceId_length (2字节)]+[deviceId]+[password_length(2字节)]+[password]+[version_length(2字节)]+[version]
注:字符串转十六进制可用在线工具:https://www.sojson.com/hexadecimal.html,也可以直接使用python中的bytes(data, "utf-8").hex()
方式。
deviceId:200020722
password:7Sfv-b_HDbLDyJ_K-0SkWqRGd-GE-b3rZp-upOr1kSU
version:1.0
登录请求编码为(16进制):
• 登录报文消息类型:0x01
• 消息Id:0x0001
• deviceId_length:0x0009
• deviceId:0x323030303230373232
• password_length:0x002b
• password:0x382D45654D57466134364D5952566C4149683557393967714E35395A5571565A34487653634C7A50456259
• version_length:0x0003
• version:0x312e30
登录请求的完整报文为:01 0001 0009 323030303230373232 002B 382D45654D57466134364D5952566C4149683557393967714E35395A5571565A34487653634C7A50456259 0003 312E30
2. 登录代码全貌
import socket
import binascii
# 创建UDP socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 服务器地址
# 获取服务端主机与端口,可在设备管理->产品->产品概况中查看,如果是域名,可在本地ping一下获得IP
host = "180.109.255.252"
# 设置端口与服务器端一致
port = 15683
server_address = (host, port)
# 发送消息
payload = "0100010009323030303230373232002B382D45654D57466134364D5952566C4149683557393967714E35395A5571565A34487653634C7A504562590003312E30"
client.sendto(binascii.unhexlify(payload), server_address)
# 接收回应
response = binascii.hexlify(client.recv(1024)).decode("utf-8")
print(f"Received response: {response}")
# 关闭socket
client.close()
根据平台定义的登录响应格式,TCP模拟器收到“050000”的报文,则登录成功,在物联网平台可查看设备状态也应该为在线。
2.4.1.2 数据上报
1.上报报文
消息格式:0x02 +数据长度(2字节) +业务数据
deviceId:200020722
1. 上行业务数据:"hello"
上行数据编码为(16进制):
• 上行数据消息类型:0x04
• 消息Id:0x0004
• deviceId_length:0x0009
• deviceId:0x323030303230373232
• data_length:0x0005
• data:0x68656c6c6f
上行数据的完整报文为:04 0004 0009 323030303230373232 0005 68656c6c6f
2. 上行代码全貌
根据业务规则,设备需先登录,才能进行数据上报。
import socket
import binascii
# 创建UDP socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 服务器地址
# 获取服务端主机与端口,可在设备管理->产品->产品概况中查看,如果是域名,可在本地ping一下获得IP
host = "180.109.255.252"
# 设置端口与服务器端一致
port = 15683
server_address = (host, port)
# 发送登录消息
payload = "0100010009323030303230373232002B382D45654D57466134364D5952566C4149683557393967714E35395A5571565A34487653634C7A504562590003312E30"
client.sendto(binascii.unhexlify(payload), server_address)
# 接收回应
response = binascii.hexlify(client.recv(1024)).decode("utf-8")
print(f"Received response: {response}")
# 发送上报消息
payload2 = "0400040009323030303230373232000568656c6c6f"
client.sendto(binascii.unhexlify(payload2), server_address)
# 接收回应
response2 = binascii.hexlify(client.recv(1024)).decode("utf-8")
print(f"Received response: {response2}")
# 关闭socket
client.close()
经过登录和上行报文的实战,有没有发现UDP协议在物联网领域中,实质是将业务数据封装在了UDP报文的data部分。data部分按照业务规则进行”拼接“即可。