SmartOS/TinyNet/TinyMessage.h

199 lines
6.7 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.

#ifndef __TinyMessage_H__
#define __TinyMessage_H__
#include "Sys.h"
#include "List.h"
#include "Net\ITransport.h"
#include "Stream.h"
#include "Timer.h"
#include "Message.h"
#include "Controller.h"
// 消息
// 头部按照内存布局,但是数据和校验部分不是
// 请求 0038-0403-0000-BC4C从0x38发往0广播功能4标识3保留字段用于业务序号0长度0校验0x4CBC小字节序
// 响应 3856-048x-0000-xxxx
// 错误 3856-044x-0000
// 负载 0038-1000-0003-030303-A936从0x38广播功能4长度3负载03-03-03
class TinyMessage : public Message
{
private:
public:
// 标准头部符合内存布局。注意凑够4字节否则会有很头疼的对齐问题
byte Dest; // 目的地址
byte Src; // 源地址
byte _Code; // 功能代码
byte Flags:3; // 标识位。也可以用来做二级命令
byte UseTTL:1; // 使用TTL。具体TTL值位于数据包最后
byte NoAck:1; // 是否不需要确认包
byte Ack:1; // 确认包
byte Error:1; // 是否错误
byte _Reply:1; // 是否响应
byte Sequence; // 序列号
byte _Length; // 数据长度
byte _Data[32]; // 数据部分
ushort Checksum;// 16位检验和
// 可选的附加数据紧跟在头数据后面,可能直接读取内存区域
byte TTL; // 路由生命周期。为方便路由不参与Crc校验
#if DEBUG
byte Retry; // 调试诊断模式下该字段表示第几次重发
#endif
// 负载数据及校验部分,并非内存布局。
ushort Crc; // 整个消息的Crc16校验计算前Checksum清零
static const uint HeaderSize = 1 + 1 + 1 + 1 + 1 + 1; // 消息头部大小
static const uint MinSize = HeaderSize + 0 + 2; // 最小消息大小
public:
// 初始化消息各字段为0
TinyMessage(byte code = 0);
TinyMessage(TinyMessage& msg);
// 消息所占据的指令数据大小。包括头部、负载数据和附加数据
virtual uint Size() const;
// 分析数据,转为消息。负载数据部分将指向数据区,外部不要提前释放内存
virtual bool Read(Stream& ms);
// 写入指定数据流
virtual void Write(Stream& ms);
// 验证消息校验码是否有效
virtual bool Valid() const;
// 计算当前消息的Crc
virtual void ComputeCrc();
// 显示消息内容
virtual void Show() const;
};
class MessageNode;
class RingQueue;
// 环形队列。记录收到消息的序列号,防止短时间内重复处理消息
class RingQueue
{
public:
int Index;
ushort Arr[32];
ulong Expired; // 过期时间,微秒
RingQueue();
void Push(ushort item);
int Find(ushort item);
bool Check(ushort item);
};
// 统计信息
class Statistics
{
public:
uint Send; // 总次数
uint Ack; // 总成功
uint Bytes; // 总字节数
uint Cost; // 从开销
uint Retry; // 总重试次数
uint Msg; // 总消息数
Statistics()
{
memset(this, 0, sizeof(this));
}
};
// 消息控制器。负责发送消息、接收消息、分发消息
class TinyController : public Controller
{
private:
FixedArray<MessageNode, 16> _Queue; // 消息队列。最多允许16个消息同时等待响应
uint _Sequence; // 控制器的消息序号
RingQueue _Ring; // 环形队列
uint _taskID; // 发送队列任务
void AckRequest(TinyMessage& msg, ITransport* port); // 处理收到的Ack包
void AckResponse(TinyMessage& msg, ITransport* port); // 向对方发出Ack包
protected:
virtual bool Dispatch(Stream& ms, Message* pmsg, ITransport* port);
// 收到消息校验后调用该函数。返回值决定消息是否有效,无效消息不交给处理器处理
virtual bool Valid(Message& msg, ITransport* port);
public:
byte Address; // 本地地址
uint Interval; // 消息队列发送间隔,微秒
uint Timeout; // 消息队列发送消息的默认超时时间50毫秒
TinyController();
virtual ~TinyController();
virtual void Open();
// 创建消息
virtual Message* Create() const;
// 发送消息,传输口参数为空时向所有传输口发送消息
virtual int Send(Message& msg, ITransport* port = NULL);
// 发送消息,传输口参数为空时向所有传输口发送消息
uint Post(byte dest, byte code, byte* buf = NULL, uint len = 0, ITransport* port = NULL);
// 把消息放入发送队列timerout毫秒超时时间内如果对方没有响应会重复发送
bool Post(TinyMessage& msg, int expire = -1, ITransport* port = NULL);
// 回复对方的请求消息
virtual int Reply(Message& msg, ITransport* port = NULL);
//bool Error(TinyMessage& msg, ITransport* port = NULL);
// 循环处理待发送的消息队列
void Loop();
public:
// 统计。平均值=(LastCost + TotalCost)/(LastSend + TotalSend)。每一组完成以后TotalXXX整体复制给LastXXX
Statistics Total; // 总统计
Statistics Last; // 最后一次统计
// 显示统计信息
void ShowStat();
};
// 消息队列。需要等待响应的消息,进入消息队列处理。
class MessageNode
{
public:
List<ITransport*> Ports; // 未收到响应消息的传输口
//byte PortCount; // 传输口数
byte Sequence; // 序列号
byte Data[32];
uint Length;
uint Interval; // 延迟间隔。每次逐步递增
ulong StartTime; // 开始时间
ulong Next; // 下一次重发时间
ulong Expired; // 过期时间,微秒
uint Times; // 发送次数
ulong LastSend; // 最后一次发送时间
void SetMessage(TinyMessage& msg);
};
#endif
/*
微网消息协议
微网协议是一个针对微型广播网进行通讯而开发的协议网络节点最大255个消息最大32字节。
每一个网络节点随时都可以发送消息,而可能会出现冲突,因此要求各节点快速发送小数据包,并且要有错误重发机制。
每个消息都有序列号,以免收到重复消息,特别是在启用错误重发机制时。
消息类型:
1普通请求。Reply=0 NoAck=0对方收到后处理业务逻辑然后普通响应不回复也行。用于不重要的数据包比如广播。
2普通响应。Reply=1 NoAck=0收到普通请求处理业务逻辑后响应。也不管对方有没有收到。
3增强请求。Reply=0 NoAck=1对方收到后马上发出Ack告诉发送方已确认收到否则发送方认为出错进行重发。
4增强响应。Reply=1 NoAck=1业务处理后做出响应要求对方发送Ack确认收到该数据包否则出错重发。
显然增强请求将会收到两个响应一个为Ack另一个带业务数据负载当然后一个也可以取消。
控制器不会把Ack的响应传递给业务层。
*/