经常与物联网打交道的人经常会遇到各种各样的modbus协议,有的长的正正规规,有的长的奇奇怪怪,不管如何,大概率都是类似于下面的样子:
一个数据帧的样子无外乎如此,因此在接入各种硬件的时候,反反复复编写类似的协议代码,十分揪心,特别是还要每次处理粘包问题,因此如果有一款通用的协议过滤器处理各类协议解析,那就非常舒服了。
目前我的想法如下:
如果有一个过滤器在数据帧和明文内容中间帮我们过滤数据,而且是傻瓜式操作,那就可以完成一个通用工具的要求。
一、过滤器应该带什么方法
一个好的通用型过滤器需要带哪些方法,我觉得应该要有如下几个:
- 数据帧规则说明
- crc校验支持
- 过滤消息体
1.1数据帧规则说明
数据帧规则说明可以帮助开发者将各式各样的数据帧规则说明以一个样式传递给过滤器,让过滤器明白这个协议长什么样子,从而支持各类型的协议。
1.2crc校验支持
每一段数据帧都会有校验码,一般为各类型的crc,因此支持各种不同的校验方法是实现消息解析的重要步骤。在过滤器中,crc校验支持一个需要知道哪些数据需要计算到crc校验中,第二个是crc校验结果是否正确。
1.3过滤数据帧
这是一个过滤数据帧的主要方法。
二、过滤器使用什么方式实现
过滤器将采用面向对象的方式实现,由于python没有接口的概念,这里我将使用抽象类来创建一个基类,子类集成基类,实现抽象方法来实现一个定制化的过滤器。
2.1采用面向对象的原因
为了能够处理粘包问题,将会把未处理的消息内容存到内存中,直到整个数据帧获取完整,因此一个对象作为过滤器为最佳,可以完美解决粘包问题。
三、数据帧规则制定
数据帧的规则将用一个数组来表现,每一个元素即为数据帧的一段内容,下面以一个实例来作为规则编写:
[
{"key": "header", "len": 1, "fmt": "B"},
{"key": "ver", "len": 1, "fmt": "B"},
{"key": "msg_len", "len": 1, "fmt": "B"},
{"key": "data", "len": "{msg_len.val} - {crc.len}", "fmt": ""},
{"key": "crc", "len": 2, "fmt": "h", "group": "{msg_len}{data}"}
]
3.1规则字段说明
- key:该数据的关键字
- len:该数据的长度,可以为数字,也可以为相应数据的运算内容字符串
- fmt:该数据的打包格式,用struct解析,当存在内容时,将尝试解析数据赋值给val字段,如果为空,则将原始字节流赋值给val
- group:当关键字是crc的时候,用来框定哪些数据进行计算
- val:数据解析后的内容,消息解析过程中自动创建的字段,无需在规则中声明
msg_len.val:获取msg_len的val字段内容
crc.len:获取crc的数据长度
msg_len:获取msg_len的字节流数据
四、具体代码
话不多说,代码直接拿去:
# -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod
import struct
import copy
import re
class AbstractProtocolBaseFilter(metaclass=ABCMeta):
def __init__(self):
self.current_index = 0
self.data = {}
self.tmp_socket_data = b""
self.__check_rule_ok = True
@abstractmethod
def rules(self):
"""
规则说明
:return:
"""
return []
@abstractmethod
def crc_imp(self, crc_data):
"""
crc实现方法
:param crc_data:
:return:
"""
return 0
def filter(self, socket_data):
"""
过滤数据方法
:param socket_data:
:return:
"""
if not self.__check_rule_ok or not self.rules():
raise Exception("the rule is not available")
if not self.data:
try:
self.clear_data()
except:
self.__check_rule_ok = False
self.tmp_socket_data += socket_data
tmp_check_index = 0
results = []
if self.__check_rule_ok:
for i, v in enumerate(self.tmp_socket_data):
rule = self.rules()[self.current_index]
key_name = rule["key"]
result = self.data[key_name]
try:
length_str = result["len"]
fmt_str = result["fmt"]
except:
self.__check_rule_ok = False
break
length = self.__get_length(length_str)
if length is None:
self.__check_rule_ok = False
break
# 长度与内容长度一致或指令中有部分内容可以为0时
if length == (i + 1 - tmp_check_index) or length == 0:
if length > 0:
result["bytes"] = self.tmp_socket_data[tmp_check_index: i + 1]
tmp_check_index = i + 1
if not fmt_str:
result["val"] = result["bytes"]
else:
result["val"] = struct.unpack(fmt_str, result["bytes"])[0]
self.current_index += 1
# 当一组协议数据解析成功,重置下标到0
if self.current_index > len(self.rules()) - 1:
is_crc_check = True
# 如果是crc校验码数据,则比对crc
if key_name == "crc":
try:
group_str = result["group"]
crc_bytes_data = self.__get_crc_group(group_str)
if crc_bytes_data is None:
self.__check_rule_ok = False
break
if self.crc_imp(crc_bytes_data) == result["val"]:
is_crc_check = True
else:
is_crc_check = False
except:
self.__check_rule_ok = False
break
if is_crc_check:
results.append(copy.deepcopy(self.data))
self.current_index = 0
self.clear_data()
self.tmp_socket_data = self.tmp_socket_data[tmp_check_index:]
return results
def clear_data(self):
"""
初始化data数据
:return:
"""
for rule in self.rules():
key_name = rule["key"]
self.data[key_name] = rule
self.data[key_name]["bytes"] = b""
self.data[key_name]["val"] = None
def __get_crc_group(self, group_str):
"""
解析获取group内容
:param group_str:
:return:
"""
if isinstance(group_str, str):
res_list = re.findall(r"{(.+?)}", group_str)
group_bytes_data = b""
for res in res_list:
try:
group_bytes_data += self.data[res]["bytes"]
except:
return None
return group_bytes_data
return None
def __get_length(self, length_str):
"""
解析获取数据长度
:param length_str:
:return:
"""
if isinstance(length_str, int):
return length_str
elif length_str.isdigit():
try:
return int(length_str)
except:
return None
elif isinstance(length_str, str):
res_list = re.findall(r"{(.+?)}", length_str)
value_list = []
for res in res_list:
split_list = res.split(".")
try:
key_name = split_list[0]
val_name = split_list[1]
value = self.data[key_name][val_name]
if value is None:
return None
value_list.append(value)
except:
return None
if len(res_list) != len(value_list):
return None
length_str = length_str.replace("{", "").replace("}", "")
for i, res in enumerate(res_list):
value = value_list[i]
length_str = length_str.replace(f"{res}", str(value))
try:
length = eval(length_str)
return length
except:
return None
return None
定制过滤器
class CustomProtocolFilter(AbstractProtocolBaseFilter):
def crc_imp(self, crc_data):
# crc处理
return 0
def rules(self):
return [
{"key": "header", "len": 1, "fmt": "B"},
{"key": "ver", "len": 1, "fmt": "B"},
{"key": "msg_len", "len": 1, "fmt": "B"},
{"key": "data", "len": "{msg_len.val} - {crc.len}", "fmt": ""},
{"key": "crc", "len": 2, "fmt": "h", "group": "{msg_len}{data}"}
]
调用
protocol_filter = CustomProtocolFilter()
try:
res = protocol_filter.filter(bytes_data)
except Exception as e:
print(e)