English Русский Español Deutsch Português
preview
MetaTrader 5用のMQTTクライアントの開発:TDDアプローチ(第4回)

MetaTrader 5用のMQTTクライアントの開発:TDDアプローチ(第4回)

MetaTrader 5統合 | 23 2月 2024, 16:48
492 1
Jocimar Lopes
Jocimar Lopes

「ソフトウェア要素は、拡張に対しては開いており、修正に対しては閉じているべきである。」(オブジェクト指向プログラミングの開放/閉鎖原則)

はじめに

目線を揃えるために、簡単な復習をしておくと役に立つかもしれません。本連載の最初の記事では、MQTT Control Packetの固定ヘッダーを生成できる関数の素朴なテストを書き始めました。最初のバイトの最初のビットから始めました。ベイビーステップがあるとすれば、それがそのひとつであることは間違いません。

第2回では、いくつかの共有関数と定義を2つのヘッダーファイルに整理しました。

第3回では、CONNACKのAcknowledgeフラグとConnect Reason Codeを読み始めました。これは、スタンダードの操作時動作セクションとの最初の接触でした。 

この時点までは、すべてが静的で、接続の試みに縛られていました。もし最初のバイトが間違っていれば、証券会社の失敗応答を受け取り、整形式のパケットで接続を再試行する場合があります。 

プロパティはまた別の話です。これらはMQTTアプリケーションメッセージの動的属性であり、接続後に変更することができます。証券会社のMaximum QoSは、運営上の理由で一時的に変更されることがあります。ネットワークのボトルネックにより、Receive Maximumが変更された可能性があります。それに加えて、Content-Type、すべてのWillプロパティ、User Propertyのように、いくつかのプロパティは設計上動的です。これらの特性は常に変化していきます。

OASISスタンダードの仕様はかなり明確ですが、現在のプロパティをどのように読み取り、反応させ、永続化し、更新するかは、クライアント開発者が決める余地が多く残されています。ところで、セッションの状態を管理するための永続化層は、100%クライアント開発者の責任です。セッション間のプロパティを適切に管理するために、永続化層を実装しなければなりません。ここでのアルゴリズムの選択は、適合性、ロバスト性、パフォーマンスにとって非常に重要です。

将来のライブラリメンテナーのために、「MQL5でMQTT v5.0のプロパティを読み取る方法」のセクションのコメントは、非公式なドキュメントとして役に立つかもしれません。ライブラリのエンドユーザーも、これらのコメントから恩恵を受けるかもしれません。このセクションでは、ライブラリ開発者の視点からプロパティを見ていきます。ここでは、データ型、識別子、バイト配列上の位置を扱い、それらをどのように読み取るかをよりよく説明します。以下では、ライブラリ利用者の視点からプロパティを見ていきます。それぞれの使用例におけるセマンティクスを説明しましょう。

注:特に断りのない限り、引用はすべてOASISスタンダードからのものです。 


MQTT v5.0のプロパティについて

プロパティは、MQTT v5.0で追加された「拡張性メカニズム」の一部です。このメジャーアップグレード前の最新バージョンであるv3.1.1には存在しませんでした。これらはMQTT v5.0ではどこにでもあります。しかし、MQTTプロパティとは何でしょうか。何のプロパティでしょうか。

その答えは「アプリケーションメッセージのプロパティ」です。OASIS標準の用語では、アプリケーションメッセージは次のように呼ばれます。

「アプリケーションのためにネットワークを介してMQTTプロトコルによって運ばれるデータ。アプリケーションメッセージがMQTTによって伝送されるとき、それはペイロードデータ、サービス品質(QoS)、プロパティのコレクション、およびトピック名を含みます(強調部分は著者によります)。

下の図1の「ペイロードデータ」を表す黄色い四角形を見てください。ここで注目していただきたいのは、重要な用語の違いです。

MQTT5.0アプリケーションメッセージ抽象図

図.01:MQTT5.0アプリケーションメッセージ抽象図

メッセージ共有プロトコルの文脈では、「メッセージ」という単語を目にしたとき、私たちはユーザーメッセージ(頻繁にテキストメッセージ)を思い浮かべることに慣れています。多くの場合、アプリケーション全体としてのメッセージについては考えません。 

しかし、ここでは、MQTTを介してユーザーから送信されるメッセージは、ペイロードデータの一部であり、プロパティはアプリケーションメッセージというプロトコルの抽象モデルの一部です。したがって、MQTT経由でユーザーメッセージを送信する場合、その「ユーザーメッセージ」に関連するプロパティだけでなく、アプリケーションメッセージ全体に関連するプロパティも持つことができます。たとえば、接続に関するプロパティ、公開に関するプロパティ、トピックの購読および購読解除に関するプロパティ、認証に関するプロパティなどです。

