記事のディレクトリ
Haiyingモノのインターネットチュートリアル:モノのインターネットRPCフレームワークGo DCOM
この記事のブログリンク:http://blog.csdn.net/jdh99、著者:jdh、転載を指定してください。
コミュニティ交換へようこそ:Haiying Internet of Things Community
前書き
RPC:リモートプロシージャコール、リモートプロシージャコール。RPCを使用すると、あるコンピューター上のプログラムが別のコンピューター上のプログラムを呼び出すことができます。
RPCは、ネットワーク通信をリモートプロシージャコールに抽象化します。リモートプロシージャの呼び出しは、ローカルサブルーチンを呼び出すのと同じくらい便利です。これにより、通信の複雑さが回避され、開発者はネットワークプログラミングの詳細に注意を払うことなく、より多くの時間と時間を節約できます。作業効率を向上させるためのビジネスロジック自体。
DCOM:デバイス通信プロトコル(DCOM)、デバイス間の通信プロトコル。DCOMは、IoTの使用シナリオ用に開発されたRPCフレームワークであり、次の特徴があります。
- プロトコルのオーバーヘッドは非常に小さく、わずか4バイトです。モノのインターネットの多くのシナリオは、数十バイトの短いフレームです。RPCプロトコル自体のオーバーヘッドが大きすぎる場合、これらのシナリオには適用できません。
- 言語を超えて通信できます。DCOMプロトコルは設計の言語とは関係がなく、C、Golang、Pythonなどに関係なくDCOMを使用できます。
- 通信メディアを介して通信できます。DCOMプロトコルは、イーサネット、シリアルポート、wifi、小型ワイヤレスなどのすべての通信メディアで機能します。
Haiyingモノのインターネットでは、DCOMを使用してノード間で通信します。この記事では、Go言語で開発されたDCOMパッケージの使用方法を紹介します。
オープンソース
インストール
go modの使用をお勧めします:github.com/jdhxyy/dcom
インストール後、インポートしてプロジェクトで使用できます。
import "https://github.com/jdhxyy/dcom"
基本コンセプト
リソースID
リソースIDは、サービス番号とも呼ばれます。リソースID、または略してRIDです。ノードはサービス番号を使用して、独自の機能またはリソースを開きます。サービス番号の値は1〜1023で、合計は1023です。
たとえば、ノードはスマートソケットであり、次の2つの機能を開きます。
- ソケットスイッチ
- ソケットの現在の状態
2つのサービスを外部から提供できます。
サービス番号 | 説明 |
---|---|
1 | ソケットスイッチ |
2 | ソケットの現在の状態 |
契約番号
プロトコル番号プロトコル。各プロトコルはリソースIDのセットにバインドでき、異なるプロトコルのリソースIDを繰り返すことができます。
パイプライン
パイプ:パイプ。DCOM通信中、データストリームは特定のパイプにバインドでき、DCOMは複数のパイプの同時通信をサポートします。パイプには、UDPポート、TCPポート、シリアルポート、4G、ワイヤレスなどがあります。各パイプには、独立した送信および受信コールバックがあります。パイプ番号を0にすることはできません。64桁の番号です。
通信模型
DCOMには、主に応答通信と無応答通信の2つの通信モデルがあります。
一般的に使用されるのは、応答との通信です。応答が受信されない場合、DCOMは通信中に再送信を試みます。Call関数を使用して宛先ノードと通信するたびに、それは新しいセッションであり、各セッションには、データがクロストークしないようにするための一意のトークン値があります。
API
// Load 模块载入
func Load(param *LoadParam)
// Receive 接收数据
// 应用模块接收到数据后需调用本函数
// 本函数接收帧的格式为DCOM协议数据
func Receive(protocol int, pipe uint64, srcIA uint64, bytes []uint8)
// Register 注册服务回调函数
func Register(protocol int, rid int, callback CallbackFunc)
// Call RPC同步调用
// timeout是超时时间,单位:ms.为0表示不需要应答
// 返回值是应答字节流和错误码.错误码非SystemOK表示调用失败
func Call(protocol int, pipe uint64, dstIA uint64, rid int, timeout int, req []uint8) ([]uint8, int)
- 補助機能
// StructToBytes 结构体转字节流
func StructToBytes(s interface{
}) ([]uint8, error)
// BytesToStruct 字节流转结构体 结构体中的元素首字母要求大写
// s是结构体指针,保存转换后的结构体
func BytesToStruct(data []uint8, s interface{
}) error
- データ構造
// LoadParam 载入参数
type LoadParam struct {
// 块传输帧重试间隔.单位:ms
BlockRetryInterval int
// 块传输帧重试最大次数
BlockRetryMaxNum int
// API接口
// 是否允许发送
IsAllowSend IsAllowSendFuncByPipeFunc
// 发送的是DCOM协议数据
Send SendByPipeFunc
}
// IsAllowSendFuncByPipeFunc 某管道是否允许发送函数类型
type IsAllowSendFuncByPipeFunc func(pipe uint64) bool
// SendByPipeFunc 向指定端口发送函数类型
type SendByPipeFunc func(protocol int, pipe uint64, dstIA uint64, bytes []uint8)
// CallbackFunc 注册DCOM服务回调函数
// 返回值是应答和错误码.错误码为0表示回调成功,否则是错误码
type CallbackFunc func(pipe uint64, srcIA uint64, req []uint8) ([]uint8, int)
- システムエラーコード
const (
// 正确值
SystemOK = 0
// 接收超时
SystemErrorRxTimeout = 0x10
// 发送超时
SystemErrorTxTimeout = 0x11
// 内存不足
SystemErrorNotEnoughMemory = 0x12
// 没有对应的资源ID
SystemErrorInvalidRid = 0x13
// 块传输校验错误
SystemErrorWrongBlockCheck = 0x14
// 块传输偏移地址错误
SystemErrorWrongBlockOffset = 0x15
// 参数错误
SystemErrorParamInvalid = 0x16
)
ロード:モジュールのロード
DCOMを使用する前に初期化する必要があります。DCOMは再送信をサポートしているため、初期化中に再送信間隔と再送信の最大数を入力する必要があります。
DCOMは通信媒体とは何の関係もありません。媒体が異なれば、パイプ番号も異なります。アプリケーションは、送信関数(IsAllowSend)と送信関数(Send)を許可するかどうかについて、異なるパイプライン操作を作成する必要があります。
- 例:ノードには2つのパイプラインがあります
func main() {
var param dcom.LoadParam
param.BlockRetryMaxNum = 5
param.BlockRetryInterval = 1000
param.IsAllowSend = isAllowSend
param.Send = send
dcom.Load(¶m)
}
func isAllowSend(pipe uin64) bool {
if pipe == 1 {
return isPipe1AllowSend()
} else {
return isPipe2AllowSend()
}
}
func send(protocol int, pipe uint64, dstIA uint64, data []uint8) {
if pipe == 1 {
pipe1Send(data)
} else {
pipe2Send(data)
}
}
プロトコルやdstIAなどのフィールドは、要件に従って処理されます。
データを受信する
アプリケーションプログラムはデータを受信し、Receive関数を呼び出してデータをDCOMに送信する必要があります。
- 例:ノードは両方のチャネルを受信できます
func pipe1Receive(data []uint8) {
dcom.Receive(0, 1, 0x2140000000000101, data)
}
func pipe2Receive(data []uint8) {
dcom.Receive(0, 2, 0x2140000000000101, data)
}
プロトコル例のプロトコル番号は0であり、アプリケーションの実際のシナリオに従って入力する必要があります。
登録:サービス登録
ノードは、登録サービスを通じて機能を開くことができます。
- 例:ノード2140 :: 101がスマートソケットであると仮定すると、ノードは2つのサービスを提供します。制御と読み取りスイッチのステータスです。
dcom.Register(0, 1, controlService)
dcom.Register(0, 2, getStateService)
// controlService 控制开关服务
// 返回值是应答和错误码.错误码为0表示回调成功,否则是错误码
func controlService(pipe uint64, srcIA uint64, req []uint8) ([]uint8, int) {
if req[0] == 0 {
off()
} else {
on()
}
return nil, dcom.SystemOK
}
// getStateService 读取开关状态服务
// 返回值是应答和错误码.错误码为0表示回调成功,否则是错误码
func getStateService(pipe uint64, srcIA uint64, req []uint8) ([]uint8, int) {
return []uint8{
state()}, dcom.SystemOK
}
呼び出し:同期呼び出し
// Call RPC同步调用
// timeout是超时时间,单位:ms.为0表示不需要应答
// 返回值是应答字节流和错误码.错误码非SystemOK表示调用失败
func Call(protocol int, pipe uint64, dstIA uint64, rid int, timeout int, req []uint8) ([]uint8, int)
同期呼び出しは、結果が得られるまでブロックされます。ノードは、同期呼び出しを介してターゲットノードの関数またはサービスを呼び出すことができます。タイムアウトフィールドは、ミリ秒単位のタイムアウト期間です。タイムアウト後にターゲットノードが応答しない場合、呼び出しは失敗します。タイムアウト期間が0で満たされている場合は、ターゲットノードが応答する必要がないことを意味します。
- 例:2141 :: 102ノードはスマートソケットを制御します2141 :: 101スイッチの状態はオンです
resp, errCode := dcom.Call(0, 1, 0x2140000000000101, 3000, []uint8{
1})
- 例:2141 :: 102ノードは、スマートソケット2141 :: 101のスイッチステータスを読み取ります
resp, errCode := dcom.Call(0, 2, 0x2140000000000101, 3000, nil)
if errCode == dcom.SystemOK {
fmt.println("开关状态:", resp[0])
}
要求と応答のデータ形式
DCOM通信の両方の当事者によって送信されるデータストリームはバイナリであり、要求(req)と応答(resp)のデータ型は両方とも[] uint8です。
バイナリはアプリケーションの処理に役立ちません。そのため、処理のために他のデータ型に変換されます。一般的に使用されるものは3つあります。
- 構造
- json
- ストリング
モノのインターネットでは、ハードウェアノードのリソースは限られており、それらのほとんどはC言語で書かれています。したがって、通信にはC言語構造を使用することをお勧めします。この構造は、1バイトのアライメント、リトルエンディアンモードを使用することに同意しています。
たとえば、Haiying Internet ofThingsのntpサービスによって提供されるデータ構造は次のとおりです。
struct {
// 时区
uint8 TimeZone
uint16 Year
uint8 Month
uint8 Day
uint8 Hour
uint8 Minute
uint8 Second
// 星期
uint8 Weekday
}
DCOMは、構造とバイナリ変換のための関数を提供します。
// StructToBytes 结构体转字节流
func StructToBytes(s interface{
}) ([]uint8, error)
// BytesToStruct 字节流转结构体 结构体中的元素首字母要求大写
// s是结构体指针,保存转换后的结构体
func BytesToStruct(data []uint8, s interface{
}) error
注:goでこれらの構造を定義するには、属性を大文字にする必要があります。大文字にしないと、バイナリへの変換が失敗します。
- 例:時間構造をバイナリに変換する
// ACK格式
type Time struct {
// 时区
TimeZone uint8
Year uint16
Month uint8
Day uint8
Hour uint8
Minute uint8
Second uint8
// 星期
Weekday uint8
}
t := Time{
8, 2021, 4, 1, 7, 32, 1, 4}
data := StructToBytes(t)
- 例:バイナリを構造に変換する
var t Time
err := BytesToStruct(data, &t)
規則:バイナリ通信をDCOM通信で直接使用する場合は、ビッグエンディアンモードを使用することをお勧めします。構造化通信を使用する場合、構造エンコーディングはリトルエンディアンです。送信が混在している場合、バイナリと構造の両方があります。バイナリ部分はビッグエンディアンを使用し、構造部分はリトルエンディアンを使用します。