SmartOS/TinyNet/TinyMessage.cpp

947 lines
21 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "Security\Crc.h"
#include "TinyMessage.h"
#include "Security\RC4.h"
//#define MSG_DEBUG DEBUG
#define MSG_DEBUG 0
#if MSG_DEBUG
#define msg_printf debug_printf
#else
#define msg_printf(format, ...)
#endif
/*================================ 微网消息 ================================*/
typedef struct{
byte Retry:4; // 重发次数。
//byte TTL:1; // 路由TTL。最多3次转发
byte NoAck:1; // 是否不需要确认包
byte Ack:1; // 确认包
byte _Error:1; // 是否错误
byte _Reply:1; // 是否响应
} TFlags;
// 初始化消息各字段为0
TinyMessage::TinyMessage(byte code) : Message(code)
{
Data = _Data;
// 如果地址不是8字节对齐长整型操作会导致异常
Buffer(&Dest, MinSize).Clear();
Checksum = 0;
Crc = 0;
}
// 分析数据,转为消息。负载数据部分将指向数据区,外部不要提前释放内存
bool TinyMessage::Read(Stream& ms)
{
// 消息至少4个头部字节、2字节长度和2字节校验没有负载数据的情况下
if(ms.Remain() < MinSize) return false;
TS("TinyMessage::Read");
auto p = ms.Current();
//ms.Read(&Dest, 0, HeaderSize);
Buffer bs(&Dest, HeaderSize);
ms.Read(bs);
// 占位符拷贝到实际数据
Code = _Code;
Length = _Length;
Reply = _Reply;
Error = _Error;
// 代码为0是非法的
if(!Code) return false;
// 没有源地址是很不负责任的
if(!Src) return false;
// 源地址和目的地址相同也是非法的
if(Dest == Src) return false;
// 校验剩余长度
ushort len = Length;
if(ms.Remain() < len + 2) return false;
// 避免错误指令超长,导致溢出
if(Data == _Data && len > ArrayLength(_Data))
{
debug_printf("错误指令,长度 %d 大于消息数据缓冲区长度 %d \r\n", len, ArrayLength(_Data));
return false;
}
//if(len > 0) ms.Read(Data, 0, len);
if(len > 0)
{
Buffer ds(Data, len);
ms.Read(ds);
}
// 读取真正的校验码
Checksum = ms.ReadUInt16();
// 计算Crc之前需要清零TTL和Retry
byte fs = p[3];
auto flag = (TFlags*)&p[3];
//flag->TTL = 0;
flag->Retry = 0;
// 连续的可以直接计算Crc16
Crc = Crc::Hash16(Buffer(p, HeaderSize + Length));
// 还原数据
p[3] = fs;
return true;
}
void TinyMessage::Write(Stream& ms) const
{
assert(Code, "微网指令码不能为空");
assert(Src, "微网源地址不能为空");
//assert(Src != Dest, "微网目的地址不能等于源地址");
if(Src == Dest) return;
TS("TinyMessage::Write");
ushort len = Length;
// 实际数据拷贝到占位符
auto p = (TinyMessage*)this;
p->_Code = Code;
p->_Length = len;
p->_Reply = Reply;
p->_Error = Error;
auto buf = ms.Current();
// 不要写入验证码
//ms.Write(&Dest, 0, HeaderSize);
ms.Write(Buffer((byte*)&Dest, HeaderSize));
if(len > 0) ms.Write(Buffer(Data, len));
// 计算Crc之前需要清零TTL和Retry
byte fs = buf[3];
auto flag = (TFlags*)&buf[3];
//flag->TTL = 0;
flag->Retry = 0;
p->Checksum = p->Crc = Crc::Hash16(Buffer(buf, HeaderSize + len));
// 还原数据
buf[3] = fs;
// 写入真正的校验码
ms.Write(Checksum);
}
// 验证消息校验码是否有效
bool TinyMessage::Valid() const
{
if(Checksum == Crc) return true;
msg_printf("Message::Valid Crc Error %04X != Checksum: %04X \r\n", Crc, Checksum);
#if MSG_DEBUG
msg_printf("指令校验错误 ");
Show();
#endif
return false;
}
// 消息所占据的指令数据大小。包括头部、负载数据和附加数据
uint TinyMessage::Size() const
{
return MinSize + Length;
}
uint TinyMessage::MaxDataSize() const
{
return Data == _Data ? ArrayLength(_Data) : Length;
}
void TinyMessage::Show() const
{
#if MSG_DEBUG
assert_ptr(this);
byte flag = *((byte*)&_Code + 1);
msg_printf("0x%02X => 0x%02X Code=0x%02X Flag=0x%02X Seq=0x%02X Retry=%d", Src, Dest, Code, flag, Seq, Retry);
if(flag)
{
if(Reply) msg_printf(" Reply");
if(Error) msg_printf(" Error");
if(Ack) msg_printf(" Ack");
if(NoAck) msg_printf(" NoAck");
}
ushort len = Length;
if(len > 0)
{
assert_ptr(Data);
msg_printf(" Data[%02d]=", len);
ByteArray(Data, len).Show();
}
if(Checksum != Crc) msg_printf(" Crc Error 0x%04x [%04X]", Crc, _REV16(Crc));
msg_printf("\r\n");
#endif
}
TinyMessage TinyMessage::CreateReply() const
{
TinyMessage msg;
msg.Dest = Src;
msg.Src = Dest;
msg.Code = Code;
msg.Reply = true;
msg.Seq = Seq;
msg.State = State;
#if MSG_DEBUG
msg.Retry = Retry;
#endif
return msg;
}
/*================================ 微网控制器 ================================*/
void SendTask(void* param);
void StatTask(void* param);
// 构造控制器
TinyController::TinyController() : Controller()
{
MinSize = TinyMessage::MinSize;
// 初始化一个随机地址
Address = Sys.ID[0];
// 如果地址为0则使用时间来随机一个
// 节点地址范围2~254网关专用0x01节点让步
while(Address < 2 || Address > 254)
{
Sys.Delay(30);
Address = Sys.Ms();
}
// 接收模式。0只收自己1接收自己和广播2接收所有。默认0
Mode = 0;
Interval = 20;
Timeout = 200;
auto cfg = TinyConfig::Create();
if(cfg)
{
//bool flag = false;
// 调整重发参数
if(cfg->Interval == 0 || cfg->Interval > 1000 || cfg->Timeout > 10000)
{
cfg->Interval = Interval;
cfg->Timeout = Timeout;
// flag = true;
}
if(cfg->Address == 0)
{
cfg->Address = Address;
// flag = true;
}
//if(flag) cfg->Save();
}
_taskID = 0;
_Queue = nullptr;
QueueLength = 8;
// 默认屏蔽心跳日志
Buffer(NoLogCodes, sizeof(NoLogCodes)).Clear();
NoLogCodes[0] = 0x03;
}
TinyController::~TinyController()
{
Sys.RemoveTask(_taskID);
delete[] _Queue;
_Queue = nullptr;
}
void TinyController::ApplyConfig()
{
auto cfg = TinyConfig::Create();
if(cfg)
{
// 调整参数
Interval = cfg->Interval;
Timeout = cfg->Timeout;
Address = cfg->Address;
}
}
void TinyController::Open()
{
if(Opened) return;
assert(Port, "还没有传输口呢");
#if MSG_DEBUG
debug_printf("TinyNet::Inited Address=%d (0x%02X) 使用传输接口 ", Address, Address);
auto obj = dynamic_cast<Object*>(Port);
if(obj) obj->Show(true);
//Port->Show(true);
debug_printf("\t间隔: %dms\r\n", Interval);
debug_printf("\t超时: %dms\r\n", Timeout);
debug_printf("\t模式: %d ", Mode);
#endif
// 接收模式。0只收自己1接收自己和广播2接收所有。默认0
switch(Mode)
{
case 0:
debug_printf("仅本地址");
break;
case 1:
debug_printf("本地址和广播");
break;
case 2:
debug_printf("接收所有");
break;
}
debug_printf("\r\n");
Controller::Open();
// 初始化发送队列
_Queue = new MessageNode[QueueLength];
//Buffer(_Queue, sizeof(*_Queue)).Clear();
Buffer(_Queue, sizeof(MessageNode) * QueueLength).Clear();
if(!_taskID)
{
_taskID = Sys.AddTask(SendTask, this, 0, 1, "微网队列");
// 默认禁用,有数据要发送才开启
Sys.SetTask(_taskID, false);
}
#if MSG_DEBUG
Sys.AddTask([](void* p){ return ((TinyController*)p)->ShowStat(); }, this, 1000, 15000, "微网统计");
#endif
}
void TinyController::ShowMessage(const TinyMessage& msg, bool send, const ITransport* port)
{
if(msg.Ack) return;
for(int i=0; i<ArrayLength(NoLogCodes); i++)
{
if(msg.Code == NoLogCodes[i]) return;
if(NoLogCodes[i] == 0) break;
}
#if MSG_DEBUG
String name ="R24";
auto obj = dynamic_cast<Object*>(Port);
if(obj)
name = obj->ToString();
msg_printf("%s", name.GetBuffer());
#endif
int blank = 6;
if(send && !msg.Reply)
{
msg_printf("::Send ");
blank -= 5;
}
else
msg_printf("::");
if(msg.Error)
{
msg_printf("Error ");
blank -= 6;
}
else if(msg.Ack)
{
msg_printf("Ack ");
blank -= 4;
}
else if(msg.Reply)
{
msg_printf("Reply ");
blank -= 6;
}
else if(!send)
{
msg_printf("Recv ");
blank -= 5;
}
if(blank > 0)
{
String str(' ', blank);
str.Show();
}
#if MSG_DEBUG
// 显示目标地址
auto st = msg.State;
if(st)
{
msg_printf("Mac=");
if(name == "R24")
ByteArray(st, 5).Show();
else if(name == "ShunCom")
ByteArray(st, 2).Show();
else
ByteArray(st, 6).Show();
msg_printf(" ");
}
#endif
msg.Show();
}
//加密。组网不加密,退网不加密
static bool Encrypt(Message& msg, Buffer& pass)
{
// 加解密。组网不加密,退网不加密
if(msg.Length > 0 && pass.Length() > 0 && !(msg.Code == 0x01 || msg.Code == 0x02))
{
Buffer bs(msg.Data, msg.Length);
RC4::Encrypt(bs, pass);
return true;
}
return false;
}
bool TinyController::Dispatch(Stream& ms, Message* pmsg, void* param)
{
/*byte* buf = ms.Current();
// 前移一个字节,确保不是自己的消息时,数据流能够移动
ms.Seek(1);
// 代码为0是非法的
if(!buf[2]) return true;
// 没有源地址是很不负责任的
if(!buf[1]) return true;
// 非广播包时,源地址和目的地址相同也是非法的
if(buf[0] == buf[1]) return false;
// 源地址是自己不要接收
if(buf[1] == Address) return true;
// 只处理本机消息或广播消息
//if(tmsg.Dest != Address && tmsg.Dest != 0) return false;
// 不是自己的消息,并且没有设置接收全部
if(Mode < 2 && buf[0] != Address)
{
// 如果不是广播或者不允许广播
if(Mode != 1 || buf[0] != 0) return true;
}
// 后移一个字节来弥补
ms.Seek(-1);*/
TinyMessage msg;
return Controller::Dispatch(ms, &msg, param);
}
// 收到消息校验后调用该函数。返回值决定消息是否有效,无效消息不交给处理器处理
bool TinyController::Valid(const Message& _msg)
{
auto& msg = (TinyMessage&)_msg;
// 代码为0是非法的
if(!msg.Code) return false;
// 没有源地址是很不负责任的
if(!msg.Src) return false;
//if(msg!=Address) return false;
// 非广播包时,源地址和目的地址相同也是非法的
if(msg.Dest == msg.Src) return false;
// 源地址是自己不要接收
if(msg.Src == Address) return false;
// 只处理本机消息或广播消息
//if(msg.Dest != Address && msg.Dest != 0) return false;
// 不是自己的消息,并且没有设置接收全部
if(Mode < 2 && msg.Dest != Address)
{
// 如果不是广播或者不允许广播
if(Mode != 1 || msg.Dest != 0) return false;
}
TS("TinyController::Valid");
#if MSG_DEBUG
// 调试版不过滤序列号为0的重复消息
if(msg.Seq != 0)
#endif
{
// Ack的包有可能重复不做处理。正式响应包跟前面的Ack有相同的源地址和序列号
if(!msg.Ack)
{
// 处理重复消息。加上来源地址,以免重复
ushort seq = (msg.Src << 8) | msg.Seq;
if(_Ring.Check(seq))
{
// 对方可能多次发同一个请求过来,都要做响应
if(!msg.Reply && AckResponse(msg)) return false;
//msg_printf("重复消息 Src=0x%02x Code=0x%02X Seq=0x%02X Retry=%d Reply=%d Ack=%d\r\n", msg.Src, msg.Code, msg.Seq, msg.Retry, msg.Reply, msg.Ack);
return false;
}
_Ring.Push(seq);
}
}
// 广播的响应消息也不要
if(msg.Dest == 0 && msg.Reply) return false;
// 仅处理本节点的确认消息
if(msg.Dest == Address)
{
// 如果是确认消息或响应消息,及时更新请求队列
if(msg.Ack)
{
AckRequest(msg);
// 如果只是确认消息,不做处理
return false;
}
// 响应消息消除请求,请求消息要求重发响应
if(msg.Reply)
AckRequest(msg);
else
{
if(AckResponse(msg))
{
debug_printf("匹配请求 ");
return false;
}
}
}
if(msg.Dest == Address)
{
ByteArray key(0);
GetKey(msg.Src, key, Param);
if(key.Length() > 0) Encrypt(msg, key);
}
#if MSG_DEBUG
// 尽量在Ack以后再输出日志加快Ack处理速度
ShowMessage(msg, false, Port);
#endif
Total.Receive++;
return true;
}
// 处理收到的响应包或Ack包消除请求
void TinyController::AckRequest(const TinyMessage& msg)
{
if(!msg.Ack && !msg.Reply) return;
for(int i=0; i<QueueLength; i++)
{
auto& node = _Queue[i];
if(node.Using && node.Seq == msg.Seq)
{
int cost = Sys.Ms() - node.StartTime;
if(cost < 0) cost = -cost;
Total.Cost += cost;
Total.Success++;
Total.Bytes += node.Length;
// 该传输口收到响应,从就绪队列中删除
node.Using = 0;
/*if(msg.Ack)
msg_printf("收到确认 ");
else
msg_printf("响应确认 ");
msg_printf("Src=0x%02x Code=0x%02X Seq=0x%02X Retry=%d Cost=%dms \r\n", msg.Src, msg.Code, msg.Seq, msg.Retry, cost);*/
return;
}
}
#if MSG_DEBUG
if(msg.Ack)
{
msg_printf("无效确认 ");
ShowMessage(msg, false, Port);
}
#endif
}
// 处理对方发出的请求,如果已响应则重发响应
bool TinyController::AckResponse(const TinyMessage& msg)
{
if(msg.Reply) return false;
// 广播消息不要给确认
if(msg.Dest == 0) return false;
TS("TinyController::AckResponse");
for(int i=0; i<QueueLength; i++)
{
auto& node = _Queue[i];
if(node.Using && node.Seq == msg.Seq)
{
// 马上重发这条响应
node.Next = 0;
Sys.SetTask(_taskID, true, 0);
//msg_printf("重发响应 ");
//msg_printf("Src=0x%02x Code=0x%02X Seq=0x%02X Retry=%d \r\n", msg.Src, msg.Code, msg.Seq, msg.Retry);
return true;
}
}
return false;
}
static byte _Sequence = 0;
// 发送消息,传输口参数为空时向所有传输口发送消息
bool TinyController::Send(Message& _msg)
{
auto& msg = (TinyMessage&)_msg;
// 附上自己的地址
msg.Src = Address;
// 附上序列号。响应消息保持序列号不变
if(!msg.Reply) msg.Seq = ++_Sequence;
ByteArray key;
GetKey(msg.Dest, key, Param);
if(key.Length() > 0) Encrypt(msg, key);
#if MSG_DEBUG
ShowMessage(msg, true, Port);
#endif
return Post(msg, -1);
}
bool TinyController::Reply(Message& _msg)
{
auto& msg = (TinyMessage&)_msg;
// 回复信息,源地址变成目的地址
if(msg.Dest == Address && msg.Src != Address) msg.Dest = msg.Src;
msg.Reply = 1;
return Send(msg);
}
// 广播消息,不等待响应和确认
bool TinyController::Broadcast(TinyMessage& msg)
{
TS("TinyController::Broadcast");
msg.NoAck = true;
msg.Src = Address;
msg.Dest = 0;
// 附上序列号。响应消息保持序列号不变
if(!msg.Reply) msg.Seq = ++_Sequence;
#if MSG_DEBUG
ShowMessage(msg, true, Port);
#endif
if(!Port->Open()) return false;
Total.Broadcast++;
return Controller::SendInternal(msg);
}
// 放入发送队列,超时之前,如果对方没有响应,会重复发送,-1表示采用系统默认超时时间Timeout
bool TinyController::Post(const TinyMessage& msg, int msTimeout)
{
TS("TinyController::Post");
// 如果没有传输口处于打开状态,则发送失败
if(!Port->Open()) return false;
if(msTimeout < 0) msTimeout = Timeout;
// 如果确定不需要响应则改用Post
if(msTimeout <= 0 || msg.NoAck || msg.Ack)
{
Total.Broadcast++;
return Controller::SendInternal(msg);
}
// 针对Zigbee等不需要Ack确认的通道
if(Timeout < 0)
{
Total.Broadcast++;
return Controller::SendInternal(msg);
}
// 准备消息队列
auto now = Sys.Ms();
int idx = -1;
for(int i=0; i<QueueLength; i++)
{
auto& node = _Queue[i];
// 未使用,或者即使使用也要抢走已过期的节点
if(!node.Using || node.EndTime < now)
{
node.Seq = 0;
node.Using = 1;
idx = i;
break;
}
}
// 队列已满
if(idx < 0)
{
debug_printf("TinyController::Post 发送队列已满! \r\n");
return false;
}
_Queue[idx].Set(msg, msTimeout);
if(msg.Reply)
Total.Reply++;
else
Total.Msg++;
Sys.SetTask(_taskID, true, 0);
return true;
}
void SendTask(void* param)
{
assert_ptr(param);
auto control = (TinyController*)param;
control->Loop();
}
void TinyController::Loop()
{
TS("TinyController::Loop");
/*
队列机制:
1定时发送请求消息直到被响应消去或者超时
2发送一次响应消息直到超时中途有该序列的请求则直接重发响应
3不管请求还是响应同样方式处理超时响应的下一次时间特别长
4如果某个请求序列对应的响应存在则直接将其下一次时间置为0让其马上重发
5发送响应消息不好统计速度和成功率
6发送次数不允许超过5次
*/
int count = 0;
for(int i=0; i<QueueLength; i++)
{
auto& node = _Queue[i];
if(!node.Using || node.Seq == 0) continue;
auto flag = (TFlags*)&node.Data[3];
bool reply = flag->_Reply;
// 可用请求消息数,需要继续轮询
if(!reply) count++;
// 检查时间。至少发送一次
if(node.Times > 0)
{
UInt64 now = Sys.Ms();
// 已过期则删除
if(node.EndTime < now || node.Times > 50)
{
//if(!reply) msg_printf("消息过期 Dest=0x%02X Seq=0x%02X Times=%d\r\n", node.Data[0], node.Seq, node.Times);
node.Using = 0;
node.Seq = 0;
continue;
}
// 下一次发送时间还没到,跳过
if(node.Next > now) continue;
//msg_printf("重发消息 Dest=0x%02X Seq=0x%02X Times=%d\r\n", node.Data[0], node.Seq, node.Times + 1);
}
node.Times++;
// 发送消息
Buffer bs(node.Data, node.Length);
if(node.Length <= 0 || node.Length > 32)
{
debug_printf("node=0x%08x Length=%d Seq=0x%02X Times=%d Next=%d EndTime=%d\r\n", &node, node.Length, node.Seq, node.Times, (uint)node.Next, (uint)node.EndTime);
}
if(node.Mac[0])
Port->Write(bs, &node.Mac[1]);
else
Port->Write(bs);
// 递增重试次数
flag->Retry++;
// 请求消息列入统计
if(!reply)
{
// 增加发送次数统计
Total.Send++;
// 分组统计
if(Total.Send >= 1000)
{
Last = Total;
Total.Clear();
}
}
// 计算下一次重发时间
{
UInt64 now = Sys.Ms();
//node.LastSend = now;
// 随机延迟。随机数1~5。每次延迟递增
//byte rnd = (uint)now % 3;
//node.Next = now + (rnd + 1) * Interval;
if(!reply)
node.Next = now + Interval;
else
node.Next = now + 60000;
}
}
// 没有可用请求时,停止轮询
if(count == 0) Sys.SetTask(_taskID, false);
}
/*void StatTask(void* param)
{
assert_ptr(param);
auto control = (TinyController*)param;
control->ShowStat();
}*/
// 显示统计信息
void TinyController::ShowStat() const
{
#if MSG_DEBUG
static uint lastSend = 0;
static uint lastReceive = 0;
int tsend = Total.Send;
if(tsend == lastSend && Total.Receive == lastReceive) return;
lastSend = tsend;
lastReceive = Total.Receive;
uint rate = 100;
uint tack = Last.Success + Total.Success;
uint tmsg = Last.Msg + Total.Msg;
uint tcost = Last.Cost + Total.Cost;
tsend += Last.Send;
if(tsend > 0)
rate = tack * 100 / tmsg;
uint cost = 0;
if(tack > 0)
cost = tcost / tack;
uint speed = 0;
if(tcost > 0)
speed = (Last.Bytes + Total.Bytes) * 1000 / tcost;
uint retry = 0;
if(tmsg > 0)
retry = tsend * 100 / tmsg;
msg_printf("Tiny::State 成功=%d%% %d/%d/%d 平均=%dms 速度=%d Byte/s 次数=%d.%02d 接收=%d 响应=%d 广播=%d \r\n", rate, tack, tmsg, tsend, cost, speed, retry/100, retry%100, Last.Receive + Total.Receive, Last.Reply + Total.Reply, Last.Broadcast + Total.Broadcast);
#endif
}
/*================================ 信息节点 ================================*/
void MessageNode::Set(const TinyMessage& msg, int msTimeout)
{
Times = 0;
//LastSend = 0;
UInt64 now = Sys.Ms();
StartTime = now;
EndTime = now + msTimeout;
Next = 0;
// 响应消息只发一次,然后长时间等待
if(msg.Reply) Next = now + 60000;
// 注意此时指针位于0而内容长度为缓冲区长度
Stream ms(Data, ArrayLength(Data));
msg.Write(ms);
Length = ms.Position();
if(Length > 32) debug_printf("Length=%d \r\n", Length);
Buffer mcs(Mac, ArrayLength(Mac));
mcs[0] = 0;
if(msg.State)
{
mcs[0] = mcs.Length();
mcs.Copy(1, msg.State, -1);
}
Seq = msg.Seq;
}
/*================================ 环形队列 ================================*/
RingQueue::RingQueue()
{
Index = 0;
Buffer(Arr, sizeof(Arr)).Clear();
Expired = 0;
}
void RingQueue::Push(ushort item)
{
Arr[Index++] = item;
if(Index == ArrayLength(Arr)) Index = 0;
// 更新过期时间10秒
Expired = Sys.Ms() + 10000;
}
int RingQueue::Find(ushort item) const
{
for(int i=0; i < ArrayLength(Arr); i++)
{
if(Arr[i] == item) return i;
}
return -1;
}
bool RingQueue::Check(ushort item)
{
// Expired为0说明还没有添加任何项
if(!Expired) return false;
// 首先检查是否过期。如果已过期,说明很长时间都没有收到消息
if(Expired < Sys.Ms())
{
//msg_printf("环形队列过期,清空 \r\n");
// 清空队列,重新开始
Index = 0;
Buffer(Arr, sizeof(Arr)).Clear();
Expired = 0;
return false;
}
// 再检查是否存在
return Find(item) >= 0;
}
TinyStat::TinyStat()
{
Clear();
}
TinyStat& TinyStat::operator=(const TinyStat& ts)
{
Buffer(this, sizeof(this[0])) = &ts;
return *this;
}
void TinyStat::Clear()
{
Buffer(this, sizeof(this[0])).Clear();
}