その他に、Will Messageに付随するWillプロパティがあります。

「Will MessageはCONNECTペイロードのWillプロパティ、Willトピック、Willペイロードフィールドから構成されます。」

プロトコルを実装し始めるとこの専門用語が少し紛らわしくなるかもしれませんが、可能な限り明確にするよう最善を尽くすつもりです。


プロパティの用途

ペイロードメタデータを運ぶ以外に、プロパティはクライアントとサーバー(証券会社)間の相互作用のあらゆる面、また異なるクライアント間の相互作用を設定するために使用することができます。接続から切断に至るまで、コンテンツタイプフォーマットの設定、証券会社への情報要求、メッセージの有効期限の定義、認証方法の選択、さらにはサーバーのリダイレクトなど、さまざまな使用例があります。以下の表からわかるように、PINGREQとPINGRESPパケットは、KeepAlive期間をリフレッシュするために使用される以外は、すべてのパケットタイプは、パケットのコンテキストに応じて、いくつかの特定のプロパティを持つことができます。

User Propertyは、すべてのパケットで使用可能なプロパティの特殊なケースであり、その意味はアプリケーションによって定義されます。つまり、そのセマンティクスはプロトコルによって定義されていません。プロトコルを拡張するためにプロパティをどのように使用できるかについて話す最後のセクションで、User Propertyについて簡単に紹介します。

プロパティの名前は目的が明示されていますが、次を知る必要があります。

  • 使用できる場合
  • 使用しなければならない場合
  • その設定を誤るとどうなるか

この後の説明を読みやすく理解しやすくするため、以下の表では機能別に色分けしています。プロパティの使用は異なるパケットタイプ間で重複しているため、グループ分けは多少恣意的であることに注意してください。

以下の説明では、OASISスタンダードで使用されている必要(MUST)と可能(MAY)という用語を使用します。OASISスタンダードは、IETF RFC 2119で説明されているようにこれらの用語を使用します。


接続プロパティ
CONNECT Session Expiry Interval、Receive Maximum、Maximum Packet Size、Topic Alias Maximum、Request Response Information、Request Problem Information、User Property、Authentication Method、Authentication Method
CONNECTペイロード Will Delay Interval、Payload Format Indicator、Message Expiry Interval、Content Type、Response Topic、Correlation Data、User Property
CONNACK Session Expiry Interval、Receive Maximum、Maximum QoS、Retain Available、Maximum Packet Size、Assigned Client Identifier、Topic Alias Maximum、Reason String、User Property、Wildcard Subscription Available、Subscriptions Identifiers Available、Shared Subscription Available、Server Keep Alive、Response Information、Server Reference、Authentication Method、Authentication Method
DISCONNECT Session Expiry Interval、Reason String、User Property、Server Reference

表1:MQTT v5.0-機能別にグループ化されたプロパティ-接続プロパティ

Assigned Client Identifier:CONNECT時に設定しなければならない[MUST]。設定されていない場合、証券会社はCONNACKで識別子を割り当ててもよいです[MAY]。

クライアント識別子はCONNECTパケットでは必須です。ただし、証券会社は長さ0バイトの識別子を受け入れ、クライアントに識別子を割り当てることが許されます。この場合、証券会社はCONNACKパケットでこのプロパティを返します。

Maximum Packet Size:設定しなくてもよい[MAY]が、ゼロに設定することはできません。

Maximum Packet Sizeは、「MQTT Control Packetの総バイト数」です。これは、クライアントと証券会社が、受け入れ可能なMaximum Packet Sizeを定義するために使用されます。クライアントがこのプロパティを設定し、証券会社がこの制限を超えるパケットを送信した場合、Reason Code 0x95 (Packet too large)でDISCONNECTしなければなりません。証券会社は、Reason String(複数可)または一部のUser Propertyを含めるとパケットがこのプロパティより大きくなる場合は、送信しません。

Maximum QoS:CONNACKで証券会社が使用します。設定しなくてもよいです[MAY]。

Maximum QoSは、証券会社がQoSレベルを処理できることをクライアントに知らせます。証券会社がQoSレベル2を受け入れる場合、このプロパティは設定されません。クライアントは、より高いQoSレベルのPUBLISHパケットを送らないことで、このサーバーの制限に従わなければなりません[MUST]。私たちのクライアントでは、QoSレベル0のみをサポートすることが許されています。

規範的でないコメント

「クライアントはQoS1またはQoS2のPUBLISHパケットをサポートする必要はありません。この場合、クライアントは、それが送るすべてのSUBSCRIBEコマンドのMaximum QoSフィールドを、サポートできる値に制限するだけです。

Message Expiry Interval:設定しなくてもよいです[MAY]。

Message Expiry IntervalもPUBLISHプロパティの一部です。Will Messageの寿命を設定します。設定されていない場合、証券会社がWill Messageを発行する際、有効期限は定義されません。

「Message Expiry Intervalが過ぎていて、サーバーがマッチする加入者に対するオンワード配送を開始できていない場合、サーバーはその加入者のメッセージのコピーを削除しなければなりません[MUST]」

また、CONNECTペイロードのWillプロパティで設定することもできます。設定されていない場合、Messageは期限切れになりません。

Payload Format Indicator:文字データを送信する場合は設定しなければなりません[MUST]。

Payload Format Indicatorは、CONNECTペイロードのWillプロパティで設定できます。ここでは、Will MessageがUTF-8エンコードされた文字データであるか、あるいは「不特定のバイト」であるかを示します。 

「サーバーはWill Messageが示されたフォーマットであることを検証してもよく[MAY]、そうでない場合は、Reason Code of 0x99 (Payload format invalid)の CONNACKを送信してもよいです[MAY]。」

PUBLISHプロパティの一部であり、ペイロードのフォーマットを示します。証券会社の検証もオプションです。しかし、それがペイロードフォーマットを検証する場合、もしそのフォーマットがアドバタイズされたものと異なっていれば、同じReason Code(0x99)を持つPUBACK、PUBREC、またはDISCONNECTを期待することができます。

設定されていない場合、ペイロードフォーマットは「不特定のバイト」であると仮定されます。つまり、文字データを送信する場合、このプロパティの設定は必須です。

Reason String:クライアントまたは証券会社によるすべてのACK、DISCONNECT、AUTHで使用してもよいです[MAY]。

Reason Stringは、MQTT v5.0の新機能の1つです。これは、人間が読める診断ツールで理由コードを補足するために使用されます。もしあれば、例えばログに使うことができます。CONNECTプロパティでRequest Problem Informationをゼロに設定することで、証券会社にそれを送信しないように要求することができます。 

Receive Maximum:設定しなくてもよいです[MAY]。ゼロに設定することはできません。 

クライアントはCONNECTのこのプロパティを使用して、現在のネットワーク接続で同時に処理するQoS1とQoS2のパブリケーションの数を制限することができます。証券会社はCONNACKに設定することができます。設定されていない場合、デフォルトは65,535です。

QoS0では、PUBACK (QoS 1)やPUBCOMP (QoS 2)を待つ必要はありません。なぜなら、ご存知のとおり、QoS 0 は「火をつけて忘れる」だからです。このプロパティは、対応するPUBACKまたはPUBCOMPを受信するまでに、クライアントまたは証券会社が送受信するメッセージ数を設定します。これは、新しいメッセージが送信できるようになるまで、いくつのメッセージを「確認待ち」状態にすることができるかを示す手段と考えることができます。

Request Problem Information:CONNECT時に設定してもよいです[MAY]。

Request Problem Informationは、失敗した場合にReason String(複数可)とUser Propertyを受け取りたいことをサーバーに通知するために使用されます。もし私たちが何も言わなければ(設定しなければ)、証券会社はそれを送ることができます。

Request Response Information:CONNECT時に設定してもよいです[MAY]。

Request Response Informationは、通常のpub/subインタラクションの代わりに、MQTTを介したリクエスト/レスポンスインタラクションの一部です。未設定の場合、証券会社に応答情報を送信してほしくないことを通知します。そうでなければ、証券会社がその応答を送ってくることを許可することになります。証券会社は、こちらが要求しても応答情報を送信しないことが許されていることに注意。このプロパティがない場合、値のデフォルトは未設定です。

Response Information:CONNECT時に設定してもよいです[MAY]。

Response Informationは、通常のpub/subインタラクションではなく、MQTT経由のRequest/Responseインタラクションの一部です。 

規範的でないコメント

「この一般的な使用法は、少なくともセッションの有効期間中、このクライアントのために予約されているトピックツリーのグローバルに一意な部分を渡すことです。リクエストクライアントと応答クライアントの両方がその名前を使用する権限を持つ必要があるため、多くの場合、ランダムな名前にすることはできません。通常、特定のクライアントのトピックツリーのルートとして使用します。サーバーがこの情報を返すためには、通常、正しく設定されている必要があります。この仕組みを使うことで、この設定を各クライアントでおこなうのではなく、サーバーで一度だけおこなうことができます。」

Response Topic:CONNECT時またはPUBLISH時に設定してもよいです[MAY]。 

Response Topicは、通常のpub/subインタラクションではなく、MQTT経由のRequest/Responseインタラクションの一部です。これを含めると、証券会社はWill Messageをリクエストとして解釈します。SUBSCRIBEパケットで使用されるTopic Filterとは異なり、Response Topicはワイルドカード文字を持つことができません。

「Request Messageは、Response Topicを持つApplication Messageです。

つまり、これはApplication Messageがリクエスト/レスポンス相互作用の一部であることを特徴づけるプロパティです。

Retain Available:CONNACK時に存在する可能性があります[MAY]。

Retain Availableプロパティは、証券会社が保持メッセージをサポートしているかどうかをクライアントに通知します。不在の場合はリテンションメッセージが利用できます。

Server Keep Alive:CONNACKに存在する可能性があります[MAY]。

Server Keep Aliveプロパティは、CONNECT時に要求されるクライアントKeep Aliveよりも優先されます。このプロパティがCONNACKに存在しない場合は、私たちのKeep Aliveを使うことができます。そうでない場合は、Server Keep Aliveのルールが適用されます。

Server Reference:CONNACK時またはDISCONNECT時に存在する可能性があります[MAY]。

Server Referenceは、サーバーのリダイレクトをクライアントに通知します。一時的または恒久的な方向転換を指すこともあります。どちらの場合も、相手サーバーはすでにクライアントに知られているか、このプロパティを使って指定されます。

規範的でないコメント

Server Referenceの例は以下の通りです。

myserver.xyz.org

myserver.xyz.org:8883

10.10.151.22:8883 [fe80::9610:3eff:fe1c]:1883

証券会社はこのプロパティを決して送らないことが許され、クライアントはこのプロパティを無視することが許されます。

Session Expiry Interval:CONNECT時に設定してもよいです[MAY]。

Session Expiry Intervalは、切断後にセッションを保持する期間を決定します。未設定または不在の場合、接続が閉じられた時点でセッションは終了します。このプロパティをUINT_MAXに設定することで、セッションを期限切れにしないように設定することが可能です。このプロパティが0より大きい場合、セッション状態を保存しなければなりません。CONNACKのSession Presentフラグで確認できます。

このプロパティは、ネットワーク接続が断続的な場合に便利で、ネットワーク接続が再開するたびに、クライアントがセッションを再開できるようにします。

Shared Subscription Available:CONNACK時に存在する可能性があります[MAY]。

Shared Subscription Availableは、証券会社が共有サブスクリプションをサポートしているかどうかをクライアントに通知します。ない場合は、証券会社がサポートしていることになります。

Subscription Identifiers Available:CONNACK時に存在する可能性があります[MAY]。

Subscription Identifiers Availableは、証券会社がSubscription Identifierをサポートしているかどうかをクライアントに通知します。ない場合は、証券会社がサポートしていることになります。

Topic Alias Maximum:CONNECT時に設定してもよく[MAY]、CONNACK時に存在する可能性があります[MAY]。

Topic Alias Maximumは、クライアントがこの特定の接続で受け入れたいTopic Aliasの最大数を証券会社に通知します。これをゼロに設定するか空白にすると、証券会社はこの接続でTopic Aliasを送信しません。逆もまた真です。このプロパティがCONNACKに存在しないか、存在してもその値がゼロである場合、クライアントはいかなるTopic Aliasも送信してはなりません。

Wildcard Subscription Available:CONNACKに存在する可能性があります[MAY]。

このプロパティが未設定(ゼロに設定)の場合、証券会社はワイルドカードサブスクリプションをサポートしません。この場合、証券会社はワイルドカードサブスクリプションを要求するSUBSCRIBEを受信した後、DISCONNECTします。しかし、証券会社がその機能をサポートしていたとしても、ワイルドカードサブスクリプションを含む特定のサブスクライブリクエストを拒否し、同じReason Code0xA2 (Wildcard Subscriptions not supported)を持つSUBACKを返すことは許可されています。CONNACKにそのプロパティがない場合、証券会社はその機能をサポートしています。

Will Delay Interval:CONNECTペイロードのWillプロパティで設定してもよいです[MAY]。

このプロパティは、Will Messageを送信する前に証券会社が監視する遅延時間を秒単位で設定します。このプロパティは、不安定または断続的なネットワーク接続下でWill Messageの送信を回避するために特に有用です。


公開プロパティ
PUBLISH Payload Format Indicator、Message Expiry Interval、Topic Alias、Response Topic、Correlation Data、User Property、Subscription Identifier、Content Type
PUBACK Reason String、User Property
PUBREC Reason String、User Property
PUBREL Reason String、User Property
PUBCOMP Reason String、User Property

表2:MQTT v5.0-機能別にグループ化されたプロパティ-公開プロパティ

Topic Alias-パブリッシュ時に設定してもよいです[MAY]。

Topic AliasもMQTT v5.0の新機能です。これにより、証券会社またはクライアントは、Topic Nameを小さな整数(エイリアス)に置き換えることで、パケットのサイズを小さくすることができます。トピック名は65,535バイト(UINT_MAX)まで拡張可能な文字列であるため、オーバーヘッドの削減は表現力豊かです。

Correlation Data:PUBLISHおよびWillプロパティで設定してもよいです[MAY]。

Correlation Dataは、通常のpub/subインタラクションではなく、MQTTを介したRequest/Responseインタラクションの一部です。その値はアプリケーション(証券会社とクライアント)に対してのみ意味を持ちます。これはRequest/Responseにおいて、「Requestメッセージの送信者が、Responseメッセージを受信したときに、そのRequestメッセージがどのリクエストに対するものかを識別するために」使われるバイナリデータです。

Content Type:PUBLISHとWillプロパティで設定してもよいです[MAY]。

Will Messageのコンテントタイプを設定するために、コンテントタイプもCONNECTで使われるかもしれません。 

証券会社が検証するのは、プロパティ自体のエンコードのみです。このプロパティの意味はクライアント次第です。


購読/購読解除プロパティ
SUBSCRIBE Subscription Identifier、User Property
SUBACK Reason String、User Property
UNSUBSCRIBE User Property
UNSUBACK  Reason String、User Property

表3:MQTT v5.0-機能別にグループ化されたプロパティ-購読/購読解除プロパティ

Subscription Identifier-SUBSCRIBEで設定してもよいです[MAY]。

これは、SUBSCRIBEで設定できる数値識別子です。これは証券会社がメッセージに返すので、クライアントはどのサブスクリプションがメッセージ配送の原因になったかを判断できます。1~268、435、455の値をとります。0に設定してはならず[MUST]、クライアントからサーバーへのPUBLISHでは使用してはなりません[MUST]。


認証プロパティ
AUTH
Authentication Method、Authentication Method、Reason String、User Property

表4:MQTT v5.0-機能別にグループ化されたプロパティ-認証プロパティ

驚くなかれ、これらのプロパティはコネクションにも使用できます。

認証方法

ユーザー名とパスワードによる基本的なネットワーク認証に加えて、MQTT v5.0では「拡張認証」が可能です。このプロパティは、選択する方法を通知します。どの方法を選択するかは、アプリケーション開発者が決めます。証券会社は、そのメソッドがサポートされているかどうかを通知します。

Authentication Methodは一般的にSASLメカニズムであり、このような登録名を使用することで、相互交換を助ける。ただし、Authentication Methodは、登録されたSASLメカニズムの使用に制約されません。

Authentication Data

このプロパティは、クライアントと証券会社が、選択した認証方法に従って、認証データを交換するために使用されます。


MQL5でMQTT v5.0のプロパティを読み取る方法

これまで、本連載の前の記事では、ビットフラグ、すなわち、CONNECTのConnectフラグ、CONNACKのReason Code、そしてCONNACKのSession Presentフラグによって設定される「セッションごと」の設定を扱ってきました。これらの設定は、1セッションにつき1回、読み取り/書き込み/永続化されます。しかし、プロパティの場合は事情が違います。アプリケーションメッセージの一部であり、アプリケーションプロファイルによっては大量の重要なデータを伝送することもあります。したがって、クライアントは常にプロパティの読み書きができるように準備しなければなりません。

サーバーから送られてきたプロパティを読み取るテストを書くには、サンプルのバイト配列が必要です。CONNACKパケットは、クライアントが最初に扱うパケットなので、バイト配列のサンプルから始めることにします。すべてのMQTT Control Packetと同様に、2バイトの固定ヘッダーと2バイトの可変ヘッダーがあり、1バイトがConnect Acknowledgeフラグ、1バイトがConnect Reason Codeです。プロパティはCONNACKパケットの最後のフィールドであり、パケット識別子とペイロードを持ちません。

MQTT5.0-CONNACKパケットの構造

図02:MQTT5.0CONNACKパケットの構造

スタンダードはこう言っています。

「プロパティのセットでは、プロパティ長の後にプロパティが続きます。」

次のこともわかっています。

「プロパティ長は可変バイト整数としてエンコードされます。プロパティ長には、それ自身をエンコードするために使用されるバイトは含まれず、プロパティ長が含まれます。プロパティがない場合は、プロパティ長をゼロにすることで示さなければなりません[MUST]。」

したがって、固定ヘッダーの残りの長さとプロパティ長は、どちらも可変バイト整数としてエンコードされており、プロパティにアクセスする前に最初に読み取る必要がある情報です。プロパティ長がゼロの場合、読み取るべきものは何もありません。

したがって、プロパティがないCONNACKの場合、バイト配列のサンプルは次のようになります。

uchar connack_response[] = {2, X, 0, 0, 0};

ここで、Xは固定ヘッダーの残りの長さです。可変バイト整数をデコードするアルゴリズムは、規格によって提供されています。MQL5では、このアルゴリズムは次のように書くことができます。

uint DecodeVariableByteInteger(uint &buf[], uint idx)
  {
   uint multiplier = 1;
   uint value = 0;
   uint encodedByte;
   do
     {
      encodedByte = buf[idx];
      value += (encodedByte & 127) * multiplier;
      if(multiplier > 128 * 128 * 128)
        {
         Print("Error(Malformed Variable Byte Integer)");
         return -1;
        }
      multiplier *= 128;
     }
   while((encodedByte & 128) != 0);
   return value;
  };

ここで、buf[idx]は「ストリームからの次のバイト」を表します。

可変バイト整数をデコードするアルゴリズムはスタンダードで提供されていますが、現段階で実装が期待通りに動作していることを確認するために、ごく簡単なテストも書いてみました。

bool TEST_DecodeVariableByteInteger()
  {
   Print(__FUNCTION__);
   uint buf[] = {1, 127, 0, 0, 0};
   uint expected = 127;
   uint result = DecodeVariableByteInteger(buf, 1);
   ZeroMemory(buf);
   return AssertEqual(expected, result);
  }

明らかに、テスト目的では、残りの長さの値はハードコードされます。上記のCONNACKの場合、何のプロパティもない場合はこうなります。

uchar connack_response[] = {2, 3, 0, 0, 0};

1バイトのPayload Format IndicatorプロパティがUTF-8 Encoded Stringペイロードフォーマットに設定されたCONNACKのバイト配列のサンプルは、以下のようになります。

uchar connack_response_one_byte_property = {2, 5, 0, 0, 2, 1, 1};

ご覧のように、CONNACKにプロパティがあるかどうかをチェックするのは非常に簡単です。プロパティ長を含む5バイト目を読めばよいのです。最初のテストは次のようになります。

bool TestProtectedMethods::TEST_HasProperties_CONNACK_No_Props()
  {
   Print(__FUNCTION__);
//--- Arrange
   bool expected = false;
   uchar connack_no_props[5] = {2, 3, 0, 0, 0};
//--- Act
   CSrvResponse *cut = new CSrvResponse();
   bool result =  this.HasProperties(connack_no_props);
//--- Assert
   bool isTrue = AssertEqual(expected, result);
//--- cleanup
   delete cut;
   ZeroMemory(result);
   return  isTrue ? true : false;
  }

protectedメソッドをテストする方法については、前回の記事をご覧ください。また、テストのFAILバリアントについては、添付のコードをご覧ください。

最初の実装は、現段階でのテストをパスするのに十分なもので、次のようになります。

bool CSrvResponse::HasProperties(uchar &resp_buf[])
  {
   return resp_buf[4] != 0 ? true : false;
  }

ここでは、コードを最小限に抑えるために三項演算子を使用しています。

プロパティ長バイトの位置はパケットタイプに依存することを考慮に入れなければならないことに注意が必要です。なぜなら、プロパティは常に可変ヘッダの最後のフィールドであるが、プロパティ長の前に2バイトのパケット識別子を必要とするパケットが存在するからです。CONNACKではこれは問題ではありません。

「それなら、このコードは他のパケットタイプには使えないですね」とおっしゃるかもしれません。実は、おっしゃる通りです。ただし、ここではTDDアプローチを使っていることを忘れないでください。この練習の主な利点のひとつは、目の前のタスクに集中し続けることであり、開発の初期段階で将来起こりうるすべての問題に対処しようとしないことです。他のパケットタイプについては、時期が来てテストが失敗したときに対処します。それからテストを書き直し、最終的にはコードをリファクタリングします。

少し直感に反するかもしれませんが、一度慣れてしまうと以前のようにコードを書くことはできません。他の理由がなくとも、それが私たちの仕事を容易にし、喜びさえ与えてくれるからです。ところで、本連載の次の記事では、PUBLISHパケットのプロパティの記述と読み込みを開始します。ご期待ください。

プロパティ長が0でなければ、次のバイトでProperty Identifierを探すことができます。Property Identifierは、プロパティのデータ型を与えます。

「プロパティは、その用途とデータ型を定義する識別子と、それに続く値で構成されます。」

uchar CSrvResponse::GetPropertyIdentifier(uchar &resp_buf[])
  {
   return resp_buf[5];
  }

データ型は、読み込むバイト数を示します。データ型は以下のいずれかです。

1バイト整数

uchar CSrvResponse::ReadOneByteProperty(uchar &resp_buf[])
  {
   return resp_buf[6];
  }

2バイト整数

void CSrvResponse::ReadTwoByteProperty(uchar &resp_buf[], uchar &dest_buf[])
  {
   ArrayCopy(dest_buf, resp_buf, 0, 6, 2);
  }

4バイト整数

void CSrvResponse::ReadFourByteProperty(uchar &resp_buf[], uchar &dest_buf[])
  {
   ArrayCopy(dest_buf, resp_buf, 0, 6, 4);
  }

可変バイト整数(Subscription Identifierのみ) 

void CSrvResponse::ReadVariableByteProperty(uint &resp_buf[], uint &dest_buf[], uint start_idx)
  {
   uint value = DecodeVariableByteInteger(resp_buf, start_idx);
   ArrayResize(dest_buf,value,7);
   ArrayFill(dest_buf, 0, 1, value);
  }

このプロパティの読み取り/デコード(および書き込み/エンコード)には、今このテストがチェックしている以上のことが必要です。

可変バイト整数は、127までの値に対して1バイトを使用する符号化方式で符号化されます。より大きな値は次のように処理されます。各バイトの下位7ビットはデータをエンコードし、最上位ビットは表現内に後続のバイトがあるかどうかを示すために使用されます。したがって、各バイトは128の値と継続ビットをエンコードします」(太字は著者によるものです)。

SUBSCRIBEパケットを実装するとき、可変バイト整数プロパティはサブスクリプションIDプロパティにのみ使用されるので、これを扱うことにします。また、UTF-8エンコードされた文字列、バイナリデータ、UTF-8文字列ペアの3つのデータ型もあります。これらの詳細については、要求/応答の使用と、User Propertyの特殊なケースの実装の文脈で説明します。

UTF-8でエンコードされた文字列には、その長さが先頭に付きます。

「これらの文字列はそれぞれ、以下の図 1.1 UTF-8でエンコードされた文字列の構造に示されているように、UTF-8エンコードされた文字列自体のバイト数を示す2バイト整数長フィールドを先頭に持ちます。その結果、UTF-8エンコード文字列の最大サイズは65,535バイトとなります。 特に断りのない限り、UTF-8でエンコードされた文字列はすべて、0〜65,535バイトの範囲で任意の長さを持つことができます。」

MQTT-v5-utf8符号化文字列構造-OASIS

図03:MQTT5.0-UTF-8エンコードされた文字列の構造-OASISテーブルからの画面キャプチャ

UTF-8でエンコードされた文字列は、許可されていないUnicodeコードポイントが存在しないか検証されなければならないことは注目に値します(これについては後で詳しく説明します)。

「セクション1.6.4では、UTF-8エンコードされた文字列に含めるべきでないUnicodeコードポイントを説明しています。クライアントまたはサーバーの実装は、これらのコードポイントがトピック名やプロパティなどのUTF-8エンコードされた文字列で使用されていないことを検証するかどうかを選択できます。」

バイナリデータには、その長さを示すプレフィックスが付きます。

「バイナリデータは、データバイト数を示す2バイトの整数長で表され、その後にそのバイト数が続きます。したがって、バイナリデータの長さは0から65,535バイトの範囲に制限されます。」

読み込んだバイト数をカウントし続けることで、すべてのプロパティを読み込んだタイミングを知ることができます。プロパティの順番を気にする必要はありません。

「異なる識別子を持つプロパティの順番に意味はありません。」


プロトコルを拡張するためのプロパティの使用方法

この記事の冒頭で述べたように、プロパティはMQTT5.0の「拡張性メカニズム」の一部であり、このメカニズムの最も顕著なプロパティは、任意のMQTT Control Packetで使用できるUser Propertyです。User Propertyはキーと値のペアで、その意味はプロトコルには不透明です。つまり、その意味はアプリケーションによって定義されます。

ここで私たちのドメインのユースケースを想像してみましょう。受信者が3つの異なるプロバイダーからの取引シグナルをコピーしています。各プロバイダーは異なる証券会社を利用しています。各証券会社は、同じ資産(例えば金)に異なる銘柄名を割り当てることがあります。

  • 証券会社AはGOLDを使用
  • 証券会社BはXAUUSDを使用
  • 証券会社CはXAUUSD.sを使用

その上、各シグナルプロバイダーは、複数の証券会社を使用することができます。したがって、signal_provider:provider_brokerのペアは、取引セッションが進行中であっても、いつでも変更することができます(そう、ここでは準組み合わせの爆発が起きています)。 受信側は、証券会社が取引要求を適切に複製するために使用している銘柄名に変換できるように、受信している銘柄名の意味を、理想的にはミリ秒単位で知る必要があります。

User Propertyがなければ、以前のバージョンのプロトコルのように、メタデータ(signal_provider:provider_broker)をペイロードに埋め込む必要があります。ここでは、必要な取引シグナル データのみを検索 (および解析) することが期待されます。

対照的に、各シグナルプロバイダーが証券会社名を持つ独自のUser Propertyを持つ場合、ペイロードは必要なシグナルデータのみを持つことができます。

これはこの使用例の単純化した例です。しかし、このメタデータは、JSON/XML文字列やファイル全体を含む、あらゆる重要な情報に拡張できることを思い出していただきたいのです。よって、ある意味、可能性は無限です。


結論

本連載の第4部では、MQTT v5.0にどのようなプロパティがあるのか、そのセマンティクス、およびいくつかの使用例について簡単に説明しました。また、CONNACKのためにどのように実装しているかを報告し、プロトコルを拡張するためにどのように使用できるかの簡単な例を示しました。次の部分では、仕様の複雑さに対処するために常にTDDアプローチを用いながら、それらをPUBLISHパケットの文脈に適用していきます。

私たちのコードベースの一部となるこのネイティブMQL5クライアントの開発に貢献してみたい方は、下記のコメント欄またはチャットを通してご連絡ください。どんな手助けでも歓迎です!:)


MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/13651

添付されたファイル |
最後のコメント | ディスカッションに移動 (1)
Yousuf Mesalm
Yousuf Mesalm | 19 11月 2023 において 20:13
パート5を待っている。
MQL5を使ったシンプルな多通貨エキスパートアドバイザーの作り方(第3回):銘柄名のプレフィックスおよび/またはサフィックスと取引時間セッションを追加しました MQL5を使ったシンプルな多通貨エキスパートアドバイザーの作り方(第3回):銘柄名のプレフィックスおよび/またはサフィックスと取引時間セッションを追加しました
数人のトレーダー仲間から、プレフィックスやサフィックスを持つ銘柄名を持つブローカーでこの多通貨EAを使用する方法、およびこの多通貨EAで取引タイムゾーンや取引タイムセッションを実装する方法についてメールやコメントをいただきました。
ソフトウェア開発とMQL5におけるデザインパターン(第1回):生成パターン ソフトウェア開発とMQL5におけるデザインパターン(第1回):生成パターン
繰り返し発生する問題の多くを解決するためには、使用できる方法があります。これらの方法の使い方を理解すれば、ソフトウェアを効果的に作成し、DRY (Do not Repeat Yourself)の概念を適用するのに非常に役立ちます。この文脈では、デザインパターンのトピックが非常に役に立ちます。なぜなら、デザインパターンは、よく説明され、繰り返される問題に対する解決策を提供するパターンだからです。
母集団最適化アルゴリズム:Mind Evolutionary Computation (MEC)アルゴリズム 母集団最適化アルゴリズム:Mind Evolutionary Computation (MEC)アルゴリズム
この記事では、Simple Mind Evolutionary Computation(Simple MEC, SMEC)アルゴリズムと呼ばれる、MECファミリーのアルゴリズムを考察します。このアルゴリズムは、そのアイデアの美しさと実装の容易さで際立っています。
エキスパートアドバイザー(EA)に指標を追加するための既製のテンプレート(第3部):トレンド指標 エキスパートアドバイザー(EA)に指標を追加するための既製のテンプレート(第3部):トレンド指標
この参考記事では、トレンド指標カテゴリから標準的な指標を取り上げます。パラメータの宣言と設定、指標の初期化と解除、EAの指標バッファからのデータとシグナルの受信など、EAで指標を使用するためのすぐに使えるテンプレートを作成します。